using System;
using System.IO;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityInjector.ConsoleUtil;

namespace BepInEx.Unix;

internal class LinuxConsoleDriver : IConsoleDriver
{
    private static readonly ConfigEntry<bool> ForceCustomTtyDriverConfig =
        ConfigFile.CoreConfig.Bind(
                                   "Logging.Console",
                                   "ForceBepInExTTYDriver",
                                   false,
                                   "If enabled, forces to use custom BepInEx TTY driver for handling terminal output on unix.");

    static LinuxConsoleDriver()
    {
        UseMonoTtyDriver = false;

        if (ForceCustomTtyDriverConfig.Value)
            return;

        var consoleDriverType = typeof(Console).Assembly.GetType("System.ConsoleDriver");

        if (consoleDriverType != null)
            UseMonoTtyDriver = typeof(Console).Assembly.GetType("System.ParameterizedStrings") != null;
    }

    public static bool UseMonoTtyDriver { get; }

    public bool StdoutRedirected { get; private set; }

    public TtyInfo TtyInfo { get; private set; }

    public TextWriter StandardOut { get; private set; }
    public TextWriter ConsoleOut { get; private set; }

    public bool ConsoleActive { get; private set; }
    public bool ConsoleIsExternal => false;

    public void PreventClose()
    {
        // Not supported by all distros
    }

    public void Initialize(bool alreadyActive, bool useManagedEncoder)
    {
        // Console is always considered active on Unix
        ConsoleActive = true;

        StdoutRedirected = UnixStreamHelper.isatty(1) != 1;

        var duplicateStream = UnixStreamHelper.CreateDuplicateStream(1);

        if (UseMonoTtyDriver && !StdoutRedirected)
        {
            // Mono implementation handles xterm for us

            var writer = ConsoleWriter.CreateConsoleStreamWriter(duplicateStream, Console.Out.Encoding, true);

            StandardOut = TextWriter.Synchronized(writer);

            var driver = AccessTools.Field(AccessTools.TypeByName("System.ConsoleDriver"), "driver").GetValue(null);
            AccessTools.Field(AccessTools.TypeByName("System.TermInfoDriver"), "stdout").SetValue(driver, writer);
        }
        else
        {
            // Handle TTY ourselves

            var writer = new StreamWriter(duplicateStream, Console.Out.Encoding);

            writer.AutoFlush = true;

            StandardOut = TextWriter.Synchronized(writer);

            TtyInfo = TtyHandler.GetTtyInfo();
        }

        ConsoleOut = StandardOut;
    }

    public void CreateConsole(uint codepage) =>
        Logger.Log(LogLevel.Warning, "An external console currently cannot be spawned on a Unix platform.");

    public void DetachConsole() =>
        throw new PlatformNotSupportedException("Cannot detach console on a Unix platform");

    public void SetConsoleColor(ConsoleColor color)
    {
        if (StdoutRedirected)
            return;

        if (UseMonoTtyDriver)
            // Use mono's inbuilt terminfo driver to set the foreground color for us
            SafeConsole.ForegroundColor = color;
        else
            ConsoleOut.Write(TtyInfo.GetAnsiCode(color));
    }

    public void SetConsoleTitle(string title)
    {
        if (StdoutRedirected)
            return;

        if (UseMonoTtyDriver && SafeConsole.TitleExists)
            SafeConsole.Title = title;
        else
            ConsoleOut.Write($"\u001B]2;{title.Replace("\\", "\\\\")}\u0007");
    }
}
