using System;
using System.Collections.Generic;
using System.Text;
using nft.core.geometry;
using nft.core.game;
using nft.framework.drawing;
using System.Diagnostics;

using Geocon = nft.core.geometry.GeometricConstants;
using ITerrainPiece = nft.core.geometry.ITerrainPiece;
namespace nft.impl.game {
    
    /// <summary>
    /// Hold a pair of TerrainPieces assigned to a map grid.
    /// マップの特定の一マスに収まるTerrainPieceのペアを保持する
    /// </summary>
    public class TerrainPiecePair {
        public delegate ITerrainPiece TerrainPieceFactory(int ne, int nw, int sw, int se);
        static readonly protected int offsetMax = Geocon.TerrainMaxHeightInUnit;
        static readonly protected double Margin = 0.01;
        static private bool[] revOrder = new bool[4];
        static TerrainPiecePair(){
            // to determine the order of P1 and P2 which GetPieces function returns.
            revOrder[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHEAST)] = true;
            revOrder[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHWEST)] = true;
            revOrder[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHEAST)] = false;
            revOrder[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHWEST)] = false;
        }

        protected ITerrainPiece P1, P2;

        static internal TerrainPiecePair CreatePair(ITerrainMap source, int x, int y, double hrate, TerrainPieceFactory factory) {
            ITerrainPiece p1, p2;
            int o = (x + y) & 1; // o = 1 for odd grid
            if (source.IsDetailedHeight) {
                double m = Margin;
                double nm = 1 - Margin;
                int[] h0 = new int[]{
                    AdjustHeight(source.DetailedHeight(x + nm, y + nm),hrate),
                    AdjustHeight(source.DetailedHeight(x + nm, y + m),hrate),
                    AdjustHeight(source.DetailedHeight(x + m, y + m),hrate),
                    AdjustHeight(source.DetailedHeight(x + m, y + nm),hrate) 
                };
                double mh = Margin / 2;
                double m2 = Margin + mh;
                double nm2 = 1 - m2;
                double nmh = 1 - mh;
                int[] h1 = new int[]{
                    AdjustHeight(source.DetailedHeight(x + nm2, y + nmh),hrate),//0
                    AdjustHeight(source.DetailedHeight(x + mh, y + nm2),hrate),
                    AdjustHeight(source.DetailedHeight(x + m2, y + nmh),hrate),
                    AdjustHeight(source.DetailedHeight(x + nmh, y + nm2),hrate),//3
                    AdjustHeight(source.DetailedHeight(x + m2, y + mh),hrate),
                    AdjustHeight(source.DetailedHeight(x + nmh, y + m2),hrate),
                    AdjustHeight(source.DetailedHeight(x + nm2, y + mh),hrate),//6 
                    AdjustHeight(source.DetailedHeight(x + mh, y + m2),hrate)
                };
                int d1 = Math.Abs(h1[0] - h1[1]) + Math.Abs(h1[4] - h1[5]);
                int d2 = Math.Abs(h1[2] - h1[3]) + Math.Abs(h1[6] - h1[7]);
                if (d1 < d2) {
                    p1 = CreateReformedPiece(factory, new int[] { h0[0], h1[6], -1, h1[3] }, 2);
                    p2 = CreateReformedPiece(factory, new int[] { -1, h1[7], h0[2], h1[2] }, 0);
                } else {
                    p1 = CreateReformedPiece(factory, new int[] { h1[0], h0[1], h1[5], -1 }, 3);
                    p2 = CreateReformedPiece(factory, new int[] { h1[1], -1, h1[4], h0[3] }, 1);
                }
            } else {
                int[] h = new int[]{
                    AdjustHeight(source.Height(x + 1, y + 1),hrate),
                    AdjustHeight(source.Height(x, y + 1),hrate),
                    AdjustHeight(source.Height(x, y),hrate),
                    AdjustHeight(source.Height(x + 1, y),hrate)
                };
                int imin;
                int imax;
                if (TerrainUtil.GetMinMax(h, out imin, out imax) > offsetMax) {
                    int d1 = Math.Abs(h[0] - h[2]);
                    int d2 = Math.Abs(h[1] - h[3]);
                    if (d1 > d2) {
                        p1 = CreateReformedPiece(factory, h, 2);
                        p2 = CreateReformedPiece(factory, h, 0);
                    } else {
                        p1 = CreateReformedPiece(factory, h, 3);
                        p2 = CreateReformedPiece(factory, h, 1);
                    }                    
                } else {
                    if (o != 0) {
                        p1 = factory(h[0], h[1], -1, h[3]);
                        p2 = factory(-1, h[1], h[2], h[3]);
                    } else {
                        p1 = factory(h[0], h[1], h[2], -1);
                        p2 = factory(h[0], -1, h[2], h[3]);
                    }
                }
            }
            //p1.BaseHeight += source.HeightOffset;
            //p2.BaseHeight += source.HeightOffset;
            //Debug.Print("Terrain ({0},{1}) = {2:X4}&{3:X4}", x, y, p1.Template.ID, p2.Template.ID);
            return new TerrainPiecePair(p1, p2);
        }

        /// <summary>
        /// Adjust height value by multiling specific rate and to fit 'TerrainHeightStep' unit.
        /// </summary>
        /// <param name="h_org"></param>
        /// <param name="rate"></param>
        /// <returns></returns>
        static protected int AdjustHeight(int h_org, double rate) {
            int h = (int)Math.Round(h_org * rate / Geocon.TerrainStepHeightInUnit);
            return h * Geocon.TerrainStepHeightInUnit;
        }

        /// <summary>
        /// Adjust heights array which are not match with any registerd TerrainPiece, 
        /// to match one of registerd piece.
        /// </summary>
        /// <param name="factory"></param>
        /// <param name="arr"></param>
        /// <param name="ignoreIdx"></param>
        /// <returns></returns>
        static protected ITerrainPiece CreateReformedPiece(TerrainPieceFactory factory, int[] arr, int ignoreIdx) {
            int[] h = (int[])arr.Clone();
            int[] order = GetIndexOrder(h, ignoreIdx);
            int d = h[order[0]] - h[order[2]];
            h[ignoreIdx] = -1;
            if (d > offsetMax) {
                h[order[0]] = h[order[2]] + offsetMax;
                int d2 = h[order[1]] - h[order[2]];
                h[order[1]] = h[order[2]] + AdjustHeight(offsetMax, (double)d2 / (double)d);
            }
            return factory(h[0], h[1], h[2], h[3]);
        }

        /// <summary>
        /// returns index order of given heights array.
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="ignoreIdx"></param>
        /// <returns></returns>
        static protected int[] GetIndexOrder(int[] arr, int ignoreIdx) {
            int[] idxorder = new int[3];
            int v = 3-ignoreIdx;
            for (int i = 0; i < 3; i++) {
                idxorder[i] = v;
            }
            for (int i = 0; i < 4; i++) {
                if (i == ignoreIdx) continue;
                if (arr[i] > arr[idxorder[0]]) { idxorder[0] = i; }
                if (arr[i] <= arr[idxorder[2]]) { idxorder[2] = i; }
            }
            // To calc proper index for idxorder[1],
            // idxorder[0] and [2] must indicate different index
            idxorder[1] = 6 - ignoreIdx - idxorder[0] - idxorder[2];
            return idxorder;
        }

        protected TerrainPiecePair(ITerrainPiece p1, ITerrainPiece p2) {
            this.P1 = p1;
            this.P2 = p2;
        }

        public int MeanHeight {
            get {
                return (P1.MeanHeight + P2.MeanHeight) / 2;
            }
        }

        public ITerrainPiece this[Direction4 cellside] {
            get {
                ushort mask = OccupationMaskLibrary.GetCellEdgeMask(cellside);
                if ((P1.OccupationMask & mask) == mask) {
                    return P1;

                } else {
                    return P2;
                }
            }
        }

        public IEnumerable<ITerrainPiece> GetPieces(InterCardinalDirection viewUpper) {
            // behined half piece must be enumerated first.
            if (revOrder[Direction.ToZeroBaseIndex(viewUpper)]) {
                yield return P2;
                yield return P1;
            } else {
                yield return P1;
                yield return P2;
            }
            yield break;
        }

    }
}
