using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace DOFerVolumeRenderer
{
	/// <summary>
	/// This is a game component that implements IUpdateable.
	/// </summary>
	public class RayMarchingComponent : Microsoft.Xna.Framework.DrawableGameComponent
	{
		Game m_game;
		IRenderTargetProvider m_renderTargetProvider;
		ITransferFunctionProvider m_transferFunctionProvider;
		IVolumeDataProvider m_volumeDataProvider;
		private IVolumeRendererSettings m_settings = null;

		const float ITERATIONS = 450.0f;

		ICamera m_camera = null;

		//Model m_boxModel = null; 
		Effect m_colorCubeFX = null;
		Effect m_RayMarchingFX = null;
		Effect m_RayMarchingVolvisFX = null;

		RenderTarget2D m_backSideBuffer = null;
		RenderTarget2D m_frontSideBuffer = null;

		Cube m_cube = null;
		Plane m_slice = new Plane();

		private Vector3 m_cursorPos = new Vector3(.5f, .5f, .5f);


		public RayMarchingComponent(Game game)
			: base(game)
		{
			m_game = game;
		}

		/// <summary>
		/// Allows the game component to perform any initialization it needs to before starting
		/// to run.  This is where it can query for any required services and load content.
		/// </summary>
		public override void Initialize()
		{
			m_renderTargetProvider = m_game.Services.GetService(typeof(IRenderTargetProvider)) as IRenderTargetProvider;
			m_transferFunctionProvider = m_game.Services.GetService(typeof(ITransferFunctionProvider)) as ITransferFunctionProvider;
			m_volumeDataProvider = m_game.Services.GetService(typeof(IVolumeDataProvider)) as IVolumeDataProvider;

			m_camera = m_game.Services.GetService(typeof(ICamera)) as ICamera;
			m_settings = m_game.Services.GetService(typeof(IVolumeRendererSettings)) as IVolumeRendererSettings;

			m_backSideBuffer = m_renderTargetProvider.RGBTarget1;
			m_frontSideBuffer = m_renderTargetProvider.RGBTarget2;

			base.Initialize();
		}

		protected override void LoadContent()
		{
			base.LoadContent();

			// load all models
			//m_boxModel = m_game.Content.Load<Model>("Models/box");
			m_cube = new Cube();
			m_cube.GenerateVertexBuffer(m_game);

			// load all effects
			m_colorCubeFX = m_game.Content.Load<Effect>("Shaders/ColorCube");
			m_RayMarchingFX = m_game.Content.Load<Effect>("Shaders/RayMarching");
			m_RayMarchingVolvisFX = m_game.Content.Load<Effect>("Shaders/RayMarchingVolvis");
		}

		/// <summary>
		/// Allows the game component to update itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		public override void Update(GameTime gameTime)
		{
			float CursorMoveSpeed = (float)(0.001 * gameTime.ElapsedGameTime.TotalMilliseconds);

			var kbstate = Keyboard.GetState();
			if (kbstate.IsKeyDown(Keys.Up))
			{
				m_cursorPos.Z -= CursorMoveSpeed;
			}
			if (kbstate.IsKeyDown(Keys.Down))
			{
				m_cursorPos.Z += CursorMoveSpeed;
			}
			if (kbstate.IsKeyDown(Keys.Left))
			{
				m_cursorPos.X -= CursorMoveSpeed;
			}
			if (kbstate.IsKeyDown(Keys.Right))
			{
				m_cursorPos.X += CursorMoveSpeed;
			}
			if (kbstate.IsKeyDown(Keys.PageUp))
			{
				m_cursorPos.Y += CursorMoveSpeed;
			}
			if (kbstate.IsKeyDown(Keys.PageDown))
			{
				m_cursorPos.Y -= CursorMoveSpeed;
			}

			m_cursorPos.X = Math.Max(0f, Math.Min(1f, m_cursorPos.X));
			m_cursorPos.Y = Math.Max(0f, Math.Min(1f, m_cursorPos.Y));
			m_cursorPos.Z = Math.Max(0f, Math.Min(1f, m_cursorPos.Z));
		}

		public override void Draw(GameTime gameTime)
		{
			m_slice.Normal = m_camera.FrontVector; // Update m_slice normal for calculations
			m_slice.D = 0; // Reset Distance

			float nearDist = Vector3.Dot(Vector3.Normalize(m_slice.Normal), m_cube.Corners[0]) - 2.0f; // -10.0 ... just make sure the plane is outside of the "unit cube" to compare the distances
			Vector3 bbNear = m_cube.Corners[0];
			Vector3 bbFar = m_cube.Corners[0];
			float farDist = nearDist;
			float tmpDist = 0.0f;
			for (int i = 1; i < 8; i++)
			{
				tmpDist = Vector3.Dot(Vector3.Normalize(m_slice.Normal), m_cube.Corners[i]) - 2.0f; // -10.0 ... just make sure the plane is outside of the "unit cube" to compare the distances
				if (tmpDist < nearDist)
				{
					bbNear = m_cube.Corners[i];
					nearDist = tmpDist;
				}

				if (tmpDist > farDist)
				{
					bbFar = m_cube.Corners[i];
					farDist = tmpDist;
				}
			}

			float distPlaneZero = 0; // Plane to (0/0/0) distance
			float distPlaneFar = (Vector3.Dot(Vector3.Normalize(m_slice.Normal), bbFar));// Plane to furthest Point distance
			float distZeroFar = distPlaneZero - distPlaneFar;

			farDist = (Vector3.Dot(Vector3.Normalize(m_slice.Normal), bbFar));
			nearDist = (Vector3.Dot(Vector3.Normalize(m_slice.Normal), bbNear));

			float marchingDistance = Math.Abs(farDist - nearDist);

			// Set color-cube effect for box-model
			//m_boxModel.RemapModel(m_colorCubeFX);


			// RENDER BACKFACES TO BUFFER

			GraphicsDevice.SetRenderTarget(m_backSideBuffer); // render to texture
			GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.CullClockwiseFace };
			GraphicsDevice.Clear(ClearOptions.Target, Color.Gray, 1.0f, 0);

			//foreach (var subMesh in m_boxModel.Meshes)
			//{
			//    foreach (Effect effect in subMesh.Effects)
			//    {
			//        effect.Parameters["World"].SetValue(Matrix.Identity);
			//        effect.Parameters["View"].SetValue(m_camera.ViewMatrix);
			//        effect.Parameters["Projection"].SetValue(m_camera.ProjectionMatrix);
			//    }
			//    subMesh.Draw();
			//}

			m_game.GraphicsDevice.SetVertexBuffer(m_cube.VertexBuffer);
			m_colorCubeFX.Parameters["World"].SetValue(Matrix.Identity);
			m_colorCubeFX.Parameters["View"].SetValue(m_camera.ViewMatrix);
			m_colorCubeFX.Parameters["Projection"].SetValue(m_camera.ProjectionMatrix);
			foreach (EffectPass pass in m_colorCubeFX.CurrentTechnique.Passes)
			{
				pass.Apply();
				GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, m_cube.Vertices.Length / 3);
			}

			// RENDER FRONTFACES TO BUFFER

			/*stat = new RasterizerState() { CullMode = CullMode.CullCounterClockwiseFace };
			GraphicsDevice.RasterizerState = stat;
			GraphicsDevice.SetRenderTarget(m_frontSideBuffer);
			GraphicsDevice.Clear(ClearOptions.Target, new Color(0,0,0,0), 1.0f, 0);

			foreach (var subMesh in m_boxModel.Meshes)
			{
			    foreach (Effect effect in subMesh.Effects)
			    {
			        effect.Parameters["World"].SetValue(Matrix.Identity);
			        effect.Parameters["View"].SetValue(m_camera.ViewMatrix);
			        effect.Parameters["Projection"].SetValue(m_camera.ProjectionMatrix);
			    }
			    subMesh.Draw();
			}*/

			// Set ray-marching effect for box-model
			//m_boxModel.RemapModel(m_RayMarchingVolvisFX);


			// RENDER THE VOLUME

			GraphicsDevice.SetRenderTarget(m_frontSideBuffer); // render to screen
			GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.CullCounterClockwiseFace }; ;
			GraphicsDevice.Clear(ClearOptions.Target, Color.Black, 1.0f, 0);

			// render the backside-buffer as screen-aligned quad
			//using (SpriteBatch spritebatch = new SpriteBatch(GraphicsDevice))
			//{
			//    spritebatch.Begin();
			//    spritebatch.Draw(m_backSideBuffer, new Rectangle(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight), Color.White);
			//    spritebatch.End();
			//}

			GraphicsDevice.BlendState = BlendState.NonPremultiplied;

			//foreach (var subMesh in m_boxModel.Meshes)
			//{
			//    foreach (Effect effect in subMesh.Effects)
			//    {
			//        effect.Parameters["World"].SetValue(Matrix.Identity);
			//        effect.Parameters["View"].SetValue(m_camera.ViewMatrix);
			//        effect.Parameters["Projection"].SetValue(m_camera.ProjectionMatrix);
			//        effect.Parameters["BackSideBuffer"].SetValue((Texture2D)m_backSideBuffer);
			//        effect.Parameters["TransferTexture"].SetValue(m_transferFunctionProvider.GetTransferFunction());
			//        effect.Parameters["VolumeTexture"].SetValue(m_volumeDataProvider.GetVolume());
			//        effect.Parameters["Iterations"].SetValue(450);
			//        effect.Parameters["StepSize"].SetValue(0.00385f);
			//    }
			//    subMesh.Draw();
			//}

			float stepSize = marchingDistance / ITERATIONS;
			//Console.WriteLine(string.Format("Iterations[{0}], StepSize[{1}], marchingDistance[{2}]", ITERATIONS, stepSize, marchingDistance));

			m_game.GraphicsDevice.SetVertexBuffer(m_cube.VertexBuffer);
			m_RayMarchingVolvisFX.Parameters["World"].SetValue(Matrix.Identity);
			m_RayMarchingVolvisFX.Parameters["View"].SetValue(m_camera.ViewMatrix);
			m_RayMarchingVolvisFX.Parameters["Projection"].SetValue(m_camera.ProjectionMatrix);
			m_RayMarchingVolvisFX.Parameters["BackSideBuffer"].SetValue((Texture2D)m_backSideBuffer);
			m_RayMarchingVolvisFX.Parameters["TransferTexture"].SetValue(m_transferFunctionProvider.GetTransferFunction().Texture);
			m_RayMarchingVolvisFX.Parameters["VolumeTexture"].SetValue(m_volumeDataProvider.GetVolume());
			m_RayMarchingVolvisFX.Parameters["Iterations"].SetValue(ITERATIONS);
			m_RayMarchingVolvisFX.Parameters["StepSize"].SetValue(marchingDistance / ITERATIONS);
			m_RayMarchingVolvisFX.Parameters["Cursor"].SetValue(m_cursorPos);
			m_RayMarchingVolvisFX.Parameters["CursorSize"].SetValue(.1f);
			m_RayMarchingVolvisFX.Parameters["CursorColor"].SetValue(new Vector4(1f, 1f, 1f, 1f));
			foreach (EffectPass pass in m_RayMarchingVolvisFX.CurrentTechnique.Passes)
			{
				pass.Apply();
				GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, m_cube.Vertices.Length / 3);
			}

			GraphicsDevice.BlendState = BlendState.Opaque;

			// Render Screen Aligned Quad
			GraphicsDevice.SetRenderTarget(m_renderTargetProvider.ResultScreenTarget);
			using (SpriteBatch sb = new SpriteBatch(GraphicsDevice))
			{
				sb.Begin(SpriteSortMode.Deferred, BlendState.Opaque);
				sb.Draw(m_frontSideBuffer, new Rectangle(0, 0, 512, 512), Color.Black);
				sb.End();

				sb.Begin(SpriteSortMode.Deferred, BlendState.Additive);
				sb.Draw(m_frontSideBuffer, new Rectangle(0, 0, 512, 512), Color.White);
				sb.End();
			}

		}
	}
}
