using System.Runtime.CompilerServices;
using Microsoft.Win32.SafeHandles;

namespace PresenterConsole;

public sealed class ConsoleFastOutput : IDisposable
{
  private const uint GENERIC_WRITE = 0x40000000;
  private const int FILE_SHARE_WRITE = 0x2;

  private readonly SafeFileHandle _handle;
  private readonly WindowsFFI.Coord _bufferCoord = new(0, 0);

  private int _width;
  private int _height;
  private WindowsFFI.CharInfo[] _buffer;
  private WindowsFFI.Coord _bufferSize;


  public ConsoleFastOutput(int parWidth, int parHeight)
  {
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth);
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);

    _handle = WindowsFFI.CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
      IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

    if (_handle.IsInvalid)
      throw new InvalidOperationException("Failed to open console handle");

    Resize(parWidth, parHeight);
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  public void WriteChar(char parCharacter, int parX, int parY, ConsoleColor parForeground, ConsoleColor parBackground)
  {
    var index = parX + parY * _width;
    ref var charInfo = ref _buffer[index];
    charInfo.Char.UnicodeChar = parCharacter;
    charInfo.Attributes = (short)((ushort)parForeground | ((ushort)parBackground << 4));
  }

  public void Flush()
  {
    var writeRegion = new WindowsFFI.SmallRect { Left = 0, Top = 0, Right = (short)_width, Bottom = (short)_height };

    WindowsFFI.WriteConsoleOutput(_handle, _buffer, _bufferSize, _bufferCoord, ref writeRegion);
  }

  public void Resize(int parWidth, int parHeight)
  {
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth);
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);

    if (parWidth == _width && parHeight == _height)
      return;

    _width = parWidth;
    _height = parHeight;
    _buffer = new WindowsFFI.CharInfo[_width * _height];
    _bufferSize = new WindowsFFI.Coord((short)_width, (short)_height);
  }

  public void Dispose()
  {
    _handle.Dispose();
  }
}