using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FredriksdalAssault2;

namespace Primitives3D
{
    /// <summary>
    /// Base class for simple geometric primitive models. This provides a vertex
    /// buffer, an index buffer, plus methods for drawing the model. Classes for
    /// specific types of primitive (CubePrimitive, SpherePrimitive, etc.) are
    /// derived from this common base, and use the AddVertex and AddIndex methods
    /// to specify their geometry.
    /// </summary>
    public abstract class GeometricPrimitive : IDisposable
    {
        #region Fields

        // During the process of constructing a primitive model, vertex
        // and index data is stored on the CPU in these managed lists.
        List<VertexPositionNormal> vertices = new List<VertexPositionNormal>();
        List<VertexPositionNormalTexture> textureVertices = new List<VertexPositionNormalTexture>();
        List<ushort> indices = new List<ushort>();


        // Once all the geometry has been specified, the InitializePrimitive
        // method copies the vertex and index data into these buffers, which
        // store it on the GPU ready for efficient rendering.
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        BasicEffect basicEffect;

        #endregion

        #region Initialization

        /// <summary>
        /// Adds a new vertex to the primitive model. This should only be called
        /// during the initialization process, before InitializePrimitive.
        /// </summary>
        protected void AddVertex(Vector3 position, Vector3 normal)
        {
            vertices.Add(new VertexPositionNormal(position, normal));
        }

        /// <summary>
        /// Adds a new vertex to the primitive model. This should only be called
        /// during the initialization process, before InitializePrimitive.
        /// </summary>
        protected void AddVertex(Vector3 position, Vector3 normal, Vector2 textureCoordinates)
        {
            textureVertices.Add(new VertexPositionNormalTexture(position, normal, textureCoordinates));
        }

        /// <summary>
        /// Adds a new index to the primitive model. This should only be called
        /// during the initialization process, before InitializePrimitive.
        /// </summary>
        protected void AddIndex(int index)
        {
            if (index > ushort.MaxValue)
                throw new ArgumentOutOfRangeException("index");

            indices.Add((ushort)index);
        }

        /// <summary>
        /// Queries the index of the current vertex. This starts at
        /// zero, and increments every time AddVertex is called.
        /// </summary>
        protected int CurrentVertex
        {
            get { return vertices.Count; }
        }

        /// <summary>
        /// Once all the geometry has been specified by calling AddVertex and AddIndex,
        /// this method copies the vertex and index data into GPU format buffers, ready
        /// for efficient rendering.
        protected void InitializePrimitive(GraphicsDevice graphicsDevice)
        {
            // Create a vertex declaration, describing the format of our vertex data.

            // Create a vertex buffer, and copy our vertex data into it.
            vertexBuffer = new VertexBuffer(graphicsDevice,
                                            typeof(VertexPositionNormal),
                                            vertices.Count, BufferUsage.None);

            vertexBuffer.SetData(vertices.ToArray());

            // Create an index buffer, and copy our index data into it.
            indexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort),
                                          indices.Count, BufferUsage.None);

            indexBuffer.SetData(indices.ToArray());

            // Create a BasicEffect, which will be used to render the primitive.
            basicEffect = new BasicEffect(graphicsDevice);
            basicEffect.EnableDefaultLighting();
        }

        /// <summary>
        /// Once all the geometry has been specified by calling AddVertex and AddIndex,
        /// this method copies the vertex and index data into GPU format buffers, ready
        /// for efficient rendering.
        protected void InitializeTexturePrimitive(GraphicsDevice graphicsDevice)
        {
            // Create a vertex buffer, and copy our vertex data into it.
            vertexBuffer = new VertexBuffer(graphicsDevice,
                                            typeof(VertexPositionNormalTexture),
                                            textureVertices.Count, BufferUsage.None);

            vertexBuffer.SetData(textureVertices.ToArray());

            // Create an index buffer, and copy our index data into it.
            indexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort),
                                          indices.Count, BufferUsage.None);

            indexBuffer.SetData(indices.ToArray());

            // Create a BasicEffect, which will be used to render the primitive.
            basicEffect = new BasicEffect(graphicsDevice);
            basicEffect.EnableDefaultLighting();

        } // End of the InitializeTexturePrimitive method

        /// <summary>
        /// Finalizer.
        /// </summary>
        ~GeometricPrimitive()
        {
            Dispose(false);
        }


        /// <summary>
        /// Frees resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// Frees resources used by this object.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (vertexBuffer != null)
                    vertexBuffer.Dispose();

                if (indexBuffer != null)
                    indexBuffer.Dispose();

                if (basicEffect != null)
                    basicEffect.Dispose();
            }
        }


        #endregion

        #region Draw


        /// <summary>
        /// Draws the primitive model, using the specified effect. Unlike the other
        /// Draw overload where you just specify the world/view/projection matrices
        /// and color, this method does not set any renderstates, so you must make
        /// sure all states are set to sensible values before you call it.
        /// </summary>
        public void Draw(Effect effect)
        {
            GraphicsDevice graphicsDevice = effect.GraphicsDevice;

            // Set our vertex declaration, vertex buffer, and index buffer.
            graphicsDevice.SetVertexBuffer(vertexBuffer);

            graphicsDevice.Indices = indexBuffer;

            foreach (EffectPass effectPass in effect.CurrentTechnique.Passes)
            {
                effectPass.Apply();

                int primitiveCount = indices.Count / 3;

                graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                     vertices.Count, 0, primitiveCount);

            }
        }


        /// <summary>
        /// Draws the primitive model, using a BasicEffect shader with default
        /// lighting. Unlike the other Draw overload where you specify a custom
        /// effect, this method sets important renderstates to sensible values
        /// for 3D model rendering, so you do not need to set these states before
        /// you call it.
        /// </summary>
        public void Draw(Matrix world, Matrix view, Matrix projection, Color color)
        {
            // Set BasicEffect parameters.
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.DiffuseColor = color.ToVector3();
            basicEffect.Alpha = color.A / 255.0f;

            GraphicsDevice device = basicEffect.GraphicsDevice;
            device.DepthStencilState = DepthStencilState.Default;
            
            if (color.A < 255)
            {
                // Set renderstates for alpha blended rendering.
                device.BlendState = BlendState.AlphaBlend;
            }
            else
            {
                // Set renderstates for opaque rendering.
                device.BlendState = BlendState.Opaque;
            }

            // Draw the model, using BasicEffect.
            Draw(basicEffect);
        }

        /// <summary>
        /// Draw the primitive model with texture for a number of buildings.
        /// </summary>
        /// <param name="buildings"></param>
        /// <param name="effect"></param>
        /// <param name="view"></param>
        /// <param name="projection"></param>
        /// <param name="texture"></param>
        public void DrawTextureVerts(List<Building> buildings, String typeOfBuilding, int numberOfBuildings, Matrix view, 
            Matrix projection, Texture2D texture)
        {
            // Set BasicEffect parameters.
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.TextureEnabled = true;
            basicEffect.Texture = texture;

            basicEffect.SpecularColor = new Vector3(0.25f);
            basicEffect.SpecularPower = 16;

            // Create a graphics device.
            GraphicsDevice device = basicEffect.GraphicsDevice;

            // Set renderstates.
            //device.DepthStencilState = DepthStencilState.Default; // Must be Default.
            
            // Set our vertex declaration, vertex buffer, and index buffer.
            device.SetVertexBuffer(vertexBuffer);
            device.Indices = indexBuffer;

            // Loop through each building
            for (int i = 0; i < numberOfBuildings; i++)
            {
                if (buildings[i].GetType().Name == typeOfBuilding)
                {

                    // Change the world parameter.
                    basicEffect.World = buildings[i].world;

                    foreach (EffectPass effectPass in basicEffect.CurrentTechnique.Passes)
                    {
                        effectPass.Apply();

                        int primitiveCount = indices.Count / 3;

                        // Draw the cube.
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                            textureVertices.Count, 0, primitiveCount);

                    }
                }
            }

        } // End of the DrawTextureVerts method.

        /// <summary>
        /// Draw a plane with a texture on it
        /// </summary>
        /// <param name="vegetation"></param>
        /// <param name="view"></param>
        /// <param name="projection"></param>
        /// <param name="texture"></param>
        public void DrawPlaneWithVegetation(List<Vegetation> vegetation, Matrix view, Matrix projection, Texture2D texture)
        {
            // Set BasicEffect parameters.
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.TextureEnabled = true;
            basicEffect.Texture = texture;

            basicEffect.SpecularColor = new Vector3(0.25f);
            basicEffect.SpecularPower = 8;

            // Create a graphics device.
            GraphicsDevice device = basicEffect.GraphicsDevice;

            // Set renderstates.
            //device.DepthStencilState = DepthStencilState.Default; // Must be Default.

            // Set our vertex declaration, vertex buffer, and index buffer.
            device.SetVertexBuffer(vertexBuffer);
            device.Indices = indexBuffer;

            // Loop through each building
            for (int i = 0; i < vegetation.Count; i++)
            {
                // Change the world parameter.
                basicEffect.World = vegetation[i].world;

                foreach (EffectPass effectPass in basicEffect.CurrentTechnique.Passes)
                {
                    effectPass.Apply();

                    int primitiveCount = indices.Count / 3;

                    // Draw the cube.
                    device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                        textureVertices.Count, 0, primitiveCount);

                }
            }

        } // End of the DrawPlaneWithVegetation method.

        #endregion

    } // End of class

} // End of namespace