using System;
using System.Collections.Generic;
using System.Text;
using nft.core.geometry;
using nft.framework.drawing;
using System.Drawing;

using Geocon = nft.core.geometry.GeometricConstants;
using TPSet = nft.impl.game.TerrainPieceTemplate.TerrainPolygonSet;
using ITerrainPiece = nft.core.geometry.ITerrainPiece;

using System.Diagnostics;
using nft.impl.game;
using nft.core.game;
using nft.core;
using nft.framework;

namespace nft.impl.view_old {
    public class SceneBuilder : IDisposable {
        SpriteQueue queueSprites = new SpriteQueue();
        MapViewDrawer mvDrawer;
        Rectangle rectRequested;
        ViewObjectList objList;
        GroundOccupationMap<Int16> occupation;
        CoordinationUtil cdUtil = null;
        public int drawCells;

        Scaler scaler;
        Size szContent;
        Size3DV svWorld;
        InterCardinalDirection upperDir;
        internal int unitWidth;
        internal int unitHeight;
        int[] work;

        public SceneBuilder(MapViewDrawer drawer, Size initialPixelSize) {
            this.mvDrawer = drawer;
            SetViewFactor(drawer.ViewFactor);
            //rectRequested.Width += unitWidth;
            // because we must draw cells on left and right border too,
            // we need to assign some more (=4) elements for work array.
            this.work = new int[initialPixelSize.Width / unitWidth + 4];
            this.occupation = new GroundOccupationMap<Int16>(this.svWorld, initialPixelSize);
        }

        public void Prepare(Rectangle requestArea, ViewObjectList ol) {
            this.rectRequested = requestArea;
            PrepareOccupationMap(ol);
            queueSprites.Clear(false);
            work.Initialize();
#if DEBUG
            Point3DV p3dv = new Point3DV(0, 0, 0);
            Location l = cdUtil.RestoreRotated(p3dv);
            int rw = 1;
            int lw = 1;
            int h = 0;// Geocon.TerrainHeightStep;
            Rect3DV rct = new Rect3DV(0, 0, mvDrawer.TerrainMap[l.X,l.Y].MeanHeight, lw, rw, h);
            QuaterViewRect qvr = new QuaterViewRect(0, 0, lw*unitWidth, rw*unitWidth, h*unitHeight/Geocon.TerrainHeightStep );
            ITextureOld tex = PrimitiveTextrueFactory.CreateCell3DBoxTexture(qvr, Color.Red);
            AssembleObject(new ViewObject(tex, rct));
#endif
            AssembleSprites(GetMasterXScanner(), null);
        }

        public void SetViewFactor(ViewFactor vf) {
            if (cdUtil != null) {
                if (vf.Scaler.Equals(this.scaler) && vf.ViewDirection.Equals(this.upperDir)) {
                    return;
                }
            }
            this.cdUtil = CoordinationUtil.Create(mvDrawer.WorldSize, vf);
            this.svWorld = cdUtil.RotatedForViewAxis(mvDrawer.WorldSize);
            this.scaler = vf.Scaler;
            this.unitWidth = scaler.Scale(Geocon.CellWidthPixel);
            this.unitHeight = scaler.Scale(Geocon.StepHeightPixel);
            this.upperDir = vf.ViewDirection;
            this.szContent = CalcContentSize(svWorld.VZ);
        }


        protected Size CalcContentSize(int gridHeight) {
            int x = svWorld.VX * unitWidth;
            int y = svWorld.VY * unitWidth;
            int w = x + y;
            int h = (w >> 1) + gridHeight * unitHeight / Geocon.TerrainHeightStep;
            Size sz = new Size(w, h);
            return sz;
        }

        public CoordinationUtil CoordinationUtil {
            get { return cdUtil; }
        }

        public Size ContentSize {
            get { return szContent; }
        }

        internal MapViewDrawer ViewDrawer {
            get { return mvDrawer; }
        }

        internal Scaler Scaler {
            get { return scaler; }
        }

        internal SpriteQueue SpriteQueue {
            get { return queueSprites; }
        }

        internal IApparentAssignor AppAssignor
        {
            get { return DefaultApparentAssignor.TheInstance; }
        }


        public Location GetVisibleGroundAtView(Point pv) {
            Location l1 = occupation.GetBottomAtView(pv.X);
            if (l1 == Location.UNPLACED) 
                return l1;
            Point3DV p1 = cdUtil.RotatedForViewAxis(l1);
            int h;
            Point pt = cdUtil.LocationToQuarterXY(l1);
            bool b = pt.X - pv.X < unitWidth;
            TerrainPiecePair tpp;
            do{
                tpp = mvDrawer.TerrainMap[l1.X, l1.Y];
                if (tpp != null) {
                    h = tpp.MeanHeight;
                    l1.Z = (short)h;
                    pt = cdUtil.LocationToQuarterXY(l1);
                    if (pt.Y <= pv.Y) 
                        return l1;
                }
                if (!b) {
                    l1 = cdUtil.OffsetLocationByRotatedAxis(l1, 1, 0);
                    p1.VX++;
                } else {
                    l1 = cdUtil.OffsetLocationByRotatedAxis(l1, 0, 1);
                    p1.VY++;
                }
                b = !b;
            } while(occupation.Contains(p1));
            return Location.UNPLACED;
        }

        protected IEnumerator<Location> GetMasterXScanner() {
            Location l = occupation.GetBottomLeft();
            Point3DV pt;

            Int16 i; // dummy
            while (true) {
                yield return l;
                // scan to right along view bottom side.
                cdUtil.OffsetLocationByRotatedAxis(ref l, 1, -1);
                pt = cdUtil.RotatedForViewAxis(l);
                if (pt.VY == -1) {
                    pt.VY = 0;
                    if (occupation.TryGetAt(pt, out i)) {
                        // scan along lower right border
                        cdUtil.OffsetLocationByRotatedAxis(ref l, 0, 1);
                        continue;
                    }
                }
                if (!occupation.TryGetAt(pt, out i)) {
                    cdUtil.OffsetLocationByRotatedAxis(ref l, -1, 1);
                    break;
                }
            }
            // reset previous modification
            cdUtil.OffsetLocationByRotatedAxis(ref l, -1, 1);
            while (true) {
                // scan upper along view right side.
                cdUtil.OffsetLocationByRotatedAxis(ref l, 1, 1);
                pt = cdUtil.RotatedForViewAxis(l);
                if (!occupation.TryGetAt(pt, out i)) {
                    yield break;
                } else {
                    yield return l;
                }
            }
        }

        protected IEnumerator<Location> GetXScanner(ViewObject vo, int ignoreIdx) {
            Rect3DV bounds = vo.Bounds;
            Point3DV pt = bounds.Location;
            //pt.VY += bounds.WidthY;
            int n = bounds.WidthX;
            Location l = cdUtil.RestoreRotated(pt);
            Int16 i; // dummy
            while (n > 0) {
                yield return l;
                cdUtil.OffsetLocationByRotatedAxis(ref l, 1, 0);
                pt.VX++;
                if (!occupation.TryGetAt(pt, out i)) yield break;
                n--;
            }
            yield break;
        }

        protected IEnumerator<Location> GetYScanner(Location start) {
            Location l = start;
            Point3DV pt;
            Int16 i; // dummy
            while (true) {
                yield return l;
                cdUtil.OffsetLocationByRotatedAxis(ref l, 0, 1);
                pt = cdUtil.RotatedForViewAxis(l);
                if (!occupation.TryGetAt(pt, out i)) yield break;
            }
        }

        protected void PrepareOccupationMap(ViewObjectList list) {
            occupation.SetViewArea(rectRequested, this);//scaler, cdUtil, -mvDrawer.TerrainMap.HeightOffset);
            if (list != null) {
                list.Sort();
                objList = list;
                int n = list.ObjectCount;
                for (int i = 0; i < n; i++) {
                    ViewObject o = list[i];
                    Rect3DV r = o.Bounds;
                    Point3DV p = r.Location;
                    int w = r.WidthX;
                    for (int j = 0; j < w; j++) {
                        // set negative index to mark occupied
                        // may overwrited larger index.
                        occupation.SetAt(p, (Int16)(i - n));
                        p.VX++;
                    }
                }
                Int16 idx;
                for (int i = 0; i < n; i++) {
                    ViewObject o = list[i];
                    Rect3DV r = o.Bounds;
                    Point3DV p = new Point3DV(r.VX2-1, r.VY, r.VZ);
                    if (occupation.TryGetAt(p, out idx)) {
                        // set positive index to mark most front of occupied cells
                        if (idx != 0) {
                            idx--;
                            if (idx >= 0 && idx != i) {
                                objList[i].NextObject = objList[idx];
                            }
                            Debug.WriteLine("set keypos " + p + ", vo=" + (i));
                            occupation.SetAt(p, (Int16)(i + 1));
                    //    } else {
                    //        Debug.WriteLine("keypos "+p +" is 0 for vo=" + (i));
                        }
                    //} else {
                    //    Debug.WriteLine("keypos " + p + " is out of area vo=" + (i));
                    }
                }
            }
        }

        protected void AssembleSprites(IEnumerator<Location> xScanner, ViewObject hint) {
            while (xScanner.MoveNext()) {
                Location l = xScanner.Current;
                Point3DV pt;
                //Debug.WriteLine("scanning loc:" + l.X + "," + l.Y);
                IEnumerator<Location> ie = GetYScanner(l);
                int n = objList.ObjectCount;
                Int16 idx;
                while (ie.MoveNext()) {
                    Location l2 = ie.Current;
                    pt = cdUtil.RotatedForViewAxis(l2);
                    if (occupation.TryGetAt(pt, out idx)) {
                        if (idx != 0) {
                            if (idx > 0) {
                                idx--;
                                ViewObject vo = objList[idx];
                                // assemble object behind area
                                if (vo != hint) {
                                    // add object sprites
                                    Debug.WriteLine("asm vo=" + idx + ",bounds=" + vo.Bounds);
                                    AssembleObject(vo);
                                    AssembleSprites(GetXScanner(vo, idx - n), vo);
                                    // abort current y-scanner
                                    //Debug.WriteLine("vo break at:" + pt + ", idx=" + idx);
                                    break;
                                }
                            } else {
                                ViewObject vo = objList[idx + n];
                                if (vo != hint) {
                                    // abort current y-scanner
                                    break;
                                }
                            }
                        }
                        foreach (ISprite sp in GetTerrainSprites(l2)) {
                            queueSprites.AddLast(sp);
                        }
                    }
                }
            }
        }

        protected void AssembleObject(ViewObject vo) {
            if (vo.NextObject != null) {
                AssembleObject(vo.NextObject);
            }
            //Debug.WriteLine("asm " + vo.GetHashCode() + ", bounds=" + vo.Bounds);
            Point3DV p = vo.Bounds.Location;
            ISprite sp = GlobalModules.GraphicManagerOld.CreateSprite(vo.Texture);
            sp.Tag = vo;
            Point pt = cdUtil.LocationToQuarterXY(p);
            Size sz = vo.Texture.Boundary.Size;
            //pt.Offset(-sz.Width, -sz.Height);
            sp.Location = pt;
            queueSprites.AddLast(sp);
        }

        public void Draw(IView owner, ISurfaceOld surfDest, Rectangle rctDest) {
            Point offset = rctDest.Location;
            Point vp = owner.ViewPosition;
            offset.Offset(-vp.X, -vp.Y);
            offset.X += unitWidth;
            drawCells = 0;
            DrawParams param = new DrawParams(surfDest, offset);
            foreach (ISprite sprite in queueSprites.GetDescendEnumerator()) {
                if (sprite != null) {
                    drawCells++;
                    sprite.Draw(param);
                }
            }
        }

        public Location GetHitSprite(IView owner, Point pt, out object tag) {
            tag = null;
            Point pv = owner.ViewPosition;
            pt.Offset(pv.X, pv.Y);
            pt.X -= unitWidth;
            SpriteQueue queue = SpriteQueue;
            Location l = Location.UNPLACED;
            foreach (ISprite sprite in queue.GetAscendEnumerator()) {
                if (sprite != null && sprite.HitTest(pt)) {
                    tag = sprite.Tag;
                    if (sprite.Tag != null) {
                        Point3DV p3d = Point3DV.UNPLACED;
                        if (sprite.Tag is Point3DV) {
                            p3d = (Point3DV)sprite.Tag;
                            l = cdUtil.RestoreRotated(p3d);
                        } else {
                            ViewObject vo = sprite.Tag as ViewObject;
                            if (vo != null) {
                                p3d = vo.Bounds.Location;
                                l = cdUtil.RestoreRotated(p3d);
                            }
                        }
                        break;
                    }
                }
            }
            return l;
        }

        public IEnumerable<ISprite> GetTerrainSprites(Location l) {
            //Debug.WriteLine("AsmSprite for loc:" + l.X + "," + l.Y);
            TerrainMapImpl map = mvDrawer.TerrainMap;
            int hstep = unitHeight;
            foreach (ITerrainPiece tp in map[l.X, l.Y, upperDir]) {
                TerrainPieceTemplate.TerrainPolygonSet tpset = tp.Template.GetPolygons(upperDir);
                l.Z = (short)(tp.BaseHeight);// / Geocon.TerrainHeightStep);
                Point3DV p3dv = cdUtil.RotatedForViewAxis(l);
                Point pos = cdUtil.LocationToQuarterXY(p3dv);
                if (tpset.Ground.IsVisible) {
                    yield return CreateSprite(tpset.Ground, pos, p3dv);
                }
                //Debug.WriteLine(String.Format("tid={0:x4}, n={1}", tpset.ID, Enum.GetName(typeof(ViewDirection), tpset.DiagonalSide)));
                if (tpset.HorzSplitted) {
                    #region tpset.HorzSplitted
                    if (tpset.DiagonalSide == ViewDirection.FRONT) {
                        int n;
                        if (cdUtil.IsDiagonalCliffVisible(map, l)) {
                            // Process Diagonal Cliff
                            n = Math.Max(occupation.GetCliffHeightL(p3dv), occupation.GetCliffHeightR(p3dv));
                            //Debug.WriteLineIf(n >= 0, "CliffDiagonal[H}=" + n);
                            while (n > 0) {
                                int h = pos.Y + n * hstep;
                                Point p0 = new Point(pos.X, h);
                                yield return CreateSprite(CliffPolygon.GetPolygon(0x2f2f), p0, p3dv);
                                n--;
                            }
                            if (n == 0 && tpset.CliffDiagonal != null) {
                                yield return CreateSprite(tpset.CliffDiagonal, pos, p3dv);
                            }
                        }
                    } else {
                        int n;
                        if (cdUtil.IsLeftCliffVisible(map, l)) {
                            // Process Left Cliff
                            n = occupation.GetCliffHeightL(p3dv);
                            //Debug.WriteLineIf(n >= 0, "CliffLeft[H}=" + n);
                            while (n > 0) {
                                int h = pos.Y + n * hstep;
                                Point p0 = new Point(pos.X, h);
                                yield return CreateSprite(CliffPolygon.GetPolygon(0x2ff2), p0, p3dv);
                                n--;
                            }
                            if (n == 0 && tpset.CliffLeft != null) {
                                yield return CreateSprite(tpset.CliffLeft, pos, p3dv);
                            }
                        }
                        if (cdUtil.IsRightCliffVisible(map, l)) {
                            // Process Right Cliff
                            n = occupation.GetCliffHeightR(p3dv);
                            //Debug.WriteLineIf(n >= 0, "CliffRight[H}=" + n);
                            while (n > 0) {
                                int h = pos.Y + n * hstep;
                                Point p0 = new Point(pos.X, h);
                                yield return CreateSprite(CliffPolygon.GetPolygon(0xff22), p0, p3dv);
                                n--;
                            }
                            if (n == 0 && tpset.CliffRight != null) {
                                yield return CreateSprite(tpset.CliffRight, pos, p3dv);
                            }
                        }
                        occupation.SetColumnMasked(p3dv);
                    }
                    #endregion
                } else {
                    #region !tpset.HorzSplitted
                    Rectangle tmp = tpset.Ground.GetBounds(scaler);
                    //Debug.WriteLine("id="+string.Format("{0:x6}", tp.Template.ID)+ "tmp=" + tmp);
                    if (tmp.X < 0) {
                        if (cdUtil.IsLeftCliffVisible(map, l)) {
                            // Process Left Cliff
                            int n = occupation.GetCliffHeightL(p3dv);
                            //Debug.WriteLineIf(n >= 0, "CliffLeft[V}=" + n);
                            while (n > 0) {
                                int h = pos.Y + n * hstep;
                                Point p0 = new Point(pos.X, h);
                                yield return CreateSprite(CliffPolygon.GetPolygon(0x2ff2), p0, p3dv);
                                n --;
                            }
                            if (n == 0 && tpset.CliffLeft != null) {
                                yield return CreateSprite(tpset.CliffLeft, pos, p3dv);
                            }
                        }
                        occupation.SetColumnMaskedL(p3dv);
                    } else {
                        if (cdUtil.IsRightCliffVisible(map, l)) {
                            // Process Right Cliff
                            int n = occupation.GetCliffHeightR(p3dv);
                            //Debug.WriteLineIf(n >= 0, "CliffRight[V}=" + n);
                            while (n > 0) {
                                int h = pos.Y + n * hstep;
                                Point p0 = new Point(pos.X, h);
                                yield return CreateSprite(CliffPolygon.GetPolygon(0xff22), p0, p3dv);
                                n --;
                            }
                            if (n == 0 && tpset.CliffRight != null) {
                                yield return CreateSprite(tpset.CliffRight, pos, p3dv);
                            }
                        }
                        occupation.SetColumnMaskedR(p3dv);
                    }
                    #endregion
                }
            }
            //Debug.WriteLine(".");
            yield break;
        }

        protected ISprite CreateSprite(GroundPolygon poly, Point pt, Point3DV hint) {
            if (poly.IsVisible) {
                IGraphicManagerOld gm = GlobalModules.GraphicManagerOld;
                ITextureOld tx = AppAssignor.DefaultLandTexture.GetTexture(scaler, poly, hint);
                ISprite sp = gm.CreateSprite(tx);
                sp.Location = pt;
                sp.Tag = hint;
                return sp;

            } else {
                //Debug.WriteLine("Sprite invisible for id="+poly.ID.ToString("X4")+" at Point("+pt.X+","+pt.Y+").");
                return null;
            }
        }

        protected ISprite CreateSprite(CliffPolygon poly, Point pt, Point3DV hint) {
            IApparentAssignor asignor = DefaultApparentAssignor.TheInstance;
            IGraphicManagerOld gm = GlobalModules.GraphicManagerOld;
            ITextureOld tx = asignor.DefaultCliffTexture.GetTexture(scaler, poly, hint);
            ISprite sp = gm.CreateSprite(tx);
            sp.Location = pt;
            sp.Tag = hint;
            return sp;
        }

        #region IDisposable implementation

        private bool disposed = false;

        public void Dispose() {
            GC.SuppressFinalize(this);
            this.Dispose(true);
        }

        ~SceneBuilder() {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing) {
            if (this.disposed) {
                return;
            }
            this.disposed = true;
            if (disposing) {
                queueSprites.Clear(false);
                if (objList != null) {
                    objList.Clear();
                }
                occupation = null;
                mvDrawer = null;
                work = null;
            }
        }
        #endregion
    }

}
