using System.Diagnostics.CodeAnalysis;
using System.IO.Ports;
using System.Reactive.Linq;
using CliWrap;
using InnovEnergy.Lib.Utils;

namespace InnovEnergy.Lib.Protocols.Modbus.Channels;

public record RemoteSerialConnection
    (
        Func<Int32, IReadOnlyList<Byte>> Read,
        Action<IReadOnlyList<Byte>> Write,
        Action Close
    );


// public class RemoteSerialChannel : ConnectionChannel<RemoteSerialConnection>
// {
//     private readonly Command _Command;
//
//     public RemoteSerialChannel(SshHost host, 
//                                 String tty, 
//                                  Int32 baudRate, 
//                                 Parity parity,
//                                  Int32 stopBits,
//                                  Int32 dataBits)
//     {
//         tty = tty.EnsureStartsWith("/dev/");
//         
//         var configureTty    = ConfigureTty(tty, baudRate, parity, stopBits, dataBits);
//         var redirectStreams = RedirectStreams(tty);
//
//         var call = $"{configureTty}; {redirectStreams}";
//                    
//         _Command = host
//                   .Command
//                   .AppendArgument(call);
//         
//         _Command.WriteLine();
//     }
//
//
//     protected override RemoteSerialConnection Open()
//     {
//         var observableProcess = new ObservableProcess(_Command);
//
//         observableProcess.Start();
//
//
//         IReadOnlyList<Byte> Read(Int32 i)
//         {
//             return observableProcess.Read(i).Result;
//         }
//
//         void Write(IReadOnlyList<Byte> data) => observableProcess.StdIn.OnNext(data.ToArray());
//
//         return new RemoteSerialConnection(Read, Write, observableProcess.Interrupt);
//     }
//
//     protected override void Close(RemoteSerialConnection connection)
//     {
//         connection.Close();
//     }
//
//     protected override IReadOnlyList<Byte> Read(RemoteSerialConnection connection, Int32 nBytes)
//     {
//         return connection.Read(nBytes);
//     }
//
//     protected override void Write(RemoteSerialConnection connection, IReadOnlyList<Byte> data)
//     {
//         connection.Write(data);
//     }
//
//
//     private static String RedirectStreams(String tty)
//     {
//         // https://unix.stackexchange.com/questions/19604/all-about-ssh-proxycommand
//         
//         return $"exec 3<>{tty}; " +
//                $"cat <&3 & cat >&3; " +
// //               $"(cat <&3 | tee -a ~/read) & cat | tee -a ~/write >&3; " +
//                $"kill $!";
//         
//         // var call = $"trap 'kill -HUP $(jobs -lp) 2>/dev/null || true' EXIT; " +
//         //            $"{configure} ; "+
//         //            $"dd if={tty} of=/dev/stdout bs=1 & " +
//         //            $"dd if=/dev/stdin of={tty} bs=1 ;"
//     }
//
//     [SuppressMessage("ReSharper", "StringLiteralTypo")]
//     private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits)
//     {
//         var oParity = parity switch
//         {
//             Parity.Even => "parenb -parodd",
//             Parity.Odd  => "parenb parodd",
//             Parity.None => "-parenb",
//             _           => throw new NotImplementedException()
//         };
//
//         var oStopBits = stopBits switch
//         {
//             1 => "-cstopb",
//             2 => "cstopb",
//             _ => throw new NotImplementedException()
//         };
//
//         var oDataBits = "cs" + dataBits;
//
//         return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}";
//     }
//     
// }

public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
{
    private readonly Command _Command;
    private readonly TcpChannel _TcpChannel;
    
    const String SsDir = "/opt/victronenergy/serial-starter";
    const String KillTasks = "kill $!";
    
    private CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource();
    
    private CommandTask<CommandResult>? _CommandTask;

    public RemoteSerialChannel(SshHost host,
                                String tty,
                                 Int32 baudRate,
                                Parity parity,
                                 Int32 dataBits,
                                 Int32 stopBits) 
    {
        const Int32 port = 6855;
        
        tty = tty.EnsureStartsWith("/dev/");
    
        var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits);
        
        var stopTty  = $"{SsDir}/stop-tty.sh {tty}";
        var startTty = $"{SsDir}/start-tty.sh {tty}";
        
        // ReSharper disable once StringLiteralTypo
        var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw";

        //var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'";
        //var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
        
        //var script = $"{configureTty} && {socat} ; {KillTasks}";
        var script = $"{configureTty} && {socat}";
        
        _Command = host.Command.AppendArgument(script);

        _Command.WriteLine();

        _TcpChannel = new TcpChannel(host.HostName, port);
    }

    private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits)
    {
        var oParity = parity switch
        {
            Parity.Even => "parenb -parodd",
            Parity.Odd  => "parenb parodd",
            Parity.None => "-parenb",
            _           => throw new NotImplementedException()
        };

        var oStopBits = stopBits switch
        {
            1 => "-cstopb",
            2 => "cstopb",
            _ => throw new NotImplementedException()
        };

        var oDataBits = "cs" + dataBits;

        return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}";
    }


    protected override TcpChannel Open()
    {
        //_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token);
        
        //Thread.Sleep(2000); // wait until socat is ready
        return _TcpChannel;
    }

    protected override void Close(TcpChannel connection)
    {
        _CancellationTokenSource.Cancel();
        connection.Dispose();
        
        _CommandTask = null;

        _CancellationTokenSource = new CancellationTokenSource();
    }

    protected override IReadOnlyList<Byte> Read(TcpChannel connection, Int32 nBytes)
    {
        return connection.Read(nBytes);
    }

    protected override void Write(TcpChannel connection, IReadOnlyList<Byte> data)
    {
        connection.Write(data);
    }
}