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 TerrainBorder : ITerrainPolygon, ISerializable {
        static Dictionary<ushort, TerrainBorder> stock = new Dictionary<ushort, TerrainBorder>(256);

        /// <summary>
        /// create Int16 color code. only higher 4-bits are valid in original RGB values
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        static protected ushort ToPackedColorCode(Color c) {
            int r = c.R & 0xf0;
            int g = c.G & 0xf0;
            int b = c.B & 0xf0;
            return (ushort)((b << 4) + g + (r >> 4));
        }

        /// <summary>
        /// create Color object from specified Int16 color code
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        static protected Color MakePackedColor(ushort code) {
            byte[] b = new byte[3];
            byte b2;
            for (int i = 0; i < 3; i++) {
                b[i] = (byte)(code & 0xf);
                code >>= 4;
                b2 = (byte)(((b[i] & 1) - 1)&0xf);
                b[i] <<= 4;
                b[i] |= b2;
            }
            return Color.FromArgb(b[0], b[1], b[2]);
        }

        static TerrainBorder() {
            initPatterns(2, 8);
        }
        #region initialize polygon pattern table
        private static void initPatterns(int step, int max) {
            for (int p1 = 0; p1 <= max; p1 += step) {
                for (int p2 = 0; p2 <= max; p2 += step) {
                    if (p1 * p2 > 0) continue;
                    registerPattern(p1, p2, -1);
                    registerPattern(p1, -1, p2);
                    registerPattern(-1, p1, p2);                    
                }
            }
        }
        private static void registerPattern(int left, int right, int far) {
            ushort id = PrivateMakePolygonID(left, right, far);
            int hp = Geocon.UnitHeightPixel >> 2;
            if (!stock.ContainsKey(id)) {
                Point3D[] pt = new Point3D[4];
                int idx = 0;
                if (left >= 0)
                    pt[idx++] = new Point3D(0, -Geocon.UnitWidthPixel, -left * hp);
                if (right >= 0)
                    pt[idx++] = new Point3D(-Geocon.UnitWidthPixel, 0, -right * hp);
                if (far >= 0)
                    pt[idx++] = new Point3D(-Geocon.UnitWidthPixel, - Geocon.UnitWidthPixel, -far * hp);
                stock.Add(id, new TerrainBorder(id, pt));
            }
        }
        #endregion

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

        protected TerrainBorder(ushort id, Point3D[] pts) {
            this.id = id;
            this.bounds = new Rectangle();
            this.verticis = Point3D.ConvertToQuarterViewPos(pts, ref bounds);
        }

        #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);
            ret.Height++;
            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 = new Point[4];
            ret[0] = sc.Scale(verticis[0]);
            ret[1] = sc.Scale(verticis[1]);
            // duplicate and lower each point to make thin area to be painted.
            ret[2] = new Point(ret[1].X, ret[1].Y + 1);
            ret[3] = new Point(ret[0].X, ret[0].Y + 1);
            return ret;
        }
        #endregion

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

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

        public static TerrainBorder GetPolygon(ushort id) {
            return stock[id];
        }

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

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

        private static ushort PrivateMakePolygonID(int left, int right, int back) {
            return (ushort)((left & 0x000f) + ((back & 0x000f) << 4) + ((right & 0x000f) << 8));
        }

        // 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();
        }
    }
}
