using Engine.Graphics;
using Engine.Input;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;

namespace Engine;

public sealed class EngineBuilder
{
  private string _title = "";
  private bool _headless;
  private int _width = 1;
  private int _height = 1;
  private string _assetFolder = "./";

  private Func<Engine, IPresenter>? _presenterFunc;
  private Func<Engine, IInputHandler>? _inputHandlerFunc;

  // Logging
  private bool _logToConsole;
  private bool _logToFile;
  private string? _logFilePath;
  private LogEventLevel _logLevel = LogEventLevel.Information;

  public EngineBuilder Title(string parTitle)
  {
    _title = parTitle;
    return this;
  }

  public EngineBuilder Headless(bool parHeadless = true)
  {
    _headless = parHeadless;
    return this;
  }

  public EngineBuilder Width(int parWidth)
  {
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth);

    _width = parWidth;
    return this;
  }

  public EngineBuilder Height(int parHeight)
  {
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);

    _height = parHeight;
    return this;
  }

  public EngineBuilder AssetFolder(string parAssetFolder)
  {
    _assetFolder = parAssetFolder;
    return this;
  }

  public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
  {
    _presenterFunc = parPresenterFunc;
    return this;
  }

  public EngineBuilder InputHandler(IInputHandler parInputHandler)
  {
    _inputHandlerFunc = _ => parInputHandler;
    return this;
  }

  public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc)
  {
    _inputHandlerFunc = parInputHandlerFunc;
    return this;
  }

  public EngineBuilder LogToConsole(bool parLogToConsole = true)
  {
    _logToConsole = parLogToConsole;
    return this;
  }

  public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null)
  {
    if (parLogToFile && parLogFilePath == null)
      throw new ArgumentNullException(nameof(parLogFilePath));

    _logToFile = parLogToFile;
    _logFilePath = parLogFilePath;
    return this;
  }

  public EngineBuilder LogLevel(LogEventLevel parLogLevel)
  {
    _logLevel = parLogLevel;
    return this;
  }

  public Engine Build()
  {
    var logger = BuildLogger();
    var engine = new Engine(_width, _height, _headless, _title, _assetFolder, logger);

    var presenter = _presenterFunc?.Invoke(engine);
    if (presenter != null)
    {
      engine.Presenter = presenter;
    }

    var inputHandler = _inputHandlerFunc?.Invoke(engine);
    if (inputHandler != null)
    {
      engine.InputHandler = inputHandler;
    }

    return engine;
  }

  private Logger BuildLogger()
  {
    const string template =
      "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}";

    var loggerConfiguration = new LoggerConfiguration()
      .MinimumLevel.Is(_logLevel)
      .Enrich.WithThreadName()
      .Enrich.WithThreadId()
      .Enrich.FromLogContext();

    if (_logToConsole)
    {
      loggerConfiguration.WriteTo.Console(
        outputTemplate: template,
        theme: AnsiConsoleTheme.Literate
      );
    }

    if (_logToFile)
    {
      loggerConfiguration.WriteTo.File(
        _logFilePath!,
        outputTemplate: template
      );
    }

    return loggerConfiguration.CreateLogger();
  }
}