using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace ContentPipelineFredriksdal
{
    /// <summary>
    /// Custom content processor for creating terrain meshes. Given an
    /// input heightfield texture, this processor uses the MeshBuilder
    /// class to programatically generate terrain geometry.
    /// </summary>
    [ContentProcessor]
    public class HillsProcessor : ContentProcessor<Texture2DContent, ModelContent>
    {
        // terrainScale: 4 , terrainBumpiness: 64, texCoordScale: 0.1f
        private static float terrainScale = 100;
        private static float terrainBumpiness = 10000;
        private static float texCoordScale = 0.1f;
        private static string textureMapTexture = "Maps/Hills/hillsTextureMap.bmp";

        // Texture map variables
        private static Texture2DContent textureMap;

        /// <summary>
        /// Generates a terrain mesh from an input heightfield texture.
        /// </summary>
        public override ModelContent Process(Texture2DContent input, ContentProcessorContext context)
        {
            // Create a new MeshBuilder to process the terrain.
            MeshBuilder builder = MeshBuilder.StartMesh("terrain");

            // Get the texture map
            textureMap = context.BuildAndLoadAsset<TextureContent, Texture2DContent>(new ExternalReference<TextureContent>(textureMapTexture), null);

            // Convert the input texture to float format, for ease of processing.
            input.ConvertBitmapType(typeof(PixelBitmapContent<float>));
            textureMap.ConvertBitmapType(typeof(PixelBitmapContent<float>));

            // Create a PixelBitmapContent for the height map
            PixelBitmapContent<float> heightfield;
            heightfield = (PixelBitmapContent<float>)input.Mipmaps[0];

            // Create a PixelBitmapContent for the texture map
            PixelBitmapContent<float> texturefield;
            texturefield = (PixelBitmapContent<float>)textureMap.Mipmaps[0];

            // Create the terrain vertices.
            for (int y = 0; y < heightfield.Height; y++)
            {
                for (int x = 0; x < heightfield.Width; x++)
                {
                    Vector3 position;

                    position.X = (x - heightfield.Width / 2) * terrainScale;
                    position.Z = (y - heightfield.Height / 2) * terrainScale;

                    position.Y = (heightfield.GetPixel(x, y) - 1) * terrainBumpiness;


                    builder.CreatePosition(position);
                }
            }

            // Create a vertex channel for holding texture coordinates.
            int texCoordId = builder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));

            // Create a vertex channel for holding weights for each vertex
            int texWeightId = builder.CreateVertexChannel<Vector4>(VertexChannelNames.TextureCoordinate(1));

            // Create a new Vector4 to hold weights for each vertex.
            Vector4 weights = new Vector4();

            // Loop through the heightfield to create vertexes for the terrain.
            for (int y = 0; y < heightfield.Height - 1; y++)
            {
                for (int x = 0; x < heightfield.Width - 1; x++)
                {

                    // Explanation
                    // Road: x or 0; 0 on textureMap
                    // Sand: y or 1; 85 on textureMap
                    // Grass: z or 2; 170 on textureMap
                    // Rock: w or 3; 255 on textureMap

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x, y, texWeightId, weights);

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x + 1, y, texWeightId, weights);

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x + 1, y + 1, texWeightId, weights);

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x, y, texWeightId, weights);

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x + 1, y + 1) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x + 1, y + 1, texWeightId, weights);

                    weights.X = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y + 1) - 0.15f)) / 0.25f, 0, 1);
                    weights.Y = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y + 1) - 0.30f)) / 0.25f, 0, 1);
                    weights.Z = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y + 1) - 0.60f)) / 0.25f, 0, 1);
                    weights.W = MathHelper.Clamp((0.25f - Math.Abs(texturefield.GetPixel(x, y + 1) - 0.90f)) / 0.25f, 0, 1);
                    weights = NormaliseWeights(weights);
                    AddVertex(builder, texCoordId, heightfield.Width, x, y + 1, texWeightId, weights);

                }
            }

            // Chain to the ModelProcessor so it can convert the mesh we just generated.
            MeshContent terrainMesh = builder.FinishMesh();

            // Create a model
            ModelContent model = context.Convert<MeshContent, ModelContent>(terrainMesh, "ModelProcessor");

            // Generate information about the height map, and attach it to the finished
            // model's tag. This will create heigth map information content.
            model.Tag = new HeightMapInfoContent(terrainMesh, terrainScale, heightfield.Width, heightfield.Height);

            return model;

        } // End of the process method

        // A method to normalize weights so that the total weight sums up to 1
        static Vector4 NormaliseWeights(Vector4 weights)
        {
            float totalWeight = weights.X;
            totalWeight += weights.Y;
            totalWeight += weights.Z;
            totalWeight += weights.W;
            weights.X /= totalWeight;
            weights.Y /= totalWeight;
            weights.Z /= totalWeight;
            weights.W /= totalWeight;
            return weights;

        } // End of the NormaliseWeights method

        /// <summary>
        /// Helper for adding a new triangle vertex to a MeshBuilder along with an associated texture coordinate value.
        /// </summary>
        static void AddVertex(MeshBuilder builder, int texCoordId, int w, int x, int y, int texWeightId, Vector4 weights)
        {
            builder.SetVertexChannelData(texCoordId, new Vector2(x, y) * texCoordScale);
            builder.SetVertexChannelData(texWeightId, weights);

            builder.AddTriangleVertex(x + y * w);

        } // End of the AddVertex method

    } // End of class

} // End of namespace