using System;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;

namespace Robust.Client.Profiling;

public sealed partial class LiveProfileViewControl : Control
{
    [Dependency] private ProfManager _profManager = default!;
    [Dependency] private IResourceCache _resourceCache = default!;

    public int MaxDepth { get; set; } = 2;

    private readonly Font? _font;
    private readonly char[] _sampleBuffer = new char[32];

    public LiveProfileViewControl()
    {
        IoCManager.InjectDependencies(this);

        if (!_resourceCache.TryGetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf", out var font))
            return;

        _font = font.MakeDefault();
    }

    protected internal override void Draw(DrawingHandleScreen handle)
    {
        base.Draw(handle);

        if (!_profManager.IsEnabled || _font == null)
            return;

        var baseLine = new Vector2(0, _font.GetAscent(UIScale));

        ref readonly var buffer = ref _profManager.Buffer;
        ref readonly var index = ref buffer.Index(buffer.IndexWriteOffset - 1);

        DrawData drawData = default;
        drawData.Font = _font;
        drawData.Buffer = buffer;
        drawData.Index = index;
        drawData.Handle = handle;

        var i = index.StartPos;

        DrawCmds(in drawData, ref i, ref baseLine, 0);
    }

    private void DrawCmds(
        in DrawData data,
        ref long i,
        ref Vector2 baseline,
        int depth)
    {
        for (; i < data.Index.EndPos; i++)
        {
            ref var cmd = ref data.Buffer.Log(i);
            DrawCmd(in data, ref i, ref baseline, depth, in cmd);
        }
    }

    private void DrawCmdSample(
        in DrawData data,
        ref Vector2 baseline,
        in ProfLogValue value)
    {
        DrawSample(in data, baseline, value.StringId, value.Value);

        baseline += new Vector2(0, data.Font.GetLineHeight(UIScale));
    }

    private void DrawSample(
        in DrawData data,
        Vector2 baseline,
        int stringId,
        in ProfValue value)
    {
        var cmdString = _profManager.GetString(stringId);
        baseline += data.Handle.DrawString(data.Font, baseline, cmdString, UIScale, Color.White);
        baseline += data.Handle.DrawString(data.Font, baseline, ": ", UIScale, Color.White);

        var str = value.Type switch
        {
            ProfValueType.TimeAllocSample =>
                FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.TimeAllocSample.Time * 1000:N2} ms, {value.TimeAllocSample.Alloc} B"),
            ProfValueType.Int32 => FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.Int32}"),
            ProfValueType.Int64 => FormatHelpers.FormatIntoMem(_sampleBuffer, $"{value.Int64}"),
            _ => "???".AsMemory()
        };

        data.Handle.DrawString(data.Font, baseline, str.Span, UIScale, Color.White);
    }

    private void DrawCmd(
        in DrawData data,
        ref long i,
        ref Vector2 baseline,
        int depth,
        in ProfLog log)
    {
        switch (log.Type)
        {
            case ProfLogType.Value:
                DrawCmdSample(in data, ref baseline, in log.Value);
                break;
            case ProfLogType.GroupStart:
                DrawEnterGroup(in data, ref i, ref baseline, depth);
                break;
        }
    }

    private void DrawEnterGroup(
        in DrawData data,
        ref long i,
        ref Vector2 baseline,
        int depth)
    {
        depth += 1;

        var indentSize = 12 * UIScale;

        var startBaseline = baseline;
        baseline += new Vector2(indentSize, data.Font.GetLineHeight(UIScale));

        if (depth > MaxDepth)
        {
            var startIdx = i;

            // Skip contents of this group.
            for (; i < data.Index.EndPos; i++)
            {
                ref var cmd = ref data.Buffer.Log(i);
                if (cmd.Type != ProfLogType.GroupEnd)
                    continue;

                if (cmd.GroupEnd.StartIndex <= startIdx)
                    break;
            }
        }
        else
        {
            i += 1;

            for (; i < data.Index.EndPos; i++)
            {
                ref var cmd = ref data.Buffer.Log(i);
                if (cmd.Type == ProfLogType.GroupEnd)
                    break;

                DrawCmd(in data, ref i, ref baseline, depth, in cmd);
            }
        }

        // Gone through entire list = unmatched begin/end pair
        if (i == data.Index.EndPos)
            return;

        ref var cmdEnd = ref data.Buffer.Log(i).GroupEnd;

        baseline -= new Vector2(indentSize, 0);

        DrawSample(in data, startBaseline, cmdEnd.StringId, cmdEnd.Value);
    }

    private struct DrawData
    {
        public ProfBuffer Buffer;
        public ProfIndex Index;
        public Font Font;
        public DrawingHandleScreen Handle;
    }
}
