#if !WITHOUT_UNISCRIBE
using FastReport.Fonts;
using FastReport.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;

namespace FastReport.Export.TTF
{
    internal partial class ExportTTFFont
    {
        #region DLL import
        [DllImport("Gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
        [DllImport("Gdi32.dll")]
        private static extern IntPtr DeleteObject(IntPtr hgdiobj);
        [DllImport("Gdi32.dll")]
        private static extern int GetOutlineTextMetrics(IntPtr hdc, int cbData, ref TrueTypeFont.OutlineTextMetric lpOTM);
        [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
        private static extern uint GetFontData(IntPtr hdc, uint dwTable, uint dwOffset, [In, Out] IntPtr lpvBuffer, uint cbData);

        [DllImport("usp10.dll")]
        private static extern int ScriptFreeCache(ref IntPtr psc);
        [DllImport("usp10.dll")]
        private static extern int ScriptItemize(
            [MarshalAs(UnmanagedType.LPWStr)] string pwcInChars, int cInChars, int cMaxItems,
            ref SCRIPT_CONTROL psControl, ref SCRIPT_STATE psState, [In, Out] SCRIPT_ITEM[] pItems, ref int pcItems);
        [DllImport("usp10.dll")]
        private static extern int ScriptLayout(
            int cRuns, [MarshalAs(UnmanagedType.LPArray)] byte[] pbLevel,
            [MarshalAs(UnmanagedType.LPArray)] int[] piVisualToLogical,
            [MarshalAs(UnmanagedType.LPArray)] int[] piLogicalToVisual);
        [DllImport("usp10.dll")]
        private static extern int ScriptShape(
            IntPtr hdc, ref IntPtr psc, [MarshalAs(UnmanagedType.LPWStr)] string pwcChars,
            int cChars, int cMaxGlyphs, ref SCRIPT_ANALYSIS psa,
            [Out, MarshalAs(UnmanagedType.LPArray)] ushort[] pwOutGlyphs,
            [Out, MarshalAs(UnmanagedType.LPArray)] ushort[] pwLogClust,
            [Out, MarshalAs(UnmanagedType.LPArray)] SCRIPT_VISATTR[] psva, ref int pcGlyphs);
        [DllImport("usp10.dll")]
        private static extern int ScriptPlace(
            IntPtr hdc, ref IntPtr psc, [MarshalAs(UnmanagedType.LPArray)] ushort[] pwGlyphs,
            int cGlyphs, [MarshalAs(UnmanagedType.LPArray)] SCRIPT_VISATTR[] psva,
            ref SCRIPT_ANALYSIS psa, [MarshalAs(UnmanagedType.LPArray)] int[] piAdvance,
            [Out, MarshalAs(UnmanagedType.LPArray)] GOFFSET[] pGoffset, ref ABC pABC);
        [DllImport("usp10.dll")]
        private static extern uint ScriptRecordDigitSubstitution(uint lcid, ref SCRIPT_DIGITSUBSTITUTE psds);
        [DllImport("usp10.dll")]
        private static extern int ScriptApplyDigitSubstitution(
            ref SCRIPT_DIGITSUBSTITUTE psds, ref SCRIPT_CONTROL psc, ref SCRIPT_STATE pss);
        #endregion

        #region Uniscribe Structures
        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_STATE
        {
            public short data;
            public int uBidiLevel
            {
                get { return data & 0x001F; }
            }
            public void SetRtl()
            {
                data = 0x801;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_ANALYSIS
        {
            public short data;
            public SCRIPT_STATE state;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_CONTROL
        {
            public int data;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_DIGITSUBSTITUTE
        {
            public short NationalDigitLanguage;
            public short TraditionalDigitLanguage;
            public byte DigitSubstitute;
            public int dwReserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_ITEM
        {
            public int iCharPos;
            public SCRIPT_ANALYSIS analysis;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SCRIPT_VISATTR
        {
            // WORD uJustification : 4;
            // WORD fClusterStart : 1;
            // WORD fDiacritic : 1;
            // WORD fZeroWidth : 1;
            // WORD fReserved : 1;
            // WORD fShapeReserved : 8;

            public ushort data;

            public ushort uJustification
            {
                get { return (ushort)(data & 15u); }
            }

            public bool fClusterStart
            {
                get { return (data & 16u) == 16u; }
            }

            public bool fDiacritic
            {
                get { return (data & 32u) == 32u; }
            }

            public bool fZeroWidth
            {
                get { return (data & 64u) == 64u; }
            }

            public bool fReserved
            {
                get { return (data & 128u) == 128u; }

            }

            public byte fShapeReserved
            {
                get { return (byte)((data & 65280u) / 256); }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct GOFFSET
        {
            public int du;
            public int dv;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ABC
        {
            public int abcA;
            public int abcB;
            public int abcC;
        }
        #endregion

        #region EMF structures
        public const int LF_FACESIZE = 32;
        public const int LF_FULLFACESIZE = 64;
        public const int ELF_VENDOR_SIZE = 4;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct EMREXTCREATEFONTINDIRECT
        {
            public int ihFont; // Font handle index
            public EXTLOGFONT elfw;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct EXTLOGFONT
        {
            public LOGFONT elfLogFont;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FULLFACESIZE)]
            public string elfFullName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
            public string elfStyle;
            public int elfVersion; // 0 for the first release of NT
            public int elfStyleSize;
            public int elfMatch;
            public int elfReserved;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = ELF_VENDOR_SIZE)]
            public byte[] elfVendorId;
            public int elfCulture; // 0 for Latin
            public PANOSE elfPanose;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Size = 92)]
        public struct LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
            public string lfFaceName;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PANOSE
        {
            public byte bFamilyType;
            public byte bSerifStyle;
            public byte bWeight;
            public byte bProportion;
            public byte bContrast;
            public byte bStrokeVariation;
            public byte bArmStyle;
            public byte bLetterform;
            public byte bMidline;
            public byte bXHeight;
        }
        #endregion

        #region Private variables
        private Cache cache = new Cache();
        private SCRIPT_DIGITSUBSTITUTE digitSubstitute;
        #endregion

        #region Private methods
        public void FillOutlineTextMetrix()
        {
            if (sourceFont != null)
            {
                using (Graphics g = Graphics.FromImage(tempBitmap))
                {
                    IntPtr hdc = g.GetHdc();
                    IntPtr f = sourceFont.ToHfont();
                    try
                    {
                        SelectObject(hdc, f);
                        GetOutlineTextMetrics(hdc, Marshal.SizeOf(typeof(TrueTypeFont.OutlineTextMetric)), ref textMetric);
                    }
                    finally
                    {
                        DeleteObject(f);
                        g.ReleaseHdc(hdc);
                    }
                }
            }
        }

        private void GDI32_GetFontData(Font font, out IntPtr font_data, out FontType CollectionMode, out uint fontDataSize)
        {
            font_data = IntPtr.Zero;
            Bitmap tempBitmap = new Bitmap(1, 1);

            using (Graphics g = Graphics.FromImage(tempBitmap))
            {
                IntPtr hdc = g.GetHdc();
                IntPtr f = font.ToHfont();
                SelectObject(hdc, f);

                try
                {
                    // Try to read TrueTypeCollection
                    CollectionMode = FontType.TrueTypeCollection;
                    fontDataSize = GetFontData(hdc, (uint)CollectionMode, 0, IntPtr.Zero, 0);
                    if (fontDataSize == uint.MaxValue)
                    {
                        CollectionMode = FontType.TrueTypeFont;
                        fontDataSize = GetFontData(hdc, (uint)CollectionMode, 0, IntPtr.Zero, 0);
                    }
                    font_data = Marshal.AllocHGlobal((int)fontDataSize);
                    GetFontData(hdc, (uint)CollectionMode, 0, font_data, fontDataSize);
                }
                finally
                {
                    DeleteObject(f);
                    g.ReleaseHdc(hdc);
                    tempBitmap.Dispose();
                }
            }
        }

        private TrueTypeFont GetTrueTypeFont(Font source_font)
        {
            TrueTypeFont ttfont = null;

            string fast_font =
                source_font.FontFamily.Name +
                (source_font.Bold ? "-B" : string.Empty) +
                (source_font.Italic ? "-I" : string.Empty);

            if (!font_collection.Collection.ContainsKey(fast_font))
            {
                IntPtr font_image;
                FontType mode;
                uint size;
                GDI32_GetFontData(sourceFont, out font_image, out mode, out size);
                foreach (TrueTypeFont ttf in TrueTypeCollection.AddFontData((FontType)mode, font_image, size))
                {
                    TrueTypeCollection.ParseFont(true, ttf, "");
                    ttfont = ttf;
                }
            }
            
            if (font_collection.Collection.ContainsKey(fast_font))
                ttfont = font_collection[fast_font];

            return ttfont;
        }

        private Font[] GetFallbackFonts(IntPtr hdc, Font originalFont, string text)
        {
            // sad but true. To obtain a fallback font, we draw a string on a metafile, then analyse its records.
            // https://chromium.googlesource.com/chromium/src/+/22aed04422b04b2cf04f7b7d61392da4e9a2c85a/ui/gfx/font_fallback_win.cc

            Metafile mf = new Metafile(hdc, EmfType.EmfOnly);
            using (Graphics g = Graphics.FromImage(mf))
            {
                g.DrawString(text, originalFont, Brushes.Black, 0, 0);
            }

            List<Font> fallbackFonts = new List<Font>();
            using (Graphics g = Graphics.FromHdc(hdc))
            {
                g.EnumerateMetafile(mf, Point.Empty, (EmfPlusRecordType recordType, int flags, int dataSize, IntPtr data, PlayRecordCallback callbackData) =>
                {
                    if (recordType == EmfPlusRecordType.EmfExtCreateFontIndirect)
                    {
                        EMREXTCREATEFONTINDIRECT rec = (EMREXTCREATEFONTINDIRECT)Marshal.PtrToStructure(data, typeof(EMREXTCREATEFONTINDIRECT));
                        fallbackFonts.Add(Font.FromLogFont(rec.elfw.elfLogFont));
                    }
                    return true;
                });
            }

            return fallbackFonts.ToArray();
        }

        private RunInfo GetRunInfo(IntPtr hdc, Run run, Font originalFont, Font measureFont)
        {
            // initialize structures
            int maxGlyphs = run.text.Length * 3;
            SCRIPT_ANALYSIS psa = run.analysis;
            ushort[] clusters = new ushort[maxGlyphs];
            SCRIPT_VISATTR[] psva = new SCRIPT_VISATTR[maxGlyphs];
            GOFFSET[] gOffset = new GOFFSET[maxGlyphs];
            ABC abc = new ABC();
            ushort[] glyphs = new ushort[maxGlyphs];
            int[] widths = new int[maxGlyphs];
            int numGlyphs = 0;
            int fallbackIndex = -1;
            bool hasEmptyGlyphs = false;
            Font font = originalFont;
            Font[] fallbackFonts = null;
            var cacheItem = cache.GetCacheItem(hdc, measureFont);

            while (true)
            {
                // make glyphs
                ScriptShape(hdc, ref cacheItem.usCache, run.text, run.text.Length, glyphs.Length, ref psa, glyphs, clusters, psva, ref numGlyphs);

                // check missing glyphs
                hasEmptyGlyphs = false;
                if (numGlyphs == 0)
                    hasEmptyGlyphs = true;
                else
                {
                    for (int i = 0; i < numGlyphs; i++)
                    {
                        if (glyphs[i] == 0)
                        {
                            hasEmptyGlyphs = true;
                            break;
                        }
                    }
                }

                if (!hasEmptyGlyphs)
                    break;
                else
                {
                    // first call - generate fallback fonts list
                    if (fallbackIndex == -1)
                    {
                        // obtain fallback fonts. Use sourceFont instead of originalFont as a source (because of round errors in case of small fonts).
                        // sourceFont is 750pt size.
                        fallbackFonts = GetFallbackFonts(hdc, sourceFont, run.text);
                    }

                    fallbackIndex++;
                    if (fallbackIndex >= fallbackFonts.Length)
                        break;

                    Font fallbackFont = fallbackFonts[fallbackIndex];

                    // fallback font size may not be the same as original (case: japanese fonts)
                    float fallbackSizeRatio = fallbackFont.SizeInPoints / sourceFont.SizeInPoints;

                    // measureFont must be the same size as sourceFont (750pt)
                    measureFont = new Font(fallbackFont.FontFamily, sourceFont.Size, sourceFont.Style);

                    // finally, the font that will be used in runInfo
                    font = new Font(fallbackFont.FontFamily, originalFont.Size * fallbackSizeRatio, originalFont.Style);

                    cacheItem = cache.GetCacheItem(hdc, measureFont);
                }
            }

            // make widths
            ScriptPlace(hdc, ref cacheItem.usCache, glyphs, numGlyphs, psva, ref psa, widths, gOffset, ref abc);

            RunInfo runInfo = new RunInfo(numGlyphs)
            {
                Font = font
            };

            if (numGlyphs > 0)
            {
                // make ToUnicode
                for (int i = 0; i < run.text.Length; i++)
                {
                    int glyphIndex = clusters[i];
                    if (String.IsNullOrEmpty(runInfo.GlyphToUnicode[glyphIndex]))
                        runInfo.GlyphToUnicode[glyphIndex] = "";
                    runInfo.GlyphToUnicode[glyphIndex] += run.text[i];
                }
            }

            for (int i = 0; i < numGlyphs; i++)
            {
                runInfo.Glyphs[i] = glyphs[i];
                runInfo.Widths[i] = widths[i];
                runInfo.Kernings[i] = -gOffset[i].du;
                // this should be converted to objfont units
                runInfo.VerticalOffsets[i] = -gOffset[i].dv * font.Size / sourceFont.Size;

                if (gOffset[i].du != 0 && widths[i] == 0 && i > 0)
                {
                    runInfo.Kernings[i - 1] += gOffset[i].du;
                }
            }

            return runInfo;
        }

        private List<Run> Itemize(string s, bool rtl)
        {
            int maxItems = s.Length * 2;
            SCRIPT_ITEM[] pItems = new SCRIPT_ITEM[maxItems];
            int pcItems = 0;

            // initialize Control and State
            SCRIPT_CONTROL control = new SCRIPT_CONTROL();
            SCRIPT_STATE state = new SCRIPT_STATE();
            if (rtl)
            {
                // this is needed to start paragraph from right
                state.SetRtl();
                // to substitute arabic digits
                ScriptApplyDigitSubstitution(ref digitSubstitute, ref control, ref state);
            }

            // itemize
            ScriptItemize(s, s.Length, pItems.Length, ref control, ref state, pItems, ref pcItems);

            // create Run list. Note that ScriptItemize actually returns pcItems+1 items, 
            // so this can be used to calculate char range easily
            List<Run> list = new List<Run>();
            for (int i = 0; i < pcItems; i++)
            {
                string text = s.Substring(pItems[i].iCharPos, pItems[i + 1].iCharPos - pItems[i].iCharPos);
                list.Add(new Run(text, pItems[i].analysis));
            }

            return list;
        }

        private List<Run> Layout(List<Run> runs)
        {
            byte[] pbLevel = new byte[runs.Count];
            int[] piVisualToLogical = new int[runs.Count];

            // build the pbLevel array
            for (int i = 0; i < runs.Count; i++)
                pbLevel[i] = (byte)runs[i].analysis.state.uBidiLevel;

            // layout runs
            ScriptLayout(runs.Count, pbLevel, piVisualToLogical, null);

            // return runs in their visual order
            List<Run> visualRuns = new List<Run>();
            for (int i = 0; i < piVisualToLogical.Length; i++)
                visualRuns.Add(runs[piVisualToLogical[i]]);

            return visualRuns;
        }

        private List<RunInfo> LayoutString(string str, bool rtl, Font originalFont)
        {
            List<RunInfo> result = new List<RunInfo>();

            using (Graphics g = Graphics.FromImage(tempBitmap))
            {
                IntPtr hdc = g.GetHdc();

                try
                {
                    List<Run> runs = Itemize(str, rtl);
                    runs = Layout(runs);
                    float offsetX = 0;

                    foreach (Run run in runs)
                    {
                        RunInfo runInfo = GetRunInfo(hdc, run, originalFont, sourceFont);
                        runInfo.OffsetX = offsetX;
                        offsetX += (runInfo.Widths.Sum() - runInfo.Kernings.Sum()) * runInfo.Font.Size / sourceFont.Size;
                        result.Add(runInfo);
                    }

                    return result;
                }
                finally
                {
                    g.ReleaseHdc(hdc);
                }
            }
        }

        private void InternalInit()
        {
            dpiFX = 96f / DrawUtils.ScreenDpi;
            digitSubstitute = new SCRIPT_DIGITSUBSTITUTE();
            ScriptRecordDigitSubstitution(0x0400, ref digitSubstitute);
        }

        private void InternalDispose()
        {
            cache.Clear();
        }
        #endregion

        #region Private classes
        private class Run
        {
            public SCRIPT_ANALYSIS analysis;
            public string text;

            public Run(string text, SCRIPT_ANALYSIS a)
            {
                this.text = text;
                this.analysis = a;
            }
        }

        private class Cache
        {
            private Dictionary<Font, CacheItem> cache = new Dictionary<Font, CacheItem>();

            public CacheItem GetCacheItem(IntPtr hdc, Font f)
            {
                CacheItem cacheItem;
                if (!cache.TryGetValue(f, out cacheItem))
                {
                    cacheItem = new CacheItem() { hFont = f.ToHfont(), usCache = IntPtr.Zero };
                    cache.Add(f, cacheItem);
                }
                SelectObject(hdc, cacheItem.hFont);
                return cacheItem;
            }

            public void Clear()
            {
                var values = cache.Values.ToArray();
                for (int i = 0; i < values.Length; i++)
                {
                    var item = values[i];
                    ScriptFreeCache(ref item.usCache);
                    DeleteObject(item.hFont);
                }
                cache.Clear();
            }
        }

        private class CacheItem
        {
            public IntPtr hFont;
            public IntPtr usCache;
        }
        #endregion

    }
}
#endif