using SharpDX;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace CodeWalker.GameFiles
{
    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndFile : GameFile, PackedFile, BasePathData
    {

        public NodeDictionary NodeDictionary { get; set; }

        public YndNode[] Nodes { get; set; }
        public YndLink[] Links { get; set; }
        public YndJunction[] Junctions { get; set; }

        public EditorVertex[] LinkedVerts { get; set; }//populated by the space (needs to use grid of all ynd's!)
        public EditorVertex[] TriangleVerts { get; set; } //used for junctions display
        public Vector4[] NodePositions { get; set; }

        public Vector3 BBMin { get; set; }
        public Vector3 BBMax { get; set; }
        public int CellX { get; set; }
        public int CellY { get; set; }
        public int AreaID
        {
            get
            {
                return CellY * 32 + CellX;
            }
            set
            {
                CellX = value % 32;
                CellY = value / 32;
                UpdateBoundingBox();
            }
        }

        public PathBVH BVH { get; set; }



        //fields used by the editor:
        public bool HasChanged { get; set; } = false;
        public List<string> SaveWarnings = null;

        public bool BuildStructsOnSave { get; set; } = true;


        public YndFile() : base(null, GameFileType.Ynd)
        {
        }
        public YndFile(RpfFileEntry entry) : base(entry, GameFileType.Ynd)
        {
        }



        public void Load(byte[] data)
        {
            //direct load from a raw, compressed ynd file (openIV-compatible format)

            RpfFile.LoadResourceFile(this, data, 1);

            Loaded = true;
            LoadQueued = true;
        }

        public void Load(byte[] data, RpfFileEntry entry)
        {
            Name = entry.Name;
            RpfFileEntry = entry;

            RpfResourceFileEntry resentry = entry as RpfResourceFileEntry;
            if (resentry == null)
            {
                throw new Exception("File entry wasn't a resource! (is it binary data?)");
            }

            ResourceDataReader rd = new ResourceDataReader(resentry, data);


            NodeDictionary = rd.ReadBlock<NodeDictionary>();

            InitNodesFromDictionary();

            UpdateAllNodePositions();

            //links will be populated by the space... maybe move that code here?



            string areaidstr = Name.ToLowerInvariant().Replace("nodes", "").Replace(".ynd", "");
            int areaid = 0;
            int.TryParse(areaidstr, out areaid);
            AreaID = areaid;

            UpdateBoundingBox();


            BuildBVH();


            Loaded = true;
            LoadQueued = true;
        }


        public byte[] Save()
        {
            if (BuildStructsOnSave)
            {
                BuildStructs();
            }

            byte[] data = ResourceBuilder.Build(NodeDictionary, 1); //ynd is version 1...


            return data;

        }

        public void BuildStructs()
        {

            List<NodeLink> newlinks = new List<NodeLink>();
            List<NodeJunction> newjuncs = new List<NodeJunction>();
            List<NodeJunctionRef> newjuncrefs = new List<NodeJunctionRef>();
            List<byte> newjuncheightmaps = new List<byte>();

            if (Nodes != null)
            {
                int count = Nodes.Length;
                var nodes = new Node[count];
                for (int i = 0; i < count; i++)
                {
                    var node = Nodes[i];
                    if (node.Links != null)
                    {
                        node.LinkCount = node.Links.Length;
                        node.LinkID = (ushort)newlinks.Count;
                        for (int l = 0; l < node.Links.Length; l++)
                        {
                            var nlink = node.Links[l];
                            var rlink = nlink.RawData;
                            if (nlink.Node2 != null)
                            {
                                rlink.AreaID = nlink.Node2.AreaID;
                                rlink.NodeID = nlink.Node2.NodeID;
                            }
                            newlinks.Add(rlink);
                        }
                    }
                    else
                    {
                        node.LinkCount = 0;
                    }

                    //LinkCount = node.LinkCountFlags.Value >> 3;
                    //LinkCountUnk = node.LinkCountFlags.Value & 7;
                    byte lcflags = (byte)((node.LinkCount << 3) + (node.LinkCountUnk & 7));
                    node._RawData.LinkCountFlags = lcflags;

                    nodes[i] = node.RawData;

                    if ((node.HasJunction) && (node.Junction != null))
                    {
                        var nj = node.Junction;
                        var heightmapoff = newjuncheightmaps.Count;
                        var heightmap = nj.Heightmap.GetBytes();
                        nj._RawData.HeightmapPtr = (ushort)heightmapoff;
                        var jref = nj.RefData;
                        jref.AreaID = node.AreaID;
                        jref.NodeID = node.NodeID;
                        jref.JunctionID = (ushort)newjuncs.Count;
                        //jref.Unk0 = 0;//always 0?
                        nj.RefData = jref;//move this somewhere else..??
                        newjuncs.Add(nj.RawData);
                        newjuncrefs.Add(jref);
                        newjuncheightmaps.AddRange(heightmap);
                    }
                }
                NodeDictionary.Nodes = nodes;
                NodeDictionary.NodesCount = (uint)count;
                NodeDictionary.Links = newlinks.ToArray();
                NodeDictionary.LinksCount = (uint)newlinks.Count;
                NodeDictionary.Junctions = newjuncs.ToArray();
                NodeDictionary.JunctionsCount = (uint)newjuncs.Count;
                NodeDictionary.JunctionRefs = newjuncrefs.ToArray();
                NodeDictionary.JunctionRefsCount0 = (ushort)newjuncrefs.Count;
                NodeDictionary.JunctionRefsCount1 = (ushort)newjuncrefs.Count;
                NodeDictionary.JunctionHeightmapBytes = newjuncheightmaps.ToArray();
                NodeDictionary.JunctionHeightmapBytesCount = (uint)newjuncheightmaps.Count;
            }
            else
            {
                NodeDictionary.Nodes = null;
                NodeDictionary.NodesCount = 0;
                NodeDictionary.Links = null;
                NodeDictionary.LinksCount = 0;
                NodeDictionary.Junctions = null;
                NodeDictionary.JunctionsCount = 0;
                NodeDictionary.JunctionRefs = null;
                NodeDictionary.JunctionRefsCount0 = 0;
                NodeDictionary.JunctionRefsCount1 = 0;
                NodeDictionary.JunctionHeightmapBytes = null;
                NodeDictionary.JunctionHeightmapBytesCount = 0;
            }
        }




        public void InitNodesFromDictionary()
        {
            if (NodeDictionary != null)
            {
                if (NodeDictionary.Nodes != null)
                {
                    var nodes = NodeDictionary.Nodes;
                    Nodes = new YndNode[nodes.Length];
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        var n = new YndNode();
                        n.Init(this, nodes[i]);
                        Nodes[i] = n;
                        if (n.NodeID != i)
                        { } //never hit here - nodeid's have to match the index!
                    }
                }
                if ((NodeDictionary.JunctionRefs != null) && (NodeDictionary.Junctions != null))
                {
                    var juncrefs = NodeDictionary.JunctionRefs;
                    var juncs = NodeDictionary.Junctions;
                    Junctions = new YndJunction[juncrefs.Length];
                    for (int i = 0; i < juncrefs.Length; i++)
                    {
                        var juncref = juncrefs[i];
                        if (juncref.JunctionID >= juncs.Length)
                        { continue; }

                        var j = new YndJunction();
                        j.Init(this, juncs[juncref.JunctionID], juncref);
                        j.Heightmap = new YndJunctionHeightmap(NodeDictionary.JunctionHeightmapBytes, j);
                        Junctions[i] = j;
                    }
                }
            }
        }

        public YndNode AddNode()
        {
            int cnt = Nodes?.Length ?? 0;
            YndNode yn = new YndNode();
            Node n = new Node();
            n.AreaID = (ushort)AreaID;
            n.NodeID = (ushort)cnt;
            yn.Init(this, n);

            int ncnt = cnt + 1;
            YndNode[] nnodes = new YndNode[ncnt];
            for (int i = 0; i < cnt; i++)
            {
                nnodes[i] = Nodes[i];
            }
            nnodes[cnt] = yn;
            Nodes = nnodes;
            NodeDictionary.NodesCount = (uint)ncnt;

            return yn;
        }

        public bool RemoveNode(YndNode node)
        {
            List<YndNode> newnodes = new List<YndNode>();
            int cnt = Nodes?.Length ?? 0;
            bool r = false;
            int ri = -1;
            for (int i = 0; i < cnt; i++)
            {
                var tn = Nodes[i];
                if (tn != node)
                {
                    newnodes.Add(tn);
                }
                else
                {
                    r = true;
                    ri = i;
                }
            }
            Nodes = newnodes.ToArray();
            NodeDictionary.NodesCount = (uint)newnodes.Count;
            NodeDictionary.NodesCountVehicle = Math.Min(NodeDictionary.NodesCountVehicle, NodeDictionary.NodesCount);
            NodeDictionary.NodesCountPed = Math.Min(NodeDictionary.NodesCountPed, NodeDictionary.NodesCount);

            //remap node ID's...
            List<YndLink> remlinks = new List<YndLink>();
            if (ri >= 0)
            {
                for (int i = 0; i < Nodes.Length; i++)
                {
                    var n = Nodes[i];
                    if (n.NodeID != i)
                    {
                        n.NodeID = (ushort)i;


                        //update nodeid's in links...
                        for (int j = 0; j < Nodes.Length; j++)
                        {
                            var tn = Nodes[j];
                            if ((tn != null) && (tn.Links != null))
                            {
                                for (int bl = 0; bl < tn.Links.Length; bl++)
                                {
                                    var backlink = tn.Links[bl];
                                    if (backlink.Node2 == n)
                                    {
                                        backlink._RawData.NodeID = (ushort)i;
                                    }
                                }
                            }
                        }
                    }

                    //remove any links referencing the node.
                    remlinks.Clear();
                    if (n.Links != null)
                    {
                        for (int l = 0; l < n.Links.Length; l++)
                        {
                            var nlink = n.Links[l];
                            if (nlink.Node2 == node)
                            {
                                remlinks.Add(nlink);
                            }
                        }
                        for (int l = 0; l < remlinks.Count; l++)
                        {
                            n.RemoveLink(remlinks[l]);
                        }
                    }

                }

            }

            UpdateAllNodePositions();

            return r;
        }





        public void UpdateBoundingBox()
        {
            Vector3 corner = new Vector3(-8192, -8192, -2048);
            Vector3 cellsize = new Vector3(512, 512, 4096);

            BBMin = corner + (cellsize * new Vector3(CellX, CellY, 0));
            BBMax = BBMin + cellsize;
        }

        public void UpdateAllNodePositions()
        {
            int cnt = Nodes?.Length ?? 0;
            if (cnt <= 0)
            {
                NodePositions = null;
                return;
            }
            var np = new Vector4[cnt];
            for (int i = 0; i < cnt; i++)
            {
                np[i] = new Vector4(Nodes[i].Position, 1.0f);
            }
            NodePositions = np;
        }

        public void UpdateTriangleVertices()
        {
            //note: called from space.BuildYndVerts()

            UpdateLinkTriangleVertices();

            //UpdateJunctionTriangleVertices();
        }

        private void UpdateLinkTriangleVertices()
        {
            //build triangles for the path links display


            int vc = 0;
            if (Links != null)
            {
                vc = Links.Length * 6;
            }

            List<EditorVertex> verts = new List<EditorVertex>(vc);
            EditorVertex v0 = new EditorVertex();
            EditorVertex v1 = new EditorVertex();
            EditorVertex v2 = new EditorVertex();
            EditorVertex v3 = new EditorVertex();
            if ((Links != null) && (Nodes != null))
            {
                foreach (var node in Nodes)
                {
                    foreach (var link in node.Links)
                    {
                        var p0 = link.Node1?.Position ?? Vector3.Zero;
                        var p1 = link.Node2?.Position ?? Vector3.Zero;
                        var diff = p1 - p0;
                        var dir = Vector3.Normalize(diff);
                        var ax = Vector3.Cross(dir, Vector3.UnitZ);


                        float lanestot = link.LaneCountForward + link.LaneCountBackward;
                        //float backfrac = Math.Min(Math.Max(link.LaneCountBackward / lanestot, 0.1f), 0.9f);
                        //float lanewidth = 7.0f;
                        //float inner = totwidth*(backfrac-0.5f);
                        //float outer = totwidth*0.5f;

                        float lanewidth = node.IsPedNode ? 0.5f : 5.5f;
                        float inner = link.LaneOffset * lanewidth;// 0.0f;
                        float outer = inner + Math.Max(lanewidth * link.LaneCountForward, 0.5f);

                        float totwidth = lanestot * lanewidth;
                        float halfwidth = totwidth * 0.5f;
                        if (link.LaneCountBackward == 0)
                        {
                            inner -= halfwidth;
                            outer -= halfwidth;
                        }
                        if (link.LaneCountForward == 0)
                        {
                            inner += halfwidth;
                            outer += halfwidth;
                        }


                        v0.Position = p1 + ax * inner;
                        v1.Position = p0 + ax * inner;
                        v2.Position = p1 + ax * outer;
                        v3.Position = p0 + ax * outer;
                        var c = (uint)link.GetColour().ToRgba();
                        v0.Colour = c;
                        v1.Colour = c;
                        v2.Colour = c;
                        v3.Colour = c;
                        verts.Add(v0);
                        verts.Add(v1);
                        verts.Add(v2);
                        verts.Add(v2);
                        verts.Add(v1);
                        verts.Add(v3);
                    }
                }
            }


            if (verts.Count > 0)
            {
                TriangleVerts = verts.ToArray();
            }
            else
            {
                TriangleVerts = null;
            }
        }

        private void UpdateJunctionTriangleVertices()
        {
            //build triangles for the junctions bytes display....

            int vc = 0;
            if (Junctions != null)
            {
                foreach (var j in Junctions)
                {
                    var d = j.Heightmap;
                    if (d == null) continue;
                    vc += d.CountX * d.CountY * 6;
                }
            }

            List<EditorVertex> verts = new List<EditorVertex>(vc);
            EditorVertex v0 = new EditorVertex();
            EditorVertex v1 = new EditorVertex();
            EditorVertex v2 = new EditorVertex();
            EditorVertex v3 = new EditorVertex();
            if (Nodes != null)
            {
                foreach (var node in Nodes)
                {
                    if (node.Junction == null) continue;
                    var j = node.Junction;
                    var d = j.Heightmap;
                    if (d == null) continue;

                    float maxz = j.MaxZ / 32.0f;
                    float minz = j.MinZ / 32.0f;
                    float rngz = maxz - minz;
                    float posx = j.PositionX / 4.0f;
                    float posy = j.PositionY / 4.0f;

                    Vector3 pos = new Vector3(posx, posy, 0.0f);
                    Vector3 siz = new Vector3(d.CountX, d.CountY, 0.0f) * 2.0f;
                    //Vector3 siz = new Vector3(jx, jy, 0.0f);
                    Vector3 cnr = pos;// - siz * 0.5f;
                    //Vector3 inc = new Vector3(1.0f/jx)

                    cnr.Z = minz;// + 2.0f;

                    for (int y = 1; y < d.CountY; y++) //rows progress up the Y axis.
                    {
                        var row0 = d.Rows[y - 1];
                        var row1 = d.Rows[y];
                        float offy = y * 2.0f;

                        for (int x = 1; x < d.CountX; x++) //values progress along the X axis.
                        {
                            var val0 = row0.Values[x - 1] / 255.0f;
                            var val1 = row0.Values[x] / 255.0f;
                            var val2 = row1.Values[x - 1] / 255.0f;
                            var val3 = row1.Values[x] / 255.0f;
                            float offx = x * 2.0f;
                            v0.Position = cnr + new Vector3(offx - 2.0f, offy - 2.0f, val0 * rngz);
                            v1.Position = cnr + new Vector3(offx + 0.0f, offy - 2.0f, val1 * rngz);
                            v2.Position = cnr + new Vector3(offx - 2.0f, offy + 0.0f, val2 * rngz);
                            v3.Position = cnr + new Vector3(offx + 0.0f, offy + 0.0f, val3 * rngz);
                            v0.Colour = (uint)new Color4(val0, 1.0f - val0, 0.0f, 1.0f).ToRgba();
                            v1.Colour = (uint)new Color4(val1, 1.0f - val1, 0.0f, 1.0f).ToRgba();
                            v2.Colour = (uint)new Color4(val2, 1.0f - val2, 0.0f, 1.0f).ToRgba();
                            v3.Colour = (uint)new Color4(val3, 1.0f - val3, 0.0f, 1.0f).ToRgba();
                            verts.Add(v0);
                            verts.Add(v1);
                            verts.Add(v2);
                            verts.Add(v2);
                            verts.Add(v1);
                            verts.Add(v3);
                        }
                    }


                }
            }

            if (verts.Count > 0)
            {
                TriangleVerts = verts.ToArray();
            }
            else
            {
                TriangleVerts = null;
            }


        }


        public void UpdateBvhForNode(YndNode node)
        {
            //this needs to be called when a node's position changes...
            //if it changes a lot, need to recalc the BVH for mouse intersection optimisation purposes.

            //if (BVH == null) return;
            //BVH.UpdateForNode(node);

            BuildBVH();

            //also updates the NodePositions for the visible vertex
            if (Nodes != null)
            {
                for (int i = 0; i < Nodes.Length; i++)
                {
                    if (Nodes[i] == node)
                    {
                        NodePositions[i] = new Vector4(node.Position, 1.0f);
                        break;
                    }
                }
            }

        }

        public void BuildBVH()
        {
            BVH = new PathBVH(Nodes, 10, 10);
        }




        public EditorVertex[] GetPathVertices()
        {
            return LinkedVerts;
        }
        public EditorVertex[] GetTriangleVertices()
        {
            return TriangleVerts;
        }
        public Vector4[] GetNodePositions()
        {
            return NodePositions;
        }



        public override string ToString()
        {
            return RpfFileEntry?.ToString() ?? string.Empty;
        }
    }


    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndNode : BasePathNode
    {
        public Node _RawData;

        public YndFile Ynd { get; set; }
        public Node RawData { get { return _RawData; } set { _RawData = value; } }
        public Vector3 Position { get; set; }
        public int LinkCount { get; set; }
        public int LinkCountUnk { get; set; }
        public YndLink[] Links { get; set; }

        public ushort AreaID { get { return _RawData.AreaID; } set { _RawData.AreaID = value; } }
        public ushort NodeID { get { return _RawData.NodeID; } set { _RawData.NodeID = value; } }
        public ushort LinkID { get { return _RawData.LinkID; } set { _RawData.LinkID = value; } }
        public FlagsByte Flags0 { get { return _RawData.Flags0; } set { _RawData.Flags0 = value; } }
        public FlagsByte Flags1 { get { return _RawData.Flags1; } set { _RawData.Flags1 = value; } }
        public FlagsByte Flags2 { get { return _RawData.Flags2; } set { _RawData.Flags2 = value; } }
        public FlagsByte Flags3 { get { return _RawData.Flags3; } set { _RawData.Flags3 = value; } }
        public FlagsByte Flags4 { get { return _RawData.Flags4; } set { _RawData.Flags4 = value; } }
        public TextHash StreetName { get { return _RawData.StreetName; } set { _RawData.StreetName = value; } }

        public Color4 Colour { get; set; }

        public YndJunction Junction { get; set; }
        public bool HasJunction;


        public bool IsPedNode
        {
            get
            {
                return false;// ((Flags4.Value >> 4) & 7) == 7;
            }
        }




        public void Init(YndFile ynd, Node node)
        {
            Ynd = ynd;
            RawData = node;
            Vector3 p = new Vector3();
            p.X = node.PositionX / 4.0f;
            p.Y = node.PositionY / 4.0f;
            p.Z = node.PositionZ / 32.0f;
            Position = p;

            LinkCount = node.LinkCountFlags.Value >> 3;
            LinkCountUnk = node.LinkCountFlags.Value & 7;

            Colour = GetColour();

        }

        public Color4 GetColour()
        {
            Color4 c = new Color4(LinkCountUnk / 7.0f, Flags0.Value / 255.0f, Flags1.Value / 255.0f, 1.0f);
            //Color4 c = new Color4(0.0f, 0.0f, 0.0f, 1.0f);

            //c.Red = (LinkCountUnk >> 1) / 3.0f;
            //c.Red = (Flags3.Value >> 1) / 127.0f;
            //c.Red = ((Flags4.Value >> 1) & 7) / 7.0f; //density value?
            //c.Green = 1.0f - c.Red;


            //if ((Flags0.Value & 1) > 0) c.Red += 1.0f; //script activated? N Yankton only + small piece in self storage
            //if ((Flags0.Value & 2) > 0) c.Red += 1.0f; //car can use / gps?
            //if ((Flags0.Value & 4) > 0) c.Red += 1.0f; //***not used
            //if ((Flags0.Value & 8) > 0) c.Red += 1.0f; //gravel surface? country roads mostly
            //if ((Flags0.Value & 16) > 0) c.Red += 1.0f; //***not used
            //if ((Flags0.Value & 32) > 0) c.Red += 1.0f; //slow speed? hills roads, prison boundary, carparks, airport roads etc
            //if ((Flags0.Value & 64) > 0) c.Red += 1.0f; //intersection entry 1 (has priority)?
            //if ((Flags0.Value & 128) > 0) c.Green += 1.0f; //intersection entry 2 unk?

            //if ((Flags1.Value & 1) > 0) c.Red += 1.0f; //left turn lane?
            //if ((Flags1.Value & 2) > 0) c.Red += 1.0f; //left turn node of no return
            //if ((Flags1.Value & 4) > 0) c.Red += 1.0f; //right turn node of no return
            //if ((Flags1.Value & 8) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc
            //if ((Flags1.Value & 16) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc + peds crossing
            //if ((Flags1.Value & 32) > 0) c.Red += 1.0f; //intersection entry 3 unk?
            //if ((Flags1.Value & 64) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc + peds crossing
            //if ((Flags1.Value & 128) > 0) c.Red += 1.0f; //intersection minor/stop, T?

            ////[16 bits pos Z here]

            //if ((Flags2.Value & 1) > 0) c.Red += 1.0f; //slow traffic? peds? carparks? military? GPS disable routing??
            //if ((Flags2.Value & 2) > 0) c.Red += 1.0f; //***not used
            //if ((Flags2.Value & 4) > 0) c.Red += 1.0f; //intersection decision?
            //if ((Flags2.Value & 8) > 0) c.Red += 1.0f; //***not used
            //if ((Flags2.Value & 16) > 0) c.Red += 1.0f; //slower traffic?
            //if ((Flags2.Value & 32) > 0) c.Red += 1.0f; //water/boat
            //if ((Flags2.Value & 64) > 0) c.Red += 1.0f; //freeways  /peds?
            //if ((Flags2.Value & 128) > 0) c.Red += 1.0f; //not a main road...?

            //if ((LinkCountUnk & 1) > 0) c.Red += 1.0f; //has junction heightmap
            //if ((LinkCountUnk & 2) > 0) c.Red += 1.0f; //speed/density/type related? not runways, not freeways
            //if ((LinkCountUnk & 4) > 0) c.Red += 1.0f; //higher speed? eg freeway
            ////[5 bits LinkCount here]

            //if ((Flags3.Value & 1) > 0) c.Red += 1.0f; //is in an interior
            //if ((Flags3.Value & 2) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 4) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 8) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 16) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 32) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 64) > 0) c.Red += 1.0f; //heuristic val?
            //if ((Flags3.Value & 128) > 0) c.Red += 1.0f; //heuristic val?

            //if ((Flags4.Value & 1) > 0) c.Red += 1.0f; //slow traffic?
            //if ((Flags4.Value & 2) > 0) c.Red += 1.0f; //density/popgroup value..?
            //if ((Flags4.Value & 4) > 0) c.Green += 1.0f; //density/popgroup value..?
            //if ((Flags4.Value & 8) > 0) c.Blue += 1.0f; //density/popgroup value..?
            //if ((Flags4.Value & 16) > 0) c.Red += 1.0f; //special/peds path?
            //if ((Flags4.Value & 32) > 0) c.Green += 1.0f; //special/peds path?
            //if ((Flags4.Value & 64) > 0) c.Blue += 1.0f; //special/peds path?
            //if ((Flags4.Value & 128) > 0) c.Blue += 1.0f; //intersection entry left turn?






            ////regarding paths.xml:
            ////rubidium - Today at 8:37 AM
            //also, quick glimpse over the xml for attributes:
            ////> grep - i "attribute name" paths.xml | awk - F'^"' ' { print $2 }' | sort - u
            //Block If No Lanes
            //Cannot Go Left
            //Cannot Go Right
            //Density
            //Disabled
            //Dont Use For Navigation
            //GpsBothWays
            //Highway
            //Indicate Keep Left
            //Indicate Keep Right
            //Lanes In
            //Lanes Out
            //Left Turns Only
            //Narrowroad
            //No Big Vehicles
            //NoGps
            //Off Road
            //Shortcut
            //Slip Lane
            //Special
            //Speed
            //Streetname
            //Tunnel
            //Water
            //Width




            return c;
        }


        public void SetPosition(Vector3 pos)
        {
            _RawData.PositionX = (short)(pos.X * 4.0f);
            _RawData.PositionY = (short)(pos.Y * 4.0f);
            _RawData.PositionZ = (short)(pos.Z * 32.0f);

            Vector3 newpos = pos;
            //newpos.X = _RawData.PositionX / 4.0f;
            //newpos.Y = _RawData.PositionY / 4.0f;
            //newpos.Z = _RawData.PositionZ / 32.0f;
            Position = newpos;

            UpdateLinkLengths();
        }


        public void UpdateLinkLengths()
        {
            if (Links == null) return;
            for (int i = 0; i < Links.Length; i++)
            {
                var link = Links[i];
                link.UpdateLength(); //update this node's links

                var n2 = link.Node2;
                if ((n2 == null) || (n2.Links == null)) continue;

                for (int j = 0; j < n2.Links.Length; j++)
                {
                    var n2l = n2.Links[j];
                    if (n2l.Node2 == this)
                    {
                        n2l.UpdateLength(); //update back links
                    }
                }
            }
        }


        public YndLink AddLink(YndNode tonode = null)
        {
            YndLink l = new YndLink();
            l._RawData.AreaID = AreaID;
            l.Node1 = this;
            if (tonode != null)
            {
                l.Node2 = tonode;
                l._RawData.AreaID = tonode.AreaID;
                l._RawData.NodeID = tonode.NodeID;
            }
            else if ((Ynd.Nodes != null) && (Ynd.Nodes.Length > 0))
            {
                l.Node2 = Ynd.Nodes[0];
            }
            else
            {
                l.Node2 = this;
                l._RawData.NodeID = NodeID;
            }
            l.UpdateLength();

            int cnt = Links?.Length ?? 0;
            int ncnt = cnt + 1;
            YndLink[] nlinks = new YndLink[ncnt];
            for (int i = 0; i < cnt; i++)
            {
                nlinks[i] = Links[i];
            }
            nlinks[cnt] = l;
            Links = nlinks;
            LinkCount = ncnt;

            return l;
        }

        public bool RemoveLink(YndLink l)
        {
            List<YndLink> newlinks = new List<YndLink>();
            int cnt = Links?.Length ?? 0;
            bool r = false;
            for (int i = 0; i < cnt; i++)
            {
                var tl = Links[i];
                if (tl != l)
                {
                    newlinks.Add(tl);
                }
                else
                {
                    r = true;
                }
            }
            Links = newlinks.ToArray();
            LinkCount = newlinks.Count;
            return r;
        }


        public override string ToString()
        {
            //return AreaID.ToString() + "." + NodeID.ToString();
            return StreetName.ToString() + ", " + Position.X.ToString() + ", " + Position.Y.ToString() + ", " + Position.Z.ToString() + ", " + NodeID.ToString();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndLink
    {
        public YndFile Ynd { get; set; }
        public YndNode Node1 { get; set; }
        public YndNode Node2 { get; set; }
        public NodeLink _RawData;
        public NodeLink RawData { get { return _RawData; } set { _RawData = value; } }
        public FlagsByte Flags0 { get { return _RawData.Flags0; } set { _RawData.Flags0 = value; } }
        public FlagsByte Flags1 { get { return _RawData.Flags1; } set { _RawData.Flags1 = value; } }
        public FlagsByte Flags2 { get { return _RawData.Flags2; } set { _RawData.Flags2 = value; } }
        public FlagsByte LinkLength { get { return _RawData.LinkLength; } set { _RawData.LinkLength = value; } }

        public int LaneCountForward { get { return (Flags2.Value >> 5) & 7; } }
        public int LaneCountBackward { get { return (Flags2.Value >> 2) & 7; } }

        public int OffsetValue { get { return (Flags1.Value >> 4) & 7; } }
        public bool NegativeOffset { get { return (Flags1.Value >> 7) > 0; } }
        public float LaneOffset { get { return (OffsetValue / 7.0f) * (NegativeOffset ? -0.5f : 0.5f); } }


        public void Init(YndFile ynd, YndNode node1, YndNode node2, NodeLink link)
        {
            Ynd = ynd;
            Node1 = node1;
            Node2 = node2;
            RawData = link;
        }


        public void UpdateLength()
        {
            if (Node1 == null) return;
            if (Node2 == null) return;

            LinkLength = (byte)Math.Min(255, (Node2.Position - Node1.Position).Length());
        }


        public void CopyFlags(YndLink link)
        {
            if (link == null) return;
            Flags0 = link.Flags0;
            Flags1 = link.Flags1;
            Flags2 = link.Flags2;
        }



        public Color4 GetColour()
        {


            //float f0 = Flags0.Value / 255.0f;
            //float f1 = Flags1.Value / 255.0f;
            //float f2 = Flags2.Value / 255.0f;
            //var c = new Color4(f0, f1, f2, 1.0f);

            var c = new Color4(0.0f, 0.0f, 0.0f, 0.5f);
            c.Green = LaneCountForward / 7.0f;
            c.Red = LaneCountBackward / 7.0f;


            //if ((Flags0.Value & 1) > 0) c.Red = 1.0f; //? some small pieces in city, roads at docks, and mall at beach
            //if ((Flags0.Value & 2) > 0) c.Red = 1.0f; //3x segments joining east canal paths to roads, also josh's driveway - scripted?
            //if ((Flags0.Value & 4) > 0) c.Red = 1.0f; //? looks fairly random, 0 for water, alternating - slope related?
            //if ((Flags0.Value & 8) > 0) c.Red = 1.0f; //? like above
            //if ((Flags0.Value & 16) > 0) c.Red = 1.0f; //? similar to above, but less
            //if ((Flags0.Value & 32) > 0) c.Red = 1.0f; //? like above
            //if ((Flags0.Value & 64) > 0) c.Red = 1.0f; //? slightly less random
            //if ((Flags0.Value & 128) > 0) c.Red = 1.0f; //? still looks random...

            //if ((Flags1.Value & 1) > 0) c.Red = 1.0f; //***not used?
            //if ((Flags1.Value & 2) > 0) c.Red = 1.0f; //?possibly width/type bit
            //if ((Flags1.Value & 4) > 0) c.Red = 1.0f; //avoid routing? no through road / no other exit?
            //if ((Flags1.Value & 8) > 0) c.Red = 1.0f; //prefer routing? exit from dead end?
            //if ((Flags1.Value & 16) > 0) c.Red = 1.0f; //offset value.   mostly single lane, carpark access, golf course, alleyways, driveways, beach area etc
            //if ((Flags1.Value & 32) > 0) c.Green = 1.0f; //offset value.  similar to above
            //if ((Flags1.Value & 64) > 0) c.Green = 1.0f; //offset value.  similar to above
            //if ((Flags1.Value & 128) > 0) c.Red = 1.0f; //offset value. (sign)  similar to above (all paired with their back links!)

            //if ((Flags2.Value & 1) > 0) c.Red = 1.0f; //angled link - merge? enter/exit divided road section, most big junctions, always paired
            //if ((Flags2.Value & 2) > 0) c.Red = 1.0f; //lane change/u-turn link?  always paired
            //if ((Flags2.Value & 4) > 0) c.Red = 1.0f; //lane count back dir
            //if ((Flags2.Value & 8) > 0) c.Red = 1.0f; //lane count back dir
            //if ((Flags2.Value & 16) > 0) c.Red = 1.0f; //lane count back dir
            //if ((Flags2.Value & 32) > 0) c.Green = 1.0f; //lane count forward dir
            //if ((Flags2.Value & 64) > 0) c.Green = 1.0f; //lane count forward dir
            //if ((Flags2.Value & 128) > 0) c.Green = 1.0f; //lane count forward dir

            ////var lanesfwd = (Flags2.Value >> 5) & 7;
            ////var lanesbck = (Flags2.Value >> 2) & 7;
            //////if ((lanesfwd > 0) && (lanesbck > 0) && (lanesfwd != lanesbck))
            //////{ }



            //var t = (Flags1.Value >> 4)&1;
            //c.Red = t / 1.0f;
            //c.Green = 1.0f - c.Red;
            ////if (((Flags1.Value & 128) > 0))// && ((Flags1.Value & 64) == 0))
            ////{ c.Red += 1.0f; }



            return c;
        }



        public override string ToString()
        {
            return Node2._RawData.ToString();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunction
    {
        public YndFile Ynd { get; set; }
        public NodeJunction _RawData;
        public NodeJunction RawData { get { return _RawData; } set { _RawData = value; } }
        public NodeJunctionRef RefData { get; set; }
        public YndJunctionHeightmap Heightmap { get; set; }
        public short MaxZ { get; set; }
        public short MinZ { get; set; }
        public short PositionX { get; set; }
        public short PositionY { get; set; }

        public void Init(YndFile ynd, NodeJunction junc, NodeJunctionRef reff)
        {
            Ynd = ynd;
            RawData = junc;
            RefData = reff;
            MaxZ = junc.MaxZ;
            MinZ = junc.MinZ;
            PositionX = junc.PositionX;
            PositionY = junc.PositionY;
        }


        public void ResizeHeightmap()
        {
            Heightmap.Resize(_RawData.HeightmapDimX, _RawData.HeightmapDimY);
        }

        public void SetHeightmap(string text)
        {
            Heightmap.SetData(text);
        }


        public override string ToString()
        {
            return RefData.ToString();
        }


    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunctionHeightmap
    {
        public YndJunctionHeightmapRow[] Rows { get; set; }
        public int CountX { get; set; }
        public int CountY { get; set; }

        public YndJunctionHeightmap(byte[] data, YndJunction junc)
        {
            if (data == null)
            { return; }

            var d = junc.RawData;
            int s = d.HeightmapPtr;
            CountX = d.HeightmapDimX;
            CountY = d.HeightmapDimY;

            if ((s + CountX * CountY) > data.Length)
            { return; }

            Rows = new YndJunctionHeightmapRow[CountY];


            for (int y = 0; y < CountY; y++)
            {
                int o = s + y * CountX;
                byte[] vals = new byte[CountX];
                Buffer.BlockCopy(data, o, vals, 0, CountX);
                Rows[y] = new YndJunctionHeightmapRow(vals);
            }
        }


        public byte[] GetBytes()
        {
            int cnt = CountX * CountY;
            var bytes = new byte[cnt];
            for (int y = 0; y < CountY; y++)
            {
                int o = y * CountX;
                var rowvals = Rows[y].Values;
                Buffer.BlockCopy(rowvals, 0, bytes, o, CountX);
            }
            return bytes;
        }


        public void Resize(int cx, int cy)
        {
            var nrows = new YndJunctionHeightmapRow[cy];
            for (int y = 0; y < cy; y++)
            {
                byte[] nvals = new byte[cx];
                int minx = Math.Min(cx, CountX);
                if ((Rows != null) && (y < Rows.Length))
                {
                    Buffer.BlockCopy(Rows[y].Values, 0, nvals, 0, minx);
                }
                nrows[y] = new YndJunctionHeightmapRow(nvals);
            }
            Rows = nrows;

            CountX = cx;
            CountY = cy;
        }

        public void SetData(string text)
        {
            var rsplit = new[] { '\n' };
            var csplit = new[] { ' ' };
            string[] rowstrs = text.Split(rsplit, StringSplitOptions.RemoveEmptyEntries);
            for (int y = 0; y < rowstrs.Length; y++)
            {
                string[] colstrs = rowstrs[y].Trim().Split(csplit, StringSplitOptions.RemoveEmptyEntries);
                int cx = colstrs.Length;
                byte[] vals = new byte[cx];
                for (int x = 0; x < cx; x++)
                {
                    byte.TryParse(colstrs[x], out vals[x]);
                }
                int minx = Math.Min(cx, CountX);
                if ((Rows != null) && (y < Rows.Length))
                {
                    var nrow = new byte[CountX];
                    Buffer.BlockCopy(vals, 0, nrow, 0, minx);
                    Rows[y].Values = nrow;
                }
            }
        }

        public string GetDataString()
        {
            StringBuilder sb = new StringBuilder();
            if (Rows != null)
            {
                foreach (var row in Rows)
                {
                    sb.AppendLine(row.ToString());
                }
            }
            return sb.ToString();
        }

        public override string ToString()
        {
            return CountX.ToString() + " x " + CountY.ToString();
        }
    }
    [TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunctionHeightmapRow
    {
        public byte[] Values { get; set; }

        public YndJunctionHeightmapRow(byte[] vals)
        {
            Values = vals;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < Values.Length; i++)
            {
                if (i > 0) sb.Append(" ");
                sb.Append(Values[i].ToString().PadLeft(3, '0'));
                //sb.Append(Convert.ToString(Values[i], 16).ToUpper().PadLeft(2, '0'));
            }
            return sb.ToString();
        }
    }




    public class PathBVHNode
    {
        public int Depth;
        public int MaxDepth;
        public int Threshold;
        public List<BasePathNode> Nodes;
        public BoundingBox Box;
        public BoundingSphere Sphere;
        public PathBVHNode Node1;
        public PathBVHNode Node2;


        public void CalcBounds()
        {
            if ((Nodes == null) || (Nodes.Count <= 0)) return;

            Box.Minimum = new Vector3(float.MaxValue);
            Box.Maximum = new Vector3(float.MinValue);
            foreach (var node in Nodes)
            {
                Box.Minimum = Vector3.Min(Box.Minimum, node.Position);
                Box.Maximum = Vector3.Max(Box.Maximum, node.Position);
            }
            Sphere.Center = (Box.Minimum + Box.Maximum) * 0.5f;
            Sphere.Radius = (Box.Maximum - Box.Minimum).Length() * 0.5f;
        }


        public void Build()
        {
            if ((Nodes == null) || (Nodes.Count <= Threshold) || (Depth >= MaxDepth)) return;

            Vector3 avgsum = Vector3.Zero;
            foreach (var node in Nodes)
            {
                avgsum += node.Position;
            }
            Vector3 avg = avgsum * (1.0f / Nodes.Count);

            int countx = 0, county = 0, countz = 0;
            foreach (var node in Nodes)
            {
                if (node.Position.X < avg.X) countx++;
                if (node.Position.Y < avg.Y) county++;
                if (node.Position.Z < avg.Z) countz++;
            }

            int target = Nodes.Count / 2;
            int dx = Math.Abs(target - countx);
            int dy = Math.Abs(target - county);
            int dz = Math.Abs(target - countz);

            int axis = -1;
            if ((dx <= dy) && (dx <= dz)) axis = 0; //x seems best
            else if (dy <= dz) axis = 1; //y seems best
            else axis = 2; //z seems best


            List<BasePathNode> l1 = new List<BasePathNode>();
            List<BasePathNode> l2 = new List<BasePathNode>();
            foreach (var node in Nodes)
            {
                bool s = false;
                switch (axis)
                {
                    default:
                    case 0: s = (node.Position.X > avg.X); break;
                    case 1: s = (node.Position.Y > avg.Y); break;
                    case 2: s = (node.Position.Z > avg.Z); break;
                }
                if (s) l1.Add(node);
                else l2.Add(node);
            }

            var cdepth = Depth + 1;

            Node1 = new PathBVHNode();
            Node1.Depth = cdepth;
            Node1.MaxDepth = MaxDepth;
            Node1.Threshold = Threshold;
            Node1.Nodes = new List<BasePathNode>(l1);
            Node1.CalcBounds();
            Node1.Build();

            Node2 = new PathBVHNode();
            Node2.Depth = cdepth;
            Node2.MaxDepth = MaxDepth;
            Node2.Threshold = Threshold;
            Node2.Nodes = new List<BasePathNode>(l2);
            Node2.CalcBounds();
            Node2.Build();
        }


        public void UpdateForNode(BasePathNode node)
        {
            if (!Nodes.Contains(node)) return;
            Box.Minimum = Vector3.Min(Box.Minimum, node.Position);
            Box.Maximum = Vector3.Max(Box.Maximum, node.Position);

            if (Node1 != null) Node1.UpdateForNode(node);
            if (Node2 != null) Node2.UpdateForNode(node);
        }

    }

    public class PathBVH : PathBVHNode
    {

        public PathBVH(IEnumerable<BasePathNode> nodes, int threshold, int maxdepth)
        {
            Threshold = threshold;
            MaxDepth = maxdepth;
            Nodes = (nodes != null) ? new List<BasePathNode>(nodes) : new List<BasePathNode>();
            CalcBounds();
            Build();
        }

    }




    public interface BasePathNode
    {
        Vector3 Position { get; set; }
    }

    public interface BasePathData
    {
        //reuse this interface for file types that need to get paths rendered...

        EditorVertex[] GetPathVertices();
        EditorVertex[] GetTriangleVertices();
        Vector4[] GetNodePositions();
    }










    public class YndXml : MetaXmlBase
    {

        public static string GetXml(YndFile ynd)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(XmlHeader);

            if ((ynd != null) && (ynd.NodeDictionary != null))
            {
                var name = "NodeDictionary";

                OpenTag(sb, 0, name);

                ynd.NodeDictionary.WriteXml(sb, 1);

                CloseTag(sb, 0, name);
            }

            return sb.ToString();
        }

    }


    public class XmlYnd
    {

        public static YndFile GetYnd(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);
            return GetYnd(doc);
        }

        public static YndFile GetYnd(XmlDocument doc)
        {
            YndFile ynd = new YndFile();
            ynd.NodeDictionary = new NodeDictionary();
            ynd.NodeDictionary.ReadXml(doc.DocumentElement);
            ynd.InitNodesFromDictionary();
            ynd.BuildStructsOnSave = false; //structs don't need to be rebuilt here!
            return ynd;
        }





        public static TextHash GetTextHash(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                return 0;
            }
            if (str.StartsWith("hash_"))
            {
                return Convert.ToUInt32(str.Substring(5), 16);
            }
            else
            {
                uint h = GlobalText.TryFindHash(str);
                if (h != 0)
                {
                    return h;
                }

                return JenkHash.GenHash(str);
            }
        }

    }


}
