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;
using nft.contributions.terrain;
using System.IO;
using System.Drawing.Drawing2D;
using nft.framework;

namespace nft.core.geometry {
    public class HeightCutMask : ITerrainPolygon, ISerializable {
        public static readonly ushort ID_NULL_MASK = 0;
        public static readonly ushort ID_FULL_MASK = 0xffff;
        static Dictionary<ushort, HeightCutMask> stock = new Dictionary<ushort, HeightCutMask>(256);
        // To reduce mask patterns, take the common value=0.28 
        // insted of both quarter(0.25) and one-third(0.33..).
        // It's same about the value=0.72 which is alternative for
        // both three-fourth(0.75) and two-third(0.66..)
        static float[] dividePts = new float[] { 0f, 0.28f, 0.5f, 0.72f };
        
        static HeightCutMask() {
            InitPatterns(Geocon.TerrainMaxHeightInUnit);
        }
        #region initialize polygon pattern table
        private static void InitPatterns(int extHeight) {
            int n = dividePts.Length;
            for (int p1 = 1; p1 < n; p1 ++) {
                for (int p2 = 1; p2 < n; p2 ++) {
                    CreatePatternSetA(p1, p2, 0);
                    CreatePatternSetA(0, p1, p2);
                    CreatePatternSetA(p2, 0, p1);
                }
                CreatePatternSetB(p1, 0, 0);
                CreatePatternSetB(0, p1, 0);
                CreatePatternSetB(0, 0, p1);
            }
        }

        private static ushort IDPairBitMask = 4;
        private static ushort ViewDirectionMask = 3;
        static ushort MakeIDBase(int s1, int n, int s2) {
            int id = (s1 << 3) + (s2 << 6) + (n << 9);
            return (ushort)id;
        }

        /// <summary>
        /// Returns LOWER mask id for specified heights of triangle terrain polygon.
        /// negative height is treated as lower, and should be masked.
        /// 指定された高さの地形にもっとも近いマスクのIDを返す
        /// 三角形の地形を▼と置いた時の右角の高さがhr,中央下角の高さがhn,左角の高さがhl
        /// 高さは０を基準に負の値はマスクされる部分と見なされる。
        /// 0は全域が可視、0xffffは全域が不可視
        /// </summary>
        /// <param name="normalAngle">direction of normal (angle) conrer</param>
        /// <param name="hr">height at right sharp corner (when placed with normal corner down)</param>
        /// <param name="hn">height at normal (angle) corner</param>
        /// <param name="hl">height at left sharp corner (when placed with normal corner down)</param>
        /// <returns></returns>
        public static ushort FindBestMatchMask(ViewDirection normalAngle, int hr, int hn, int hl)
        {
            int s1 = FindBestMatchDividerIndex(hn, hr);
            int s2 = FindBestMatchDividerIndex(hn, hl);
            int n = FindBestMatchDividerIndex(hl, hr);
            if (s1 > 0 || s2 > 0 || n > 0) {
                ushort idbase = MakeIDBase(s1, n, s2);
                idbase += (ushort)normalAngle;
                bool b = s2 == 0 ? hr >= 0 : hn >= 0;
                if (b) idbase += IDPairBitMask;
                return idbase;
            } else {
                return (hr + hn + hl < 0) ? ID_FULL_MASK : ID_NULL_MASK;
            }
        }

        public static ushort MakeInvertedMaskID(ushort srcid) {
            return (ushort)(srcid ^ IDPairBitMask);
        }

        private static int FindBestMatchDividerIndex(int h1, int h2) {
            if (h1 * h2 >= 0) return 0;
            float f = ((float)h1) / ((float)(h1-h2));
            int i;
            for (i = 1; i < dividePts.Length; i++) {
                if (dividePts[i] > f) break;
            }
            if (i == dividePts.Length) {
                bool b = 1.0f - f < f - dividePts[i - 1];
                return b ? 0 : i - 1;
            } else {
                bool b = dividePts[i] - f < f - dividePts[i - 1];
                return b ? i : i - 1;
            }
        }

        private static void CreatePatternSetA(int s1, int n, int s2) {
            Point[] pts = new Point[2];
            int wp = Geocon.UnitWidthPixel;
            int idx;
            ushort idbase = MakeIDBase(s1, n, s2);
            idx = 0;
            if (s1 > 0) {
                int x = (int)(wp * dividePts[s1]);
                pts[idx++] = new Point(x, -x);
            }
            if (s2 > 0) {
                int x = (int)(wp * dividePts[s2]);
                pts[idx++] = new Point(-x, -x);
            }
            bool b = n > 0;
            if (b) {
                if (pts[0].Y == wp) return;
                int x = (int)(wp * dividePts[n]);
                pts[idx++] = new Point(x * 2 - wp, -wp);
            }
            CreateMaskPairBase(idbase, pts[0], pts[1], b && pts[0].Y != pts[1].Y);
        }

        private static void CreatePatternSetB(int s1, int n, int s2) {
            Point[] pts = new Point[2];
            int wp = Geocon.UnitWidthPixel;
            ushort idbase = MakeIDBase(s1, n, s2);
            if (s1 > 0) {
                int x = (int)(wp * dividePts[s1]);
                pts[0] = new Point(x, -x);
                pts[1] = new Point(-wp, -wp);
            } else if (s2 > 0) {
                int x = (int)(wp * dividePts[s2]);
                pts[0] = new Point(-x, -x);
                pts[1] = new Point(wp, -wp);
            } else if (n > 0) {
                int x = (int)(wp * dividePts[n]);
                pts[0] = new Point(x * 2 - wp, -wp);
                pts[1] = new Point(0, 0);
            } else {
                throw new ArgumentException("one of the parameters must be positive.");
            }
            CreateMaskPairBase(idbase, pts[0], pts[1], true);
        }

        private static void CreateMaskPairBase(ushort idbase, Point p1, Point p2, bool vertSplit) {
            int wp = Geocon.UnitWidthPixel;
            double a, b, r;
            Point ps1, ps2;
            Point[][] vertarray = new Point[2][];
            if (vertSplit) {
                // X = aY + b;
                r = (double)(p1.Y-p2.Y); // !=0 because p1-p2 lays verticaly.
                a = (double)(p1.X - p2.X) / r;
                b = (double)(p2.X * p1.Y - p1.X * p2.Y) / r;
                ps1 = new Point((int)(b/* -0*a+ */), 0);
                ps2 = new Point((int)(b -(wp * a)), -wp);
                // 垂直分割パターンの場合、ps2は必ずセル矩形上辺上にある。
                if (ps1.X < -wp) {
                    //下の点がセル矩形左端をはみ出る場合→左辺との交点に
                    ps1 = new Point(-wp, (int)((-wp - b) / a));
                    //セル矩形右下・五角形
                    vertarray[0] = new Point[] { 
                        ps1, new Point(-wp,0), new Point(wp,0), new Point(wp,-wp), ps2
                    };
                    //セル矩形左上・三角形
                    vertarray[1] = new Point[] { 
                        ps1, new Point(-wp, -wp), ps2
                    };
                } else if (ps1.X > wp) {
                    //下の点がセル矩形右端をはみ出る場合→右辺との交点に
                    ps1 = new Point(wp, (int)((wp - b) / a));
                    //セル矩形右上・三角形
                    vertarray[0] = new Point[] { 
                        ps1, new Point(wp, -wp), ps2
                    };
                    //セル矩形左下・五角形
                    vertarray[1] = new Point[] { 
                        ps1, new Point(wp,0), new Point(-wp,0), new Point(-wp,-wp), ps2
                    };
                } else {
                    //セル矩形右半分・台形
                    vertarray[0] = new Point[] { 
                        ps1, new Point(wp,0), new Point(wp,-wp), ps2
                    };
                    //セル矩形左半分・台形
                    vertarray[1] = new Point[] { 
                        ps1, new Point(-wp, 0), new Point(-wp,-wp), ps2
                    };
                }
            } else { // !vertSplit
                bool t = false;
                // Y = aX + b;
                r = (double)(p1.X - p2.X); // !=0
                a = (double)(p1.Y - p2.Y) / r;
                b = (double)(p2.Y * p1.X - p1.Y * p2.X) / r;
                ps1 = new Point(-wp, (int)((-wp) * a + b));
                ps2 = new Point(wp, (int)(wp * a + b));
                
                {
                    //左右の点がセル矩形上枠より上にあることはないはず
                    //セル矩形・上半分の台形
                    vertarray[0] = new Point[] { 
                                ps1, new Point(-wp,-wp), new Point(wp,-wp), ps2            
                            };
                    if (ps1.Y > ps2.Y) {
                        if (ps1.Y >= 0) {
                            //右の点がセル矩形下辺より上にあるので下辺を拡張
                            //セル矩形・下半分の三角形
                            vertarray[1] = new Point[] { 
                                ps1, new Point(wp,ps1.Y), ps2
                            };
                        } else {
                            //セル矩形・下半分の台形
                            vertarray[1] = new Point[] { 
                                ps1, new Point(-wp,0), new Point(wp,0), ps2
                            };
                        }
                    } else {
                        if (ps2.Y >= 0) {
                            //左の点がセル矩形下辺より上にあるので下辺を拡張
                            //セル矩形・下半分の三角形
                            vertarray[1] = new Point[] { 
                                ps1, new Point(-wp,ps2.Y), ps2
                            };
                        } else {
                            //セル矩形・下半分の台形
                            vertarray[1] = new Point[] { 
                                ps1, new Point(-wp,0), new Point(wp,0), ps2
                            };
                        }
                    }
                }
                /*
                if(ps1.Y > 0 ){
                    //左の点がセル矩形下端をはみ出る場合→下辺との交点に
                    t = true;
                    ps1 = new Point((int)(- b/ a), 0);
                } else if (ps1.Y < -wp) {
                    //左の点がセル矩形上端をはみ出る場合→上辺との交点に
                    t = true;
                    ps1 = new Point((int)((-wp - b) / a), -wp);
                }
                if (ps2.Y > 0) {
                    //右の点がセル矩形下端をはみ出る場合→下辺との交点に
                    ps2 = new Point((int)(-b / a), 0);
                    if (t) { //ps1（左）は上辺上にあるはず
                        //セル矩形・右半分の台形（下辺＞上辺）
                        vertarray[0] = new Point[] { 
                            ps1, new Point(wp,-wp), new Point(wp,0), ps2
                        };
                        //セル矩形・左半分の台形（下辺＜上辺）
                        vertarray[1] = new Point[] { 
                            ps1, new Point(-wp,-wp), new Point(-wp,0), ps2
                        };
                    } else {
                        //セル矩形右上・五角形
                        vertarray[0] = new Point[] { 
                            ps1, new Point(-wp,-wp), new Point(wp,-wp), new Point(wp, 0), ps2
                        };
                        //セル矩形左下・三角形
                        vertarray[1] = new Point[] { 
                            ps1, new Point(-wp,0), ps2
                        };
                    }
                } else if (ps2.Y < -wp) {
                    //右の点がセル矩形上端をはみ出る場合→上辺との交点に
                    ps2 = new Point((int)((-wp - b) / a), -wp);
                    if (t) { //ps1（左）は下辺上にあるはず
                        //セル矩形・右半分の台形（下辺＞上辺）
                        vertarray[0] = new Point[] { 
                            ps1, new Point(wp,0), new Point(wp,-wp), ps2
                        };
                        //セル矩形・左半分の台形（下辺＜上辺）
                        vertarray[1] = new Point[] { 
                            ps1, new Point(-wp,0), new Point(-wp,-wp), ps2
                        };
                    } else {
                        //セル矩形右下・五角形
                        vertarray[0] = new Point[] { 
                            ps1, new Point(-wp,0), new Point(wp,0), new Point(wp, -wp), ps2
                        };
                        //セル矩形左上・三角形
                        vertarray[1] = new Point[] { 
                            ps1, new Point(-wp,-wp), ps2
                        };
                    }
                } else {
                    //セル矩形・上半分の台形
                    vertarray[0] = new Point[] { 
                        ps1, new Point(-wp,-wp), new Point(wp,-wp), ps2
                    };
                    //セル矩形・下半分の台形
                    vertarray[1] = new Point[] { 
                        ps1, new Point(-wp,0), new Point(wp,0), ps2
                    };
                }
                 */

            }
            RegisterRotatedPatterns(idbase , vertarray[0]);
            RegisterRotatedPatterns((ushort)(idbase + IDPairBitMask), vertarray[1]);
        }

        private static void RegisterRotatedPatterns(ushort idbase, Point[] verts) {
            int n = verts.Length;
            Point[][] parr = new Point[][] { new Point[n], new Point[n], new Point[n], new Point[n] };
            int wp = Geocon.UnitWidthPixel;
            for (int i = 0; i < n; i++) {
                Point p = verts[i];
                parr[(int)ViewDirection.FRONT][i] = new Point(p.X, p.Y >> 1);
                parr[(int)ViewDirection.LEFT][i] = new Point(-p.Y - wp, (p.X - wp) >> 1);
                parr[(int)ViewDirection.BEHIND][i] = new Point(-p.X, -wp - (p.Y >> 1));
                parr[(int)ViewDirection.RIGHT][i] = new Point(wp + p.Y, (-p.X - wp) >> 1);
            }
            foreach (ViewDirection dir in Enum.GetValues(typeof(ViewDirection))) {
                int i = (int)dir;
                ushort id = (ushort)(idbase + i);
                HeightCutMask mask = new HeightCutMask(id, parr[i]);
                Debug.Assert(!stock.ContainsKey(id),"The Mask is already registerd id="+id);
                //if (stock.ContainsKey(id)) continue;
                stock.Add(id, mask);
            }
        }

        #endregion

        public readonly ushort id;
        protected Point[] verticis;
        protected Rectangle bounds;

        protected HeightCutMask(ushort id, Point[] pts) {
            this.id = id;
            this.bounds = RectUtil.GetBounds(pts);
            this.verticis = pts;
        }

        #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);
            if (verticis.Length == 2) {
                // If this polygon is perpendicular to the camera angle,
                // make 'thickness' to make visible as thin line.
                Point p0 = new Point(ret[0].X, ret[0].Y - 1);
                Point p1 = new Point(ret[1].X, ret[1].Y - 1);
                return new Point[] { p0, ret[0], ret[1], p1 };
            } else {
                return ret;
            }
        }
        #endregion

        public ViewDirection ViewDirection {
            get {
                return (ViewDirection)((short)id & ViewDirectionMask);
            }
        }

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

        public static Dictionary<ushort, HeightCutMask>.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 HeightCutMask TryGetPolygon(ushort id) {
            HeightCutMask 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 HeightCutMask GetPolygon(ushort id) {
            return stock[id];
        }

        // 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];
            }
        }


        public Point3D[] GetVerticis3D()
        {
            throw new NotImplementedException();
        }
    }

    public class HeightCutMaskImgSet : ScaledImgCacheGenerator<HeightCutMask> {
        private static Dictionary<Color, HeightCutMaskImgSet> cache
            = new Dictionary<Color, HeightCutMaskImgSet>(8);
        public static HeightCutMaskImgSet GetOrCreate(Color foreground) {
            HeightCutMaskImgSet ret;
            if (cache.TryGetValue(foreground, out ret)) return ret;
            
            int rgb = foreground.ToArgb();
            string basedir = Directories.MakeDataDirNameFor(typeof(HeightCutMaskImgSet));
            string subdir = string.Format("{0:X6}", rgb & 0xffffff);
            string path = Path.Combine(basedir, subdir);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            rgb &= 0xc0c0c0;
            rgb ^= 0x00c000;
            Color bg = (rgb == 0) ? Color.Magenta : Color.Lime;
            ret = new HeightCutMaskImgSet(path, foreground, bg);
            ret.PrepareCache();
            cache.Add(foreground, ret);
            return ret;
        }
        
        protected readonly Color texBGColor;
        protected readonly Color texFGColor;

        public HeightCutMaskImgSet(string imageCacheDir, Color fore, Color back)
            : base(imageCacheDir) {
            texFGColor = fore;
            texBGColor = back;
        }

        public Color BackgroundColor { get { return texBGColor; } }
        public Color ForegroundColor { get { return texFGColor; } }

        protected override void PrepareImages(Scaler scaler) {
            foreach (ushort id in HeightCutMask.GetAllID()) {
                HeightCutMask polygon = HeightCutMask.GetPolygon(id);
                //if (polygon.IsVisible) {
                string filepath = MakeImageFullPath(scaler, id);
                if (!File.Exists(filepath)) {
                    Bitmap bmp = CreateBitmap(scaler, polygon);
                    if (bmp != null) {
                        bmp.Save(filepath);
                        bmp.Dispose();
                    }
                }
            }
        }

        protected override bool OnImageNotFound(uint id, out Image img) {
            img = null;
            // if id is valid, suppress error log
            return HeightCutMask.TryGetPolygon((ushort)id)==null;
        }

        protected override int CalcPatternCounts() {
            return HeightCutMask.StockCount * (Enum.GetValues(typeof(ZoomScale)).Length + 1);
        }

        protected Bitmap CreateBitmap(Scaler sc, HeightCutMask polygon) {
            Point[] pts = polygon.GetVerticis(sc);
            Rectangle rect = new Rectangle();
            return PrimitiveTextrueFactory.CreatePolygonBitmap
                (pts, texFGColor, texBGColor, ref rect);
        }
    }
}
