using System;
using System.Runtime.InteropServices;

using nft.core.geometry;
using nft.core.game;

using Geocon = nft.core.geometry.GeometricConstants;
using System.Diagnostics;

namespace nft.impl.game {
    /// <summary>
	/// TerrainPiece の概要の説明です。
	/// 地形エディタで使用するため、メモリ容量を喰うが、操作は高速。
	/// </summary>
	public class TerrainPieceE : ITerrainPiece {
		protected int height;
        protected TerrainPieceTemplate topPiece;

		/// <summary>
        /// TerrainPieceE assumes triangle polygon.
        /// So one of 'ne','se','sw','nw' should be negative which means unused vertex.
		/// </summary>
		/// <param name="baseHight"></param>
		/// <param name="ne"></param>
		/// <param name="se"></param>
		/// <param name="sw"></param>
		/// <param name="nw"></param>
        public TerrainPieceE(int ne, int nw, int sw, int se) {
			setSlope(ne,nw,sw,se);
		}
        /*
        internal TerrainPieceE(int[] p) {
            setSlope(p);
        }
        */
        #region implementation of ITerrainPiece
        public int BaseHeight {
			get { return height; }
			set { height = (short)value; }
		}

		public int MaxHeight {
            get { return height + topPiece.MaxHeight; } 
		}		

		public int MeanHeight { 
			get{
				return height+topPiece.MeanHeight;
			} 
		}

        public ushort OccupationMask { get { return topPiece.OccupationMask; } }

        public TerrainPieceTemplate Template { get { return topPiece; } }        

		public int GetHeightAt( InterCardinalDirection dir ){
            int oh = topPiece.GetOffsetHeightAt(dir);
#if DEBUG
            //if (oh == -1) throw new Exception("!");
#endif
            return BaseHeight + oh;
		}

		public int GetOffsetHeightAt( InterCardinalDirection dir ) {
            return topPiece.GetOffsetHeightAt(dir);
		}

        #endregion

        /// <summary>
        /// Increase height at the vertex specified by 'dir'
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="fixBase">If true, increase height 
        /// only when BaseHeight need not to be changed.</param>
        /// <returns>true if successfuly increased.</returns>
        public bool VertexUp(InterCardinalDirection dir, bool fixBase) {
            int idx = Direction.ToZeroBaseIndex(dir);
            int[] slope = topPiece.CloneHeightArray();
            if (slope[idx] < Geocon.TerrainMaxHeightInUnit) {
                slope[idx] += Geocon.TerrainStepHeightInUnit;
            } else {
                if (fixBase || slope[idx] < 0) {
                    return false;
                }
                int iop = Direction.ToZeroBaseIndex(Direction.GetOppositeOf(dir));
                if (slope[iop] < 0) {
                    // dir points the normal angle of diagonal triangle
                    int i;
                    i = Direction.ToZeroBaseIndex(Direction.GetLeftOf(dir));
                    slope[i] = Math.Max(0, slope[i] - Geocon.TerrainStepHeightInUnit);
                    i = Direction.ToZeroBaseIndex(Direction.GetRightOf(dir));
                    slope[i] = Math.Max(0, slope[i] - Geocon.TerrainStepHeightInUnit);
                } else {
                    slope[iop] = Math.Max(0, slope[iop] - Geocon.TerrainStepHeightInUnit);
                    int i;
                    i = Direction.ToZeroBaseIndex(Direction.GetLeftOf(dir));
                    if (slope[i] > 0) {
                        slope[i] -= Geocon.TerrainStepHeightInUnit;
                    } else {
                        i = Direction.ToZeroBaseIndex(Direction.GetRightOf(dir));
                        if (slope[i] > 0) {
                            slope[i] -= Geocon.TerrainStepHeightInUnit;
                        }
                    }
                }
                BaseHeight += Geocon.TerrainStepHeightInUnit;
            }
            updatePieceTemplate(ref slope);
            return true;
        }

        /// <summary>
        /// Decrease height at the vertex specified by 'dir'
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="fixBase">If true, decrease height 
        /// only when BaseHeight need not to be changed.</param>
        /// <returns>true if successfuly decreased.</returns>
        public bool VertexDown(InterCardinalDirection dir, bool fixBase) {
            int[] slope = topPiece.CloneHeightArray();
            int idx = Direction.ToZeroBaseIndex(dir);
            if (slope[idx] > 0) {
                slope[idx] -= Geocon.TerrainStepHeightInUnit;
            } else {
                if (fixBase || slope[idx]<0) {
                    return false;
                }
                int iop = Direction.ToZeroBaseIndex(Direction.GetOppositeOf(dir));
                int hmax = Geocon.TerrainMaxHeightInUnit;
                if (slope[iop] < 0) {
                    // dir points the normal angle of diagonal triangle
                    int i;
                    i = Direction.ToZeroBaseIndex(Direction.GetLeftOf(dir));
                    slope[i] = Math.Min(hmax, slope[i] + Geocon.TerrainStepHeightInUnit);
                    i = Direction.ToZeroBaseIndex(Direction.GetRightOf(dir));
                    slope[i] = Math.Min(hmax, slope[i] + Geocon.TerrainStepHeightInUnit);
                } else {
                    slope[iop] = Math.Min(hmax, slope[iop] + Geocon.TerrainStepHeightInUnit);
                    int i;
                    i = Direction.ToZeroBaseIndex(Direction.GetLeftOf(dir));
                    if (slope[i] >= 0) {
                        slope[i] = Math.Min(hmax, slope[i] + Geocon.TerrainStepHeightInUnit);
                    } else {
                        i = Direction.ToZeroBaseIndex(Direction.GetRightOf(dir));
                        if (slope[i] >= 0) {
                            slope[i] = Math.Min(hmax, slope[i] + Geocon.TerrainStepHeightInUnit);
                        }
                    }
                }
                BaseHeight += Geocon.TerrainStepHeightInUnit;
            }
            updatePieceTemplate(ref slope);
            return true;
        }

		public void setSlope( int ne, int nw, int sw, int se ) {
            int[] a = new int[4];
            a[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHEAST)] = ne;
            a[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHWEST)] = nw;
            a[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHWEST)] = sw;
            a[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHEAST)] = se;
            height = normalizeSlope(ref a);
            updatePieceTemplate(ref a);
        }

        protected virtual int normalizeSlope(ref int[] p) {
            int vmin = int.MaxValue;
            for (int i = 0; i < 4; i++) {
                if (p[i] >= 0 && p[i] < vmin) {
                    vmin = p[i];
                }
            }
            for (int i = 0; i < 4; i++) {
                if (p[i] >= 0) {
                    p[i] -= vmin;
                }
            }
            return vmin;
        }

        protected virtual void updatePieceTemplate(ref int[] slope) {
            ushort id = TerrainPieceTemplate.MakeTemplateID(
                slope[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHWEST)],
                slope[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHWEST)],
                slope[Direction.ToZeroBaseIndex(InterCardinalDirection.NORTHEAST)],
                slope[Direction.ToZeroBaseIndex(InterCardinalDirection.SOUTHEAST)]
                );
            topPiece = TerrainPieceTemplate.GetInstance(id);
#if DEBUG
            int[] arr = topPiece.CloneHeightArray();
            for (int i = 0; i < 4; i++) {
               Debug.Assert(slope[i] == arr[i]);
            }
#endif
        }
    }
}
