using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Quik.Media;

namespace Quik.Typography
{
    /// <summary>
    /// An atomic horizontal block of text which cannot be further divided.
    /// </summary>
    public struct HorizontalTextBlock
    {
        /// <summary>
        /// The font associated with the text block.
        /// </summary>
        /// <value></value>
        // public QuikFont Font { get; }
        /// <summary>
        /// Textual contents of the text block.
        /// </summary>
        public string Text { get; }
        /// <summary>
        /// Indicates this text block should be layed out right to left.
        /// </summary>
        public bool IsRTL { get; }

        /// <summary>
        /// Indicates this is a whitespace block.
        /// </summary>
        public bool IsWhitespace => string.IsNullOrWhiteSpace(Text);

        public float Width { get; }
        public float Ascend { get; }
        public float Descend { get; }
        public float Height => Ascend - Descend;

        public HorizontalTextBlock(object font, string text, bool rtl = false)
        {
            // Font = font;
            Text = text;
            IsRTL = rtl;

            float width = 0.0f;
            float ascend = 0.0f;
            float descend = 0.0f;

            foreach (char chr in text)
            {
                // font.GetCharacter(chr, out _, out QGlyphMetrics glyph);
                // width += glyph.Advance.X;
                // ascend = Math.Max(ascend, glyph.HorizontalBearing.Y);
                // descend = Math.Min(descend, glyph.HorizontalBearing.Y - glyph.Size.Y);
            }

            Width = width;
            Ascend = ascend;
            Descend = descend;
        }

        public HorizontalTextBlock(float width)
        {
            // Font = null;
            Text = string.Empty;
            IsRTL = false;
            Width = width;
            Ascend = Descend = 0.0f;
        }
    }

    /// <summary>
    /// An atomic vertical block of text which cannot be further divided.
    /// </summary>
    public struct VerticalTextBlock
    {
        // public QuikFont Font { get; }
        public string Text { get; }
        public bool IsWhitespace => string.IsNullOrWhiteSpace(Text);
        public float Width { get; }
        public float Height { get; }

        public VerticalTextBlock(object font, string text)
        {
            // Font = font;
            Text = text;

            float width = 0.0f;
            float height = 0.0f;

            foreach(char chr in text)
            {
                // font.GetCharacter(chr, out _, out QGlyphMetrics glyph);
                // width = Math.Max(width, - glyph.VerticalBearing.X * 2);
                // height += glyph.Advance.Y;
            }

            Width = width;
            Height = height;
        }

        public VerticalTextBlock(float height)
        {
            // Font = null;
            Text = string.Empty;
            Width = 0.0f;
            Height = height;
        }
    }

    public abstract class Paragraph
    {
        public abstract bool IsVertical { get; }

        public float JustifyLimit { get; set; } = 30.0f;
        public TextAlignment Alignment { get; set; } = TextAlignment.Default;
        public float PreSpace { get; set; } = 0.0f;
        public float PostSpace { get; set; } = 0.0f;
        public float FirstLineInset { get; set; } = 0.0f;
        public float LineGap { get; set; } = 12.0f;

        public abstract void Typeset(TypesetGroup group, float width);

        protected abstract void AppendBlock(object font, string text, bool rtl = false);

        public void ConsumeText(object font, string text)
        {
            StringBuilder segment = new StringBuilder();
            bool rtl = false;
            bool ws = false;

            foreach(char chr in text)
            {
                UnicodeCategory cat = char.GetUnicodeCategory(chr);
                // FIXME: don't ignore control characters like a barbarian.
                // TODO: how do I detect text flow direction???
                if (char.IsWhiteSpace(chr) && chr != '\u00a0')
                {
                    if (ws)
                    {
                        segment.Append(chr);
                    }
                    else
                    {
                        AppendBlock(font, segment.ToString());
                        segment.Clear();
                        segment.Append(chr);
                        ws = true;
                    }
                }
                else
                {
                    if (ws)
                    {
                        AppendBlock(font, segment.ToString(), rtl);
                        segment.Clear();
                        segment.Append(chr);
                        ws = false;
                    }
                    else
                    {
                        segment.Append(chr);
                    }
                }
            }

            if (segment.Length > 0)
            {
                AppendBlock(font, segment.ToString(), rtl);
            }
        }
    }

    public class HorizontalParagraph : Paragraph
    {
        public override bool IsVertical => false;
        public List<HorizontalTextBlock> Blocks { get; } = new List<HorizontalTextBlock>();

        public override void Typeset(TypesetGroup group, float width)
        {
            Queue<HorizontalTextBlock> line = new Queue<HorizontalTextBlock>();
            int index = 0;
            bool firstLine = true;

            QVec2 pen = new QVec2(0, -PreSpace);

            while (index < Blocks.Count)
            {
                index
                    += GatherLine(
                        index,
                        width - (firstLine ? FirstLineInset : 0),
                        line,
                        out float excess,
                        out float ascend,
                        out float descend);

                firstLine = false;

                pen.Y -= ascend;

                float interblockWs =
                    Alignment.HasFlag(TextAlignment.Justify) && excess < JustifyLimit
                        ? excess / (line.Count - 1)
                        : 0.0f;

                switch (Alignment & TextAlignment.HorizontalMask)
                {
                default:
                case TextAlignment.AlignLeft:
                    if (firstLine) pen.X += FirstLineInset;
                    break;
                case TextAlignment.AlignCenterH:
                    pen.X += excess / 2;
                    break;
                case TextAlignment.AlignRight:
                    pen.X += excess;
                    break;
                }

                PutBlock(group, line, interblockWs, ref pen);

                pen.Y -= LineGap - descend;
                pen.X = 0.0f;
            }

            pen.Y -= PostSpace;

            group.BoundingBox = new QRectangle(width, 0, 0, pen.Y);
            group.Translate(-pen);
        }

        private int GatherLine(
            int index,
            float width,
            Queue<HorizontalTextBlock> line,
            out float excess,
            out float ascend,
            out float descend)
        {
            float currentWidth = 0.0f;
            ascend = descend = 0.0f;

            for (int i = index; i < Blocks.Count; i++)
            {
                HorizontalTextBlock block = Blocks[i];

                if (currentWidth + block.Width > width)
                {
                    break;
                }

                ascend = Math.Max(ascend, block.Ascend);
                descend = Math.Min(descend, block.Descend);
                currentWidth += block.Width;
                line.Enqueue(block);
            }

            excess = width - currentWidth;
            return line.Count;
        }

        public void PutBlock(
            TypesetGroup group,
            Queue<HorizontalTextBlock> line,
            float interblockWs,
            ref QVec2 pen)
        {
            QVec2 penpal = pen;

            while (line.TryDequeue(out HorizontalTextBlock block))
            {
                if (block.IsWhitespace)
                {
                    penpal.X += block.Width + interblockWs;
                    continue;
                }

                if (block.IsRTL)
                {
                    for (int i = block.Text.Length - 1; i >= 0; i--)
                    {
                        char chr = block.Text[i];
                        // block.Font.GetCharacter(chr, out QuikTexture texture, out QGlyphMetrics metrics);
                        // group.Add(
                        //     new TypesetCharacter(
                        //         chr,
                        //         texture,
                        //         new QRectangle(
                        //             penpal.X + metrics.Advance.X,
                        //             penpal.Y + metrics.HorizontalBearing.Y,
                        //             penpal.X + metrics.HorizontalBearing.X,
                        //             penpal.Y - metrics.Size.Y + metrics.HorizontalBearing.Y),
                        //         metrics.Location
                        //     )
                        // );
                        // penpal.X += metrics.Advance.X;
                    }
                }
                else
                {
                    for (int i = 0; i < block.Text.Length; i++)
                    {
                        char chr = block.Text[i];
                        // block.Font.GetCharacter(chr, out QuikTexture texture, out QGlyphMetrics metrics);
                        // group.Add(
                        //     new TypesetCharacter(
                        //         chr,
                        //         texture,
                        //         new QRectangle(
                        //             penpal.X + metrics.Advance.X,
                        //             penpal.Y + metrics.HorizontalBearing.Y,
                        //             penpal.X + metrics.HorizontalBearing.X,
                        //             penpal.Y - metrics.Size.Y + metrics.HorizontalBearing.Y),
                        //         metrics.Location
                        //     )
                        // );

                        // penpal.X += metrics.Advance.X;
                    }
                }

                penpal.X += interblockWs;
            }

            penpal.X -= interblockWs;

            pen = penpal;
        }

        protected override void AppendBlock(object font, string text, bool rtl = false)
        {
            Blocks.Add(new HorizontalTextBlock(font, text, rtl));
        }
    }

    public class VerticalParagraph : Paragraph
    {
        public override bool IsVertical => true;
        public List<VerticalTextBlock> Blocks { get; } = new List<VerticalTextBlock>();

        public override void Typeset(TypesetGroup group, float width)
        {
            throw new NotImplementedException();
        }

        protected override void AppendBlock(object font, string text, bool rtl = false)
        {
            Blocks.Add(new VerticalTextBlock(font, text));
        }
    }

    public struct TypesetCharacter
    {
        public int Character;
        public QuikTexture Texture;
        public QRectangle Position;
        public QRectangle UV;

        public TypesetCharacter(
            int chr,
            QuikTexture texture,
            in QRectangle position,
            in QRectangle uv)
        {
            Character = chr;
            Texture = texture;
            Position = position;
            UV = uv;
        }
    }

    public class TypesetGroup : ICollection<TypesetCharacter>
    {
        private int _count = 0;
        private TypesetCharacter[] _array = Array.Empty<TypesetCharacter>();

        public QRectangle BoundingBox;

        public int Count => _count;

        public bool IsReadOnly => false;

        public void Add(TypesetCharacter item)
        {
            if (_count == _array.Length)
            {
                Array.Resize(ref _array, _array.Length + 256);
            }

            _array[_count++] = item;
        }

        public void Clear()
        {
            _count = 0;
        }

        public void Translate(QVec2 offset)
        {
            BoundingBox.Translate(offset);

            for (int i = 0; i < _count; i++)
            {
                _array[i].Position.Translate(offset);
            }
        }

        public bool Contains(TypesetCharacter item)
        {
            throw new NotSupportedException();
        }

        public void CopyTo(TypesetCharacter[] array, int arrayIndex)
        {
            _array.CopyTo(array, arrayIndex);
        }

        public IEnumerator<TypesetCharacter> GetEnumerator()
        {
            for (int i = 0; i < _count; i++)
            {
                yield return _array[i];
            }
        }

        public bool Remove(TypesetCharacter item)
        {
            throw new NotSupportedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }

        public void SortBy(IComparer<TypesetCharacter> comparer)
        {
            Array.Sort(_array, 0, _count, comparer);
        }

        public static IComparer<TypesetCharacter> SortByTexture { get; } = new SortByTextureComparer();

        private class SortByTextureComparer : IComparer<TypesetCharacter>
        {
            public int Compare(TypesetCharacter x, TypesetCharacter y)
            {
                return y.Texture.GetHashCode() - x.Texture.GetHashCode();
            }
        }
    }

    /// <summary>
    /// An enumeration of possible text alignments.
    /// </summary>
    [Flags]
    public enum TextAlignment
    {
        /// <summary>
        /// Align to the left margin, horizontally.
        /// </summary>
        AlignLeft = 0x01,

        /// <summary>
        /// Align text to center of left and right margins.
        /// </summary>
        AlignCenterH = 0x03,

        /// <summary>
        /// Align to the right margin, horizontally.
        /// </summary>
        AlignRight = 0x02,

        /// <summary>
        /// A bitmask for values relating to horizontal alignment.
        /// </summary>
        HorizontalMask = 0x03,

        /// <summary>
        /// Align text to the top margin.
        /// </summary>
        AlignTop = 0x00,

        /// <summary>
        /// Align text between the top and bottom margins.
        /// <summary>
        AlignCenterV = 0x04,

        /// <summary>
        /// Align text to the bottom margin.
        /// </summary>
        AlignBottom = 0x08,

        /// <summary>
        /// A bitmask for values relating to the vertical alignment.
        /// </summary>
        VerticalMask   = 0x0C,

        /// <summary>
        /// Distribute characters uniformly on the line, when possible.
        /// <summary>
        Justify      = 0x10,

        /// <summary>
        /// The default text alignment value.
        /// </summary>
        Default = AlignTop | AlignLeft
    }
}