using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Abstractions.Models;

namespace NodePipeline.Engine.NodeFields;

public static class PortConnector
{
    /// <summary>
    ///     Connects node input and node output
    /// </summary>
    /// <param name="from">
    ///     Node output <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Output" />
    /// </param>
    /// <param name="to">
    ///     Node input <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Input" />
    /// </param>
    /// <typeparam name="T"></typeparam>
    public static void ConnectStatic<T>(INodeField<T> from, INodeField<T> to)
    {
        to.BindStatic(from);
    }

    /// <summary>
    ///     Connects node input and node output
    /// </summary>
    /// <param name="from">
    ///     Node output <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Output" />
    /// </param>
    /// <param name="to">
    ///     Node input <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Input" />
    /// </param>
    /// <typeparam name="T"></typeparam>
    public static void ConnectStatic<T>(NodeField<T> from, NodeField<T> to)
    {
        to.BindStatic(from);
    }

    /// <summary>
    ///     Connects node input and node output
    /// </summary>
    /// <param name="from">
    ///     Node output <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Output" />
    /// </param>
    /// <param name="to">
    ///     Node input <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Input" />
    /// </param>
    /// <typeparam name="T"></typeparam>
    public static void ConnectStatic<T>(INodeField<T> from, NodeField<T> to)
    {
        to.BindStatic(from);
    }

    /// <summary>
    ///     Connects node input and node output
    /// </summary>
    /// <param name="from">
    ///     Node output <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Output" />
    /// </param>
    /// <param name="to">
    ///     Node input <see cref="NodeField{T}" /> with direction <see cref="FieldDirection" /> =
    ///     <see cref="FieldDirection.Input" />
    /// </param>
    /// <typeparam name="T"></typeparam>
    public static void ConnectStatic<T>(NodeField<T> from, INodeField<T> to)
    {
        to.BindStatic(from);
    }

    public static void ConnectRuntime<T>(INodeField<T> from, INodeField<T> to)
        where T : notnull
    {
        to.BindRuntime(() => from.Value, v => from.Value = v);
    }

    public static void ConnectRuntimeBothNullable<T>(INodeField<T?> from, INodeField<T?> to)
        where T : struct
    {
        to.BindRuntime(() => from.Value, v => from.Value = v);
    }

    public static void ConnectRuntimeBothNullable<T>(INodeField<T?> from, INodeField<T?> to)
        where T : class
    {
        to.BindRuntime(() => from.Value, v => from.Value = v);
    }

    public static void ConnectRuntimeToNullableStruct<T>(INodeField<T> from, INodeField<T?> to)
        where T : struct
    {
        to.BindRuntime(() => from.Value, v => to.Value = v);
    }

    public static void ConnectRuntimeToNullableClass<T>(INodeField<T> from, INodeField<T?> to)
        where T : class
    {
        to.BindRuntime(() => from.Value, v => to.Value = v);
    }

    public static void ConnectRuntimeFromNullableStruct<T>(INodeField<T?> from, INodeField<T> to,
        string currentNodeId, string inputPortName, string sourceNodeId, string outputPortName)
        where T : struct
    {
        to.BindRuntime(
            () => GetValueFromNullableStruct(from.Value, currentNodeId, inputPortName, sourceNodeId, outputPortName),
            v => to.Value = v);
    }

    public static void ConnectRuntimeFromNullableClass<T>(INodeField<T?> from, INodeField<T> to,
        string currentNodeId, string inputPortName, string sourceNodeId, string outputPortName)
        where T : class
    {
        to.BindRuntime(
            () => GetValueFromNullableClass(from.Value, currentNodeId, inputPortName, sourceNodeId, outputPortName),
            v => to.Value = v);
    }

    private static T GetValueFromNullableStruct<T>(T? value, string currentNodeId, string inputPortName,
        string sourceNodeId, string outputPortName)
        where T : struct
    {
        if (value is not null) return (T)value;
        var message =
            string.Concat("Input port [", inputPortName, "] of node [", currentNodeId,
                "] does not allow a null value received from output port [", outputPortName, "] of [", sourceNodeId,
                "] node");
        throw new Exception(message); //TODO: add new exception
    }

    private static T GetValueFromNullableClass<T>(T? value, string currentNodeId, string inputPortName,
        string sourceNodeId, string outputPortName)
        where T : class
    {
        if (value is not null) return value;
        var message =
            string.Concat("Input port [", inputPortName, "] of node [", currentNodeId,
                "] does not allow a null value received from output port [", outputPortName, "] of [", sourceNodeId,
                "] node");
        throw new Exception(message); //TODO: add new exception
    }
}