using CliWrap;
using InnovEnergy.Lib.Utils;

namespace InnovEnergy.Lib.Channels.V2;

public class CommandChannel : IChannel<IReadOnlyList<Byte>>
{
    private UInt32 _BufferSize;
    private Command Command { get; }

    private readonly MemoryStream _StdIn  = new MemoryStream();
    private readonly MemoryStream _StdOut = new MemoryStream();
    
    private CommandTask<CommandResult>? _CommandTask;

    public CommandChannel(Command command, UInt32 bufferSize = 64)
    {
        _BufferSize = bufferSize;

        Command = command
                 .WithStandardInputPipe(PipeSource.FromStream(_StdIn))
                 .WithStandardOutputPipe(PipeTarget.ToStream(_StdOut, true));
    }

    private void Open()
    {
        if (_CommandTask is null || _CommandTask.Task.IsCompleted)
        {
            Close();
            _CommandTask = Command.ExecuteAsync(); // TODO: support cancellation
        }
    }

    private void Close()
    {
        _StdIn.Seek(0, SeekOrigin.Begin);
        _StdOut.Seek(0, SeekOrigin.Begin);

        _CommandTask?.Dispose();
        _CommandTask = null;
    }


    public async Task<IReadOnlyList<Byte>> Read()
    {
        try
        {
            Open();

            var buffer = new Byte[_BufferSize];
            var nRead = await _StdOut.ReadAsync(buffer, 0, buffer.Length);

            if (nRead >= buffer.Length)
                _BufferSize *= 2;
            else if (nRead <= 0)
                throw new IOException("Broken pipe (StdOut)");

            return new ArraySegment<Byte>(buffer, 0, nRead);
        }
        catch
        {
            Close();
            throw;
        }
    }

    public async Task Write(IReadOnlyList<Byte> tx)
    {
        try
        {
            Open();
            await _StdIn.WriteAsync(tx as Byte[] ?? tx.ToArray(), 0, tx.Count);
        }
        catch
        {
            Close();
            throw;
        }
    }
}