/*
* Copyright (c) 2007-2009 SlimDX Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using SharpDX.Direct3D9;

namespace SampleFramework
{
	class Direct3D9Settings : ICloneable
	{
		public int AdapterOrdinal
		{
			get;
			set;
		}

		public DeviceType DeviceType
		{
			get;
			set;
		}

		public Format AdapterFormat
		{
			get;
			set;
		}

		public CreateFlags CreationFlags
		{
			get;
			set;
		}

		public PresentParameters PresentParameters
		{
			get;
			set;
		}

		public Direct3D9Settings()
		{
			PresentParameters = new PresentParameters();
			DeviceType = DeviceType.Hardware;
			AdapterFormat = Format.Unknown;
			CreationFlags = CreateFlags.HardwareVertexProcessing;

			var pp = this.PresentParameters;
			pp.Windowed = true;
			pp.BackBufferFormat = Format.Unknown;
			pp.BackBufferCount = 1;
			pp.MultiSampleType = MultisampleType.None;
			pp.SwapEffect = SwapEffect.Discard;
			pp.EnableAutoDepthStencil = true;
			pp.AutoDepthStencilFormat = Format.Unknown;
			pp.PresentFlags = PresentFlags.DiscardDepthStencil;
			pp.PresentationInterval = PresentInterval.Default;
			this.PresentParameters = pp;
		}

		public Direct3D9Settings Clone()
		{
			Direct3D9Settings clone = new Direct3D9Settings();
			clone.AdapterFormat = AdapterFormat;
			clone.AdapterOrdinal = AdapterOrdinal;
			clone.CreationFlags = CreationFlags;
			clone.DeviceType = DeviceType;
			clone.PresentParameters = PresentParameters;

			return clone;
		}

		object ICloneable.Clone()
		{
			return Clone();
		}

		public static Direct3D9Settings BuildOptimalSettings(DeviceSettings settings)
		{
			DisplayMode desktopMode = GraphicsDeviceManager.Direct3D9Object.GetAdapterDisplayMode(0);
			Direct3D9Settings optimal = new Direct3D9Settings();
			var pp = optimal.PresentParameters;

			optimal.AdapterOrdinal = settings.AdapterOrdinal;
			optimal.DeviceType = settings.DeviceType;
			pp.Windowed = settings.Windowed;
			pp.BackBufferCount = settings.BackBufferCount;
			pp.MultiSampleType = settings.MultisampleType;
			pp.MultiSampleQuality = settings.MultisampleQuality;
			pp.FullScreenRefreshRateInHz = settings.RefreshRate;

			if(settings.Multithreaded)
				optimal.CreationFlags |= CreateFlags.Multithreaded;

			if(optimal.PresentParameters.Windowed || ConversionMethods.GetColorBits(desktopMode.Format) >= 8)
				optimal.AdapterFormat = desktopMode.Format;
			else
				optimal.AdapterFormat = Format.X8R8G8B8;

			if(settings.BackBufferWidth == 0 || settings.BackBufferHeight == 0)
			{
				if(optimal.PresentParameters.Windowed)
				{
					pp.BackBufferWidth = 640;
					pp.BackBufferHeight = 480;
				}
				else
				{
					pp.BackBufferWidth = desktopMode.Width;
					pp.BackBufferHeight = desktopMode.Height;
				}
			}
			else
			{
				pp.BackBufferWidth = settings.BackBufferWidth;
				pp.BackBufferHeight = settings.BackBufferHeight;
			}

			if(settings.BackBufferFormat == Format.Unknown)
				pp.BackBufferFormat = optimal.AdapterFormat;
			else
				pp.BackBufferFormat = settings.BackBufferFormat;

			if(settings.DepthStencilFormat == Format.Unknown)
			{
				if(ConversionMethods.GetColorBits(optimal.PresentParameters.BackBufferFormat) >= 8)
					pp.AutoDepthStencilFormat = Format.D32;
				else
					pp.AutoDepthStencilFormat = Format.D16;
			}
			else
				pp.AutoDepthStencilFormat = settings.DepthStencilFormat;

			if(!settings.EnableVSync)
				pp.PresentationInterval = PresentInterval.Immediate;

			optimal.PresentParameters = pp;
			return optimal;
		}

		public static float RankSettingsCombo(SettingsCombo9 combo, Direct3D9Settings optimal, DisplayMode desktopMode)
		{
			float ranking = 0.0f;

			if(combo.AdapterOrdinal == optimal.AdapterOrdinal)
				ranking += 1000.0f;

			if(combo.DeviceType == optimal.DeviceType)
				ranking += 100.0f;

			if(combo.DeviceType == DeviceType.Hardware)
				ranking += 0.1f;

			if(combo.Windowed == optimal.PresentParameters.Windowed)
				ranking += 10.0f;

			if(combo.AdapterFormat == optimal.AdapterFormat)
				ranking += 1.0f;
			else
			{
				int bitDepthDelta = Math.Abs(ConversionMethods.GetColorBits(combo.AdapterFormat) -
					ConversionMethods.GetColorBits(optimal.AdapterFormat));
				float scale = Math.Max(0.9f - bitDepthDelta * 0.2f, 0.0f);
				ranking += scale;
			}

			if(!combo.Windowed)
			{
				bool match;
				if(ConversionMethods.GetColorBits(desktopMode.Format) >= 8)
					match = (combo.AdapterFormat == desktopMode.Format);
				else
					match = (combo.AdapterFormat == Format.X8R8G8B8);

				if(match)
					ranking += 0.1f;
			}

			if((optimal.CreationFlags & CreateFlags.HardwareVertexProcessing) != 0 &&
				(optimal.CreationFlags & CreateFlags.MixedVertexProcessing) != 0)
			{
				if((combo.DeviceInfo.Capabilities.DeviceCaps & DeviceCaps.HWTransformAndLight) != 0)
					ranking += 1.0f;
			}

			if((combo.DeviceInfo.Capabilities.DeviceCaps & DeviceCaps.HWTransformAndLight) != 0)
				ranking += 0.1f;

			foreach(DisplayMode displayMode in combo.AdapterInfo.DisplayModes)
			{
				if(displayMode.Format == combo.AdapterFormat &&
					displayMode.Width == optimal.PresentParameters.BackBufferWidth &&
					displayMode.Height == optimal.PresentParameters.BackBufferHeight)
				{
					ranking += 1.0f;
					break;
				}
			}

			if(combo.BackBufferFormat == optimal.PresentParameters.BackBufferFormat)
				ranking += 1.0f;
			else
			{
				int bitDepthDelta = Math.Abs(ConversionMethods.GetColorBits(combo.BackBufferFormat) -
					ConversionMethods.GetColorBits(optimal.PresentParameters.BackBufferFormat));
				float scale = Math.Max(0.9f - bitDepthDelta * 0.2f, 0.0f);
				ranking += scale;
			}

			if(combo.BackBufferFormat == combo.AdapterFormat)
				ranking += 0.1f;

			for(int i = 0; i < combo.MultisampleTypes.Count; i++)
			{
				MultisampleType type = combo.MultisampleTypes[i];
				int quality = combo.MultisampleQualities[i];

				if(type == optimal.PresentParameters.MultiSampleType && quality == optimal.PresentParameters.MultiSampleQuality)
				{
					ranking += 1.0f;
					break;
				}
			}

			if(combo.DepthStencilFormats.Contains(optimal.PresentParameters.AutoDepthStencilFormat))
				ranking += 1.0f;

			foreach(DisplayMode displayMode in combo.AdapterInfo.DisplayModes)
			{
				if(displayMode.Format == combo.AdapterFormat &&
					displayMode.RefreshRate == optimal.PresentParameters.FullScreenRefreshRateInHz)
				{
					ranking += 1.0f;
					break;
				}
			}

			if(combo.PresentIntervals.Contains(optimal.PresentParameters.PresentationInterval))
				ranking += 1.0f;

			return ranking;
		}

		public static Direct3D9Settings BuildValidSettings(SettingsCombo9 combo, Direct3D9Settings input)
		{
			Direct3D9Settings settings = new Direct3D9Settings();
			var pp = settings.PresentParameters;

			settings.AdapterOrdinal = combo.AdapterOrdinal;
			settings.DeviceType = combo.DeviceType;
			settings.AdapterFormat = combo.AdapterFormat;
			pp.Windowed = combo.Windowed;
			pp.BackBufferFormat = combo.BackBufferFormat;
			pp.SwapEffect = input.PresentParameters.SwapEffect;
			pp.PresentFlags = input.PresentParameters.PresentFlags | PresentFlags.DiscardDepthStencil;

			settings.CreationFlags = input.CreationFlags;
			if((combo.DeviceInfo.Capabilities.DeviceCaps & DeviceCaps.HWTransformAndLight) == 0 &&
				((settings.CreationFlags & CreateFlags.HardwareVertexProcessing) != 0 ||
				(settings.CreationFlags & CreateFlags.MixedVertexProcessing) != 0))
			{
				settings.CreationFlags &= ~CreateFlags.HardwareVertexProcessing;
				settings.CreationFlags &= ~CreateFlags.MixedVertexProcessing;
				settings.CreationFlags |= CreateFlags.SoftwareVertexProcessing;
			}

			if((settings.CreationFlags & CreateFlags.HardwareVertexProcessing) == 0 &&
				(settings.CreationFlags & CreateFlags.MixedVertexProcessing) == 0 &&
				(settings.CreationFlags & CreateFlags.SoftwareVertexProcessing) == 0)
			{
				if((combo.DeviceInfo.Capabilities.DeviceCaps & DeviceCaps.HWTransformAndLight) != 0)
					settings.CreationFlags |= CreateFlags.HardwareVertexProcessing;
				else
					settings.CreationFlags |= CreateFlags.SoftwareVertexProcessing;
			}

			DisplayMode bestDisplayMode = FindValidResolution(combo, input);
			pp.BackBufferWidth = bestDisplayMode.Width;
			pp.BackBufferHeight = bestDisplayMode.Height;

			pp.BackBufferCount = input.PresentParameters.BackBufferCount;
			if(pp.BackBufferCount > 3)
				pp.BackBufferCount = 3;
			if(pp.BackBufferCount < 1)
				pp.BackBufferCount = 1;

			if(input.PresentParameters.SwapEffect != SwapEffect.Discard)
			{
				pp.MultiSampleType = MultisampleType.None;
				pp.MultiSampleQuality = 0;
			}
			else
			{
				MultisampleType bestType = MultisampleType.None;
				int bestQuality = 0;

				for(int i = 0; i < combo.MultisampleTypes.Count; i++)
				{
					MultisampleType type = combo.MultisampleTypes[i];
					int quality = combo.MultisampleQualities[i];

					if(Math.Abs(type - input.PresentParameters.MultiSampleType) < Math.Abs(bestType - input.PresentParameters.MultiSampleType))
					{
						bestType = type;
						bestQuality = Math.Min(quality - 1, input.PresentParameters.MultiSampleQuality);
					}
				}

				pp.MultiSampleType = bestType;
				pp.MultiSampleQuality = bestQuality;
			}

			List<int> rankings = new List<int>();
			int inputDepthBitDepth = ConversionMethods.GetDepthBits(input.PresentParameters.AutoDepthStencilFormat);
			int inputStencilBitDepth = ConversionMethods.GetStencilBits(input.PresentParameters.AutoDepthStencilFormat);

			foreach(Format format in combo.DepthStencilFormats)
			{
				int currentBitDepth = ConversionMethods.GetDepthBits(format);
				int currentStencilDepth = ConversionMethods.GetStencilBits(format);

				int ranking = Math.Abs(currentBitDepth - inputDepthBitDepth);
				ranking += Math.Abs(currentStencilDepth - inputStencilBitDepth);
				rankings.Add(ranking);
			}

			int bestRanking = int.MaxValue;
			foreach(int ranking in rankings)
			{
				if(ranking < bestRanking)
					bestRanking = ranking;
			}
			int bestIndex = rankings.IndexOf(bestRanking);

			if(bestIndex >= 0)
			{
				pp.AutoDepthStencilFormat = combo.DepthStencilFormats[bestIndex];
				pp.EnableAutoDepthStencil = true;
			}
			else
			{
				pp.AutoDepthStencilFormat = Format.Unknown;
				pp.EnableAutoDepthStencil = false;
			}

			if(combo.Windowed)
				pp.FullScreenRefreshRateInHz = 0;
			else
			{
				int match = input.PresentParameters.FullScreenRefreshRateInHz;
				bestDisplayMode.RefreshRate = 0;
				if(match != 0)
				{
					bestRanking = 100000;
					foreach(DisplayMode displayMode in combo.AdapterInfo.DisplayModes)
					{
						if(displayMode.Format != combo.AdapterFormat ||
							displayMode.Width != bestDisplayMode.Width ||
							displayMode.Height != bestDisplayMode.Height)
							continue;

						int ranking = Math.Abs(displayMode.RefreshRate - match);

						if(ranking < bestRanking)
						{
							bestDisplayMode.RefreshRate = displayMode.RefreshRate;
							bestRanking = ranking;

							if(bestRanking == 0)
								break;
						}
					}
				}

				pp.FullScreenRefreshRateInHz = bestDisplayMode.RefreshRate;
			}

			if(combo.PresentIntervals.Contains(input.PresentParameters.PresentationInterval))
				pp.PresentationInterval = input.PresentParameters.PresentationInterval;
			else
				pp.PresentationInterval = PresentInterval.Default;

			settings.PresentParameters = pp;
			return settings;
		}

		static DisplayMode FindValidResolution(SettingsCombo9 combo, Direct3D9Settings input)
		{
			DisplayMode bestMode = new DisplayMode();

			if(combo.Windowed)
			{
				bestMode.Width = input.PresentParameters.BackBufferWidth;
				bestMode.Height = input.PresentParameters.BackBufferHeight;
				return bestMode;
			}

			int bestRanking = 100000;
			int ranking;
			foreach(DisplayMode mode in combo.AdapterInfo.DisplayModes)
			{
				if(mode.Format != combo.AdapterFormat)
					continue;

				ranking = Math.Abs(mode.Width - input.PresentParameters.BackBufferWidth) +
					Math.Abs(mode.Height - input.PresentParameters.BackBufferHeight);

				if(ranking < bestRanking)
				{
					bestMode = mode;
					bestRanking = ranking;

					if(bestRanking == 0)
						break;
				}
			}

			if(bestMode.Width == 0)
			{
				bestMode.Width = input.PresentParameters.BackBufferWidth;
				bestMode.Height = input.PresentParameters.BackBufferHeight;
			}

			return bestMode;
		}
	}
}
