using System;
using System.IO;
using Quik.FreeType;
using Quik.Media;
using Quik.Media.Color;

namespace Quik.Media.Defaults
{
    public class QFontFreeType : QFont
    {
        private MemoryStream ms;
        private FTFace face;

        public override FontInfo Info => throw new NotImplementedException();

        public QFontFreeType(Stream stream)
        {
            ms = new MemoryStream();
            stream.CopyTo(ms);

            FTError e = FT.NewMemoryFace(Ft, ms.GetBuffer(), ms.Length, 0, out face);
            if (e != FTError.None)
            {
                throw new Exception("Could not load font face from stream.");
            }
        }

        public override bool HasRune(int rune)
        {
            return FT.GetCharIndex(face, (ulong)rune) != 0;
        }

        public override QFontPage RasterizePage(int codepage, float size, in FontRasterizerOptions options)
        {
            FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
            QGlyphMetrics[] allMetrics = new QGlyphMetrics[256];

            // Figure out the map size needed.
            int pixels = 0;
            for (int i = 0; i < 256; i++)
            {
                uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
                FT.LoadGlyph(face, index, FTLoadFlags.Default);

                ref readonly FTGlyphMetrics metrics = ref face.Glyph.Metrics;
                allMetrics[i] = new QGlyphMetrics(
                    codepage + i,
                    new QVec2(metrics.Width, metrics.Height),
                    new QVec2(metrics.HorizontalBearingX/64f, metrics.HorizontalBearingY/64f),
                    new QVec2(metrics.VerticalBearingX/64f, metrics.VerticalBearingY/64f),
                    new QVec2(metrics.HorizontalAdvance/64f, metrics.VerticalAdvance/64f)
                );
                pixels = (int)Math.Max(pixels, Math.Max(face.Glyph.Metrics.Width/64f, face.Glyph.Metrics.Height/64f));
            }

            int bits = Math.ILogB(pixels);
            if (1 << bits != pixels)
            {
                pixels = 1 << bits + 1;
            }

            // Now we can create a bitmap and render our glyphs.

            QImageBuffer buffer = new QImageBuffer(QImageFormat.RedU8, (int)pixels, (int)pixels, 256);

            for (int i = 0; i < 256; i++)
            {
                uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
                FT.LoadGlyph(face, index, FTLoadFlags.Default);
                FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Mono);

                ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;

                buffer.LockBits3d(out QImageLock dst, QImageLockOptions.Default, i);

                if (bitmap.Buffer != IntPtr.Zero) unsafe
                {
                    for (int j = 0; j < bitmap.Rows; j++)
                    {
                        Buffer.MemoryCopy(
                            (byte*)bitmap.Buffer + (j * bitmap.Pitch),
                            (byte*)dst.ImagePtr  + (j * dst.Width),
                            dst.Width,
                            bitmap.Width);
                    }
                }

                buffer.UnlockBits();
            }

            return new QFontPage(this, codepage, size, options, buffer, allMetrics);
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                ms.Dispose();
            }

            FT.DoneFace(face);
        }

        private static FTLibrary Ft => FTProvider.Ft;
    }
}