
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using nft.framework.drawing;
using nft.core.game;
using nft.impl.game;
using nft.core.geometry;
using Geocon = nft.core.geometry.GeometricConstants;
using nft.framework;
using nft.core;
using System.Diagnostics;

namespace nft.impl.view {
    public class TerrainViewDrawer : ViewDrawerBase {
        TerrainMapImpl map;
        ViewFactor factor;
        SpriteQueue spStack = new SpriteQueue();

        public TerrainViewDrawer(ITerrainMap srcmap) {
            if (!(srcmap is TerrainMapImpl)) {
                map = new TerrainMapImpl(srcmap);
            } else {
                map = srcmap as TerrainMapImpl;
            }
            factor = new ViewFactor(InterCardinalDirection.NORTHEAST);
        }

        public ViewFactor ViewFactor {
            get {
                return factor.Clone();
            }
            set {
                if (factor.Equals(value)) {
                    bool b = (value.Scale != factor.Scale || value.ViewDirection != factor.ViewDirection);
                    Size szold = ContentSize;
                    ViewFactor vwold = factor;
                    factor = value.Clone();
                    if (b) {
                        NotifyContentSizeChanged(vwold, szold);
                    } else {
                        NotifyAllUpdated(false);
                    }
                }
            }
        }

        protected virtual void NotifyContentSizeChanged(ViewFactor oldVF, Size oldSize) {
            double v = ViewFactor.Scaler.Value / oldVF.Scaler.Value;
            // TODO: consider rotation
            foreach (IView vw in views) {
                Rectangle rectPrv = vw.VisibleSourceRect;
                int wo = rectPrv.Width >> 1;
                int ho = rectPrv.Height >> 1;
                Point pos = rectPrv.Location;
                pos.Offset(wo,ho);
                pos.X = (int)(pos.X * v);
                pos.Y = (int)(pos.Y * v);
                pos.Offset(-wo, -ho);
                vw.NotifyContentSizeChanged(pos);
            }
        }

        protected virtual Location ViewToMap(ViewFactor factor, Point pt, int Z) {
            throw new NotImplementedException();
        }

        #region IViewDrawer implementation
        protected override void DrawCore(ISurface surfDest, Graphics g, IView owner, Rectangle rctDest, Region requestSrc) {
            int t1 = System.Environment.TickCount;
            int t2;
            surfDest.Clear(rctDest, Color.Navy);
            Rectangle rectReq = Rectangle.Round(requestSrc.GetBounds(g));
            Scaler scl = ViewFactor.Scaler;
            Size3D szmap = map.Size;
            spStack.Clear(false);
            Point offset = owner.ViewPosition;
            offset.Offset(rctDest.Location);
            using (TreeBuilder builder = new TreeBuilder(this, owner, rectReq)) {
                t2 = System.Environment.TickCount;
                t1 = t2 - t1;
                foreach (TreeElement elm in spStack.GetDescendEnumerator()) {                    
                    ISprite sprite = elm.Sprite;
                    if (sprite != null) {
                        sprite.Draw(surfDest, offset, Scaler.Default, 0);
                    }
                }
            }
            t2 = System.Environment.TickCount - t2;
#if DEBUG
            Debug.WriteLine("Draw ticks=" + t1 + "/" + t2 + ": dest=" + rctDest);
#else
            Console.WriteLine("Draw ticks=" + t1 + "/" + t2 + ": dest=" + rctDest);
#endif
        }

        internal protected TerrainMapImpl TerrainMap { get { return map; } }

        public Size3D WorldSize {
            get { return map.Size; }
        }

        public override Size ContentSize {
            get {
                int x = map.Size.sx*Geocon.CellWidthPixel;
                int y = map.Size.sy*Geocon.CellWidthPixel;
                int w = x + y;
                int h = (w >> 1) + map.Size.sz * Geocon.CellHeightPixel * Geocon.TerrainHeightStep;
                Size sz = new Size(w, h);
                factor.Scaler.Scale(ref sz);
                return sz;
            }
        }

        public override Size ScrollUnit {
            get {
                int u = Geocon.CellWidthPixel;
                Size sz = new Size(u, u >> 1);
                factor.Scaler.Scale(ref sz);
                return sz;
            }
        }
        #endregion

        protected class TreeBuilder : IDisposable{
            TerrainViewDrawer mvDrawer;
            Rectangle rectRequested;
            Scaler scaler;
            Size3D szMap;
            QVCellSelector qvSelector;
            InterCardinalDirection upperDir;
            int unitWidth;
            int unitHeight;
            int[] work;

            public TreeBuilder(TerrainViewDrawer drawer, IView owner, Rectangle requestArea) {
                this.mvDrawer = drawer;
                this.rectRequested = requestArea;
                this.scaler = drawer.ViewFactor.Scaler;
                this.szMap = drawer.map.Size;
                this.qvSelector = QVCellSelector.GetSelector(drawer.ViewFactor.ViewDirection);
                this.unitWidth = scaler.Scale(Geocon.CellWidthPixel);
                this.unitHeight = scaler.Scale(Geocon.CellHeightPixel)>>1;
                //rectRequested.Width += unitWidth;
                this.upperDir = mvDrawer.ViewFactor.ViewDirection;
                // 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[rectRequested.Width / unitWidth + 4];
                foreach (Location l in qvSelector.GetBottomCells(requestArea, mvDrawer)) {
                    Process(l);
                }
            }
            
            protected void Process(Location l) {
                SpriteQueue queue = mvDrawer.spStack;
                Rectangle bounds = qvSelector.GetQVBoundsOfCell(mvDrawer, l);
                //if (rectRequested.IntersectsWith(bounds)) {
                int h = rectRequested.Bottom - bounds.Bottom;
                int i0 = Math.Max(0,(bounds.Right - rectRequested.Left) / unitWidth);
                if (i0 >= work.Length) return;
                ProcData data = new ProcData(l);
                // TODO 位置合わせ適当・Cliffなし
                #region foreach (ITerrainPiece)
                foreach (ITerrainPiece tp in mvDrawer.TerrainMap[l.X, l.Y, upperDir]) {
                    int th = tp.BaseHeight * unitHeight;
                    int h2 = h + th;
                    int i = i0;                        
                    data.tpSet = tp.Template.GetPolygons(upperDir);
                    data.drawPos = bounds.Location;
                    data.drawPos.Y -= th;
                    Rectangle tmp = data.tpSet.Ground.GetBounds(scaler);
                    if (data.tpSet.HorzSplitted) {
                        if (tmp.Bottom == 0) {
                            data.hgap = h2 - work[i];
                            if (0 <= data.hgap) {
                                ProcessLeftCliff(data);
                                work[i] = h2 - bounds.Height;
                                if (++i < work.Length && 0 <= (data.hgap = h2 - work[i])) {
                                    ProcessRightCliff(data);
                                    work[i] = h2 - bounds.Height;
                                }
                                CreateSprite(data.tpSet.Ground, data.drawPos, queue);
                            } else if (++i < work.Length && 0 <= (data.hgap = h2 - work[i])) {
                                ProcessRightCliff(data);
                                work[i] = h2 - bounds.Height;
                                CreateSprite(data.tpSet.Ground, data.drawPos, queue);
                            }
                        } else {
                            data.hgap = h2 - work[i];
                            if (0 <= data.hgap) {
                                work[i] = h2 - bounds.Height;
                            }
                            int d;
                            if (++i < work.Length && 0 <= (d = h2 - work[i])) {
                                data.hgap = Math.Max(data.hgap, d);
                                work[i] = h2 - bounds.Height;
                            }
                            if (0 <= data.hgap) {
                                ProcessDiagonalCliff(data);
                                CreateSprite(data.tpSet.Ground, data.drawPos, queue);
                            }
                        }
                    } else {
                        if ( tmp.X < 0) {
                            data.hgap = h2 - work[i];
                            if (0 <= data.hgap) {
                                ProcessLeftCliff(data);
                                work[i] = h2 - bounds.Height;
                                CreateSprite(data.tpSet.Ground, data.drawPos, queue);
                            }
                        } else {
                            if (++i < work.Length && 0 <= (data.hgap = h2 - work[i])) {
                                ProcessRightCliff(data);
                                work[i] = h2 - bounds.Height;
                                CreateSprite(data.tpSet.Ground, data.drawPos, queue);
                            }
                        }
                    }
                }
                #endregion                    
                if(bounds.Bottom>rectRequested.Top && bounds.Right>rectRequested.Left ) {
                    if (qvSelector.MoveToUpperLeftCell(ref l, szMap)) {
                        Process(l);
                    }
                }                    
                //}
            }

            protected void ProcessDiagonalCliff(ProcData data) {
                SpriteQueue queue = mvDrawer.spStack;
                if (qvSelector.IsDiagonalCliffVisible(mvDrawer.TerrainMap, data.cellLoc)!=0) {
                    Point pt = new Point(data.drawPos.X, data.drawPos.Y);
                    if (data.tpSet.CliffDiagonal != null) {
                        CreateSprite(data.tpSet.CliffDiagonal, pt, queue);
                    }
                    while (data.hgap > 0) {
                        int h = Geocon.TerrainHeightMax * unitHeight;
                        pt.Y += h;
                        CreateSprite(CliffPolygon.GetPolygon(0x8f8f), pt, queue);
                        data.hgap -= h;
                    }
                }
            }

            protected void ProcessLeftCliff(ProcData data) {
                SpriteQueue queue = mvDrawer.spStack;
                if (qvSelector.IsLeftCliffVisible(mvDrawer.TerrainMap, data.cellLoc)) {
                    Point pt = new Point(data.drawPos.X, data.drawPos.Y);
                    if (data.tpSet.CliffLeft != null) {
                        CreateSprite(data.tpSet.CliffLeft, pt, queue);
                    }
                    while (data.hgap > 0) {
                        int h = Geocon.TerrainHeightMax * unitHeight;
                        pt.Y += h;
                        CreateSprite(CliffPolygon.GetPolygon(0x8ff8), pt, queue);
                        data.hgap -= h;
                    }
                }
            }

            protected void ProcessRightCliff(ProcData data) {
                SpriteQueue queue = mvDrawer.spStack;
                if (qvSelector.IsRightCliffVisible(mvDrawer.TerrainMap, data.cellLoc)) {
                    Point pt = new Point(data.drawPos.X, data.drawPos.Y);
                    if (data.tpSet.CliffRight != null) {
                        CreateSprite(data.tpSet.CliffRight, pt, queue);
                    }
                    while (data.hgap > 0) {
                        int h = Geocon.TerrainHeightMax * unitHeight;
                        pt.Y += h;
                        CreateSprite(CliffPolygon.GetPolygon(0xff88), pt, queue);
                        data.hgap -= h;
                    }
                }
            }

            protected void CreateSprite(GroundPolygon poly, Point pt, SpriteQueue queue) {
                if(poly.IsVisible){
                    IApparentAssignor asignor = DefaultApparentAssignor.TheInstance;
                    IGraphicManager gm = GlobalModules.GraphicManager;
                    ITexture tx = asignor.DefaultLandTexture.GetTexture(scaler, poly);
                    ISprite sp = gm.CreateSprite(tx);
                    sp.Location = pt;
                    queue.AddLast(sp);
#if DEBUG
                } else {
                    //Debug.WriteLine("Sprite invisible for id="+poly.ID.ToString("X4")+" at Point("+pt.X+","+pt.Y+").");
#endif
                }
            }

            protected void CreateSprite(CliffPolygon poly, Point pt, SpriteQueue queue) {
                IApparentAssignor asignor = DefaultApparentAssignor.TheInstance;
                IGraphicManager gm = GlobalModules.GraphicManager;
                ITexture tx = asignor.DefaultCliffTexture.GetTexture(scaler, poly);
                ISprite sp = gm.CreateSprite(tx);
                sp.Location = pt;
                queue.AddLast(sp);
            }

            public class ProcData {
                public Point drawPos;
                public Location cellLoc;
                public TerrainPieceTemplate.TerrainPolygonSet tpSet;
                public int hgap;
                public ProcData(Location l) {
                    this.cellLoc = l;
                }
            }

            #region IDisposable メンバ
            public void Dispose() {
                qvSelector = null;
            }
            #endregion
        }
    }
}
