//#define DETAIL_LOG

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

using Geocon = nft.core.geometry.GeometricConstants;
using nft.core.game;

namespace nft.impl.view_old {
    public class GroundOccupationMap<T>{
        public static readonly UInt16 BitmaskCellBoundsLeft = 2;//矩形の斜め左下半分をマスク
        public static readonly UInt16 BitmaskCellBoundsRight = 1;//矩形の斜め右下半分をマスク
        private Size3DV szMaxIdx;
        private Size szUnit;
        private Size szFullGroundView;
        private Rectangle rctGround;
        private Rectangle rctView;
        private Size szArray;
        private CoordinationUtil cdUtil;
        private int baseHeight;

        public GroundOccupationMap(Size3DV rotatedWorld, Size initalCapacity) {
            //this.qvDrawer = drawer;
            //this.szMaxIdx = rotatedWorld;
            this.szMaxIdx = new Size3DV( rotatedWorld.VX-1, rotatedWorld.VY-1, rotatedWorld.VZ-1);
            int n = szMaxIdx.VX + szMaxIdx.VY;
            this.szFullGroundView = new Size(n, n);
            this.szArray = initalCapacity;
            this.rctGround = Rectangle.Empty;
            this.array = new T[szArray.Width * szArray.Height];
            this.colMask = new UInt16[szArray.Width];
        }

        public Size3DV RotatedWorldSize { get { return new Size3DV(szMaxIdx.VX+1, szMaxIdx.VY+1, szMaxIdx.VZ+1); } }

        public Size ArraySize { get { return szArray; } }

        public void SetViewArea(Rectangle viewrect, SceneBuilder builder ) {
            MapViewDrawer drawer = builder.ViewDrawer;
            this.baseHeight = drawer.TerrainMap.HeightOffset;
            this.cdUtil = builder.CoordinationUtil;
            Scaler scaler = builder.Scaler;
            int uw = scaler.Scale(Geocon.CellWidthPixel);
            int uh = scaler.Scale(Geocon.CellHeightPixel);
            szUnit = new Size(uw, uh);
            this.rctView = viewrect;
            rctView.Inflate(uw * 2, uh * 2);
            rctView.Y -= (baseHeight * uh) >> 1;
            this.rctGround = RectUtil.DivideCircumscribe(rctView, uw, uw >> 1);
            rctGround.Inflate(2, rctGround.Height >> 1);
            rctGround.Height *= 2;
            rctGround.Y += baseHeight;
            idxBase = rctGround.X + rctGround.Y * rctGround.Width;
            int b = rctGround.Width * rctGround.Height;
            //// For Debug -->
            Debug.WriteLine("baseHeight=" + baseHeight + ", viewrect=" + viewrect);
            Debug.WriteLine("rctView=" + rctView + ", rctGround=" + rctGround);
            Location l = cdUtil.QuarterXYToLocation(rctView.Left, rctView.Bottom, 0);
            Point3DV pt = cdUtil.RotatedForViewAxis(l);
            //ToIndexDebug(pt);
            //// <-- For Debug
            if (array.Length < b || colMask.Length < rctGround.Width) {
                array = new T[b];
                colMask = new UInt16[szArray.Width];
            } else {
                Clear();
            }
        }

        public void Clear() {
            Array.Clear(array, 0, array.Length);
            Array.Clear(colMask, 0, colMask.Length);
        }

        #region implementation for occupation grid
        private T[] array;
        private int idxBase;

        public Location GetBottomLeft() {
            Point3DV pt = new Point3DV(0,0,0);
            if (ToIndex(pt) >= 0) {
                // Select QVPoint(0,0) if its visible;
                Debug.WriteLine("case 0 :Pt(0,0) is visible");
                return cdUtil.RestoreRotated(pt);
            }
            Location l = cdUtil.QuarterXYToLocation(rctView.Left, rctView.Bottom, 0);
            pt = cdUtil.RotatedForViewAxis(l);
            Debug.Write("Pt("+pt.VX+", "+pt.VY+"):");
            if (ToIndex(pt) >= 0) {
                // BottomLeft is in valid range;
                Debug.WriteLine("[case 1] valid cell");
                return l;
            } else if (pt.VX >= 0) {
                if (pt.VY < 0) {
                    // BottomLeft is in the lower-right outside of valid range.
                    // Shift point to upper direction of the view.
                    cdUtil.OffsetLocationByRotatedAxis(ref l, -pt.VY, -pt.VY);
                    Debug.WriteLine("[case 2] shift up ->(" + (pt.VX - pt.VY) + "," + (pt.VY - pt.VY) + ")");
                } else {
                    int o = pt.VY - szMaxIdx.VY;
                    if (o > 0) {
                        // BottomLeft seems to be in the upper outside of valid range.
                        // Shift point to right direction of the view.
                        cdUtil.OffsetLocationByRotatedAxis(ref l, o, -o);
                        Debug.WriteLine("[case 3] shift right->(" + (pt.VX + o) + "," + (pt.VY - o) + ")");
                    } else {
                        Debug.WriteLine("[case 4] out of range?->(" + (pt.VX) + "," + (pt.VY) + ")");
                    }
                }
            } else {//(pt.VX < 0)
                // BottomLeft is in the lower-left outside of valid range.
                if (pt.VY < pt.VX) {
                    int o = Math.Min(pt.VX, pt.VY);
                    // Shift point to upper direction of the view.
                    Debug.WriteLine("[case 5] shift up->(" + (pt.VX - o) + "," + (pt.VY - o) + ")");
                    cdUtil.OffsetLocationByRotatedAxis(ref l, -o, -o);
                } else {
                    // check first if view bottom-right is on valid range.
                    Location l2 = cdUtil.QuarterXYToLocation(rctView.Right, rctView.Bottom, 0);
                    Point3DV pt2 = cdUtil.RotatedForViewAxis(l2);
                    if (pt2.VX < 0) {
                        Debug.WriteLine("[case 6] shift right and up->(" + 0 + "," + (pt2.VY - pt2.VX) + ")");
                        cdUtil.OffsetLocationByRotatedAxis(ref l2, -pt2.VX, -pt2.VX);
                        l = l2;
                    } else {
                        // Shift point to upper direction of the view.
                        Debug.WriteLine("[case 7] shift right->(" + 0 + "," + (pt.VY + pt.VX) + ")");
                        cdUtil.OffsetLocationByRotatedAxis(ref l, -pt.VX, pt.VX);
                    }
                }
                ToIndexDebug(cdUtil.RotatedForViewAxis(l));
            }
            return l;
        }

        public Location GetBottomAtView(int xpos) {
            //Debug.Write("xpos="+xpos+", viewLeft="+rctView.Left);
            if (xpos < 0) {
                Debug.WriteLine("1");
                return Location.UNPLACED;
            }
            Point pt = new Point(xpos, rctView.Bottom);
            if (pt.X >= rctView.Right) {
                Debug.WriteLine("2");
                return Location.UNPLACED;
            }
            Location l = cdUtil.QuarterXYToLocation(pt, 0);
            Point3DV p3d = cdUtil.RotatedForViewAxis(l);
            if (p3d.VX < 0 || p3d.VY < 0) {
                int o = Math.Min(p3d.VX, p3d.VY);
                cdUtil.OffsetLocationByRotatedAxis(ref l, -o, -o);
                p3d.VX -= o;
                p3d.VY -= o;
            }
            if (Contains(p3d))
                return l;
            else {
                Debug.WriteLine("3");
                return Location.UNPLACED;
            }
        }

        public T this[Point3DV pt]{
            get {
                int i = ToIndex(pt);
                if (i >= 0) {
                    return array[i];
                } else {
                    throw new IndexOutOfRangeException(
                        string.Format("index must within the value {0} to {1}"
                        , idxBase,idxBase+array.Length-1));
                }
            }
            set {
                int i = ToIndex(pt);
                if (i >= 0) {
                    array[i] = value;
                } else {
                    throw new IndexOutOfRangeException(
                        string.Format("index must within the value {0} to {1}"
                        , idxBase, idxBase + array.Length - 1));
                }
            }
        }

        /// <summary>
        /// Check if the point is in the range and return occupation object.
        /// </summary>
        /// <param name="pt"></param>
        /// <param name="o"></param>
        /// <returns>false if specfied point is out of range</returns>
        public bool TryGetAt(Point3DV pt, out T o) {
            int i = ToIndex(pt);
            if (i >= 0) {
                o = array[i];
                return true;
            } else {
                o = default(T);
                return false;
            }
        }

        public bool SetAt(Point3DV pt, T o) {
            int i = ToIndex(pt);
            if (i>=0) {
                array[i] = o;
                return true;
            } else {
                return false;
            }
        }

        public bool Contains(Point3DV pt) {
            int i = ToIndex(pt);
            return i >= 0;
        }

        /// <summary>
        /// Convert point to array index.
        /// If the point is out of range, returns -1.
        /// </summary>
        /// <param name="pt"></param>
        /// <returns>correspond array index, or -1 if out of range.</returns>
        protected int ToIndex(Point3DV pt) {
            if (pt.VX < 0 || pt.VX > szMaxIdx.VX) return -1;
            if (pt.VY < 0 || pt.VY > szMaxIdx.VY) return -1;
            int vx = pt.VX - pt.VY + szMaxIdx.VY;
            int vy = szMaxIdx.VX - pt.VX + szMaxIdx.VY - pt.VY;
            //vy <<= 1;
            //vy += szWorld.VZ;// -baseHeight;
            if (rctGround.Contains(vx,vy)) {
                int i = (vy - rctGround.Top) * rctGround.Width + (vx - rctGround.Left);
                //Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->pt2d(" + vx + "," + vy + ")->idx=" + i);
                return i;
            } else {
                //Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->pt2d(" + vx + "," + vy + ")->OUT!");
                return -1;
            }
        }

        protected int ToIndexDebug(Point3DV pt) {
            if (pt.VX < 0 || pt.VX > szMaxIdx.VX) {
                Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->WorldX-OUT!");
                return -1;
            }
            if (pt.VY < 0 || pt.VY > szMaxIdx.VY) {
                Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->WorldY-OUT!");
                return -1;
            }
            int vx = pt.VX - pt.VY + szMaxIdx.VY;
            int vy = szMaxIdx.VX - pt.VX + szMaxIdx.VY - pt.VY;
            //vy <<= 1;
            //vy += szWorld.VZ - baseHeight;
            if (rctGround.Contains(vx, vy)) {
                int i = (vy - rctGround.Top) * rctGround.Width + (vx - rctGround.Left);
                Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->pt2d(" + vx + "," + vy + ")->idx=" + i);
                return i;
            } else {
                Debug.WriteLine("pt3d(" + pt.VX + "," + pt.VY + ")->pt2d(" + vx + "," + vy + ")->OUT!");
                return -1;
            }
        }
        #endregion
        #region implementation for quater view grid column mask
        private UInt16[] colMask;
#if DETAIL_LOG
        private static readonly string DebugFmt = "{0}[{1}]={2} for pos({3},{4})";
#endif
        protected Point ToQVUnit(Point3DV pt) {
            Point pret = new Point(-2, Int16.MaxValue);
            if (pt.VX < 0 || pt.VX > szMaxIdx.VX) return pret;
            if (pt.VY < 0 || pt.VY > szMaxIdx.VY) return pret;
            int vx = pt.VX - pt.VY + szMaxIdx.VY;
            //Point p2 = cdUtil.LocationToQuarterXY(pt);
            if (rctGround.X<=vx && rctGround.Right > vx) {
                int vy = szMaxIdx.VX - pt.VX + szMaxIdx.VY - pt.VY;
                pret.X = vx - rctGround.Left;
                pret.Y = (vy<<1) + (szMaxIdx.VZ - pt.VZ)/Geocon.TerrainHeightStep;
                //Debug.WriteLine("pt3d("+pt.VX+","+pt.VY+")->pt2d("+vx+","+vy+")->pret("+pret.X+","+pret.Y+")");
            }
            return pret;
        }
        
        public void SetColumnMasked(Point3DV pt) {
            Point p = ToQVUnit(pt);
            if (p.X >= 0) {
                int i = p.X ;
                int uh = p.Y;
                uh <<= 2;
                if (colMask[i] == 0) {
                    colMask[i] = (UInt16)(uh + BitmaskCellBoundsRight);
                } else if (colMask[i] - uh >= 4) {
                    colMask[i] = Math.Min((UInt16)(uh + BitmaskCellBoundsRight), colMask[i]);
                }
#if DETAIL_LOG
                Debug.WriteLine(string.Format(DebugFmt,new object[]{"SET l", i, colMask[i], pt.VX, pt.VY}));
#endif
                i++;
                if (i < colMask.Length ){
                    if (colMask[i] == 0) {
                        colMask[i] = (UInt16)(uh + BitmaskCellBoundsLeft);
                    } else if (colMask[i] - uh >= 4) {
                        colMask[i] = Math.Min((UInt16)(uh + BitmaskCellBoundsLeft), colMask[i]);
                    }
                }
#if DETAIL_LOG
                Debug.WriteLine(string.Format(DebugFmt, new object[] { "SET r", i, colMask[i], pt.VX, pt.VY }));
#endif
            }
        }

        public void SetColumnMaskedL(Point3DV pt) {
            Point p = ToQVUnit(pt);
            if (p.X >= 0) {
                int i = p.X;
                int uh = p.Y;
                uh <<= 2;
                if (colMask[i] == 0) {
                    colMask[i] = (UInt16)(uh + BitmaskCellBoundsRight);
                } else if (colMask[i] - uh >= 4) {
                    colMask[i] = Math.Min((UInt16)(uh + BitmaskCellBoundsRight), colMask[i]);
                }
#if DETAIL_LOG
                Debug.WriteLine(string.Format(DebugFmt, new object[] { "SET L", i, colMask[i], pt.VX, pt.VY }));
#endif
            }
        }

        public void SetColumnMaskedR(Point3DV pt) {
            Point p = ToQVUnit(pt);
            if (p.X >= 0) {
                int i = p.X + 1;
                if (i < colMask.Length) {
                    int uh = p.Y;
                    uh <<= 2;
                    if (colMask[i] == 0) {
                        colMask[i] = (UInt16)(uh + BitmaskCellBoundsLeft);
                    } else if (colMask[i] - uh >= 4) {
                        colMask[i] = Math.Min((UInt16)(uh + BitmaskCellBoundsLeft), colMask[i]);
                    }
#if DETAIL_LOG
                    Debug.WriteLine(string.Format(DebugFmt, new object[] { "SET R", i, colMask[i], pt.VX, pt.VY }));
#endif
                }
            }
        }

        public int GetCliffHeightL(Point3DV pt) {
            Point p = ToQVUnit(pt);
            if (p.X >= 0) {
                int i = p.X;
                int uh = colMask[i];
                if (uh == 0) {
#if DETAIL_LOG
                    Debug.WriteLine(string.Format(DebugFmt, new object[] { "Get L0", i, colMask[i], pt.VX, pt.VY }));
#endif
                    pt.VZ = 0;// baseHeight;
                    Point p2 = ToQVUnit(pt);
                    return p2.Y - p.Y;
                }
                if ((uh & BitmaskCellBoundsLeft)!=0) {
                    p.Y--;
                }
#if DETAIL_LOG
                Debug.WriteLine(string.Format(DebugFmt, new object[] { "Get L1", i, colMask[i], pt.VX, pt.VY }));
#endif
                return (uh >> 2)-p.Y;
            }
            return int.MinValue;
        }

        public int GetCliffHeightR(Point3DV pt) {
            Point p = ToQVUnit(pt);
            if (p.X >= 0) {
                int i = p.X + 1;
                if (i < colMask.Length) {
                    int uh = colMask[i];
                    if (uh == 0) {
#if DETAIL_LOG
                        Debug.WriteLine(string.Format(DebugFmt, new object[] { "Get R0", i, colMask[i], pt.VX, pt.VY }));
#endif
                        pt.VZ = 0;// baseHeight;
                        Point p2 = ToQVUnit(pt);
                        return p2.Y - p.Y;
                    }
                    if ((uh & BitmaskCellBoundsRight) != 0) {
                        p.Y--;
                    }
#if DETAIL_LOG
                    Debug.WriteLine(string.Format(DebugFmt, new object[] { "Get R1", i, colMask[i], pt.VX, pt.VY }));
#endif
                    return (uh >> 2) - p.Y;
                }
            }
            return int.MinValue;
        }
        #endregion


    }

}
