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;
using nft.core.structure;

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;
        protected StructureCollection structures = new StructureCollection();

        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 StructureCollection Structures {
            get {
                return structures;
            }
        }

        public void GetObjectEnumerator(Rect3D hint, out IEnumerable<TerrainPiecePair> terrains, out IEnumerable<Lot> structures) {
            terrains = new TerrainPairEnumerator(this, hint);
            structures = this.structures.GetEnumeratorForView(hint);
        }

        public IEnumerable<ITerrainPiece> this[int x, int y, ICDir viewupper] {
            get {
                try {
                    return hmap[x, y].GetPieces(viewupper);
                } catch (IndexOutOfRangeException) {
                    Debug.WriteLine("The location (" + x + ", y=" + y + ") is OUTSIDE of the map{ size:(" + 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

        public class TerrainPairEnumerator : IEnumerable<TerrainPiecePair> {
            TerrainMapImpl map;
            Rect3D region;
            internal protected TerrainPairEnumerator(TerrainMapImpl map, Rect3D region) {
                this.map = map;
                this.region = region;
            }

            public IEnumerator<TerrainPiecePair> GetEnumerator() {
                int x2 = region.X2;
                int y2 = region.Y2;
                for (int y = region.Y; y < y2; y++) {
                    for (int x = region.X; x < x2; x++) {
                        yield return map.hmap[x, y];
                    }
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return GetEnumerator();
            }
        }

    }
}
