using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.Serialization;
using nft.framework.drawing;

using Geocon = nft.core.geometry.GeometricConstants;

namespace nft.core.geometry {
    public class HeightCutPolygonSet {
        static Dictionary<ushort, HeightCutPolygon> stock = new Dictionary<ushort, HeightCutPolygon>(256);
        static HeightCutPolygonSet() {
            initPatterns(Geocon.TerrainHeightStep, Geocon.TerrainHeightMax);
        }
        public static HeightCutPolygonSet Get(ITerrainPiece tp, int waterLvl) {
            return Get(tp.Template.ID, tp.BaseHeight, waterLvl);
        }

        public static HeightCutPolygonSet Get(ushort idTerrainPiece, int baseHeight, int waterLvl) {
            return null;
        }

        #region initialize polygon pattern table
        private static void initPatterns(int step, int max) {
            registerRectPattern(0, 0, 0, 0);
            for (int p1 = 0; p1 <= max; p1 += step) {
                for (int p2 = 0; p2 <= max; p2 += step) {
                    for (int p3 = 0; p3 <= max; p3 += step) {
                        if (p1 * p2 * p3 > 0) continue;
                        int pmax = Math.Max(p1, Math.Max(p2, p3));
                        if (pmax <= step) continue;
                        for (int h = step; h <= pmax; h += step) {
                            registerTrianglePattern(p1, p2, p3, -1, h);
                            registerTrianglePattern(p1, p2, -1, p3, h);
                            registerTrianglePattern(p1, -1, p2, p3, h);
                            registerTrianglePattern(-1, p1, p2, p3, h);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Note that normal vector of plane will be determined by first 3 points.
        /// All points should be on the same plane.
        /// </summary>
        /// <param name="near"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <param name="far"></param>
        private static void registerRectPattern(int near, int left, int far, int right) {
            uint idm = PrivateMakePolygonID(near, left, right, far, hcut);
            int hp = Geocon.CellWidthPixel >> 2;
            if (!stock.ContainsKey(id)) {
                Location[] pt = new Location[4];
                int idx = 0;
                pt[idx++] = new Location(Geocon.CellWidthPixel, 0, left * hp);
                pt[idx++] = new Location(0, 0, near * hp);
                pt[idx++] = new Location(0, Geocon.CellWidthPixel, right * hp);
                pt[idx++] = new Location(Geocon.CellWidthPixel, Geocon.CellWidthPixel, far * hp);
                stock.Add(id, new HeightCutPolygon(id, pt));
            }
        }
        /// <summary>
        /// One of the argument should be negative so that which means unused point.
        /// </summary>
        /// <param name="near"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <param name="far"></param>
        private static void registerTrianglePattern(int near, int left, int far, int right, int hcut) {
            ushort id = GroundPolygon.PrivateMakePolygonID(near, left, right, far);
            int hp = Geocon.CellWidthPixel >> 2;
            if (!stock.ContainsKey(id)) {
                Location[] pt = new Location[3];
                int idx = 0;
                if (left >= 0)
                    pt[idx++] = new Location(0, Geocon.CellWidthPixel, left * hp);
                if (near >= 0)
                    pt[idx++] = new Location(0, 0, near * hp);
                if (right >= 0)
                    pt[idx++] = new Location(Geocon.CellWidthPixel, 0, right * hp);
                if (far >= 0)
                    pt[idx++] = new Location(Geocon.CellWidthPixel, Geocon.CellWidthPixel, far * hp);

                stock.Add(id, new HeightCutPolygon(id, pt));
            }
        }
        #endregion
        //HorizontalPolygon
    }

    public class HeightCutPolygon : ITerrainPolygon, ISerializable {
        public readonly ushort id;
        public readonly bool IsVisible;
        protected Point[] verticis;
        protected Rectangle bounds;
        protected Vect3D normal;

        /// <summary>
        /// Determined by surface normal angle between the sun light. 255 is maximum.
        /// Visible surface has positive value, effected at least by environment light.
        /// Zero value means inverted or perpendicular surface against camera angle.
        /// </summary>
        public readonly int Brightness;

        protected HeightCutPolygon(ushort id, Location[] pts) {
            this.id = id;
            this.bounds = new Rectangle();
            this.verticis = Location.ConvertToQuarterViewPos(pts, ref bounds);
            normal = Vect3D.CalcNormalVector(pts);
            if (normal.Z < 0.0) {
                normal.Multiple(-1.0);
            }
            double inprd = normal.InnerProduct(Geocon.CameraEye);
            if (inprd > 0.0) {
                IsVisible = false;
            } else {
                IsVisible = true;
                if (inprd == 0) {
                    Point left = verticis[0];
                    Point right = verticis[0];
                    for (int i = 1; i < verticis.Length; i++) {
                        if (verticis[i].X < left.X)
                            left = verticis[i];
                        if (verticis[i].X > right.X)
                            right = verticis[i];
                    }
                    this.verticis = new Point[] { left, right };
                }
            }
            inprd = normal.InnerProduct(Geocon.SunLight);
            double l = Geocon.EnvironmentLight;
            l += (l - 1.0) * inprd;
            Brightness = IsVisible ? (int)(0xff * l) : 0;
        }


        #region ITerrainPolygon メンバ

        public ushort ID {
            get { return id; }
        }

        /// <summary>
        /// Returns 2D bounds rectangle which contains all verticis points.
        /// </summary>
        public Rectangle GetBounds(Scaler sc) {
            Rectangle ret = sc.Scale(bounds);
            if (ret.Height == 0) {
                // If this polygon is perpendicular to the camera angle,
                // make 'thickness' to make visible as thin line.
                ret.Height = 1;
            }
            return ret;
        }

        /// <summary>
        /// Calc verticis according to the ViewFactor. A bit heavy process.
        /// </summary>
        /// <param name="vf"></param>
        /// <returns></returns>
        public Point[] GetVerticis(Scaler sc) {
            Point[] ret = sc.Scale(verticis);
            return ret;
        }
        #endregion

        /// <summary>
        /// Get total count of stock polygon patterns.
        /// </summary>
        public static int StockCount {
            get { return stock.Count; }
        }

        public static Dictionary<ushort, HeightCutPolygon>.KeyCollection GetAllID() {
            return stock.Keys;
        }

        /// <summary>
        /// Try to get the polygon associated with an ID.
        /// returns null if the ID is not registerd.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static HeightCutPolygon TryGetPolygon(ushort id) {
            HeightCutPolygon v;
            if (stock.TryGetValue(id, out v)) {
                return v;
            } else {
                return null;
            }
        }

        /// <summary>
        /// get the polygon associated with an ID.
        /// throw exception if the ID is not registerd.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static HeightCutPolygon GetPolygon(ushort id) {
            return stock[id];
        }

        public static HeightCutPolygon GetPolygon(int near, int left, int right, int far) {
            return stock[MakePolygonID(near, left, right, far)];
        }

        public static ushort MakePolygonID(int near, int left, int right, int far) {
            Debug.Assert(near < 15 && left < 15 && right < 15 && far < 15);
            ushort id = PrivateMakePolygonID(near, left, right, far);
            if (stock.ContainsKey(id)) {
                return id;
            } else {
                throw new IndexOutOfRangeException("No corresponding id.");
            }
        }

        internal static UInt32 PrivateMakePolygonID(int near, int left, int right, int far, int hcut) {
            ushort idorg = GroundPolygon.PrivateMakePolygonID(near, left, right, far);
            return 0;
        }

        // serialize this object by reference
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.SetType(typeof(ReferenceImpl));
            info.AddValue("id", ID);
        }


        [Serializable]
        internal sealed class ReferenceImpl : IObjectReference {
            private ushort id = 0xffff;
            public object GetRealObject(StreamingContext context) {
                return stock[id];
            }
        }
    }
}
