using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Quik.Media;
using Quik.Media.Font;

namespace Quik.Media
{
    /// <summary>
    /// Abstract class that represents a font.
    /// </summary>
    public abstract class QFont : IDisposable
    {
        public abstract FontFace Face { get; }
        public string Family => Face.Family;
        public FontSlant Slant => Face.Slant;
        public FontWeight Weight => Face.Weight;
        public FontStretch Stretch => Face.Stretch;

        public abstract bool HasRune(int rune);
        protected abstract QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options);
        

        private readonly Dictionary<float, SizedFontCollection> _atlasses = new Dictionary<float, SizedFontCollection>();

        public void Get(int codepoint, float size, out FontGlyph glyph)
        {
            SizedFontCollection? collection;

            if (!_atlasses.TryGetValue(size, out collection))
            {
                collection = new SizedFontCollection(size);
                _atlasses.Add(size, collection);    
            }

            collection.Get(codepoint, out glyph, this);
        }

        // IDisposable
        private bool isDisposed = false;
        private void DisposePrivate(bool disposing)
        {
            if (isDisposed) return;

            Dispose(disposing);

            isDisposed = true;
        }
        protected virtual void Dispose(bool disposing) { }
        public void Dispose() => DisposePrivate(true);

        private class SizedFontCollection
        {
            public float Size { get; }
            private readonly Dictionary<int, FontGlyph> glyphs = new Dictionary<int, FontGlyph>();
            private readonly FontAtlas atlas;

            public SizedFontCollection(float size)
            {
                Size = size;

                QuikApplication.Current.Platform.GetMaximumImage(out int height, out int width);

                // Do no allow to create a texture that is greater than 16 square characters at 200 DPI.
                width = Math.Min(width, (int)(size * 200 * 16));
                height = Math.Min(height, (int)(size * 200 * 16));
                // width = height = 256;

                atlas = new FontAtlas(width, height, QuikApplication.Current.FontProvider.RasterizerOptions.Sdf);
            }

            public void Get(int codepoint, out FontGlyph glyph, QFont font)
            {
                if (glyphs.TryGetValue(codepoint, out glyph))
                    return;
                
                QImage image = font.Render(
                    out QGlyphMetrics metrics,
                    codepoint,
                    Size,
                    QuikApplication.Current.FontProvider.RasterizerOptions);

                if (image != null)
                {
                    image.LockBits2d(out QImageLock l, QImageLockOptions.Default);
                    atlas.PutGlyph(codepoint, l, out FontAtlasGlyphInfo glyphInfo);
                    image.UnlockBits();
                    image.Dispose();

                    glyph = new FontGlyph(codepoint, glyphInfo.Image, metrics, glyphInfo.UVs);
                }
                else
                {
                    glyph = new FontGlyph(codepoint, null, metrics, default);
                }

                glyphs[codepoint] = glyph;
            }
        }
    }

    public readonly struct FontGlyph
    {
        public readonly int CodePoint;
        public readonly QImage? Image;
        public readonly QGlyphMetrics Metrics;
        public readonly QRectangle UVs;

        public FontGlyph(int codepoint, QImage? image, in QGlyphMetrics metrics, in QRectangle uvs)
        {
            CodePoint = codepoint;
            Image = image;
            Metrics = metrics;
            UVs = uvs;
        }
    }

    public struct FontRasterizerOptions
    {
        public float Resolution { get; set; }
        public bool Sdf { get; set; }

        public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
        {
            Resolution = 96.0f,
            Sdf        = false
        };
    }
}