using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace Ryujinx.Graphics.OpenGL
{
    public static class Debugger
    {
        private static DebugProc _debugCallback;

        private static int _counter;

        public static void Initialize(GraphicsDebugLevel logLevel)
        {
            // Disable everything
            GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, false);

            if (logLevel == GraphicsDebugLevel.None)
            {
                GL.Disable(EnableCap.DebugOutputSynchronous);
                GL.DebugMessageCallback(null, IntPtr.Zero);

                return;
            }

            GL.Enable(EnableCap.DebugOutputSynchronous);

            if (logLevel == GraphicsDebugLevel.Error)
            {
                GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true);
            }
            else if (logLevel == GraphicsDebugLevel.Slowdowns)
            {
                GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true);
                GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypePerformance, DebugSeverityControl.DontCare, 0, (int[])null, true);
            }
            else
            {
                GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, true);
            }

            _counter = 0;
            _debugCallback = GLDebugHandler;

            GL.DebugMessageCallback(_debugCallback, IntPtr.Zero);

            Logger.Warning?.Print(LogClass.Gpu, "OpenGL Debugging is enabled. Performance will be negatively impacted.");
        }

        private static void GLDebugHandler(
            DebugSource source,
            DebugType type,
            int id,
            DebugSeverity severity,
            int length,
            IntPtr message,
            IntPtr userParam)
        {
            string msg = Marshal.PtrToStringUTF8(message).Replace('\n', ' ');

            switch (type)
            {
                case DebugType.DebugTypeError:
                    Logger.Error?.Print(LogClass.Gpu, $"{severity}: {msg}\nCallStack={Environment.StackTrace}", "GLERROR");
                    break;
                case DebugType.DebugTypePerformance:
                    Logger.Warning?.Print(LogClass.Gpu, $"{severity}: {msg}", "GLPERF");
                    break;
                case DebugType.DebugTypePushGroup:
                    Logger.Info?.Print(LogClass.Gpu, $"{{ ({id}) {severity}: {msg}", "GLINFO");
                    break;
                case DebugType.DebugTypePopGroup:
                    Logger.Info?.Print(LogClass.Gpu, $"}} ({id}) {severity}: {msg}", "GLINFO");
                    break;
                default:
                    if (source == DebugSource.DebugSourceApplication)
                    {
                        Logger.Info?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLINFO");
                    }
                    else
                    {
                        Logger.Debug?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLDEBUG");
                    }
                    break;
            }
        }

        // Useful debug helpers
        public static void PushGroup(string dbgMsg)
        {
            int counter = Interlocked.Increment(ref _counter);

            GL.PushDebugGroup(DebugSourceExternal.DebugSourceApplication, counter, dbgMsg.Length, dbgMsg);
        }

        public static void PopGroup()
        {
            GL.PopDebugGroup();
        }

        public static void Print(string dbgMsg, DebugType type = DebugType.DebugTypeMarker, DebugSeverity severity = DebugSeverity.DebugSeverityNotification, int id = 999999)
        {
            GL.DebugMessageInsert(DebugSourceExternal.DebugSourceApplication, type, id, severity, dbgMsg.Length, dbgMsg);
        }
    }
}
