using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using NodePipeline.Abstractions;
using NodePipeline.Application;
using NodePipeline.Application.Nodes;
using NodePipeline.ConsoleApp;
using NodePipeline.Engine.Abstractions;
using NodePipeline.Engine.DependencyInjection;
using NodePipeline.Engine.Execution;
using NodePipeline.Engine.Generated;

NodeNamePrefixSettings.SetNodePrefixes(["global::NodePipeline.Application."]);


var fileImageProvider = new FileImageProvider();
var nodesFactory = new GeneratedNodeFactory();
var nodesValidator = new GeneratedNodeValidator();
var emptyValidationLocalizationProvider = new PipelineLocalizationProvider();
nodesValidator.NodeFieldValidators.Add(typeof(ExtensionValidator),
    new ExtensionValidator(emptyValidationLocalizationProvider));
nodesValidator.NodeValidators.Add(typeof(FileImageSourceNodeValidator),
    new FileImageSourceNodeValidator(emptyValidationLocalizationProvider));
nodesFactory.NodeFactories.Add(nameof(FileImageSource2Node)[..^4], () => new FileImageSource2Node(fileImageProvider));


var cultureInfo = CultureInfo.CurrentCulture;
var pipelineRegistry = new PipelineRegistry(cultureInfo);

var pipelineRegistrator =
    new PipelineRegistrator(pipelineRegistry, nodesFactory, nodesValidator, emptyValidationLocalizationProvider);
var pipelineRegistrationStopwatch1 = Stopwatch.StartNew();
var pipelineId1 = pipelineRegistrator.RegisterPipeline("p1.yaml");
pipelineRegistrationStopwatch1.Stop();
Console.WriteLine($"Pipeline registration: {pipelineRegistrationStopwatch1.ElapsedMilliseconds}ms");

if (!string.IsNullOrWhiteSpace(pipelineId1))
{
    RunPipeline1(pipelineRegistry, nodesFactory, pipelineId1, 1);
    RunPipeline1(pipelineRegistry, nodesFactory, pipelineId1, 2);
    RunPipeline1(pipelineRegistry, nodesFactory, pipelineId1, 3);
}

Console.WriteLine("--- Di --- ");
var services = new ServiceCollection();
//TODO: add custom IPipelineLocalizationProvider
services.AddPipeline()
    .AddPipelineNodeValidator<FileImageSourceNodeValidator>()
    .AddPipelineNodeFieldValidator<ExtensionValidator>()
    .AddPipelineProvider<IFileImageProvider, FileImageProvider>()
    .AddPipelineProvider<IFileNameProvider, FileNameProvider>()
    .AddPipelineNode<FileImageSource2Node>()
    .AddPipelineNode<SaveImage2Node>()
    ;

services.AddScoped<PipelineRegistrator>(); //TODO: remove this


var serviceProvider = services.BuildServiceProvider();
var diPipelineRegistrationStopwatch1 = Stopwatch.StartNew();
var diPipelineRegistrator = serviceProvider.GetRequiredService<PipelineRegistrator>();
var diPipelineId1 = diPipelineRegistrator.RegisterPipeline("p1.yaml");
diPipelineRegistrationStopwatch1.Stop();
var diPipelineId2 = diPipelineRegistrator.RegisterPipeline("p2.yaml");
Console.WriteLine($"DI Pipeline registration: {diPipelineRegistrationStopwatch1.ElapsedMilliseconds}ms");
if (!string.IsNullOrWhiteSpace(diPipelineId1))
{
    await RunPipeline1Async(serviceProvider, diPipelineId1, 1);
    await RunPipeline1Async(serviceProvider, diPipelineId1, 2);
    await RunPipeline1Async(serviceProvider, diPipelineId1, 3);
}

Console.WriteLine("pipeline with provider");
var imagesDirectory = Path.Combine(Environment.CurrentDirectory, "images");
var resultDirectory = Path.Combine(Environment.CurrentDirectory, "result");
if (!string.IsNullOrWhiteSpace(diPipelineId2))
{
    await RunPipeline2Async(serviceProvider, Path.Combine(imagesDirectory, "im1.jpg"),
        Path.Combine(resultDirectory, "r-im1.jpg"), diPipelineId2, 1);
    await RunPipeline2Async(serviceProvider, Path.Combine(imagesDirectory, "im2.jpg"),
        Path.Combine(resultDirectory, "r-im2.jpg"), diPipelineId2, 2);
    await RunPipeline2Async(serviceProvider, Path.Combine(imagesDirectory, "im3.jpg"),
        Path.Combine(resultDirectory, "r-im3.jpg"), diPipelineId2, 3);
}

return;

//---

double GetStopwatchElapsedMicroseconds(long ticks)
{
    return ticks * 1_000_000.0 / Stopwatch.Frequency;
}

void RunPipeline1(PipelineRegistry reg, INodeFactory factory, string id, int number)
{
    var sw1 = Stopwatch.StartNew();
    var sw2 = Stopwatch.StartNew();
    var p = reg.Build(factory, id);
    sw2.Stop();

    var sw3 = Stopwatch.StartNew();
    p.Execute();
    sw3.Stop();
    sw1.Stop();
    Console.WriteLine(
        $"Pipeline [{number}]:\r\nBuild: {GetStopwatchElapsedMicroseconds(sw2.ElapsedTicks)}us\r\nExecute: {sw3.ElapsedMilliseconds}ms\r\nTotal: {sw1.ElapsedMilliseconds}ms\r\n\r\n");
}

async Task RunPipeline1Async(IServiceProvider sp, string id, int number)
{
    var sw1 = Stopwatch.StartNew();
    await using var scope = sp.CreateAsyncScope();
    var registry = scope.ServiceProvider.GetRequiredService<PipelineRegistry>();
    var factory = scope.ServiceProvider.GetRequiredService<INodeFactory>();
    var sw2 = Stopwatch.StartNew();
    var diPipeline = registry.Build(factory, id);
    sw2.Stop();
    var sw3 = Stopwatch.StartNew();
    diPipeline.Execute();
    sw3.Stop();
    sw1.Stop();
    Console.WriteLine(
        $"DI Pipeline 1 [{number}]:\r\nBuild: {GetStopwatchElapsedMicroseconds(sw2.ElapsedTicks)}us\r\nExecute: {sw3.ElapsedMilliseconds}ms\r\nTotal: {sw1.ElapsedMilliseconds}ms\r\n\r\n");
}

async Task RunPipeline2Async(IServiceProvider sp, string sourceImagePath, string resultImagePath, string id, int number)
{
    var sw1 = Stopwatch.StartNew();
    await using var scope = sp.CreateAsyncScope();
    var registry = scope.ServiceProvider.GetRequiredService<PipelineRegistry>();
    var factory = scope.ServiceProvider.GetRequiredService<INodeFactory>();
    var p1 = scope.ServiceProvider.GetRequiredService<FileImageProvider>();
    p1.AddNext(sourceImagePath);
    var p2 = scope.ServiceProvider.GetRequiredService<FileNameProvider>();
    p2.AddNext(resultImagePath);
    var sw2 = Stopwatch.StartNew();
    var diPipeline = registry.Build(factory, id);
    sw2.Stop();
    var sw3 = Stopwatch.StartNew();
    diPipeline.Execute();
    sw3.Stop();
    sw1.Stop();
    Console.WriteLine(
        $"DI Pipeline 2 [{number}]:\r\nBuild: {GetStopwatchElapsedMicroseconds(sw2.ElapsedTicks)}us\r\nExecute: {sw3.ElapsedMilliseconds}ms\r\nTotal: {sw1.ElapsedMilliseconds}ms\r\n\r\n");
}