#region Using directives

using System;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Drawing;
using System.Windows.Forms;



#endregion


namespace PunkMan.Game
{
    /// <summary>
    /// Map cell container
    /// </summary>
    public class Map
    {
        /// <summary>
        /// Type of box side
        /// </summary>
        private enum BoxSide{
            Bottom = 0, Front = 1, Right = 2, Back = 3, Left = 4, Top = 5
        }

        /// <summary>
        /// Vertex buffer for unit cube
        /// </summary>
        private VertexBuffer vertexBuffer = null;

        /// <summary>
        /// Direct3D device
        /// </summary>
        private Device dev = null;
        /// <summary>
        /// Real container
        /// </summary>
        private MapCell[,] data = null;
        /// <summary>
        /// Access real container of MapCell
        /// </summary>
        /// <value></value>
        public MapCell[,] Data
        {
            get { return data; }
            set { data = value; }
        }

        /// <summary>
        /// width of map
        /// </summary>
        private int dataWidth = 0;
        /// <summary>
        /// height of map
        /// </summary>
        private int dataHeight = 0;
        /// <summary>
        /// width of map
        /// </summary>
        /// <value></value>
        public int DataWidth
        {
            get { return dataWidth; }
            //set { dataWidth = value; }
        }

        /// <summary>
        /// height of map
        /// </summary>
        /// <value></value>
        public int DataHeight
        {
            get { return dataHeight; }
        }

        /// <summary>
        /// texture manager object
        /// </summary>
        private TextureManager texMgr;


        /// <summary>
        /// mighty constructor
        /// </summary>
        /// <param name="dev">Direct3D device</param>
        /// <param name="mgr">texture manager object</param>
        /// <param name="levelPath">path of map file</param>
        public Map(Device dev, TextureManager mgr, string levelPath)
        {
            this.texMgr = mgr;
            this.dev = dev;
            //dev.DeviceReset += new EventHandler(this.CreateBuffer);
            vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormalTextured), 24, dev, Usage.WriteOnly,
                CustomVertex.PositionNormalTextured.Format, Pool.Default);
            vertexBuffer.Created += new EventHandler(OnCreateBuffer);
            OnCreateBuffer(vertexBuffer, null);
            
            Load(levelPath);
        }

        /// <summary>
        /// Create empty map
        /// </summary>
        /// <param name="width">width of map</param>
        /// <param name="height">height of map</param>
        public Map(int width, int height)
        {
            dev = null;
            dataWidth = width;
            dataHeight = height;
            data = new MapCell[width, height];
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    data[x, y] = new MapCell(false, 0);
            data[0, 0].Entity = MapCell.EntityType.Start;
            data[dataWidth - 1, dataHeight - 1].Entity = MapCell.EntityType.End;

            
        }

        /// <summary>
        /// Load map form file
        /// </summary>
        /// <param name="fileName">map file path</param>
        public void Load(string fileName)
        {
            BinaryReader reader = new BinaryReader(new FileStream(fileName, FileMode.Open));
            
            dataWidth = reader.ReadInt32();
            dataHeight = reader.ReadInt32();
            data = new MapCell[dataWidth, dataHeight];
            
            for (int x = 0; x < dataWidth; x++)
                for (int y = 0; y < dataHeight; y++)
                {
                    data[x, y] = new MapCell();
                    //byte w = reader.ReadByte();
                    data[x, y].Walkable = reader.ReadBoolean();
                    data[x, y].Index = reader.ReadInt32();
                    data[x, y].Entity = (MapCell.EntityType)reader.ReadInt32();
                }
            reader.Close();
        }

        /// <summary>
        /// Save map into file
        /// </summary>
        /// <param name="fileName">map file path</param>
        public void Save(string fileName)
        {
            BinaryWriter writer = new BinaryWriter(new FileStream(fileName, FileMode.Create));
            writer.Write(dataWidth);
            writer.Write(dataHeight);
            
            for (int x = 0; x < dataWidth; x++)
                for (int y = 0; y < dataHeight; y++)
                {
                    //byte w = (data[x, y].Walkable) ? (byte)1 : (byte)0;
                    //writer.Write(w);
                    writer.Write(data[x, y].Walkable);
                    writer.Write(data[x, y].Index);
                    writer.Write((int)data[x, y].Entity);
                }
            writer.Close();
        }

        /// <summary>
        /// Find start point
        /// </summary>
        /// <returns>point of player start</returns>
        public Point FindStartPoint()
        {
            for (int x = 0; x < dataWidth; x++)
            {
                for (int y = 0; y < dataHeight; y++)
                {
                    if (data[x, y].Entity == MapCell.EntityType.Start)
                        return new Point(x, y);
                }
            }
            return Point.Empty;
        }

        /// <summary>
        /// Find end point
        /// </summary>
        /// <returns>point of level exit</returns>
        public Point FindEndPoint()
        {
            for (int x = 0; x < dataWidth; x++)
            {
                for (int y = 0; y < dataHeight; y++)
                {
                    if (data[x, y].Entity == MapCell.EntityType.End)
                        return new Point(x, y);
                }
            }
            return Point.Empty;
        }



        /// <summary>
        /// Device Reset resurection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnCreateBuffer(object sender, EventArgs args)
        {
            VertexBuffer vertexBuffer = (VertexBuffer)sender;

            CustomVertex.PositionNormalTextured[] vert = (CustomVertex.PositionNormalTextured[])vertexBuffer.Lock(0, 0);
            Vector3[] v = new Vector3[8];
            v[0] = new Vector3(-1, -1, -1); v[1] = new Vector3(+1, -1, -1); v[2] = new Vector3(+1, -1, +1); v[3] = new Vector3(-1, -1, +1);
            v[4] = new Vector3(-1, +1, -1); v[5] = new Vector3(+1, +1, -1); v[6] = new Vector3(+1, +1, +1); v[7] = new Vector3(-1, +1, +1);
            Vector3[] n = new Vector3[6];
            n[0] = new Vector3(0, +1, 0); n[1] = new Vector3(0, 0, -1); n[2] = new Vector3(+1, 0, 0);
            n[3] = new Vector3(0, 0, +1); n[4] = new Vector3(-1, 0, 0); n[5] = new Vector3(0, +1, 0);
            Vector2[] t = new Vector2[4];
            t[0] = new Vector2(0, 0); t[1] = new Vector2(0, 1); t[2] = new Vector2(1, 1); t[3] = new Vector2(1, 0);
            int[] idx = {0,1,3,2, 0,1,4,5, 1,2,5,6, 2,3,6,7, 0,3,4,7, 4,5,7,6};

            for (int i = 0; i < 24; i++)
            {
                vert[i].Position = (v[idx[i]]);
                int ni = i / 4;
                int ti = i % 4;
                vert[i].Tu = t[ti].X;
                vert[i].Tv = t[ti].Y;
                vert[i].Normal = (n[ni]);
            }
            vertexBuffer.Unlock();

        }

        /// <summary>
        /// Draw part of unit cube
        /// </summary>
        /// <param name="partNo"></param>
        public void Render(int partNo)
        {
            if (partNo > 5 || partNo < 0) return;

            dev.SetStreamSource(0, vertexBuffer, 0);
            dev.VertexFormat = CustomVertex.PositionNormalTextured.Format;
            dev.DrawPrimitives(PrimitiveType.TriangleStrip, partNo * 4, 2);
        }

        /// <summary>
        /// Draw part of map cell
        /// </summary>
        /// <param name="partNo">number of part</param>
        /// <param name="x">postion of map cell</param>
        /// <param name="y">postion of map cell</param>
        public void RenderCell(int partNo, int x, int y)
        {
            Matrix oldWorld = dev.Transform.World;
            dev.Transform.World = Matrix.Translation(new Vector3(x * 2.0f, 0, y * 2.0f)) * oldWorld;
            Render(partNo);
            dev.Transform.World = oldWorld;
        }

        /// <summary>
        /// Draw map range
        /// </summary>
        /// <param name="x1">start position</param>
        /// <param name="y1">start position</param>
        /// <param name="x2">end position</param>
        /// <param name="y2">end position</param>
        public void Render(int x1, int y1, int x2, int y2)
        {
            for (int y = y1; y <= y2; y++)
            {
                for (int x = x1; x <= x2; x++)
                {
                    texMgr.Activate(data[x, y].Index);
                    if (data[x, y].Walkable)
                    {
                        //for (int i = 0; i < 6; i++) RenderCell(i, x, y);
                        RenderCell((int)BoxSide.Bottom, x, y);
                    }
                    else
                    {
                        RenderCell((int)BoxSide.Top, x, y);
                        
                        if (x <= 0) RenderCell((int)BoxSide.Left, x, y);
                        else if (data[x - 1, y].Walkable) RenderCell((int)BoxSide.Left, x, y);
                        
                        if (y <= 0) RenderCell((int)BoxSide.Front, x, y);
                        else if (data[x, y - 1].Walkable) RenderCell((int)BoxSide.Front, x, y);
                        
                        if (x >= dataWidth - 1) RenderCell((int)BoxSide.Right, x, y);
                        else if (data[x + 1, y].Walkable) RenderCell((int)BoxSide.Right, x, y);
                        
                        if (y >= dataHeight - 1) RenderCell((int)BoxSide.Back, x, y);
                        else if (data[x, y + 1].Walkable) RenderCell((int)BoxSide.Back, x, y);
                    }
                }
            }
        }

        /// <summary>
        /// Draw map layout
        /// </summary>
        /// <param name="g">graphics object</param>
        /// <param name="imgs">list of texture thumbnails</param>
        /// <param name="gridWidth">width of layout grid</param>
        /// <param name="gridHeight">height of layout grid</param>
        public void RenderToGraphics(Graphics g, ImageList imgs, int gridWidth, int gridHeight)
        {
            Bitmap bmp = new Bitmap(gridWidth * dataWidth, gridHeight * dataHeight);
            for (int x = 0; x < dataWidth; x++)
            {
                for (int y = 0; y < dataHeight; y++)
                {
                    g.DrawImageUnscaled(imgs.Images[data[x, y].Index], x * gridWidth, y * gridHeight);
                    
                    //g.DrawImage(imgs.Images[data[x, y].Index], new Point(x * gridWidth, y * gridHeight));

                }
            }
            int ex = gridWidth * dataWidth;
            int ey = gridHeight * dataHeight;
            for (int i = 0; i < dataWidth; i++)
            {
                g.DrawLine(Pens.Black, i * gridWidth, 0, i * gridWidth, ex);
            }
            for (int i = 0; i < dataHeight; i++)
                g.DrawLine(Pens.Black, 0, i * gridHeight, ey, i * gridHeight);
        }


        /// <summary>
        /// Copy cells
        /// </summary>
        /// <param name="cells">source map data</param>
        /// <param name="width">width of source, destination</param>
        /// <param name="height">height of source, destination</param>
        public void SetCells(MapCell[,] cells, int width, int height)
        {
            dataWidth = width;
            dataHeight = height;
            data = new MapCell[width, height];
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    data[x, y] = cells[x, y];
                }
            }
        }

        /// <summary>
        /// Converte world-coordinate to map-coordinate
        /// </summary>
        /// <param name="v">world coordinate</param>
        /// <returns>map coordinate</returns>
        public static Point Convert(Vector3 v)
        {
            
            int x = (int)v.X / 2;
            int y = (int)v.Z / 2;
            return new Point(x, y);
        }

        /// <summary>
        /// Convert map-coordinate to world-coordinate
        /// </summary>
        /// <param name="p">map coordinate</param>
        /// <returns>world coordinate</returns>
        public static Vector3 Convert(Point p)
        {
            return new Vector3(p.X * 2, 0, p.Y * 2);
        }

        /// <summary>
        /// Access cell by Point
        /// </summary>
        /// <param name="p">pointer</param>
        /// <returns></returns>
        public MapCell this[Point p]
        {
            get
            {
                if (p.X < 0 || p.X >= dataWidth || p.Y < 0 || p.Y >= dataHeight) 
                    return null;
                return data[p.X, p.Y];
            }
            set
            {
                if (!(p.X < 0 || p.X >= dataWidth || p.Y < 0 || p.Y >= dataHeight))
                    data[p.X, p.Y] = value;
            }
        }

        /// <summary>
        /// Count of pickable items
        /// </summary>
        /// <returns>count of ...</returns>
        public int PickableItemCount()
        {
            int n = 0;
            for (int x = 0; x < dataWidth; x++)
                for (int y = 0; y < dataHeight; y++)
                    if ((data[x, y].Entity == MapCell.EntityType.Absinth ||
                        data[x, y].Entity == MapCell.EntityType.Rum) && data[x,y].Walkable) 
                        n++;
            return n;
        }

        /// <summary>
        /// Has pickable items
        /// </summary>
        /// <returns>has?</returns>
        public bool HasItem()
        {
            int n = PickableItemCount();
            return n > 0;
        }

        /// <summary>
        /// Count of monsters
        /// </summary>
        /// <returns>count of monsters in map</returns>
        public uint MonsterCount()
        {
            uint n = 0;
            for (int i = 0; i < dataWidth; i++)
            {
                for (int j = 0; j < dataHeight; j++)
                    if (data[i, j].Entity == MapCell.EntityType.Monster) n++;
            }
            return n;
        }
    }
}
