using System.Text;
using Engine.Asset;
using Engine.Graphics;
using Engine.Graphics.Buffer;
using Engine.Graphics.Framebuffer;
using Engine.Graphics.Texture;
using Engine.Resource;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using ShaderResource = PresenterConsole.Resource.ShaderResource;

namespace PresenterConsole;

public class ConsolePresenter : IPresenter
{
  public bool IsExiting { get; private set; }
  public int Width { get; private set; } = 2;
  public int Height { get; private set; } = 1;
  public event Action<ResizeEventArgs>? Resize;

  private readonly Engine.Engine _engine;

  private readonly Framebuffer _framebuffer;
  private readonly Engine.Graphics.Shader.Program _asciiProgram;
  private Image<AsciiPixel>? _asciiImage;

  private readonly IndexBuffer _indexBuffer;
  private readonly VertexBuffer<AsciiVertex> _vertexBuffer;
  private readonly VertexArray _vertexArray;

  private readonly ConsoleFastOutput _consoleOutput;
  private static readonly char[] LIGHTMAP = " .,:;=*#%@".Reverse().ToArray();

  public ConsolePresenter(Engine.Engine parEngine)
  {
    _engine = parEngine;
    ((MemoryResourceStreamProvider)_engine.EngineResourceManager.StreamProvider).AddResource("shader/ascii",
      Encoding.UTF8.GetBytes(ShaderResource.Ascii));

    _asciiProgram = _engine.EngineResourceManager.Load<Engine.Graphics.Shader.Program>("shader/ascii");

    _framebuffer = Framebuffer.Builder(Width / 2, Height)
      .AddColorAttachment<AsciiPixel>()
      .Build();

    _indexBuffer = new IndexBuffer([0, 1, 2, 2, 3, 0]);
    _vertexBuffer = new VertexBuffer<AsciiVertex>([
      new AsciiVertex { _position = new Vector2(-1.0f, -1.0f), _uv = new Vector2(0.0f, 0.0f) },
      new AsciiVertex { _position = new Vector2(1.0f, -1.0f), _uv = new Vector2(1.0f, 0.0f) },
      new AsciiVertex { _position = new Vector2(1.0f, 1.0f), _uv = new Vector2(1.0f, 1.0f) },
      new AsciiVertex { _position = new Vector2(-1.0f, 1.0f), _uv = new Vector2(0.0f, 1.0f) }
    ]);
    _vertexArray = new VertexArray();
    _vertexArray.BindIndexBuffer(_indexBuffer);
    _vertexArray.BindVertexBuffer(_vertexBuffer);

    Console.CursorVisible = false;
    _consoleOutput = new ConsoleFastOutput(Width, Height);
  }

  public void Present(IConstTexture parTexture)
  {
    var openglTexture = (Texture)parTexture;

    // GL.Viewport(0, 0, Width / 2, Height);

    _framebuffer.Bind();

    openglTexture.BindUnit();

    _asciiProgram.Bind();
    _asciiProgram.SetUniform("uInputTexture", 0);

    _vertexArray.Bind();
    GL.DrawElements(PrimitiveType.Triangles, _indexBuffer.Count, DrawElementsType.UnsignedInt, 0);

    _framebuffer.Unbind();

    // GL.Viewport(0, 0, Width / 2 * 4, Height * 4);

    var asciiTexture = _framebuffer.TextureInternal;
    if (asciiTexture == null)
      throw new InvalidOperationException("Framebuffer texture is null");

    if (_asciiImage == null || asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
      _asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);

    asciiTexture.ReadPixels(_asciiImage);
    DrawImage(_asciiImage);
  }

  private void DrawImage(Image<AsciiPixel> parImage)
  {
    for (var y = 0; y < parImage.Height; y++)
    {
      for (var x = 0; x < parImage.Width; x++)
      {
        var pixel = parImage[y, x];
        var lightnessIndex = (byte)(pixel.Luminance / 255.0f * (LIGHTMAP.Length - 1));
        var colorIndex = (ConsoleColor)(pixel.Color / 255.0f * 15.0f);
        _consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x, y, 0, colorIndex);
        _consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x + 1, y, 0, colorIndex);
      }
    }

    _consoleOutput.Flush();
  }

  public void Update(double parDeltaTime)
  {
  }

  public void Render()
  {
    var consoleWidth = Console.WindowWidth;
    var consoleHeight = Console.WindowHeight;
    if (Width != consoleWidth || Height != consoleHeight)
    {
      Width = consoleWidth;
      Height = consoleHeight;

      Resize?.Invoke(new ResizeEventArgs(Width / 2 * 4, Height * 4));

      _framebuffer.Resize(Width / 2, Height);
      _consoleOutput.Resize(Width, Height);
    }
  }

  public void Exit()
  {
    IsExiting = true;
  }
}