using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.D2d1;
using static Vanara.PInvoke.Dwrite;

namespace Vanara.PInvoke.Tests
{
	public class DirectWriteTests : GenericComTester<IDWriteFactory>
	{
		private const string fontDir = @"C:\Temp\Fonts";

		protected override IDWriteFactory InitInstance() => DWriteCreateFactory<IDWriteFactory>();

		[Test]
		public void EnumFontsTest()
		{
			Instance.GetSystemFontCollection(out var coll);
			using var pColl = ComReleaserFactory.Create(coll);
			EnumFonts(pColl.Item);
		}

		private static void EnumFonts(IDWriteFontCollection coll)
		{
			var count = coll.GetFontFamilyCount();
			var locale = System.Globalization.CultureInfo.CurrentCulture.Name;
			for (var i = 0U; i < count; i++)
			{
				try
				{
					using var pFontFam = ComReleaserFactory.Create(coll.GetFontFamily(i));
					using var pFamNames = ComReleaserFactory.Create(pFontFam.Item.GetFamilyNames());
					pFamNames.Item.FindLocaleName(locale, out var index, out var exists);
					if (!exists) index = 0;
					var len = pFamNames.Item.GetStringLength(index) + 1;
					var str = new StringBuilder((int)len);
					pFamNames.Item.GetString(index, str, len);
					TestContext.WriteLine(str);
				}
				catch (Exception ex)
				{
					TestContext.WriteLine("ERROR: " + ex.Message);
				}
			}
		}

		[Test]
		public void LoadCustomFontsTest()
		{
			var loader = new FontLoader();
			Instance.RegisterFontCollectionLoader(loader);
			try
			{
				using var pDir = new SafeCoTaskMemString(fontDir);
				using var pColl = ComReleaserFactory.Create(Instance.CreateCustomFontCollection(loader, pDir, pDir.Size));
				EnumFonts(pColl.Item);
			}
			finally
			{
				Instance.UnregisterFontCollectionLoader(loader);
			}
		}

		[Test]
		public void CreateFontFaceTest()
		{
			var ldr = new FontLoader();
			var pList = Directory.EnumerateFiles(fontDir).Take(1).Select(f => Instance.CreateFontFileReference(f)).ToArray();
			try
			{
				pList[0].Analyze(out var sup, out var ft, out var fc, out var fcn);
				TestContext.WriteLine($"Supported={sup}; FileType={ft}; FaceType={fc}; FaceCnt={fcn}");
				var pFF = ComReleaserFactory.Create(Instance.CreateFontFace(fc, (uint)pList.Length, pList, 0, DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE));
				Assert.That(pFF.Item.GetType(), Is.EqualTo(fc));
				var ff = new IDWriteFontFile[pList.Length];
				var fflen = (uint)ff.Length;
				Assert.That(() => pFF.Item.GetFiles(ref fflen, ff), Throws.Nothing);
				ff.WriteValues();
				TestContext.WriteLine($"Index in font files: {pFF.Item.GetIndex()}");
				TestContext.WriteLine($"Simulations: {pFF.Item.GetSimulations()}");
				Assert.That(pFF.Item.IsSymbolFont(), Is.False);
				Assert.That(() => { pFF.Item.GetMetrics(out var faceMetrics); faceMetrics.WriteValues(); }, Throws.Nothing);
				var glCnt = (int)pFF.Item.GetGlyphCount();
				Assert.That(glCnt, Is.GreaterThan(0));
				glCnt = Math.Min(3, glCnt);
				var glm = new DWRITE_GLYPH_METRICS[glCnt];
				var glidx = Enumerable.Range(0, glCnt).Select(i => (ushort)i).ToArray();
				Assert.That(() => pFF.Item.GetDesignGlyphMetrics(glidx, (uint)glCnt, glm), Throws.Nothing);
				glm.WriteValues();
				Assert.That(() =>
				{
					var tag = DWRITE_FONT_FEATURE_TAG.DWRITE_FONT_FEATURE_TAG_DEFAULT;
					pFF.Item.TryGetFontTable((uint)tag, out var tableData, out var tableSize, out var tableCtx, out var tableExists);
					TestContext.WriteLine($"Table: {tag} = Sz:{tableSize}, {tableExists}");
					pFF.Item.ReleaseFontTable(tableCtx);
				}, Throws.Nothing);
				var rParams = Instance.CreateRenderingParams();
				pFF.Item.GetRecommendedRenderingMode(9f, 72f, DWRITE_MEASURING_MODE.DWRITE_MEASURING_MODE_NATURAL, rParams).WriteValues();
				var hMon = User32.MonitorFromPoint(System.Drawing.Point.Empty, User32.MonitorFlags.MONITOR_DEFAULTTOPRIMARY);
				rParams = Instance.CreateMonitorRenderingParams(hMon);
				pFF.Item.GetRecommendedRenderingMode(9f, 72f, DWRITE_MEASURING_MODE.DWRITE_MEASURING_MODE_NATURAL, rParams).WriteValues();
				rParams = Instance.CreateCustomRenderingParams(2.2f, 1f, 1f, DWRITE_PIXEL_GEOMETRY.DWRITE_PIXEL_GEOMETRY_BGR, DWRITE_RENDERING_MODE.DWRITE_RENDERING_MODE_ALIASED);
				pFF.Item.GetRecommendedRenderingMode(9f, 72f, DWRITE_MEASURING_MODE.DWRITE_MEASURING_MODE_NATURAL, rParams).WriteValues();
				pFF.Item.GetGdiCompatibleMetrics(9f, 72f).WriteValues();
				Assert.That(() => pFF.Item.GetGdiCompatibleGlyphMetrics(9f, 72f, default, true, glidx, (uint)glCnt, glm), Throws.Nothing);
				glm.WriteValues();
			}
			finally
			{
				foreach (var i in pList)
					Marshal.ReleaseComObject(i);
			}
		}

		[Test]
		public void CreateTextFormatTest()
		{
			using var pTF = ComReleaserFactory.Create(Instance.CreateTextFormat("Arial", default, DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL, 11f, "en-us"));
			Assert.That(pTF.Item.GetFontSize(), Is.GreaterThan(0f));
			Assert.That(() => pTF.Item.SetTextAlignment(DWRITE_TEXT_ALIGNMENT.DWRITE_TEXT_ALIGNMENT_CENTER), Throws.Nothing);
			var sb = new StringBuilder(20);
			Assert.That(() => pTF.Item.GetLocaleName(sb, (uint)sb.Capacity), Throws.Nothing);
			Assert.That(sb.ToString(), Is.EqualTo("en-us"));
		}

		[Test]
		public void CreateTypographyTest()
		{
			using var pTyp = ComReleaserFactory.Create(Instance.CreateTypography());
			var cnt = pTyp.Item.GetFontFeatureCount();
			if (cnt > 0)
				pTyp.Item.GetFontFeature(0).WriteValues();
		}

		[Test]
		public void GetGdiInteropTest()
		{
			Assert.That(() =>
			{
				using var p = ComReleaserFactory.Create(Instance.GetGdiInterop());
			}, Throws.Nothing);
		}

		[Test]
		public void FontFileLoaderTest()
		{
			var loader = new FileLoader();
			Assert.That(() => Instance.RegisterFontFileLoader(loader), Throws.Nothing);
			try
			{

			}
			finally
			{
				Assert.That(() => Instance.UnregisterFontFileLoader(loader), Throws.Nothing);
			}
		}

		[ComVisible(true)]
		public class FontLoader : IDWriteFontCollectionLoader
		{
			public HRESULT CreateEnumeratorFromKey([In] IDWriteFactory factory, IntPtr collectionKey, uint collectionKeySize, out IDWriteFontFileEnumerator fontFileEnumerator)
			{
				fontFileEnumerator = null;
				if (factory is null || collectionKey == default)
					return HRESULT.E_INVALIDARG;

				fontFileEnumerator = new FontEnumerator(factory, StringHelper.GetString(collectionKey, CharSet.Unicode, collectionKeySize));

				return HRESULT.S_OK;
			}
		}

		[ComVisible(true)]
		public class FileLoader : IDWriteFontFileLoader
		{
			public FileLoader()
			{

			}

			public HRESULT CreateStreamFromKey([In] IntPtr fontFileReferenceKey, uint fontFileReferenceKeySize, out IDWriteFontFileStream fontFileStream)
			{
				fontFileStream = new FileStream(Directory.EnumerateFiles(fontDir).First());
				return default;
			}
		}

		[ComVisible(true)]
		public class FileStream : IDWriteFontFileStream, IDisposable
		{
			FileInfo fi;
			SafeHGlobalHandle mem;

			public FileStream(string path)
			{
				fi = new FileInfo(path);
				mem = new SafeHGlobalHandle(File.ReadAllBytes(path));
			}

			public HRESULT ReadFileFragment(out IntPtr fragmentStart, ulong fileOffset, ulong fragmentSize, [Out] out IntPtr fragmentContext)
			{
				fragmentContext = default;
				if (fileOffset + fragmentSize >= (ulong)fi.Length)
				{
					fragmentStart = default;
					return HRESULT.E_FAIL;
				}
				fragmentStart = ((IntPtr)mem).Offset((long)fileOffset);
				return default;
			}

			public void ReleaseFileFragment([In] IntPtr fragmentContext)
			{
			}

			public HRESULT GetFileSize(out ulong fileSize)
			{
				fileSize = (ulong)fi.Length;
				return default;
			}

			public HRESULT GetLastWriteTime(out System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime)
			{
				lastWriteTime = fi.LastWriteTime.ToFileTimeStruct();
				return default;
			}

			public void Dispose() => ((IDisposable)mem).Dispose();
		}

		[ComVisible(true)]
		public class FontEnumerator : IDWriteFontFileEnumerator
		{
			private IEnumerator<string> enumerator;
			private IDWriteFactory factory;

			public FontEnumerator(IDWriteFactory fact, string path)
			{
				factory = fact;
				enumerator = Directory.EnumerateFiles(path).GetEnumerator();
			}

			public HRESULT MoveNext([MarshalAs(UnmanagedType.Bool)] out bool hasCurrentFile) { hasCurrentFile = enumerator.MoveNext(); return HRESULT.S_OK; }

			public HRESULT GetCurrentFontFile(out IDWriteFontFile fontFile)
			{
				try { fontFile = factory.CreateFontFileReference(enumerator.Current); }
				catch (COMException ex) { fontFile = null; return ex.HResult; }
				return default;
			}
		}
	}
}