using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Innovation
{
    public class MaterialManager
    {
        List<Material> materials = new List<Material>();

        public MaterialManager()
        {
            if (!Engine.Services.ContainsService(typeof(MaterialManager)))
                Engine.Services.AddService(typeof(MaterialManager), this);

            TexturedMaterial material = new TexturedMaterial();
            material.Name = "Default Material";
            materials.Add(material);
        }

        public void RegisterMaterial(Material Material)
        {
            if (!materials.Contains(Material))
                materials.Add(Material);
        }

        public Material GetMaterial(string Name)
        {
            foreach (Material material in materials)
                if (material.Name == Name)
                    return material;

            return null;
        }

        public void DeleteMaterial(string Name)
        {
            foreach (Material material in materials)
                if (material.Name == Name)
                {
                    materials.Remove(material);
                    break;
                }
        }
    }

    // Base Material class keeps track of an effect and handles basic
    // properties for it
    public class Material : Component
    {
        // The effect to handle
        Effect effect = new BasicEffect(Engine.GraphicsDevice, null);
        string effectName = "XNA.BasicEffect";

        public virtual Effect Effect
        {
            get { return effect; }
        }

        public virtual string EffectName
        {
            get { return effectName; }
            set
            {
                effectName = value;

                if (effectName == "XNA.BasicEffect")
                {
                    this.effect = new BasicEffect(Engine.GraphicsDevice, null);
                    ((BasicEffect)effect).TextureEnabled = true;
                    ((BasicEffect)effect).EnableDefaultLighting();
                }
                else
                    this.effect = Engine.Content.Load<Effect>(value);
            }
        }

        public Material()
        {
            if (!Engine.Services.ContainsService(typeof(MaterialManager)))
                new MaterialManager();

            Engine.Services.GetService<MaterialManager>().RegisterMaterial(this);
            
            ((BasicEffect)effect).TextureEnabled = true;
            ((BasicEffect)effect).EnableDefaultLighting();
        }

        // Set World, View, and Projection values
        public virtual void Prepare3DDraw(Matrix World)
        {
            effect.Parameters["World"].SetValue(World);
            effect.Parameters["View"].SetValue(
                Engine.Services.GetService<Camera>().View);
            effect.Parameters["Projection"].SetValue(
                Engine.Services.GetService<Camera>().Projection);
        }

        public override void SaveSerializationData(SerializationData Data)
        {
            base.SaveSerializationData(Data);
            Data.AddData("Material.Effect", EffectName);
        }

        public override void LoadFromSerializationData(SerializationData Data)
        {
            base.LoadFromSerializationData(Data);
            this.EffectName = Data.GetData<string>("Material.Effect");
        }

        public override void DisableComponent()
        {
            Engine.Services.GetService<MaterialManager>().DeleteMaterial(this.Name);
            base.DisableComponent();
        }
    }

    // Material that handles a texture
    public class TexturedMaterial : Material
    {
        // The texture to keep track of
        Texture2D texture;
        string textureName;

        // Set accesor sets the texture to the underlying effect
        public virtual Texture2D Texture
        {
            get { return texture; }
        }

        public virtual string TextureName
        {
            get { return textureName; }
            set
            {
                textureName = value;
                texture = Engine.Content.Load<Texture2D>(value);
            }
        }

        // Method to set the texture to various types of effect
        internal static void SetTextureToEffect(Effect Effect, Texture2D Texture)
        {
            if (Effect == null)
                return;

            if (Effect is BasicEffect)
                ((BasicEffect)Effect).Texture = Texture;
            else if (Effect.Parameters["Texture"] != null)
                Effect.Parameters["Texture"].SetValue(Texture);
        }

        public override void Prepare3DDraw(Matrix World)
        {
            base.Prepare3DDraw(World);

            SetTextureToEffect(Effect, texture);
        }

        public override void SaveSerializationData(SerializationData Data)
        {
            base.SaveSerializationData(Data);
            Data.AddData("TexturedMaterial.Texture", (TextureName != null ? TextureName : "unset"));
        }

        public override void LoadFromSerializationData(SerializationData Data)
        {
            base.LoadFromSerializationData(Data);

            string texName = Data.GetData<string>("TexturedMaterial.Texture");
            if (texName != "unset")
                this.TextureName = texName;
        }
    }
}
