using System;
using System.Collections.Generic;
using System.Text;
using Ika.Graphics.Effects;
using Ika.Graphics;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Zombids.Graphics;

namespace Ika.Graphics
{
    /// <summary>
    /// A renderer providing basic functionalities
    /// </summary>
    public class BasicRenderer : Microsoft.Xna.Framework.GameComponent
    {
        /// <summary>
        /// The graphics device
        /// </summary>
        protected GraphicsDevice m_device;

        /// <summary>
        /// The renderer main camera
        /// </summary>
        protected Camera m_camera;
        public Camera Camera
        {
            get { return m_camera; }
            set { m_camera = value; }
        }

        /// <summary>
        /// An orthographic camera for 2D effects
        /// </summary>
        protected Camera m_cameraHUD;
        public Camera CameraHUD
        {
            get { return m_cameraHUD; }
            set { m_cameraHUD = value; }
        }

        private Color m_debugColor;
        public Color DebugColor
        {
            get { return m_debugColor; }
            set { m_debugColor = value; m_uniformEffect.Color = value; }
        }
	

        /// <summary>
        /// A unit quad mesh for sprite and fullscreen rendering
        /// </summary>
        protected Mesh m_spriteMesh;

        protected Mesh m_lineMesh;
        protected Mesh m_circleMesh;

        protected SpriteEffect m_spriteEffect;
        protected UniformEffect m_uniformEffect;

        /// <summary>
        /// Matrix used for fullscreen rendering
        /// </summary>
        Matrix m_fullScreenQuadMatrix;

        /// <summary>
        /// The last mesh sent to the device
        /// </summary>
        Mesh m_lastMesh;

        /// <summary>
        /// An object containing stats about the last frame
        /// </summary>
        protected FrameInfo m_lastFrameInfo;
        public FrameInfo LastFrameInfo
        {
            get { return m_lastFrameInfo; }
        }

        /// <summary>
        /// An object containing stats about the current frame
        /// </summary>
        protected FrameInfo m_currentFrameInfo;

        /// <summary>
        /// Create a renderer
        /// </summary>
        /// <param name="game">The XNA game object</param>
        public BasicRenderer(Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Initialize the renderer
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            m_device = Game.GraphicsDevice;

            OrbitCamera orbitCam = new OrbitCamera();
            orbitCam.SetPointOfInterest(Vector3.Zero);
            orbitCam.Radius = 850.0f;
            m_camera = orbitCam;

            TopDownCamera topCam = new TopDownCamera();
            topCam.Height = 100;
            topCam.SetPointOfInterest(Vector3.Zero);
            m_camera = topCam;

            m_cameraHUD = new OrthoCam(new Vector2(0, 0), new Vector2(m_device.PresentationParameters.BackBufferWidth, m_device.PresentationParameters.BackBufferHeight));

            m_lastFrameInfo = new FrameInfo();
            m_currentFrameInfo = new FrameInfo();

            m_fullScreenQuadMatrix = Matrix.Identity;
            m_fullScreenQuadMatrix *= Matrix.CreateScale(400,300,1);
            m_fullScreenQuadMatrix *= Matrix.CreateTranslation(new Vector3(400, 300, 0.0f));

            m_lastMesh = null;

            m_spriteEffect = new SpriteEffect(Game.Content.Load<Effect>("Shaders/SpriteEffect"));
            m_uniformEffect = new UniformEffect(Game.Content.Load<Effect>("Shaders/UniformEffect"));
            m_uniformEffect.Color = Color.Orange;

            m_spriteMesh = new PlanMesh(Game.GraphicsDevice);
            m_lineMesh = new LineMesh(m_device);
            m_circleMesh = new CircleMesh(m_device);
        }

        /// <summary>
        /// Compute a frustrum culling mesh using the current camera
        /// </summary>
        /// <param name="mesh">The mesh to be culled</param>
        /// <param name="world">The mesh transform matrix</param>
        /// <returns></returns>
        protected bool TestFrustrumCulling(Mesh mesh, Matrix world)
        {
            BoundingSphere sphere = MeshHelper.TransformBoundingSphere(mesh.BoundingSphere, world);
            BoundingFrustum frustrum = m_camera.Frustrum;

            if (frustrum.Contains(sphere) != ContainmentType.Disjoint)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Draw a mesh to the current target
        /// </summary>
        /// <param name="mesh">The mesh to be drawn</param>
        /// <param name="world">The mesh transform matrix</param>
        /// <param name="effect">The shader used for the draw</param>
        /// <param name="testFrustrumCulling">Indicate wether to test the frustrum culling</param>
        protected void DrawMesh(Mesh mesh, Matrix world, Effect effect, bool testFrustrumCulling)
        {
            if (testFrustrumCulling == false || TestFrustrumCulling(mesh, world))
            {
                //Send the mesh to the device if needed
                if (mesh != m_lastMesh)
                {
                    m_device.VertexDeclaration = mesh.VertexDeclaration;
                    m_device.Vertices[0].SetSource(mesh.VertexBuffer, 0, mesh.VertexDeclaration.GetVertexStrideSize(0));
                    m_device.Indices = mesh.IndexBuffer;
                }

                //Set the shader parameters
                EffectWrapper wrapper = new EffectWrapper(effect);
                wrapper.World = world;
                wrapper.View = Camera.ViewMatrix;
                wrapper.Projection = Camera.ProjectionMatrix;

                //Draw the mesh
                effect.Begin();
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    {
                        m_device.DrawIndexedPrimitives(mesh.PrimitiveType, 0, 0, mesh.VerticesCount, 0, mesh.PrimitiveCount);
                    }
                    m_currentFrameInfo.PrimitiveCount += mesh.PrimitiveCount;
                    pass.End();
                }
                m_currentFrameInfo.MeshCount++;
                effect.End();
            }
        }

        /// <summary>
        /// Draw a sprite to the screen.
        /// The screen coordinates are defined by:
        /// (0,0) in the top-left corner
        /// (100,100) in the bottom-right corner
        /// </summary>
        /// <param name="sprite">The sprite to be drawn</param>
        /// <param name="spriteCenter">The sprite position</param>
        /// <param name="spriteRotation">The sprite orientation in radians</param>
        public void DrawHUD(Sprite sprite, Vector2 spriteCenter, float spriteRotation)
        {
            DrawHUD(sprite, spriteCenter, spriteRotation, 1);
        }

        /// <summary>
        /// Draw a sprite to the screen.
        /// The screen coordinates are defined by:
        /// (0,0) in the top-left corner
        /// (100,100) in the bottom-right corner
        /// </summary>
        /// <param name="sprite">The sprite to be drawn</param>
        /// <param name="spriteCenter">The sprite position</param>
        /// <param name="spriteRotation">The sprite orientation in radians</param>
        /// <param name="alpha">The sprite alpha</param>
        public void DrawHUD(Sprite sprite, Vector2 spriteCenter, float spriteRotation, float alpha)
        {
            Camera cam = Camera;
            Camera = CameraHUD;

            m_device.RenderState.DepthBufferEnable = false;

            Rectangle textureSource = new Rectangle((int)sprite.TextureSourceOrigin.X, (int)sprite.TextureSourceOrigin.Y, (int)sprite.TextureSourceEnd.X - (int)sprite.TextureSourceOrigin.X, (int)sprite.TextureSourceEnd.Y - (int)sprite.TextureSourceOrigin.Y);

            DrawSprite(sprite.Texture, textureSource, spriteCenter, sprite.Size, spriteRotation, alpha);

            Camera = cam;
        }

        /// <summary>
        /// Draw a sprite full screen
        /// </summary>
        /// <param name="sprite">The sprite to be drawn</param>
        /// <param name="alpha">The sprite alpha</param>
        public void DrawFullScreen(Sprite sprite, float alpha)
        {
            Camera cam = Camera;
            Camera = CameraHUD;
            m_device.RenderState.DepthBufferEnable = false;
            Rectangle textureSource = new Rectangle((int)sprite.TextureSourceOrigin.X, (int)sprite.TextureSourceOrigin.Y, (int)sprite.TextureSourceEnd.X - (int)sprite.TextureSourceOrigin.X, (int)sprite.TextureSourceEnd.Y - (int)sprite.TextureSourceOrigin.Y);

            DrawSprite(sprite.Texture, textureSource, new Vector2(50, 50), new Vector2(100, 100), 0, alpha);

            Camera = cam;
        }

        /// <summary>
        /// Use a shader on a fullscreen quad.
        /// </summary>
        /// <param name="effect">The shader to be used</param>
        protected void DrawFullScreen(Effect effect)
        {
            Camera cam = Camera;
            Camera = CameraHUD;

            m_device.RenderState.CullMode = CullMode.None;
            DrawMesh(m_spriteMesh, m_fullScreenQuadMatrix, effect, false);

            Camera = cam;
        }

        /// <summary>
        /// Draw a texture to the screen.
        /// The screen coordinates are defined by:
        /// (0,0) in the top-left corner
        /// (100,100) in the bottom-right corner
        /// </summary>
        /// <param name="texture">The texture to be drawn</param>
        /// <param name="textureSource">The rectangle to read the texture from</param>
        /// <param name="spriteCenter">The sprite position</param>
        /// <param name="spriteRotation">The sprite orientation in radians</param>
        /// <param name="alpha">The sprite alpha</param>
        public void DrawSprite(Texture2D texture, Rectangle textureSource, Vector2 spriteCenter, Vector2 spriteSize, float spriteRotation, float alpha)
        {
            Matrix World = Matrix.Identity;
            World *= Matrix.CreateScale(new Vector3(spriteSize.X * 0.5f, spriteSize.Y * 0.5f, 1));
            World *= Matrix.CreateRotationZ(spriteRotation);
            World *= Matrix.CreateTranslation(new Vector3(spriteCenter.X, spriteCenter.Y, 0));

            m_spriteEffect.Texture = texture;

            Vector2 UVScale = new Vector2((float)textureSource.Width / (float)texture.Width, (float)textureSource.Height / (float)texture.Height);
            Vector2 UVOffset = new Vector2((float)textureSource.Left / (float)texture.Width, (float)textureSource.Top / (float)texture.Height);

            m_spriteEffect.UVScale = UVScale;
            m_spriteEffect.UVOffset = UVOffset;

            m_spriteEffect.Alpha = alpha;

            m_device.RenderState.CullMode = CullMode.None;

            DrawMesh(m_spriteMesh, World, m_spriteEffect.Effect, true);
        }

        public void DrawEffect(Effect effect, Rectangle textureSource, Vector2 center, Vector2 size)
        {
            Matrix World = Matrix.Identity;
            World *= Matrix.CreateScale(new Vector3(size.X * 0.5f, size.Y * 0.5f, 1));
            World *= Matrix.CreateTranslation(new Vector3(center.X, center.Y, 0));

            m_device.RenderState.CullMode = CullMode.None;
            DrawMesh(m_spriteMesh, World, effect, true);
        }

    }
}


