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

namespace nft.impl.view {
    public class DrawIndexUtil {
        private const UInt32 IndexForUnknown = 0xffffffff;
        private const UInt32 TypeMask =  0xf0000000;
        private const UInt32 ValueMask = 0x0fffffff;
        private const UInt32 Mask_GroundPolygon = 0x80000000;
        private const UInt32 Mask_GroundPolygon2 = 0xa0000000;
        private const UInt32 Mask_CliffPolygon = 0xc0000000;
        private const UInt32 Mask_CliffPolygon_L = 0xc0000000;
        private const UInt32 Mask_CliffPolygon_R = 0xe0000000;
        private const UInt32 Mask_CliffPolygon_D = 0xf0000000;
        private static UInt32[] cliffMaskMap;

        static DrawIndexUtil() {
            cliffMaskMap = new UInt32[Enum.GetValues(typeof(CliffPolygon.CliffSide)).Length];
            cliffMaskMap[(int)CliffPolygon.CliffSide.Left] = Mask_CliffPolygon_L;
            cliffMaskMap[(int)CliffPolygon.CliffSide.Right] = Mask_CliffPolygon_R;
            cliffMaskMap[(int)CliffPolygon.CliffSide.Diagonal] = Mask_CliffPolygon_D;
        }

        public static UInt32 GetIndexForTerrain(Point3DV pos, ITerrainPolygon polygon) {
            if (polygon is GroundPolygon) {
                return GetIndexForGroundPolygon((GroundPolygon)polygon, pos);
            } else if (polygon is CliffPolygon) {
                return GetIndexForCliffPolygon((CliffPolygon)polygon, pos);
            }
            return IndexForUnknown;
        }

        public static UInt32 GetIndexForGroundPolygon(GroundPolygon polygon, Point3DV pos) {
            bool b = polygon.DiagonalSide == ViewDirection8.FRONT || polygon.DiagonalSide == ViewDirection8.LEFT;
            UInt32 idx = (UInt32)(pos.VX + pos.VY + (b ? Mask_GroundPolygon2 : Mask_GroundPolygon));
            return idx;
        }

        public static UInt32 GetIndexForCliffPolygon(CliffPolygon polygon, Point3DV pos) {
            UInt32 idx = (UInt32)(pos.VX + pos.VY + cliffMaskMap[(int)polygon._Side]);
            return idx;
        }


        internal static bool ResolveTerrain(ref SelectionInfo info) {
            Point mousePos = info.ScreenPosition;
            //mousePos.Y++;
            Debug.Write("ResolveTerrain:  mouse=(" + mousePos.X + "," + mousePos.Y + ")");
            PointF3D p3dv = info.Surface.ToWorldPosition(mousePos);
            UInt32 idx = info.Surface.HitTestAt(mousePos.X, mousePos.Y);
            info.HitTestValue = idx;
            TerrainMapImpl map = info.TerrainMap as TerrainMapImpl;
            if (AdjustPointForGround(ref p3dv, idx)) {
                info.WorldPosition = p3dv;
                int gx = (int)Math.Round(p3dv.X / Geocon.UnitWidthPixel);
                int gy = (int)Math.Round(p3dv.Y / Geocon.UnitWidthPixel);
                PointF3D p3d = new PointF3D(gx * Geocon.UnitWidthPixel, gy * Geocon.UnitWidthPixel, 0);
                Point rep = info.Surface.ToScreenPosition(p3d);
                int xoff = mousePos.X - rep.X;
                int yoff = mousePos.Y - rep.Y; 
                Debug.Write(":Ground->(" + p3dv.X + "," + p3dv.Y + ") : grid[" + gx + "," + gy + "]");
                Location l = info.SceneBuilder.cdUtil.IsometricGridToLocation(gx, gy, 0);
                InterCardinalDirection updir = info.SceneBuilder.upperDir;
                foreach (ITerrainPiece p in map[l.X, l.Y, updir]) {
                    TerrainPieceTemplate.TerrainPolygonSet tpset = p.Template.GetPolygons(updir);
                    if (tpset.Ground.IsVisible) {
                        int h1 = p.BaseHeight * Geocon.UnitHeightPixel;
                        Point offset = new Point(xoff, yoff+h1);
                        if (TerrainUtil.IsInnerPoint2D(tpset.Ground.GetVerticis(info.SceneBuilder.scaler), offset)) {
                            Debug.WriteLine("+(" + xoff + "," + yoff + ")");
                            info.TerrainPiece = p;
                            info.Polygon = tpset.Ground;
                            info.Offset = offset;
                            info.Location = new Location(l.X, l.Y, p.BaseHeight);
                            info.SceneBuilder.CreateMarker(gx, gy, SceneBuilder.TrMk_Ground);
                            break;
                        //} else if (pol == null) {
                        //    pol = tpset.Ground;
                        }
                    }
                }
                //Debug.WriteLineIf(pol == null, "WTF!?");
                return info.Polygon != null;
            } else if (AdjustPointForCliff(ref p3dv, idx)) {
                info.WorldPosition = p3dv;
                int gx = (int)Math.Round(p3dv.X / Geocon.UnitWidthPixel);
                int gy = (int)Math.Round(p3dv.Y / Geocon.UnitWidthPixel);
                PointF3D p3d = new PointF3D(gx * Geocon.UnitWidthPixel, gy * Geocon.UnitWidthPixel, 0);
                Point rep = info.Surface.ToScreenPosition(p3d);
                int xoff = mousePos.X - rep.X;
                int yoff = mousePos.Y - rep.Y;
                Debug.Write(":Cliff->(" + p3dv.X + "," + p3dv.Y + ") : grid[" + gx + "," + gy + "]");
                Location l = info.SceneBuilder.cdUtil.IsometricGridToLocation(gx, gy, 0);
                InterCardinalDirection updir = info.SceneBuilder.upperDir;
                UInt32 testval = (idx & Mask_CliffPolygon_D);
                TerrainPiecePair pair = map[l.X, l.Y];
                if (pair != null) {
                    TerrainPiecePair.ArrangedTerrainPieces atp = pair.GetTerrainPieces(updir);
                    //if (p.BaseHeight < p3dv.Z) continue;
                    if (testval == Mask_CliffPolygon_D) {
                        Debug.WriteLine(":Diagonal");
                        info.Polygon = atp.CliffDiagonal;
                        info.TerrainPiece = atp.Pieces[atp.Index4DiagonalCliff];
                        info.Location = new Location(l.X, l.Y, info.TerrainPiece.BaseHeight);
                        int h1 = info.TerrainPiece.BaseHeight * Geocon.UnitHeightPixel;
                        info.Offset = new Point(xoff, yoff + h1);
                        info.SceneBuilder.CreateMarker(gx, gy, SceneBuilder.TrMk_CliffDiag);
                    } else if (testval == Mask_CliffPolygon_R) {
                        Debug.WriteLine(":Right");
                        info.Polygon = atp.CliffRight;
                        info.TerrainPiece = atp.Pieces[atp.Index4RightCliff];
                        info.Location = new Location(l.X, l.Y, info.TerrainPiece.BaseHeight);
                        int h1 = info.TerrainPiece.BaseHeight * Geocon.UnitHeightPixel;
                        info.Offset = new Point(xoff, yoff + h1);
                        info.SceneBuilder.CreateMarker(gx, gy, SceneBuilder.TrMk_CliffRight);
                    } else {
                        Debug.WriteLine(":Left");
                        info.Polygon = atp.CliffLeft;
                        info.TerrainPiece = atp.Pieces[atp.Index4LeftCliff];
                        info.Location = new Location(l.X, l.Y, info.TerrainPiece.BaseHeight);
                        int h1 = info.TerrainPiece.BaseHeight * Geocon.UnitHeightPixel;
                        info.Offset = new Point(xoff, yoff + h1);
                        info.SceneBuilder.CreateMarker(gx, gy, SceneBuilder.TrMk_CliffLeft);
                    }
                    //Debug.WriteLineIf(pol == null, "WTF2!?");
                    return info.TerrainPiece != null;
                }
            }
            //Debug.WriteLine("--");
            return false;
        }

        internal static bool AdjustPointForCliff(ref PointF3D p3dv, UInt32 idx) {
            if ((idx & Mask_CliffPolygon) == Mask_CliffPolygon) {
                AdjustPoint(ref p3dv, ValueMask & idx);
                return true;
            } else {
                return false;
            }
        }

        internal static bool AdjustPointForGround(ref PointF3D p3dv, UInt32 idx) {
            UInt32 test = (idx & TypeMask);
            if (test == Mask_GroundPolygon || test == Mask_GroundPolygon2) {
                AdjustPoint(ref p3dv, ValueMask & idx);
                return true;
            } else {
                return false;
            }
        }

        private static void AdjustPoint(ref PointF3D p3dv, UInt32 idxval) {
            float diff = p3dv.X + p3dv.Y - idxval;
            p3dv.Z = diff;
            diff = diff/2.0f;
            float x = p3dv.X - diff;
            float y = p3dv.Y - diff;
            if (x < 0) {
                p3dv.Z -= x * 2;
                x = 0;
            }
            if (y < 0) {
                p3dv.Z -= y * 2;
                y = 0;
            }
            int ix = (int)Math.Floor(x);
            int iy = (int)Math.Floor(y);
            int v2 = ix + iy;
            p3dv.X = ix;
            p3dv.Y = iy;
            //Debug.Write(" [check idxval-v2=" + (idxval - v2) + ", fx=" + x + ", ix=" + ix + ", fy=" + y + ", iy=" + iy + "]");
            if (idxval != v2) {
                /*
                float w = (x - ix) / Geocon.UnitWidthPixel;
                if (w > 0.5f) {
                    p3dv.X += 1;
                } else {
                    p3dv.Y += 1;
                }
                 */
            }
        }
    }
}
