using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Dependency;
using Chernobyl.Graphics.Drawing;
using Chernobyl.Graphics.Material;
using Chernobyl.Graphics.Material.Shader.Parameters;
using Chernobyl.Graphics.Polygon.Buffers;
using Chernobyl.Graphics.Texture;
using Chernobyl.Graphics.Writing;
using Chernobyl.Graphics.Xna.Controllers;
using Chernobyl.Graphics.Xna.Controllers.Shaders;
using Chernobyl.Graphics.Xna.Resources;
using Chernobyl.Graphics.Xna.Texture;
using Chernobyl.Graphics.Xna.Writing;
using Chernobyl.Plugin;
using Chernobyl.Resources;
using Microsoft.Xna.Framework.Graphics;

namespace Chernobyl.Graphics.Xna
{
    /// <summary>
    /// The XNA graphics plug-in for Chernobyl.Graphics.
    /// </summary>
    public class XnaPlugin : IPlugin
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="services">The <see cref="IEventCollection{T}"/> instance that 
        /// gives and takes services.</param>
        public XnaPlugin(IEventCollection<object> services)
        {
            Trace = new TraceSource("Chernobyl.Graphics.Xna");
            Services = services;

            services.Add((MeshFactoryServiceCreator)XnaMeshFactoryServiceCreator);
            services.Add((IndexBufferFactoryServiceCreator)XnaIndexBufferServiceCreator);
            RenderFactory = (serv, primType, drawable, transform, volume) => new XnaRender(serv, primType, drawable, transform, volume);
            Texture2DFactory = (serv, width, height, mipmapLevels, textureFormat) => new XnaTexture2D(serv, width, height, mipmapLevels, textureFormat);
            BackBuffer = new XnaBackBuffer();
            TextureParameterFactory = (serv, val, paramName) => new XnaTextureParameter(serv, val, paramName);
            BasicEffectFactory = (serv) => new Effects.BasicEffect(serv);
            TextFactory = (serv, font) => new XnaText(serv, font);

            // Create the effect parameter converters
            {
                EffectParameterConverters = new SortedList<EffectParameterType, Func<EffectParameter, IShaderParameter>>(1);

                // if you add more parameters, make sure to update the capacity set in the SortedList constructor above
                EffectParameterConverters.Add(EffectParameterType.Texture2D, param => new XnaTextureParameter(services, new XnaTexture2D(services, param.GetValueTexture2D()), param.Name));
            }
            
            services.Inject(this);
        }

        /// <summary>
        /// The services provided by this plug-in or null if no services are
        /// provided.
        /// </summary>
        public IEventCollection<object> Services { get; private set; }

        /// <summary>
        /// A <see cref="TraceSource"/> used to output errors, warnings, information, 
        /// etc., that are specific to Chernobyl.Graphics.Xna. This 
        /// <see cref="TraceSource"/> will have the name "Chernobyl.Graphics.Xna".
        /// </summary>
        public static TraceSource Trace { get; private set; }

        /// <summary>
        /// Holds vertex declarations that were previously created
        /// so that they can be re-used.
        /// </summary>
        [Provide]
        public IDictionary<Type, Information> VertexDeclarationStore
        {
            get { return _vertexDeclarationStore ?? (_vertexDeclarationStore = new Dictionary<Type, Information>()); }
        }

        /// <summary>
        /// Creates DrawBufferFactory controllers.
        /// </summary>
        [Provide]
        public IDrawBufferFactory DrawBufferFactory
        {
            get { return _drawBufferFactory ?? (_drawBufferFactory = new XnaDrawBuffer.Factory()); }
        }

        /// <summary>
        /// Creates IRender controllers.
        /// </summary>
        [Provide]
        public RenderFactory RenderFactory { get; set; }

        /// <summary>
        /// Creates Texture2D controllers
        /// </summary>
        [Provide]
        public Texture2DFactory Texture2DFactory { get; set; }

        /// <summary>
        /// The back buffer provided by XNA.
        /// </summary>
        [Provide]
        public IBackBuffer BackBuffer { get; private set; }

        /// <summary>
        /// The texture shader parameter factory.
        /// </summary>
        [Provide]
        public ShaderParameterFactory<ITexture> TextureParameterFactory { get; set; }

        /// <summary>
        /// The factory that creates basic effects.
        /// </summary>
        [Provide]
        public BasicEffectFactory BasicEffectFactory { get; set; }

        /// <summary>
        /// The factory that produces the IText instances.
        /// </summary>
        [Provide]
        public TextFactory TextFactory { get; set; }

        /// <summary>
        /// A <see cref="SortedList{TKey, TValue}"/> that holds mappings of an
        /// <see cref="EffectParameterType"/> to converter methods. These 
        /// converter methods are responsible for converting the passed in XNA
        /// <see cref="EffectParameter"/> (which of type <see cref="EffectParameterType"/>) 
        /// to a Chernobyl <see cref="IShaderParameter"/>.
        /// </summary>
        [Provide]
        public SortedList<EffectParameterType, Func<EffectParameter, IShaderParameter>> EffectParameterConverters { get; private set; }

        /// <summary>
        /// The root shader IResourceProcessor that was added by Chernobyl.Graphics.
        /// </summary>
        [Inject]
        public IResourceProcessor<IEffect> RootEffectProcessor 
        { 
            set
            {
                FileStreamProcessor<IEffect> effectFileStreamProcessor = value.OfType<FileStreamProcessor<IEffect>>().First();
                // TODO: add in the XNA IEffect processor
                //effectFileStreamProcessor.FileStreamProcessors.Add("[a-zA-Z]*\\.fx", );
            }
        }

        /// <summary>
        /// The root font resource processor.
        /// </summary>
        [Inject]
        public IResourceProcessor<IFont> RootFontProcessor
        {
            set
            {
                // get the resource processor at the end of the resource processing chain
                IResourceProcessor<IFont> lastFontProcessor = value.First(proc => proc.NextResourceProcessor == null);
                lastFontProcessor.NextResourceProcessor = new XnaResourceProcessor<IFont, SpriteFont>(Services, spriteFont => new XnaFont(Services, spriteFont));
            }
        }

        /// <summary>
        /// The root texture resource processor.
        /// </summary>
        [Inject]
        public IResourceProcessor<ITexture> RootTextureProcessor
        {
            set
            {
                // get the resource processor at the end of the resource processing chain
                IResourceProcessor<ITexture> lastTextureProcessor = value.First(proc => proc.NextResourceProcessor == null);
                lastTextureProcessor.NextResourceProcessor = new XnaResourceProcessor<ITexture, Microsoft.Xna.Framework.Graphics.Texture2D>(Services, texture => new XnaTexture2D(Services, texture));
            }
        }

        /// <summary>
        /// Method that creates the <see cref="MeshFactory{TData}"/>.
        /// </summary>
        /// <param name="dataType">Type of the data that will be stored in the 
        /// <see cref="IBuffer{TData}"/> created by the <see cref="MeshFactory{TData}"/>.</param>
        /// <returns>The <see cref="MeshFactory{TData}"/> in the form of an 
        /// <see cref="Object"/>.</returns>
        public static object XnaMeshFactoryServiceCreator(Type dataType)
        {
            MethodInfo method = typeof(XnaPlugin).GetMethod("MeshFactory").MakeGenericMethod(dataType);
            Type methodType = typeof (MeshFactory<>).MakeGenericType(dataType);
            return Delegate.CreateDelegate(methodType, method);
        }

        /// <summary>
        /// Method that creates the <see cref="IndexBufferFactory{TData}"/>.
        /// </summary>
        /// <param name="dataType">Type of the data.</param>
        /// <returns></returns>
        public static object XnaIndexBufferServiceCreator(Type dataType)
        {
            MethodInfo method = typeof(XnaPlugin).GetMethod("IndexBufferFactory").MakeGenericMethod(dataType);
            Type methodType = typeof(MeshFactory<>).MakeGenericType(dataType);
            return Delegate.CreateDelegate(methodType, method);
        }

        /// <summary>
        /// A method that creates meshes that interface with XNA vertex buffers.
        /// </summary>
        /// <typeparam name="TData">The type of data that is going to be stored in
        /// the buffer like ints, floats, shorts, a custom mesh element type, etc.</typeparam>
        /// <param name="services">The list of services to inject into this buffer.</param>
        /// <param name="meshElements">The mesh element data to place in the mesh.</param>
        /// <returns>The <see cref="IBuffer{TData}"/> that represents the mesh.</returns>
        public static IBuffer<TData> MeshFactory<TData>(IEventCollection<object> services, TData[] meshElements) where TData : struct
        {
            return new XnaMesh<TData>(services, meshElements);
        }

        /// <summary>
        /// A method that creates index buffer that interface with XNA index buffers.
        /// </summary>
        /// <typeparam name="TData">The type of data that is going to be stored in
        /// the buffer like ints, floats, shorts, a custom mesh element type, etc.</typeparam>
        /// <param name="services">The list of services to inject into this buffer.</param>
        /// <param name="indices">The index data to place in this buffer.</param>
        /// <returns>The <see cref="IBuffer{TData}"/> that represents the mesh.</returns>
        public static IBuffer<TData> IndexBufferFactory<TData>(IEventCollection<object> services, TData[] indices) where TData : struct
        {
            return new XnaIndexBuffer<TData>(services, indices);
        }

        /// <summary>
        /// The backing field to <see cref="VertexDeclarationStore"/>.
        /// </summary>
        IDictionary<Type, Information> _vertexDeclarationStore;

        /// <summary>
        /// The backing field to <see cref="DrawBufferFactory"/>.
        /// </summary>
        IDrawBufferFactory _drawBufferFactory;
    }
}