using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using FastReport.Utils;
using FastReport.Fonts;
using System.Text;
using System.Linq;

namespace FastReport.Export.TTF
{
    /// <summary>
    /// Specifies the export font class.
    /// </summary>
    internal partial class ExportTTFFont : IDisposable
    {
        /// <summary>
        /// These fonts do not support Bold or Itailc styles
        /// </summary>
        static readonly string[] NeedStyleSimulationList = new string[]
        {
            "\uff2d\uff33 \u30b4\u30b7\u30c3\u30af",
            "\uff2d\uff33 \uff30\u30b4\u30b7\u30c3\u30af",
            "\uff2d\uff33 \u660e\u671d",
            "\uff2d\uff33 \uff30\u660e\u671d",
            "MS Gothic",
            "MS UI Gothic",
            "Arial Black",
            "Tahoma"
        };
#if !CROSSPLATFORM  // remove if need
        static readonly TrueTypeCollection font_collection = new TrueTypeCollection();
#endif

        #region Private variables
        private Bitmap tempBitmap;
        private float dpiFX;

        private Dictionary<ushort, GlyphChar> usedGlyphChars;
        private TrueTypeFont.OutlineTextMetric textMetric;
        private string name;
        private Font sourceFont;
        private float baseSize;
        private long reference;
        private bool saved;
        private bool simulateBold;
        private bool simulateItalic;
        private bool editable;
        private bool repack_I2L = false;
        private string postscript_name;

        #endregion

        #region Public fields

        public IEnumerable<GlyphChar> UsedGlyphChars
        {
            get { return usedGlyphChars.Values; }
        }

        /// <summary>
        /// Return text metric structure, need to use after FillOutlineTextMetrix()
        /// </summary>
        public TrueTypeFont.OutlineTextMetric TextMetric
        {
            get { return textMetric; }
        }

        /// <summary>
        /// Gets or sets internal font name
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// Return source font used in constructor
        /// </summary>
        public Font SourceFont
        {
            get { return sourceFont; }
        }

        /// <summary>
        /// Returns multiplier for stroke bold emulation
        /// </summary>
        public double SourceFontBoldMultiplier
        {
            get { return Math.E / (2 * Math.PI) * baseSize / 9; }
        }

        /// <summary>
        /// Gets or sets internal reference
        /// </summary>
        public long Reference
        {
            get { return reference; }
            set { reference = value; }
        }

        /// <summary>
        /// Gets or sets internal property - save flag 
        /// </summary>
        public bool Saved
        {
            get { return saved; }
            set { saved = value; }
        }

        /// <summary>
        /// True if bold style is not supported by font
        /// </summary>
        public bool NeedSimulateBold
        {
            get { return simulateBold; }
            set { simulateBold = value; }
        }

        /// <summary>
        /// True if italic style is not supported by font
        /// </summary>
        public bool NeedSimulateItalic
        {
            get { return simulateItalic; }
            set { simulateItalic = value; }
        }

        /// <summary>
        /// Mark font as editable for InteractiveForms
        /// </summary>
        public bool Editable
        {
            get { return editable; }
            set { editable = value; }
        }
        
        /// <summary>
        /// Get PostScript name of font
        /// </summary>
        public string PostscriptName { get { return postscript_name; } }
        #endregion 

        #region Public Methods
        /// <summary>
        /// Return font file
        /// </summary>
        /// <returns></returns>
        public byte[] GetFontData(bool subsetting)
        {
            byte[] result = null;
            if (sourceFont != null)
            {
                TrueTypeFont font = GetTrueTypeFont(sourceFont);
                if (font != null)
                {
                    int LicensingRights = font.LicensingRights;

                    if ((LicensingRights & 0x000f) == 2)
                        throw new System.ComponentModel.LicenseException(typeof(TrueTypeFont), this, 
                            String.Format(new MyRes("Fonts").Get("FontMustNotBeModifiedEmbeddedOrExchanged"), font.Name));
                    if ((LicensingRights & 0x0200) == 0x0200)
                        throw new System.ComponentModel.LicenseException(typeof(TrueTypeFont), this,
                            String.Format("Font {0}: Bitmap embedding only", font.Name));
                    if (subsetting)
                    {
                        if ((LicensingRights & 0x0100) == 0x0100)
                        {

                            throw new System.ComponentModel.LicenseException(typeof(TrueTypeFont), this,
                                String.Format(new MyRes("Fonts").Get("FontMayNotBeSubsettedPriorToEmbedding"), font.Name));
                        }
                        font.PrepareIndexes();
                        if (font.IsClassicFormat)
                        {
                            TrueTypeFont.FontPackOptions options = TrueTypeFont.FontPackOptions.Default_Options;
                            if (this.repack_I2L)
                                options = TrueTypeFont.FontPackOptions.Pack_Indexes;
                            result = font.PackFont(ref usedGlyphChars, options);
                        }
                        else if (font.Type == FontType.OpenTypeFont)
                        {
                            // TODO: Pack Compact Font Format 
                            result = font.GetFontData();
                        }
                        else
                        {
                            // Generate warning and load full 
                            result = font.GetFullRawFontData();
                        }
                    }
                    else
                        result = font.GetRawFontData();

                    // Style simulation here
                    if (sourceFont.Bold != font.Bold)
                    {
                        simulateBold = true;
                    }
                    if (sourceFont.Italic != font.Italic)
                    {
                        simulateItalic = true;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Get font data and set NeedSimulateBold and NeedSimulateItalic properties. Call this method after FillOutlineTextMetrix
        /// </summary>
        public void FillEmulate()
        {
            TrueTypeFont ttf = GetTrueTypeFont(sourceFont);
            postscript_name = ttf.PostscriptName;
            NeedSimulateBold = SourceFont.Bold && !ttf.Bold;
            NeedSimulateItalic = SourceFont.Italic && !ttf.Italic;
        }

        /// <summary>
        /// Returns a list of font runs from the string specified. Also adds glyphs to the used glyphs table.
        /// </summary>
        /// <param name="str"></param>
        /// <param name="rtl"></param>
        /// <param name="originalFont"></param>
        /// <returns></returns>
        public RunInfo[] GetFontRuns(string str, bool rtl, Font originalFont = null)
        {
            // control chars should not be here...
            str = str.Replace("\r", "").Replace("\n", "").Replace((char)0xa0, ' ').Replace((char)0x2011, '-');

            if (string.IsNullOrEmpty(str))
                return new RunInfo[] { };

            if (originalFont == null)
                originalFont = sourceFont;

            List<RunInfo> runs = LayoutString(str, rtl, originalFont);

            // try to coalesce runs
            List<RunInfo> result = new List<RunInfo>();
            Font currentFont = runs[0].Font;
            int start = 0;
            
            for (int i = 0; i < runs.Count; i++)
            {
                RunInfo run = runs[i];
                if (run.Font != currentFont)
                {
                    result.Add(CoalesceRuns(runs, start, i));
                    currentFont = run.Font;
                    start = i;
                }
            }

            result.Add(CoalesceRuns(runs, start, runs.Count));

            // now extract runs with non-zero VerticalOffset
            for (int i = 0; i < result.Count; i++)
            {
                RunInfo run = result[i];
                for (int j = 0; j < run.VerticalOffsets.Length; j++)
                {
                    if (run.VerticalOffsets[j] != 0)
                    {
                        if (j == 0)
                            j = 1;
                        if (j < run.VerticalOffsets.Length)
                        {
                            var remainRun = SplitRun(run, j);
                            result.Insert(i + 1, remainRun);
                        }
                        break;
                    }
                }
            }

            return result.ToArray();
        }

        private RunInfo CoalesceRuns(List<RunInfo> runs, int start, int end)
        {
            int glyphLen = 0;
            for (int i = start; i < end; i++)
            {
                glyphLen += runs[i].Glyphs.Length;
            }

            RunInfo r = new RunInfo(glyphLen)
            {
                Font = runs[start].Font,
                OffsetX = runs[start].OffsetX,
            };
            
            int offset = 0;
            for (int i = start; i < end; i++)
            {
                var run = runs[i];
                int len = run.Glyphs.Length;
                Array.Copy(run.Glyphs, 0, r.Glyphs, offset, len);
                Array.Copy(run.GlyphToUnicode, 0, r.GlyphToUnicode, offset, len);
                Array.Copy(run.Widths, 0, r.Widths, offset, len);
                Array.Copy(run.Kernings, 0, r.Kernings, offset, len);
                Array.Copy(run.VerticalOffsets, 0, r.VerticalOffsets, offset, len);
                offset += len;
            }

            return r;
        }

        // leaves first splitPos elements in the run, returns the new run with remain elements
        private RunInfo SplitRun(RunInfo run, int splitPos)
        {
            int newLen = run.Glyphs.Length - splitPos;

            RunInfo r = new RunInfo(newLen)
            {
                Font = run.Font
            };

            // copy remain elements to the new run
            Array.Copy(run.Glyphs, splitPos, r.Glyphs, 0, newLen);
            Array.Copy(run.GlyphToUnicode, splitPos, r.GlyphToUnicode, 0, newLen);
            Array.Copy(run.Widths, splitPos, r.Widths, 0, newLen);
            Array.Copy(run.Kernings, splitPos, r.Kernings, 0, newLen);
            Array.Copy(run.VerticalOffsets, splitPos, r.VerticalOffsets, 0, newLen);

            int oldLen = splitPos;

            // resize original run's elements
            Array.Resize<ushort>(ref run.Glyphs, oldLen);
            Array.Resize<string>(ref run.GlyphToUnicode, oldLen);
            Array.Resize<float>(ref run.Widths, oldLen);
            Array.Resize<float>(ref run.Kernings, oldLen);
            Array.Resize<float>(ref run.VerticalOffsets, oldLen);

            // calc OffsetX for the new run
            float offsetX = run.OffsetX;
            for (int i = 0; i < oldLen; i++)
            {
                offsetX += (run.Widths[i] + run.Kernings[i]) * run.Font.Size / sourceFont.Size;
            }
            r.OffsetX = offsetX;

            return r;
        }

        internal void AddUsedGlyphs(RunInfo run)
        {
            for (int glyphIndex = 0; glyphIndex < run.Glyphs.Length; glyphIndex++)
            {
                ushort c = run.Glyphs[glyphIndex];
                // Uniscribe: uncommenting the following line leads to visual misalignment of '11111111' followed by '1' string aligned to right
                // this is due to the kerning 
#if WITHOUT_UNISCRIBE
                if (!usedGlyphChars.ContainsKey(c))
#endif
                {
                    string codePoint = run.GlyphToUnicode[glyphIndex];
                    if (codePoint != null && codePoint.Length == 1)
                        usedGlyphChars[c] = new GlyphChar(c, run.Widths[glyphIndex], codePoint[0]);
                    else if (codePoint != null && codePoint.Length > 1)
                        usedGlyphChars[c] = new GlyphChar(c, run.Widths[glyphIndex], codePoint);
                    else
                        usedGlyphChars[c] = new GlyphChar(c, run.Widths[glyphIndex]);
                }
            }
        }

        /// <summary>
        /// Set pattern for generation of alphabet's subset
        /// </summary>
        /// <param name="pattern">Regular expression with pattern generator</param>
        /// <param name="rtl">Use left-to-right rules</param>
        public void SetPattern(string pattern, bool rtl)
        {
            // Insert pattern extractor here
            RegexPatternExtractor regex = new RegexPatternExtractor();
            regex.AddExpression(pattern);
            // just to add pattern symbols to used glyphs table
            GetFontRuns(regex.Pattern, rtl);
        }

        public GlyphTTF[] GetGlyphPath(RunInfo run)
        {
            float size = run.Font.Size;
            List<GlyphTTF> result = new List<GlyphTTF>();

            for (int i = 0; i < run.Glyphs.Length; i++)
            {
                TrueTypeFont ttfont = GetTrueTypeFont(sourceFont);
                FastGraphicsPath aPath = ttfont.GetGlyph(run.Glyphs[i], size);
                
                GraphicsPath path;
                if (aPath.PointCount != 0)
                    path = new GraphicsPath(aPath.PathPoints, aPath.PathTypes, FillMode.Winding);
                else
                    path = new GraphicsPath();

                result.Add(new GlyphTTF(path,
                                run.Widths[i] / sourceFont.Size * size,
                                ttfont.baseLine * size / 0.75f));
            }

            return result.ToArray();
        }

        /// <summary>
        /// Return english name of source font
        /// </summary>
        /// <returns></returns>
        public string GetEnglishFontName()
        {
            // get the english name of a font
            string fontName = sourceFont.FontFamily.GetName(1033);
            FastString Result = new FastString(fontName.Length * 3);
            foreach (char c in fontName)
            {
                switch (c)
                {
                    case ' ':
                    case '%':
                    case '(':
                    case ')':
                    case '<':
                    case '>':
                    case '[':
                    case ']':
                    case '{':
                    case '}':
                    case '/':
                    case '#':
                        Result.Append("#");
                        Result.Append(((int)c).ToString("X2"));
                        break;
                    default:
                        if ((int)c > 126 || (int)c < 32)
                        {
                            Result.Append('#');
                            Result.Append(((int)c).ToString("X2"));
                        }
                        else
                            Result.Append(c);
                        break;
                }
            }
            FastString Style = new FastString(9);
            if ((sourceFont.Style & FontStyle.Bold) > 0 && !this.simulateBold)
                Style.Append("Bold");
            if ((sourceFont.Style & FontStyle.Italic) > 0 && !this.simulateItalic)
                Style.Append("Italic");
            if (Style.Length > 0)
                Result.Append(",").Append(Style.ToString());
            return Result.ToString();
        }

#endregion

        /// <summary>
        /// Create object of ExportTTFFont.
        /// </summary>
        /// <param name="font"></param>
        public ExportTTFFont(Font font)
        {
            InternalInit();

            baseSize = font.Size;

            bool isPrivateFont = String.IsNullOrEmpty(font.OriginalFontName);
            if (isPrivateFont)
                sourceFont = new Font(font.FontFamily, 750 * dpiFX, font.Style);
            else
                sourceFont = new Font(font.Name, 750 * dpiFX, font.Style);

            saved = false;
            textMetric = new TrueTypeFont.OutlineTextMetric();
            usedGlyphChars = new Dictionary<ushort, GlyphChar>();
            tempBitmap = new Bitmap(1, 1);
            simulateItalic = false;
            simulateBold = false;
            editable = false;
            foreach (string s in NeedStyleSimulationList)
                if (s == font.Name)
                {
                    simulateBold = true;
                    simulateItalic = true;
                    break;
                }
        }

        /// <summary>
        /// Destructor
        /// </summary>
        public void Dispose()
        {
            InternalDispose();
            tempBitmap.Dispose();
            sourceFont.Dispose();
        }

#region Private classes
        public class GlyphTTF
        {
            public GraphicsPath Path;
            public float Width;
            public float BaseLine;

            public GlyphTTF(GraphicsPath path, float width)
            {
                Path = path;
                Width = width;
            }

            public GlyphTTF(GraphicsPath path, float width, float baseLine) : this(path, width)
            {
                BaseLine = baseLine;
            }
        }

        internal class RunInfo
        {
            public Font Font;
            public float OffsetX;
            public ushort[] Glyphs;
            public string[] GlyphToUnicode;
            public float[] Widths;
            public float[] Kernings;
            public float[] VerticalOffsets;

            public RunInfo(int len)
            {
                Glyphs = new ushort[len];
                GlyphToUnicode = new string[len];
                Widths = new float[len];
                Kernings = new float[len];
                VerticalOffsets = new float[len];
            }
        }
#endregion
    }
}