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 ITerrainPiece = nft.core.geometry.ITerrainPiece;
using System.Diagnostics;
using nft.impl.game;
using nft.core.game;

namespace nft.impl.view_old {
    /// <summary>
    /// 廃止の方向 （TerrainDrawerで利用）
    /// </summary>
    public abstract class QVCellSelector {
        static private QVCellSelector[] stock;

        static QVCellSelector(){
            stock = new QVCellSelector[Enum.GetValues(typeof(InterCardinalDirection)).Length];
            stock[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHEAST)] = new NECellSelector();
            // TODO create each direction specific selector.
            stock[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHWEST)] = new NECellSelector();
            stock[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHEAST)] = new NECellSelector();
            stock[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHWEST)] = new NECellSelector();
        }

        public static QVCellSelector GetSelector(InterCardinalDirection upperside) {
            return stock[Direction.ToZeroBaseIndex(upperside)];
        }

        protected abstract InterCardinalDirection UpperSide { get; }

        /// <summary>
        /// Convert screen coordinate point to the location of the cell in map world.
        /// </summary>
        /// <param name="drawer">map drawer (which has it's world data)</param>
        /// <param name="screen">convert point on the screen</param>
        /// <param name="locZ">world Z coordination value</param>
        /// <returns>north east corner of the cell which contains specified screen point</returns>
        public abstract Location FromScreenPointToGrid(TerrainDrawer drawer, Point screen, int locZ);

        /// <summary>
        /// Convert screen coordinate point to the location in map world.
        /// </summary>
        /// <param name="drawer">map drawer (which has it's world data)</param>
        /// <param name="screen">convert point on the screen</param>
        /// <param name="locZ">world Z coordination value (-1 = find first visible object)</param>
        /// <returns>new LocationF object corresponds to the screen point</returns>
        public abstract LocationF FromScreenPoint(TerrainDrawer drawer, Point screen, float locZ);

        /// <summary>
        /// Convert world location to a point on screen coordinate
        /// </summary>
        /// <param name="drawer">map drawer contains the location</param>
        /// <param name="loc">the location to be converted</param>
        /// <returns>converted point on the screen</returns>
        public abstract Point ToScreenPoint(TerrainDrawer drawer, Location loc);

        /// <summary>
        /// Offset Location 'l' by the grid count of first person view coordination.
        /// </summary>
        /// <param name="drawer">target map drawer</param>
        /// <param name="l">location to modify</param>
        /// <param name="gridUpRight">offset grid count toword upper right</param>
        /// <param name="gridUpLeft">offset grid count toword upper left</param>
        public abstract void ToOffsetGridLocation(TerrainDrawer drawer, ref Location l, int gridUpRight, int gridUpLeft);

        /// <summary>
        /// Offset Location 'l' by the grid count of first person view coordination.
        /// </summary>
        /// <param name="drawer">target map drawer</param>
        /// <param name="origin">the origin location</param>
        /// <param name="gridUpRight">offset grid count toword upper right</param>
        /// <param name="gridUpLeft">offset grid count toword upper left</param>
        /// <returns>new location object</returns>
        public abstract Location GetOffsetGridLocation(TerrainDrawer drawer, Location origin, int gridUpRight, int gridUpLeft);

        /// <summary>
        /// Returns enumeration of locations which indicate cells on the bottom line of the view.
        /// </summary>
        /// <param name="viewrect">visible rectangle of the view</param>
        /// <param name="drawer">the drawer of target view</param>
        /// <returns></returns>
        public abstract IEnumerable<Location> GetBottomCells(Rectangle viewrect, TerrainDrawer drawer);

        /// <summary>
        /// Move location to upper left neighbor cell.
        /// </summary>
        /// <param name="?"></param>
        /// <returns>returns false if the cell is out of the world bounds.</returns>
        public abstract bool MoveToUpperLeftCell(ref Location l, Size3D szWorld);

        /// <summary>
        /// Returns locations of all cells which are on the front side
        /// (in the first person view) of the specified cube.
        /// </summary>
        /// <param name="drawer">target map drawer</param>
        /// <param name="cuberegion">cubic region on the map</param>
        /// <returns>locations of cells on the cube front edge.</returns>
        public abstract IEnumerable<Location> GetCubeFrontCells(TerrainDrawer drawer, Rect3D cuberegion);

        /// <summary>
        /// Returns locations of all cells which are the nearest behind of
        /// (in the first person view) the specified cube.
        /// </summary>
        /// <param name="drawer">target map drawer</param>
        /// <param name="cuberegion">cubic region on the map</param>
        /// <returns>locations of cells the nearest behind of the cube bottom plane.</returns>
        public abstract IEnumerable<Location> GetCubeBehindCells(TerrainDrawer drawer, Rect3D cuberegion);

        /// <summary>
        /// Returns locations of all cells which are the nearest behind of
        /// (in the first person view) the specified cube.
        /// </summary>
        /// <param name="drawer">target map drawer</param>
        /// <param name="orgcell">the location of origin cell</param>
        /// <returns>locations of cells the nearest behind of the specified cell.</returns>
        public abstract IEnumerable<Location> GetCellBehindCells(TerrainDrawer drawer, Location orgcell);

        /// <summary>
        /// returns quarter view projection bounds of the cell at specified location.
        /// the coordination is mapped for virtual view which contains whole world view.
        /// </summary>
        /// <param name="drawer">target map darwer</param>
        /// <param name="loc">target location</param>
        /// <returns>world view projection bounds.</returns>
        public abstract Rectangle GetQVBoundsOfCell(TerrainDrawer drawer, Location loc);

        protected bool IsInTheWorld(Size3D szWorld, Location l) {
            return (l.X >= 0 && l.X < szWorld.sx && l.Y >= 0 && l.Y < szWorld.sy && l.Z >= 0 && l.Z < szWorld.sz);
        }

        /// <summary>
        /// returns true if the cliff on the left side of the cell is visible.
        /// </summary>
        /// <param name="terrainMapImpl"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public abstract bool IsLeftCliffVisible(ITerrainMap map, Location l);

        /// <summary>
        /// returns true if the cliff on the left side of the cell is visible.
        /// </summary>
        /// <param name="terrainMapImpl"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public abstract bool IsRightCliffVisible(ITerrainMap map, Location l);

        public abstract int IsDiagonalCliffVisible(TerrainMapImpl map, Location l);
    }

    class NECellSelector : QVCellSelector {
        protected override InterCardinalDirection UpperSide 
        { get { return InterCardinalDirection.NORTHEAST; } }

        public override Location FromScreenPointToGrid(TerrainDrawer drawer, Point screen, int locZ) {
            Scaler scaler = drawer.ViewFactor.Scaler;
            Size3D szWorld = drawer.WorldSize;
            int unit = scaler.Scale(Geocon.CellWidthPixel);
            screen.X /= unit;
            screen.Y /= unit;
            int vz = (szWorld.sz - locZ) * scaler.Scale(Geocon.CellHeightPixel)/unit;
            int locX = ((szWorld.sx + screen.X) >> 1) - screen.Y + vz;
            int locY = szWorld.sy + locX - screen.X;
            return new Location(locX, locY, locZ);
        }

        public override LocationF FromScreenPoint(TerrainDrawer drawer, Point screen, float locZ) {
            Scaler scaler = drawer.ViewFactor.Scaler;
            Size3D szWorld = drawer.WorldSize;
            float unit = (float)scaler.Scale(Geocon.CellWidthPixel);
            float vz = (szWorld.sz - locZ) * scaler.Scale(Geocon.CellHeightPixel);
            float sx = screen.X / unit;
            float sy;
            if (screen.Y < vz) {
                vz = screen.Y;
                sy = 0;
            } else {
                sy = (screen.Y - vz) / unit;
            }
            float locX = ((szWorld.sx + sx) / 2.0f) - sy;
            float locY = szWorld.sy + locX - sx;
//#if DEBUG
//            float cx = (locX+szWorld.sy-locY)*unit;
//            float cy = (szWorld.sx-locX+szWorld.sy-locY)*unit/2;
//            cy += vz;
//            Debug.WriteLine("Point:x=" + cx + ",y=" + cy);
//#endif
            return new LocationF(locX, locY, locZ);
        }

        public override Point ToScreenPoint(TerrainDrawer drawer, Location loc) {
            throw new NotImplementedException();
        }

        public override void ToOffsetGridLocation(TerrainDrawer drawer, ref Location l, int gridUpRight, int gridUpLeft) {
            throw new NotImplementedException();
        }

        public override Location GetOffsetGridLocation(TerrainDrawer drawer, Location origin, int gridUpRight, int gridUpLeft) {
            throw new NotImplementedException();
        }

        public override IEnumerable<Location> GetBottomCells(Rectangle viewrect, TerrainDrawer drawer) {
            ITerrainMap map = drawer.TerrainMap;
            Point pt = new Point(viewrect.Left, viewrect.Bottom);
            LocationF l0 = FromScreenPoint(drawer, pt , 0.0f);
            Location loc = Location.Ceiling(l0);
            Size3D szWorld = drawer.WorldSize;
            
            int step = drawer.ViewFactor.Scaler.Scale(Geocon.CellWidthPixel);
            if (loc.X < 0) {
                pt.Y += loc.X * step;
                loc.Y -= loc.X;
                if (loc.Y >= szWorld.sy) {
                    loc.Y = szWorld.sy;
                    loc.Y--;
                }
                loc.X = 0;
            } else if(loc.Y<0){
                pt.Y += (loc.Y * step);
                loc.X -= loc.Y;
                if (loc.X >= szWorld.sx) {
                    loc.X = szWorld.sx;
                    loc.X--;
                }
                loc.Y = 0;                    
            }
            int stp2 = step >> 1;
            // Scan bottom side (left to right).
            while (true) {
                yield return loc;
                if (pt.X > viewrect.Right) break;
                if (loc.Y > 0) {
                    pt.X += step;
                    //if (pt.X > viewrect.Right) break;
                    loc.Y--;
                    if (viewrect.Bottom - pt.Y > stp2) {
                        pt.Y += stp2;
                    } else {
                        pt.X += step;
                        //if (pt.X > viewrect.Right) break;
                        loc.X++;
                        if (loc.X >= szWorld.sx) break;
                    }
                } else {
                    pt.X += step;
                    //if (pt.X > viewrect.Right) break;
                    loc.X++;
                    if (loc.X >= szWorld.sx) break;
                }
            }
            // Scan right side (bottom to up).
            while (loc.X<szWorld.sx) {
                loc.Y++;
                if (loc.Y >= szWorld.sy) break;
                pt.Y -= stp2;
                if (pt.Y <= 0) break;
                yield return loc;
                loc.X++;
            }
            yield break;
        }

        public override bool MoveToUpperLeftCell(ref Location l, Size3D szWorld) {
            l.Y++;
            return szWorld.sy > l.Y;
        }

        public override bool IsLeftCliffVisible(ITerrainMap map, Location l) {
            return map.IsCliffedBounds(Direction4.WEST, l.X, l.Y);
        }

        public override bool IsRightCliffVisible(ITerrainMap map, Location l) {
            return map.IsCliffedBounds(Direction4.SOUTH, l.X, l.Y);
        }

        public override int IsDiagonalCliffVisible(TerrainMapImpl map, Location l) {
            TerrainPiecePair pair = map[l.X, l.Y];
            ITerrainPiece p1 = pair[Direction4.NORTH];
            ITerrainPiece p2 = pair[Direction4.SOUTH];
            int d1 = p1.GetHeightAt(InterCardinalDirection.NORTHWEST) 
                - p2.GetHeightAt(InterCardinalDirection.NORTHWEST);
            int d2 = p1.GetHeightAt(InterCardinalDirection.SOUTHEAST) 
                - p2.GetHeightAt(InterCardinalDirection.SOUTHEAST);
            return (Math.Abs(d1) > Math.Abs(d2)) ? d1 : d2;
        }

        public override IEnumerable<Location> GetCubeFrontCells(TerrainDrawer drawer, Rect3D cuberegion) {
            throw new NotImplementedException();
        }

        public override IEnumerable<Location> GetCubeBehindCells(TerrainDrawer drawer, Rect3D cuberegion) {
            throw new NotImplementedException();
        }

        public override IEnumerable<Location> GetCellBehindCells(TerrainDrawer drawer, Location orgcell) {
            Size3D szWorld = drawer.WorldSize;
            bool b = (szWorld.sx - orgcell.X) > 1;
            if (szWorld.sy - orgcell.Y > 1) {
                yield return new Location(orgcell.X, orgcell.Y + 1, orgcell.Z);
                if(b)
                    yield return new Location(orgcell.X + 1, orgcell.Y + 1, orgcell.Z);
            }
            if (b) {
                yield return new Location(orgcell.X + 1, orgcell.Y, orgcell.Z);
            }
            yield break;
        }

        public override Rectangle GetQVBoundsOfCell(TerrainDrawer drawer, Location loc) {
            Scaler scaler = drawer.ViewFactor.Scaler;
            Size3D szWorld = drawer.WorldSize;
            int vx = szWorld.sy + loc.X - loc.Y;
            int vy = szWorld.sx - loc.X + szWorld.sy - loc.Y;
            int uw = scaler.Scale(Geocon.CellWidthPixel);
            int uh = scaler.Scale(Geocon.CellHeightPixel);
            vx *= uw;
            vy *= uw;
            vy >>= 1;
            vy += (szWorld.sz - loc.Z) * uh;
            return new Rectangle(vx, vy, uw<<1, uw);
        }

    }

}
