/* -LICENSE-START-
** Copyright (c) 2020 Blackmagic Design
**  
** Permission is hereby granted, free of charge, to any person or organization 
** obtaining a copy of the software and accompanying documentation (the 
** "Software") to use, reproduce, display, distribute, sub-license, execute, 
** and transmit the Software, and to prepare derivative works of the Software, 
** and to permit third-parties to whom the Software is furnished to do so, in 
** accordance with:
** 
** (1) if the Software is obtained from Blackmagic Design, the End User License 
** Agreement for the Software Development Kit ("EULA") available at 
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
** 
** (2) if the Software is obtained from any third party, such licensing terms 
** as notified by that third party,
** 
** and all subject to the following:
** 
** (3) the copyright notices in the Software and this entire statement, 
** including the above license grant, this restriction and the following 
** disclaimer, must be included in all copies of the Software, in whole or in 
** part, and all derivative works of the Software, unless such copies or 
** derivative works are solely in the form of machine-executable object code 
** generated by a source language processor.
** 
** (4) 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT 
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
** DEALINGS IN THE SOFTWARE.
** 
** A copy of the Software is available free of charge at 
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
** 
** -LICENSE-END-
*/

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using DeckLinkAPI;

namespace SignalGenCSharp
{
	using ColorspaceColorLUT = Dictionary<_BMDColorspace, Color10BitYUV>;

	struct Color10BitYUV
	{
		public ushort Y { get; set; }
		public ushort Cb { get; set; }
		public ushort Cr { get; set; }
	};
	
	static class ColorBars
	{
		static readonly ColorspaceColorLUT k75pcWhite = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 721, Cb = 512, Cr = 512 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 721, Cb = 512, Cr = 512 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 721, Cb = 512, Cr = 512 } }
		};
		static readonly ColorspaceColorLUT k75pcYellow = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 646,  Cb = 176, Cr = 566 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 674, Cb = 176, Cr = 543 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 682, Cb = 176, Cr = 539 } }
		};
		static readonly ColorspaceColorLUT k75pcCyan = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 525, Cb = 625, Cr = 176 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 581, Cb = 589, Cr = 176 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 548, Cb = 606, Cr = 176 } }
		};
		static readonly ColorspaceColorLUT k75pcGreen = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 450, Cb = 289, Cr = 231 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 534, Cb = 253, Cr = 207 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 509, Cb = 270, Cr = 203 } }
		};
		static readonly ColorspaceColorLUT k75pcMagenta = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 335, Cb = 734, Cr = 793 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 251, Cb = 771, Cr = 817 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 276, Cb = 754, Cr = 821 } }
		};
		static readonly ColorspaceColorLUT k75pcRed = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 260, Cb = 399, Cr = 848} },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 204, Cb = 435, Cr = 848 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 237, Cb = 418, Cr = 848 } }
		};
		static readonly ColorspaceColorLUT k75pcBlue = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 139, Cb = 848, Cr = 457 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 111, Cb = 848, Cr = 481 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 103, Cb = 848, Cr = 485 } }
		};
		static readonly ColorspaceColorLUT k0pcBlack = new ColorspaceColorLUT {
			{ _BMDColorspace.bmdColorspaceRec601, new Color10BitYUV() { Y = 64,  Cb = 512, Cr = 512 } },
			{ _BMDColorspace.bmdColorspaceRec709, new Color10BitYUV() { Y = 64,  Cb = 512, Cr = 512 } },
			{ _BMDColorspace.bmdColorspaceRec2020, new Color10BitYUV() { Y = 64,  Cb = 512, Cr = 512 } }
		};

		static IReadOnlyList<ColorspaceColorLUT> kColorBarList = new List<ColorspaceColorLUT> {
			k75pcWhite,
			k75pcYellow,
			k75pcCyan,
			k75pcGreen,
			k75pcMagenta,
			k75pcRed,
			k75pcBlue,
			k0pcBlack,
		};

		internal class BufferAccess : IDisposable
		{
			private readonly IDeckLinkVideoBuffer videoBuffer;
			private readonly _BMDBufferAccessFlags flags;

			public BufferAccess(IDeckLinkVideoFrame videoFrame, _BMDBufferAccessFlags flags )
			{
				videoBuffer = videoFrame as IDeckLinkVideoBuffer;
				videoBuffer.StartAccess(flags);
				this.flags = flags;
			}

			public IntPtr FrameBytePtr
			{
				get
				{
					videoBuffer.GetBytes(out IntPtr bytes);
					return bytes;
				}
			}

			#region IDisposable Support
			private bool disposedValue = false; // To detect redundant calls

			protected virtual void Dispose(bool disposing)
			{
				if (!disposedValue)
				{
					if (disposing)
					{
						videoBuffer.EndAccess(flags);
					}

					disposedValue = true;
				}
			}

			public void Dispose()
			{
				Dispose(true);
			}
			#endregion
		}


		public static void FillColorBars(IDeckLinkVideoFrame colorBarsFrame, _BMDColorspace colorspace, bool reverse)
		{
			using (BufferAccess lockedVideoBuffer = new BufferAccess(colorBarsFrame, _BMDBufferAccessFlags.bmdBufferAccessWrite))
			{
				IntPtr lineBuffer = lockedVideoBuffer.FrameBytePtr;
				int width = (ushort)colorBarsFrame.GetWidth();
				int height = (ushort)colorBarsFrame.GetHeight();
				int rowBytes = colorBarsFrame.GetRowBytes();
				int index = 0;

				var colorBarList = new List<ColorspaceColorLUT>(kColorBarList);
				if (reverse)
					colorBarList.Reverse();

				Int32[] lineData = new Int32[(rowBytes + 3) / 4];

				for (int i = 0; i < width; i += 2)
				{
					Color10BitYUV pixelData = colorBarList[(i * 8) / width][colorspace];

					// Write out macropixels as 10-bit YUV 
					// Refer to the bmdFormat10BitYUV description in DeckLink SDK Manual for packing structure
					switch ((i / 2) % 3)
					{
						case 0:
							lineData[index++] = (Int32)(((pixelData.Cr & 0x3FF) << 20) | ((pixelData.Y & 0x3FF) << 10) | (pixelData.Cb & 0x3FF));
							lineData[index] = (Int32)((pixelData.Y & 0x3FF));
							break;

						case 1:
							lineData[index++] |= (Int32)(((pixelData.Y & 0x3FF) << 20) | ((pixelData.Cb & 0x3FF) << 10));
							lineData[index] = (Int32)(((pixelData.Y & 0x3FF) << 10) | (pixelData.Cr & 0x3FF));
							break;

						case 2:
							lineData[index++] |= (Int32)((pixelData.Cb & 0x3FF) << 20);
							lineData[index++] = (Int32)(((pixelData.Y & 0x3FF) << 20) | ((pixelData.Cr & 0x3FF) << 10) | (pixelData.Y & 0x3FF));
							break;
					}
				}

				for (uint j = 0; j < height; j++)
				{
					// Copy line data directly into unmanaged buffer
					Marshal.Copy(lineData, 0, lineBuffer, lineData.Length);
					lineBuffer += rowBytes;
				}
			}
		}

		public static void FillBlack(IDeckLinkVideoFrame blackFrame)
		{
			// Define black macro-pixel
			Color10BitYUV blackFrameData = k0pcBlack[_BMDColorspace.bmdColorspaceRec709];

			int width = (ushort)blackFrame.GetWidth();
			int height = (ushort)blackFrame.GetHeight();
			int rowBytes = blackFrame.GetRowBytes();
			int index = 0;

			using (BufferAccess lockedVideoBuffer = new BufferAccess(blackFrame, _BMDBufferAccessFlags.bmdBufferAccessWrite))
			{
				IntPtr lineBuffer = lockedVideoBuffer.FrameBytePtr;

				Int32[] lineData = new Int32[(rowBytes + 3) / 4];

				for (int i = 0; i < width; i += 6)
				{
					// Write out data in 10-bit YUV
					// Refer to the bmdFormat10BitYUV description in DeckLink SDK Manual for packing structure
					lineData[index++] = (Int32)(((blackFrameData.Cr & 0x3FF) << 20) | ((blackFrameData.Y & 0x3FF) << 10) | (blackFrameData.Cb & 0x3FF));
					lineData[index++] = (Int32)(((blackFrameData.Y & 0x3FF) << 20) | ((blackFrameData.Cb & 0x3FF) << 10) | (blackFrameData.Y & 0x3FF));
					lineData[index++] = (Int32)(((blackFrameData.Cb & 0x3FF) << 20) | ((blackFrameData.Y & 0x3FF) << 10) | (blackFrameData.Cr & 0x3FF));
					lineData[index++] = (Int32)(((blackFrameData.Y & 0x3FF) << 20) | ((blackFrameData.Cr & 0x3FF) << 10) | (blackFrameData.Y & 0x3FF));
				}

				for (uint j = 0; j < height; j++)
				{
					// Copy line data directly into unmanaged buffer
					Marshal.Copy(lineData, 0, lineBuffer, lineData.Length);
					lineBuffer += rowBytes;
				}
			}
		}
	}
}
