using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using GraphiXNA.Graphics.Library;
using GraphiXNA.Graphics;
using GraphiXNA.Cameras;

namespace GraphiXNA.Materials
{
    public abstract class MaterialEffect 
    {   
        internal GraphicsDevice GraphicsDevice = null;
        internal Effect _Effect = null;

        public MaterialEffect(GraphicsDevice graphicsDevice)
        {
            ResourceManager cm = ResourceManager.FindBy(graphicsDevice);

            GraphicsDevice = graphicsDevice;

        }

        internal virtual void OnBegin()
        {

        }

        internal virtual void OnBeginPass()
        {
        }

        public void Begin()
        {
            OnBegin();
            _Effect.Begin();
        }

        public void Draw(Model model, Matrix world)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            Matrix w = Matrix.Identity;

            for (int i = 0; i < model.Meshes.Count; i++)
            {
                ModelMesh modelMesh = model.Meshes[i];
                GraphicsDevice.Indices = modelMesh.IndexBuffer;

                for (int j = 0; j < modelMesh.MeshParts.Count; j++)
                {
                    ModelMeshPart p = modelMesh.MeshParts[j];

                    GraphicsDevice.Vertices[0].SetSource(modelMesh.VertexBuffer, p.StreamOffset, p.VertexStride);
                    GraphicsDevice.VertexDeclaration = p.VertexDeclaration;
                    w = transforms[model.Meshes[i].ParentBone.Index] * world;
                    SetWorld(ref w);
                    DrawIndexedPrimitives(PrimitiveType.TriangleList, p.BaseVertex, 0, p.NumVertices, p.StartIndex, p.PrimitiveCount);
                }
            }
        }

        public void Draw(ModelMesh modelMesh)
        {
            GraphicsDevice.Indices = modelMesh.IndexBuffer;
            
            for (int j = 0; j < modelMesh.MeshParts.Count; j++)
            {
                ModelMeshPart p = modelMesh.MeshParts[j];

                GraphicsDevice.Vertices[0].SetSource(modelMesh.VertexBuffer, p.StreamOffset, p.VertexStride);
                GraphicsDevice.VertexDeclaration = p.VertexDeclaration;

                DrawIndexedPrimitives(PrimitiveType.TriangleList, p.BaseVertex, 0, p.NumVertices, p.StartIndex, p.PrimitiveCount);
            }
        }

        public void DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount)
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                GraphicsDevice.DrawIndexedPrimitives(primitiveType, baseVertex, minVertexIndex, numVertices, startIndex, primitiveCount);
                pass.End();
            }
        }

        public void DrawPrimitives(PrimitiveType primitiveType, int startVertex, int primitiveCount)
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                GraphicsDevice.DrawPrimitives(primitiveType, startVertex, primitiveCount);
                pass.End();
            }
        }
        public void DrawUserIndexedPrimitives<T>(PrimitiveType primitiveType, T[] vertexData, int vertexOffset, int numVertices, int[] indexData, int indexOffset, int primitiveCount) where T : struct
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {

                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                GraphicsDevice.DrawUserIndexedPrimitives<T>(primitiveType, vertexData, vertexOffset, numVertices, indexData, indexOffset, primitiveCount);
                pass.End();
            }
        }
        public void DrawUserIndexedPrimitives<T>(PrimitiveType primitiveType, T[] vertexData, int vertexOffset, int numVertices, short[] indexData, int indexOffset, int primitiveCount) where T : struct
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                GraphicsDevice.DrawUserIndexedPrimitives<T>(primitiveType, vertexData, vertexOffset, numVertices, indexData, indexOffset, primitiveCount);
                pass.End();
            }
        }
        public void DrawUserPrimitives<T>(PrimitiveType primitiveType, T[] vertexData, int vertexOffset, int primitiveCount) where T : struct
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                GraphicsDevice.DrawUserPrimitives<T>(primitiveType, vertexData, vertexOffset, primitiveCount);
                pass.End();
            }
        }

        public void Draw(IRenderable renderable)
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                renderable.Draw();
                pass.End();
            }
        }

        public void Draw(Object3D obj)
        {
            OnBeginPass();
            for (int i = 0; i < _Effect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = _Effect.CurrentTechnique.Passes[i];
                pass.Begin();
                obj.Draw();
                pass.End();
            }
        }

        public void End()
        {
            _Effect.End();
        }

        public abstract void SetWorld(Camera camera, ref Matrix world);
        public void SetWorld(Matrix world)
        {
            SetWorld(Camera._ActiveCamera, ref world);
        }

        public void SetWorld(ref Matrix world)
        {
            SetWorld(Camera._ActiveCamera, ref world);
        }

        public void SetWorld(Camera camera, Matrix world)
        {
            SetWorld(camera, ref world);
        }

        public virtual void Update(GameTime gameTime)
        {
        }
    }
}
