using System;
using ANX.Framework.Graphics;
using SharpDX;
using SharpDX.Direct3D;
using Windows.Foundation;
using Dx11 = SharpDX.Direct3D11;

// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license

namespace ANX.RenderSystem.Windows.Metro
{
	public class NativeDxDevice : IDisposable
	{
		#region Private
		private PresentationParameters presentationParameters;
		private FeatureLevel featureLevel;
		private Dx11.RenderTargetView renderTargetView;
		private Dx11.DepthStencilView depthStencilView;
		private SwapChainMetro swapChain;
		private Dx11.Device nativeDevice;
		private Dx11.DeviceContext nativeContext;
        private SharpDX.ViewportF viewport;

		internal Dx11.Device NativeDevice
		{
			get
			{
				return nativeDevice;
			}
		}

		internal Dx11.DeviceContext NativeContext
		{
			get
			{
				return nativeContext;
			}
		}

		internal Dx11.OutputMergerStage OutputMerger
		{
			get
			{
				return nativeContext.OutputMerger;
			}
		}

		internal static NativeDxDevice Current
		{
			get;
			private set;
		}
		#endregion

		#region Public
		public Rect RenderTargetBounds
		{
			get;
			protected set;
		}
		#endregion

		#region Constructor
		public NativeDxDevice(PresentationParameters presentationParameters)
		{
			Current = this;

			this.presentationParameters = presentationParameters;
			swapChain = new SwapChainMetro(this, presentationParameters);

			var creationFlags = Dx11.DeviceCreationFlags.VideoSupport | Dx11.DeviceCreationFlags.BgraSupport;
#if DEBUG
			creationFlags |= Dx11.DeviceCreationFlags.Debug;
#endif
			using (var defaultDevice = new Dx11.Device(DriverType.Hardware, creationFlags))
			{
				nativeDevice = defaultDevice.QueryInterface<Dx11.Device>();
			}
			featureLevel = NativeDevice.FeatureLevel;

			nativeContext = NativeDevice.ImmediateContext.QueryInterface<Dx11.DeviceContext>();
		}
		#endregion

		#region Resize
		public void Resize(PresentationParameters presentationParameters)
		{
			DisposeScreenBuffers();

			swapChain.ResizeOrCreate(presentationParameters);

			using (var backBuffer = swapChain.CreateTexture())
			{
				renderTargetView = new Dx11.RenderTargetView(NativeDevice, backBuffer);

				var backBufferDesc = backBuffer.Description;
				RenderTargetBounds = new Rect(0, 0, backBufferDesc.Width, backBufferDesc.Height);
			}

			using (var depthBuffer = new Dx11.Texture2D(NativeDevice, new Dx11.Texture2DDescription()
			{
				Format = FormatConverter.Translate(presentationParameters.DepthStencilFormat),
				ArraySize = 1,
				MipLevels = 1,
				Width = (int)RenderTargetBounds.Width,
				Height = (int)RenderTargetBounds.Height,
				SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
				BindFlags = Dx11.BindFlags.DepthStencil,
			}))
			{
				depthStencilView = new Dx11.DepthStencilView(NativeDevice, depthBuffer, new Dx11.DepthStencilViewDescription()
				{
					Dimension = Dx11.DepthStencilViewDimension.Texture2D
				});
			}
            
			viewport = new SharpDX.ViewportF((float)RenderTargetBounds.X, (float)RenderTargetBounds.Y,
					(float)RenderTargetBounds.Width, (float)RenderTargetBounds.Height, 0.0f, 1.0f);

            SetDefaultTargets();
		}
		#endregion

		#region ClearDepthAndStencil
		public void ClearDepthAndStencil(Dx11.DepthStencilClearFlags flags, float depth, byte stencil)
		{
			// TODO: find better solution to lazy init the swapChain from the coreWindow!!
			EnsureScreenBuffersAvailable();

			nativeContext.ClearDepthStencilView(depthStencilView, flags, depth, stencil);
		}
		#endregion

		#region SetDefaultTargets
		public void SetDefaultTargets()
        {
            nativeContext.Rasterizer.SetViewport(viewport);
            nativeContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);
        }
		#endregion
		
		#region Clear
		public void Clear(Color4 color)
		{
			// TODO: find better solution to lazy init the swapChain from the coreWindow!!
			EnsureScreenBuffersAvailable();
            
			nativeContext.ClearRenderTargetView(renderTargetView, color);
		}
		#endregion
		
		#region EnsureScreenBuffersAvailable
		private void EnsureScreenBuffersAvailable()
		{
			if (renderTargetView == null)
			{
				Resize(presentationParameters);
			}
		}
		#endregion

		#region Present
		public void Present(int interval)
		{
			swapChain.Present(interval);
		}
		#endregion

		#region MapSubresource
		public SharpDX.DataStream MapSubresource(Dx11.Buffer resource)
		{
			SharpDX.DataStream result;
			nativeContext.MapSubresource(resource, Dx11.MapMode.WriteDiscard,
				Dx11.MapFlags.None, out result);
			return result;
		}

		public SharpDX.DataBox MapSubresource(Dx11.Resource resource, int subresource)
		{
			return nativeContext.MapSubresource(resource, subresource,
				Dx11.MapMode.WriteDiscard, Dx11.MapFlags.None);
		}
		#endregion

		#region UnmapSubresource
		public void UnmapSubresource(Dx11.Resource resource, int subresource)
		{
			nativeContext.UnmapSubresource(resource, subresource);
		}
		#endregion

		#region Dispose
		public void Dispose()
		{
			DisposeScreenBuffers();

			SafeDispose(ref swapChain);
			SafeDispose(ref nativeDevice);
			SafeDispose(ref nativeContext);
		}

		private void DisposeScreenBuffers()
		{
			SafeDispose(ref renderTargetView);
			SafeDispose(ref depthStencilView);
		}

		private void SafeDispose<T>(ref T disposable)
			where T : class, IDisposable
		{
			if (disposable != null)
			{
				disposable.Dispose();
				disposable = null;
			}
		}
		#endregion
	}
}