using System.Runtime.InteropServices;
using ImageCatalog;
using ImageCatalog_2.Services;
using MaddoShared;
using Microsoft.Extensions.DependencyInjection;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using System.IO;
using Microsoft.Extensions.Options;

namespace ImageCatalog_2;

static class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AllocConsole();

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetStdHandle(int nStdHandle);

    private const int STD_OUTPUT_HANDLE = -11;
    private const int STD_ERROR_HANDLE = -12;

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool SetStdHandle(int nStdHandle, IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetConsoleWindow();

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AttachConsole(int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr CreateFile(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    private const uint GENERIC_WRITE = 0x40000000;
    private const uint OPEN_EXISTING = 3;

    private static void RedirectConsoleOutput()
    {
        var stdOutHandle = CreateFile("CONOUT$", GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
        var safeFileHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(stdOutHandle, true);
        var fileStream = new FileStream(safeFileHandle, FileAccess.Write);
        var standardOutput = new StreamWriter(fileStream) { AutoFlush = true };
        Console.SetOut(standardOutput);
        Console.SetError(standardOutput);
    }

    public static IServiceProvider ServiceProvider { get; private set; }
    [STAThread]
    static void Main()
    {
        Application.SetHighDpiMode(HighDpiMode.SystemAware);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        AllocConsole();
        RedirectConsoleOutput();

        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);

        ServiceProvider = serviceCollection.BuildServiceProvider();

        // Resolve WPF MainWindow when available, otherwise fall back to WinForms MainForm
        var serviceProvider = ServiceProvider;

        // NOTE: By default the app will attempt to run the WPF window when available and fall back
        // to the WinForms MainForm. If you want to force the WinForms UI for testing, uncomment
        // the block below and comment out the WPF branch.
        // -----------------------------------------------------------------------------
        // // Force WinForms UI (uncomment to enable)
        //var mainForm = serviceProvider.GetRequiredService<MainForm>();
        //// If you want to set the DataModel explicitly on the WinForms form use the lines below
        //// var mainViewModel = serviceProvider.GetRequiredService<DataModel>();
        //// mainForm.Model = mainViewModel;
        //Application.Run(mainForm);
        // -----------------------------------------------------------------------------

        if (serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) is ImageCatalog_2.MainWindow wpfMain)
        {
            // Start WPF app
            var app = new System.Windows.Application();
            app.Run(wpfMain);
        }
        else
        {
            var mainForm = serviceProvider.GetRequiredService<MainForm>();
            Application.Run(mainForm);
        }
    }

    private static void ConfigureServices(ServiceCollection services)
    {
        // Register AutoMapper (new AddAutoMapper overload — provide config and marker types)
        services.AddAutoMapper(cfg => { }, typeof(Program));

        // Register your services here
        services.AddTransient<ITestService, TestService>();
        services.AddTransient<ISettingsService, SettingsService>();

        services.AddTransient<DataModel>(sp =>
        {
            // Resolve optional version provider and pass to DataModel
            var testService = sp.GetRequiredService<ITestService>();
            var settingsService = sp.GetRequiredService<ISettingsService>();
            var imageCreation = sp.GetRequiredService<ImageCreationStuff>();
            var picSettings = sp.GetRequiredService<PicSettings>();
            var mapper = sp.GetRequiredService<IMapper>();
            var logger = sp.GetRequiredService<ILogger<DataModel>>();
            var versionProvider = sp.GetService<MaddoShared.IVersionProvider>();

            return new DataModel(testService, settingsService, imageCreation, picSettings, mapper, logger, versionProvider);
        });

        services.AddTransient<ImageCreationStuff>();
        services.AddTransient<ImageCreatorSharp>();
        services.AddTransient<ImageCreatorAlternate>();
        services.AddTransient<ImageCreatorMapper>();

        // Register IImageCreator to be resolved via ImageCreatorMapper which selects concrete implementation at call time
        services.AddTransient<IImageCreator>(sp => sp.GetRequiredService<ImageCreatorMapper>());

        // Register a ParametriSetup singleton that persists user preferences in LocalApplicationData
        var userPrefsPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "ImageCatalog", "userprefs.xml");
        services.AddSingleton(new ParametriSetup(userPrefsPath));
        services.AddSingleton<PicSettings>();

        // Register your forms
        services.AddTransient<MainForm>();
        // Register WPF MainWindow so it can be resolved with the existing DataModel
        services.AddTransient<ImageCatalog_2.MainWindow>();

        // Version provider for UI and logging
        services.AddSingleton<MaddoShared.IVersionProvider, MaddoShared.VersionProvider>();

        services.AddLogging(configure =>
        {
            configure.AddCustomFormatter();
            configure.AddConsole();
            configure.SetMinimumLevel(LogLevel.Debug);
        });
    }
}

public static class ConsoleLoggerExtensions
{
    public static ILoggingBuilder AddCustomFormatter(
        this ILoggingBuilder builder) =>
        builder
        .AddConsole(options => options.FormatterName = nameof(CustomLoggingFormatter))
        .AddConsoleFormatter<CustomLoggingFormatter, ConsoleFormatterOptions>()
        .AddFilter("LuckyPennySoftware.AutoMapper.License", LogLevel.None);
}
public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable
{
    private readonly IDisposable _optionsReloadToken;
    private ConsoleFormatterOptions _formatterOptions;
    public CustomLoggingFormatter(IOptionsMonitor<ConsoleFormatterOptions> options)
        // Case insensitive  
        : base(nameof(CustomLoggingFormatter)) =>
        (_optionsReloadToken, _formatterOptions) =
        (options.OnChange(ReloadLoggerOptions), options.CurrentValue);
    private void ReloadLoggerOptions(ConsoleFormatterOptions options) =>
        _formatterOptions = options;

    public override void Write<TState>(
        in LogEntry<TState> logEntry,
        IExternalScopeProvider? scopeProvider,
        TextWriter? textWriter)
    {
        string? message =
            logEntry.Formatter?.Invoke(
                logEntry.State, logEntry.Exception);

        if (message is null)
        {
            return;
        }

        textWriter.WriteLine($"{message}");
    }
    public void Dispose() => _optionsReloadToken?.Dispose();
}