using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Dashboard.Drawing
{
    public class TextExtension : DrawExtension
    {
        public TextCommand TextCommand { get; }

        private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
        {
            TextCommand = new TextCommand(this);
        }

        public static readonly TextExtension Instance = new TextExtension();
    }

    public class TextCommand : IDrawCommand<TextCommandArgs>
    {
        public string Name { get; } = "Text";
        public IDrawExtension Extension { get; }
        public int Length { get; } = -1;

        public TextCommand(TextExtension ext)
        {
            Extension = ext;
        }

        public int WriteParams(DrawQueue queue, TextCommandArgs obj, Span<byte> param)
        {
            int size = Unsafe.SizeOf<Header>() + obj.Text.Length * sizeof(char) + sizeof(char);

            if (param.Length < size)
                return size;

            ref Header header = ref MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0];
            Span<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]);

            header = new Header()
            {
                Font = queue.RequireResource(obj.Font),
                TextBrush = queue.RequireResource(obj.TextBrush),
                BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
                BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,
                Anchor = obj.Anchor,
                Position = obj.Position,
                BorderKind = obj.BorderKind,
            };
            obj.Text.CopyTo(text);

            return size;
        }

        public TextCommandArgs GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
        {
            Header header = MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0];
            ReadOnlySpan<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]);

            if (header.BorderBrush != -1 && header.BorderRadius != 0)
            {
                return new TextCommandArgs(
                    (IFont)queue.Resources[header.Font],
                    (IBrush)queue.Resources[header.TextBrush],
                    header.Anchor,
                    header.Position,
                    text.ToString())
                {
                    BorderBrush = (IBrush)queue.Resources[header.BorderBrush],
                    BorderRadius = header.BorderRadius,
                    BorderKind = header.BorderKind,
                };
            }
            else
            {
                return new TextCommandArgs(
                    (IFont)queue.Resources[header.Font],
                    (IBrush)queue.Resources[header.TextBrush],
                    header.Anchor,
                    header.Position,
                    text.ToString());
            }
        }

        int IDrawCommand.WriteParams(DrawQueue queue, object? obj, Span<byte> param)
        {
            return WriteParams(queue, (TextCommandArgs)obj!, param);
        }

        object? IDrawCommand.GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
        {
            return GetParams(queue, param);
        }

        private struct Header
        {
            private int _flags;
            public int Font;
            public int TextBrush;
            public int BorderBrush;
            public Vector3 Position;
            public float BorderRadius;

            public Anchor Anchor
            {
                get => (Anchor)(_flags & 0xF);
                set => _flags = (_flags & ~0xF) | (int)value;
            }

            public BorderKind BorderKind
            {
                get => (_flags & INSET) switch
                {
                    OUTSET => BorderKind.Outset,
                    INSET => BorderKind.Inset,
                    _ => BorderKind.Center,
                };
                set => _flags = value switch
                {
                    BorderKind.Outset => (_flags & ~INSET) | OUTSET,
                    BorderKind.Inset => (_flags & ~INSET) | INSET,
                    _ => (_flags & ~INSET) | CENTER,
                };
            }

            private const int INSET  = 0x30;
            private const int CENTER = 0x00;
            private const int OUTSET = 0x10;
        }
    }

    public record struct TextCommandArgs(IFont Font, IBrush TextBrush, Anchor Anchor, Vector3 Position, string Text)
    {
        public IBrush? BorderBrush { get; init; } = null;
        public float BorderRadius { get; init; } = 0;

        public BorderKind BorderKind { get; init; } = BorderKind.Center;
    }
}