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 the main type for your game
	/// </summary>
	public class VolumeRenderer : Microsoft.Xna.Framework.Game, IVolumeDataProvider, ITransferFunctionProvider, IRenderTargetProvider
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;

		Camera m_camera = null;
		InputHelper m_inputHelper = null;
		VolumeRendererSettings m_settings = null;

		RenderTarget2D m_RGBTarget1;
		RenderTarget2D m_RGBTarget2;
		RenderTarget2D m_FloatingTarget1;
		RenderTarget2D m_FloatingTarget2;
		RenderTarget2D m_FloatingTarget3;
		RenderTarget2D m_FloatingTarget4;
        RenderTarget2D m_FloatingTarget5;
		RenderTarget2D m_result;

		int m_volTexIndex = 0;
		List<Texture3D> m_volumeTextures = new List<Texture3D>();

		Dictionary<string, Model> m_models = new Dictionary<string, Model>();
		Dictionary<string, Effect> m_effects = new Dictionary<string, Effect>();
		Dictionary<string, Texture2D> m_textures = new Dictionary<string, Texture2D>();

		int m_activeTransfer = 0;
		List<TransferFunction> m_transferFunctions = new List<TransferFunction>();

		public VolumeRenderer()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
		}


		/// <summary>
		/// Attempt to set the display mode to the desired resolution.  Itterates through the display
		/// capabilities of the default graphics adapter to determine if the graphics adapter supports the
		/// requested resolution.  If so, the resolution is set and the function returns true.  If not,
		/// no change is made and the function returns false.
		/// </summary>
		/// <param name="iWidth">Desired screen width.</param>
		/// <param name="iHeight">Desired screen height.</param>
		/// <param name="bFullScreen">True if you wish to go to Full Screen, false for Windowed Mode.</param>
		private bool InitGraphicsMode(int iWidth, int iHeight, bool bFullScreen)
		{
			// If we aren't using a full screen mode, the height and width of the window can
			// be set to anything equal to or smaller than the actual screen size.
			if (bFullScreen == false)
			{
				if ((iWidth <= GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width)
					&& (iHeight <= GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height))
				{
					graphics.PreferredBackBufferWidth = iWidth;
					graphics.PreferredBackBufferHeight = iHeight;
					graphics.IsFullScreen = bFullScreen;
					graphics.ApplyChanges();
					return true;
				}
			}
			else
			{
				// If we are using full screen mode, we should check to make sure that the display
				// adapter can handle the video mode we are trying to set.  To do this, we will
				// iterate thorugh the display modes supported by the adapter and check them against
				// the mode we want to set.
				foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes)
				{
					// Check the width and height of each mode against the passed values
					if ((dm.Width == iWidth) && (dm.Height == iHeight))
					{
						// The mode is supported, so set the buffer formats, apply changes and return
						graphics.PreferredBackBufferWidth = iWidth;
						graphics.PreferredBackBufferHeight = iHeight;
						graphics.IsFullScreen = bFullScreen;
						graphics.ApplyChanges();
						return true;
					}
				}
			}
			return false;
		}


		/// <summary>
		/// Allows the game to perform any initialization it needs to before starting to run.
		/// This is where it can query for any required services and load any non-graphic
		/// related content.  Calling base.Initialize will enumerate through any components
		/// and initialize them as well.
		/// </summary>
		protected override void Initialize()
		{
			// set desired resolution
			InitGraphicsMode(1200, 840, false);

	
			Services.AddService(typeof(IVolumeDataProvider), this);
			Services.AddService(typeof(ITransferFunctionProvider), this);
			Services.AddService(typeof(IRenderTargetProvider), this);

			m_inputHelper = new InputHelper(this);
			Components.Add(m_inputHelper);
			Services.AddService(typeof(IInputHelper), m_inputHelper);

			m_settings = new VolumeRendererSettings();
			m_settings.AlphaFactor = 270.0f;
			m_settings.BlurFactor = 30.0f;
			m_settings.BlurEnabled = true;
			Services.AddService(typeof(IVolumeRendererSettings), m_settings);

			m_camera = new Camera(this);
			Components.Add(m_camera);
			Services.AddService(typeof(ICamera), m_camera);

			//Components.Add(new RayMarchingComponent(this));
			//Components.Add(new SliceBasedComponent(this));
			Components.Add(new DOFComponent(this));

			Components.Add(new TransferFunctionEditorComponent(this));


			// Initialize Render Targets
			var pp = GraphicsDevice.PresentationParameters;
			m_RGBTarget1 = new RenderTarget2D(GraphicsDevice, 512, 512, false, GraphicsDevice.DisplayMode.Format, DepthFormat.None);
			m_RGBTarget2 = new RenderTarget2D(GraphicsDevice, 512, 512, false, GraphicsDevice.DisplayMode.Format, DepthFormat.None);
            m_FloatingTarget1 = new RenderTarget2D(GraphicsDevice, 512, 512, false, SurfaceFormat.Rgba64, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);
            m_FloatingTarget2 = new RenderTarget2D(GraphicsDevice, 512, 512, false, SurfaceFormat.Rgba64, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);
            m_FloatingTarget3 = new RenderTarget2D(GraphicsDevice, 512, 512, false, SurfaceFormat.Rgba64, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);
            m_FloatingTarget4 = new RenderTarget2D(GraphicsDevice, 512, 512, false, SurfaceFormat.Rgba64, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);
            m_FloatingTarget5 = new RenderTarget2D(GraphicsDevice, 512, 512, false, SurfaceFormat.Rgba64, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);
			m_result = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, DepthFormat.None, 1, RenderTargetUsage.PreserveContents);


			base.Initialize();
		}


		public Texture3D GetVolume()
		{
			return m_volumeTextures[m_volTexIndex];
		}

		public TransferFunction GetTransferFunction()
		{
			return m_transferFunctions[m_activeTransfer];
		}


		/// <summary>
		/// LoadContent will be called once per game and is the place to load
		/// all of your content.
		/// </summary>
		protected override void LoadContent()
		{
			// Create a new SpriteBatch, which can be used to draw textures.
			spriteBatch = new SpriteBatch(GraphicsDevice);

			//m_transferTexture.Add(Content.Load<Texture2D>("TransferTex/transfer01"));
			//m_transferTexture.Add(Content.Load<Texture2D>("TransferTex/transfer02"));
			//m_transferTexture.Add(Content.Load<Texture2D>("TransferTex/transfer03"));
			//m_transferTexture.Add(Content.Load<Texture2D>("TransferTex/transfer04"));

			// generate 4 default transfer-function, each sized 256x1 px
			m_transferFunctions.Add(new TransferFunction(this, 256, new ControlPoint[]
				{
					new ControlPoint(0.000, new Color(0, 0, 0, 0)),
					new ControlPoint(0.077, new Color(0, 255, 255, 0)),
					new ControlPoint(0.288, new Color(243, 254, 242, 12)),
					new ControlPoint(0.558, new Color(255, 255, 255, 255)),
					new ControlPoint(0.750, new Color(0, 255, 255, 255)),
					new ControlPoint(1.000, new Color(0, 255, 255, 0)),
				}));
			m_transferFunctions.Add(new TransferFunction(this, 256, new ControlPoint[]
				{
					new ControlPoint(0.0, new Color(0f, 0f, 0f, 0f)),
					new ControlPoint(0.1, new Color(0f, 1f, 1f, 0.125f)),
					new ControlPoint(0.2, new Color(0f, 1f, 1f, 0.25f)),
					new ControlPoint(0.6, new Color(0f, 1f, 1f, 0.5f)),
					new ControlPoint(0.9, new Color(0f, 1f, 1f, 0.75f)),
					new ControlPoint(1.0, new Color(0f, 1f, 1f, 1f)),
				}));
			m_transferFunctions.Add(new TransferFunction(this, 256));
			m_transferFunctions.Add(new TransferFunction(this, 256, new ControlPoint[]
				{
					new ControlPoint(0.00, new Color(52, 44, 0, 3)),
					new ControlPoint(0.09, new Color(255, 0, 255, 230)),
					new ControlPoint(0.37, new Color(156, 156, 66, 137)),
					new ControlPoint(0.58, new Color(0, 255, 255, 161)),
					new ControlPoint(0.78, new Color(255, 0, 255, 77)),
					new ControlPoint(1.00, new Color(210, 0, 25, 249)),
				}));
			// Generate color and upload to GPU
			foreach (var tf in m_transferFunctions)
			{
				tf.Update();
			}

			//m_volumeTexture = Volume.LoadFromFile(this, "Content/Volumes/nucleon_41x41x41_8bit.raw", 41, 41, 41, VolumeLoadingOptions.None, true);
			m_volumeTextures.Add(Volume.LoadFromFile(this, "Content/Volumes/BostonTeapot_256x256x178_8bit.raw", 256, 256, 178, VolumeLoadingOptions.FlipY, true));
			m_volumeTextures.Add(Volume.LoadFromFile(this, "Content/Volumes/aneurism_256x256x256_8bit.raw", 256, 256, 256, VolumeLoadingOptions.None, true));
			m_volumeTextures.Add(Volume.LoadFromFile(this, "Content/Volumes/male.raw", 128, 256, 256, VolumeLoadingOptions.None, true));
			//m_volumeTexture = Volume.LoadFromFile(this, "Content/Volumes/skull_256x256x256_8bit.raw", 256, 256, 256, VolumeLoadingOptions.None);
			//m_volumeTexture = Volume.LoadFromFile(this, "Content/Volumes/engine_256x256x128_8bit.raw", 256, 256, 128, VolumeLoadingOptions.None);
			//m_volumeTexture = Volume.LoadFromFile(this, "Content/Volumes/silicium_98x34x34_8bit.raw", 98, 34, 34, VolumeLoadingOptions.None);
			//m_volumeTexture = Volume.CreateUniformSampleData(this);
		}




		/// <summary>
		/// UnloadContent will be called once per game and is the place to unload
		/// all content.
		/// </summary>
		protected override void UnloadContent()
		{
			// TODO: Unload any non ContentManager content here
		}

		/// <summary>
		/// Allows the game to run logic such as updating the world,
		/// checking for collisions, gathering input, and playing audio.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Update(GameTime gameTime)
		{
			// Allows the game to exit
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();

			if (m_inputHelper.IsNewKeyPress(Keys.Escape))
				Exit();

			if (m_inputHelper.IsNewKeyPress(Keys.T))
			{
				m_activeTransfer = (m_activeTransfer + 1) % m_transferFunctions.Count;
			}

			if (m_inputHelper.IsNewKeyPress(Keys.V))
			{
				m_volTexIndex = (m_volTexIndex + 1) % m_volumeTextures.Count;
			}

			base.Update(gameTime);
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.SetRenderTarget(ResultScreenTarget);
			GraphicsDevice.Clear(ClearOptions.Target, new Color(0.15f, 0.15f, 0.15f, 1.0f), 1.0f, 0);
			GraphicsDevice.SetRenderTarget(null);

			base.Draw(gameTime);

			GraphicsDevice.SetRenderTarget(null);
			GraphicsDevice.Clear(Color.Tomato);
			var pp = GraphicsDevice.PresentationParameters;
			using (SpriteBatch sb = new SpriteBatch(GraphicsDevice))
			{
				sb.Begin();
				sb.Draw(ResultScreenTarget, new Rectangle(0, 0, pp.BackBufferWidth, pp.BackBufferHeight), Color.White);
				sb.End();
			}
		}


		public RenderTarget2D RGBTarget1
		{
			get { return m_RGBTarget1; }
		}

		public RenderTarget2D RGBTarget2
		{
			get { return m_RGBTarget2; }
		}

		public RenderTarget2D FloatingTarget1
		{
			get { return m_FloatingTarget1; }
		}

		public RenderTarget2D FloatingTarget2
		{
			get { return m_FloatingTarget2; }
		}

		public RenderTarget2D FloatingTarget3
		{
			get { return m_FloatingTarget3; }
		}

		public RenderTarget2D FloatingTarget4
		{
			get { return m_FloatingTarget4; }
		}

        public RenderTarget2D FloatingTarget5
        {
            get { return m_FloatingTarget5; }
        }

		public RenderTarget2D ResultScreenTarget
		{
			get { return m_result; }
		}

	}
}
