using System;
using System.Drawing;
using System.Diagnostics;
using nft.core.game;
using nft.core.geometry;
using System.Collections.Generic;
using ICDir = nft.core.geometry.InterCardinalDirection;
using ITerrainPiece = nft.core.geometry.ITerrainPiece;

namespace nft.impl.game
{
	/// <summary>
	/// TerrainMapImplは実際に地形データを配列に展開して保持する。
	/// </summary>
	[Serializable]
	public class TerrainMapImpl : ITerrainMap {
        protected delegate bool DiscontinuousChecker(TerrainMapImpl map, int x, int y);
        static protected DiscontinuousChecker[] checker;
        static TerrainMapImpl() {
            checker = new DiscontinuousChecker[4];
            checker[(int)Direction4.NORTH] = IsNorthSideDiscontinuous;
            checker[(int)Direction4.SOUTH] = IsSouthSideDiscontinuous;
            checker[(int)Direction4.EAST] = IsEastSideDiscontinuous;
            checker[(int)Direction4.WEST] = IsWestSideDiscontinuous;
        }

        protected TerrainPiecePair[,] hmap;
        protected short[,] water;
        protected int heightOffset;
        protected Size3D size;

        public TerrainMapImpl(ITerrainMap source) : 
            this(source, new Rectangle(0,0,source.Size.sx, source.Size.sy)) {}

        public TerrainMapImpl(ITerrainMap source, Rectangle region) {
            this.heightOffset = source.HeightOffset;
            int sx = region.Width;
            int sy = region.Height;
            if (!source.IsDetailedHeight) {
                sx--;
                sy--;
            }
            this.size = new Size3D(sx, sy, source.Size.sz);
            this.hmap = new TerrainPiecePair[sx, sy];
			int ox = region.Left;
			int oy = region.Top;

            for (int x = 0; x < sx; x++) {
                for (int y = 0; y < sy; y++) {
                    hmap[x, y] = TerrainPiecePair.CreatePair(source, ox + x, oy + y, 1.0, CreatePiece);
                }
            }
		}

        protected virtual TerrainPieceE CreatePiece(int ne, int nw, int sw, int se) {
            return new TerrainPieceE(ne, nw, sw, se);
        }

        ~TerrainMapImpl() {
            Dispose(false);
        }

		#region ITerrainMap メンバ
		public Size3D Size { get{ return size; } }

		public int Height(int x, int y) {
            TerrainPiecePair pair = this[x, y];
            if (pair != null) {
                return (short)pair.MeanHeight;
            } else {
				string t = string.Format("Height({0},{1}): argument out of range.",x,y);
				Debug.WriteLine(t);
				return -1;
			}
		}

        public int DetailedHeight(double x, double y) {
            throw new NotImplementedException();
        }

        public TerrainPiecePair this[int x, int y] {
            get {
                if (0 <= x && x < size.sx && 0 <= y && y < size.sy) {
                    return hmap[x, y];
                } else {
                    string t = string.Format("terrain({0},{1}): argument out of range.", x, y);
                    Debug.WriteLine(t);
                    return null;
                }
            }
        }

        public bool IsDetailedHeight { get { return false; } }

        public int WaterDepth(int x, int y) {
            try {
                return water[x, y];
            } catch (IndexOutOfRangeException) {
                string t = string.Format("WaterDepth({0},{1}): argument out of range.", x, y);
                Debug.WriteLine(t);
                return -1;
            }
        }

        public int HeightOffset {
            get { return heightOffset; }
        }

        #endregion

        public IEnumerable<ITerrainPiece> this[int x, int y, ICDir viewupper] {
            get {
                try {
                    return hmap[x, y].GetPieces(viewupper);
                } catch (IndexOutOfRangeException) {
                    Debug.WriteLine("x="+x+", y="+y+", map="+hmap.GetUpperBound(0)+","+hmap.GetUpperBound(1));
                    return NullEnumerator();
                }
            }
		}

        [Obsolete]
        public bool IsCliffedBounds(Direction4 cellside, int x, int y) {
            bool b = checker[(int)cellside](this, x, y);
            return b;
        }

        protected void Dispose(bool disposing) {
            hmap = null;
            water = null;
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /*
        public Rectangle[] Districts
		{
			get	{ return districts; }
			set	{ districts = value; }
		}
		protected Rectangle[] districts;		
        */
        
        // for fail safe
        static protected IEnumerable<ITerrainPiece> NullEnumerator() {
            yield break;
        }

        #region static DiscontinuousChecker delegates.
        static protected bool IsNorthSideDiscontinuous(TerrainMapImpl map, int x, int y) {
            if (y < 0 || map.Size.sy - y <= 1 || x < 0 || x >= map.Size.sx)
                return true;
            try {
                TerrainPiecePair p1 = map[x, y];
                TerrainPiecePair p2 = map[x, y + 1];
                ITerrainPiece tp1 = p1[Direction4.NORTH];
                ITerrainPiece tp2 = p2[Direction4.SOUTH];
                if (tp1.GetHeightAt(ICDir.NORTHEAST) > tp2.GetHeightAt(ICDir.SOUTHEAST)) {
                    //Debug.Write("n");
                    return true;
                }
                if (tp1.GetHeightAt(ICDir.NORTHWEST) > tp2.GetHeightAt(ICDir.SOUTHWEST)) {
                    //Debug.Write("n");
                    return true;
                }
                return false;
            } catch (IndexOutOfRangeException) {
                string t = string.Format("checking discontinuity at ({0},{1})N: argument out of range.", x, y);
                Debug.WriteLine(t);
                return true;
            }
        }

        static protected bool IsSouthSideDiscontinuous(TerrainMapImpl map, int x, int y) {
            if (y < 1 || y >= map.Size.sy || x < 0 || x >= map.Size.sx)
                return true;
            try {
                TerrainPiecePair p1 = map[x, y];
                TerrainPiecePair p2 = map[x, y - 1];
                ITerrainPiece tp1 = p1[Direction4.SOUTH];
                ITerrainPiece tp2 = p2[Direction4.NORTH];
                if (tp1.GetHeightAt(ICDir.SOUTHEAST) > tp2.GetHeightAt(ICDir.NORTHEAST)) {
                    //Debug.Write("s");
                    return true;
                }
                if (tp1.GetHeightAt(ICDir.SOUTHWEST) > tp2.GetHeightAt(ICDir.NORTHWEST)) {
                    //Debug.Write("s");
                    return true;
                }
                return false;
            } catch (IndexOutOfRangeException) {
                string t = string.Format("checking discontinuity at ({0},{1})S: argument out of range.", x, y);
                Debug.WriteLine(t);
                return true;
            }
        }

        static protected bool IsEastSideDiscontinuous(TerrainMapImpl map, int x, int y) {
            if ( y < 0 || y >= map.Size.sy || x < 0 || map.Size.sx - x <= 1)
                return true;
            try {
                TerrainPiecePair p1 = map[x, y];
                TerrainPiecePair p2 = map[x + 1, y];
                ITerrainPiece tp1 = p1[Direction4.EAST];
                ITerrainPiece tp2 = p2[Direction4.WEST];
                if (tp1.GetHeightAt(ICDir.NORTHEAST) > tp2.GetHeightAt(ICDir.NORTHWEST)) {
                    //Debug.Write("e");
                    return true;
                }
                if (tp1.GetHeightAt(ICDir.SOUTHEAST) > tp2.GetHeightAt(ICDir.SOUTHWEST)) {
                    //Debug.Write("e");
                    return true;
                }
                return false;
            } catch (IndexOutOfRangeException) {
                string t = string.Format("checking discontinuity at ({0},{1})E: argument out of range.", x, y);
                Debug.WriteLine(t);
                return true;
            }
        }
        static protected bool IsWestSideDiscontinuous(TerrainMapImpl map, int x, int y) {
            if (y < 0 || y >= map.Size.sy || x < 1 || x >= map.Size.sx)
                return true;
            try {
                TerrainPiecePair p1 = map[x, y];
                TerrainPiecePair p2 = map[x - 1, y];
                ITerrainPiece tp1 = p1[Direction4.WEST];
                ITerrainPiece tp2 = p2[Direction4.EAST];
                if (tp1.GetHeightAt(ICDir.NORTHWEST) > tp2.GetHeightAt(ICDir.NORTHEAST)) {
                    //Debug.Write("w");
                    return true;
                }
                if (tp1.GetHeightAt(ICDir.SOUTHWEST) > tp2.GetHeightAt(ICDir.SOUTHEAST)) {
                    //Debug.Write("w");
                    return true;
                }
                return false;
            } catch (IndexOutOfRangeException) {
                string t = string.Format("checking discontinuity at ({0},{1})W: argument out of range.", x, y);
                Debug.WriteLine(t);
                return true;
            }
        }
        #endregion
    }
}
