using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;
using Vortex.Debugging;
using Vortex.Drawing.Performance;

namespace Vortex.Drawing {

	/// <summary>
	/// Encapsulates operations with vertex/index buffers
	/// </summary>
	unsafe class PrelockedVertexStream : Component {
		Direct3DVertexBuffer9 _vertexBuffer;
		Direct3DIndexBuffer9 _indexBuffer;
		Direct3DVertexDeclaration9 _vertexDeclaration;
		//data pointers
		Vertex2D* _vertexData;
		ushort* _indexData;
		//maximum vertices/indices in buffer
		int _maxVertexCount, _maxIndexCount;
		//common vertices/indices in buffer
		int _defaultVertexCount, _defaultIndexCount;
		//type of data in current batch
		PrimitiveType _primitiveType;
		//current limits;
		int _vertexCountLimit, _indexCountLimit;
		//current counts
		int _vertexCount, _indexCount;
		/// <summary>Flag that batch is currently is opened</summary>
		bool _batchIsOpened;

		public PrelockedVertexStream(DrawingDevice device) : base(device) {
			_maxVertexCount = (int)ushort.MaxValue;
			_maxIndexCount  = (int)ushort.MaxValue;

			Log.Details("Size of VertexElement is " + sizeof(D3DVertexElement));
			HResult result = device.ApiRef.CreateVertexDeclaration(Vertex2D.DeclarationElements, out _vertexDeclaration);
			Watcher.AssertCritical(result, "Create primary vertex declaration");
			
			//initialize on creation
			Initialize();
		}

		/// <summary>
		/// Gets a value indicating whether this vertex stream is empty.
		/// </summary>
		/// <value><c>true</c> if this vertex stream is empty; otherwise, <c>false</c>.</value>
		public bool IsEmpty {
			get { return _indexCount == 0; }
		}

		/// <summary>
		/// Opens this vertex stream.
		/// </summary>
		public void Open() {
			HResult result;
			result = _d3dDevice.SetStreamSource(0, _vertexBuffer, 0, (uint)sizeof(Vertex2D));
			Watcher.AssertCritical(result, "Unable to set stream source");

			result = _d3dDevice.SetIndices(_indexBuffer);
			Watcher.AssertCritical(result, "Unable to set indices");

			result = _d3dDevice.SetVertexDeclaration(_vertexDeclaration);
			Watcher.AssertCritical(result, "Unable to set vertex declaration");
		}

		/// <summary>
		/// Closes this vertex stream.
		/// </summary>
		public void Close() {
			Flush();
		}

		/// <summary>
		/// Begins the batch for stream.
		/// </summary>
		/// <param name="type">The type of primitives.</param>
		/// <param name="vertexCount">The vertex count requested.</param>
		/// <param name="indexCount">The index count requested.</param>
		void BeginBatch(PrimitiveType type, int vertexCount, int indexCount) {
			if (vertexCount > _maxVertexCount) {
				throw new ArgumentOutOfRangeException(string.Format("Vertex count {0} is greater than maximum allowed {1}", vertexCount, _maxVertexCount));
			}
			if (indexCount > _maxIndexCount) {
				throw new ArgumentOutOfRangeException(string.Format("Index count {0} is greater than maximum allowed {1}", indexCount, _maxIndexCount));
			}

			LockBuffers(vertexCount, indexCount);

			_vertexCount = _indexCount = 0;
			_vertexCountLimit = vertexCount;
			_indexCountLimit = indexCount;
			_primitiveType = type;
			_batchIsOpened = true;
		}

		/// <summary>
		/// Ends the batch.
		/// </summary>
		public void EndBatch() {
			if (_batchIsOpened) {
				DrawBatch();
				_batchIsOpened = false;
			}
		}

		/// <summary>
		/// Requests stream to add the vertex fragment.
		/// </summary>
		/// <param name="fragment">The fragment description.</param>
		/// <param name="location">The location of fragment in memory.</param>
		public unsafe void AddFragment(VertexFragment fragment, out FragmentLocation location) {
			//check should we open batch automatically
			if ( _batchIsOpened &&
					(fragment.VertexCount + _vertexCount > _vertexCountLimit ||
					 fragment.IndexCount + _indexCount > _indexCountLimit ||
					 fragment.PrimitiveType != _primitiveType) ) {
				EndBatch();	//terminate current batch we can't fit data in it
			}
			//check if batch was closed for a some reasons, lets open new one, but use best size for it
			if (!_batchIsOpened) {
				BeginBatch(fragment.PrimitiveType, Math.Max(fragment.VertexCount, _defaultVertexCount), Math.Max(fragment.IndexCount, _defaultIndexCount));
			}

			//location = new FragmentLocation();
			//fill queryResult parameters
			location.Vertices = _vertexData + _vertexCount;
			location.Indices = _indexData + _indexCount;
			location.BaseIndex = (ushort)_vertexCount;
			
			//update internal counters
			_vertexCount += fragment.VertexCount;
			_indexCount += fragment.IndexCount;
		}

		/// <summary>
		/// Flushes this vertex stream cached data.
		/// </summary>
		public void Flush() {
			EndBatch();
		}

		#region Private Methods

		/// <summary>
		/// Initializes this vertex stream.
		/// </summary>
		private void Initialize() {
			int vbSize = _maxVertexCount * sizeof(Vertex2D);
			int ixSize = _maxIndexCount * sizeof(ushort);

			HResult result = _d3dDevice.CreateVertexBuffer((uint)vbSize, D3DUsage.WriteOnly | D3DUsage.Dynamic, D3DFVF.None, D3DPool.Default, out _vertexBuffer);
			Watcher.AssertCritical(result, "Create vertex buffer of size {0} bytes", vbSize);

			result = _d3dDevice.CreateIndexBuffer((uint)ixSize, D3DUsage.WriteOnly | D3DUsage.Dynamic, D3DFormat.INDEX16, D3DPool.Default, out _indexBuffer);
			Watcher.AssertCritical(result, "Create index buffer of size {0} bytes", ixSize);

		}

		/// <summary>
		/// Destroys vertex stream. Frees the internal data of component.
		/// </summary>
		public override void Destroy() {
			_vertexBuffer.Dispose();
			_indexBuffer.Dispose();

			_vertexBuffer = null;
			_indexBuffer = null;
		}

		/// <summary>
		/// Locks the vertex/index buffers.
		/// </summary>
		/// <param name="vertexCount">The vertex amount to lock.</param>
		/// <param name="indexCount">The index amount to lock.</param>
		private void LockBuffers(int vertexCount, int indexCount) {
			void* pVertexData;
			void* pIndexData;

#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.BufferLock);
#endif

			HResult result = _vertexBuffer.Lock(0, (uint)(vertexCount * sizeof(Vertex2D)), out pVertexData, D3DLockFlags.Disacrd);
			Watcher.AssertCritical(result, "Lock vertex buffer");

			result = _indexBuffer.Lock(0, (uint)(indexCount * sizeof(ushort)), out pIndexData, D3DLockFlags.Disacrd);
			Watcher.AssertCritical(result, "Lock index buffer");

			_vertexData = (Vertex2D*)pVertexData;
			_indexData = (ushort*)pIndexData;

#if PERF_STATS
			Device.PerfMonitor.StopTimer(DeviceTimer.BufferLock);
#endif
		}

		/// <summary>
		/// Unlocks vertex/index buffers.
		/// </summary>
		private void UnlockBuffers() {
#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.BufferUnlock);
#endif

			_vertexBuffer.Unlock();
			_indexBuffer.Unlock();
			_vertexData = null;
			_indexData = null;

#if PERF_STATS
			Device.PerfMonitor.StopTimer(DeviceTimer.BufferUnlock);
#endif
		}

		/// <summary>
		/// Draws current batch.
		/// </summary>
		private void DrawBatch() {
			UnlockBuffers();
			Draw(_primitiveType, _vertexCount, _indexCount, CalculatePrimitiveCount(_primitiveType, _indexCount));
		}

		/// <summary>
		/// Calculates the primitive count based on index count primitive type.
		/// </summary>
		/// <param name="primitiveType">Type of the primitive.</param>
		/// <param name="indexCount">The index count.</param>
		/// <returns></returns>
		private static int CalculatePrimitiveCount(PrimitiveType primitiveType, int indexCount) {
			switch (primitiveType) {
				case PrimitiveType.PointList:
					return indexCount;
				case PrimitiveType.LineList:
					return indexCount / 2;
				case PrimitiveType.TriangleList:
					return indexCount / 3;
				case PrimitiveType.TriangleFan: case PrimitiveType.TriangleStrip:
					return indexCount - 1;
				default:
					return 0;
			}
		}

		/// <summary>
		/// Wraps the primitive drawing
		/// </summary>
		/// <param name="primitiveType">Type of the primitives to draw.</param>
		/// <param name="vertexCount">The vertex count to draw.</param>
		/// <param name="primitiveCount">The primitive count to draw.</param>
		private void Draw(PrimitiveType primitiveType, int vertexCount, int indexCount, int primitiveCount) {
#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.Drawing);
#endif
			//Log.Details("V: {0}, I: {1}, P: {2}", vertexCount, indexCount, primitiveCount);

			HResult result = _d3dDevice.DrawIndexedPrimitive((D3DPrimitiveType)primitiveType, 0, 0, (uint)vertexCount, 0, (uint)primitiveCount);
			Watcher.AssertCritical(result, "DrawIndexedPrimitive failed for type {0} and {1} vertices, {2} primitives", primitiveType, vertexCount, primitiveCount);
			//_d3dDevice.DebugDraw();

#if PERF_STATS
			Device.PerfMonitor.AddCounter(DeviceCounter.VerticesDrawn, (int)vertexCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.IndicesDrawn, (int)indexCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.PrimitivesDrawn, (int)primitiveCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.BatchesRendered);
			Device.PerfMonitor.StopTimer(DeviceTimer.Drawing);
#endif
		}

		#endregion

		/// <summary>
		/// Sets the batch size hint.
		/// </summary>
		/// <param name="batchSizeHint">The batch size hint.</param>
		public void SetBatchSizeHint(int batchSizeHint) {
			_defaultVertexCount = (int)Math.Min(batchSizeHint, (int)_maxVertexCount);
			_defaultIndexCount = (int)Math.Min(batchSizeHint * 2, (int)_maxIndexCount);
		}

		protected override void BeforeDeviceReset() {
			Destroy();
		}

		protected override void AfterDeviceReset() {
			Initialize();
		}
	}

}
