using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Wabbajack.Common.CSP
{
    public abstract class AChannel<TIn, TOut> : IChannel<TIn, TOut>
    {
        public abstract bool IsClosed { get; }
        public abstract void Close();
        public abstract (AsyncResult, bool) Put(TIn val, Handler<Action<bool>> handler);
        public abstract (AsyncResult, TOut) Take(Handler<Action<bool, TOut>> handler);

        private Task<(bool, TOut)> _take_cancelled_task;

        private Task<(bool, TOut)> TakeCancelledTask
        {
            get
            {
                if (_take_cancelled_task == null)
                    _take_cancelled_task = Task.FromCanceled<(bool, TOut)>(CancellationToken.None);
                return _take_cancelled_task;
            }
        }

        private Task<bool> _put_cancelled_task;

        private Task<bool> PutCancelledTask
        {
            get
            {
                if (_put_cancelled_task == null)
                    _put_cancelled_task = Task.FromCanceled<bool>(CancellationToken.None);
                return _put_cancelled_task;
            }
        }

        public ValueTask<(bool, TOut)> Take(bool onCaller) 
        {
            var handler = new TakeTaskHandler<TOut>();
            var (resultType, val) = Take(handler);

            switch (resultType)
            {
                case AsyncResult.Closed:
                    return new ValueTask<(bool, TOut)>((false, default));
                case AsyncResult.Completed:
                    return new ValueTask<(bool, TOut)>((true, val));
                case AsyncResult.Enqueued:
                    return new ValueTask<(bool, TOut)>(handler.TaskCompletionSource.Task);
                case AsyncResult.Canceled:
                    return new ValueTask<(bool, TOut)>(TakeCancelledTask); 
                default:
                    // Should never happen
                    throw new InvalidDataException();
            }
        }

        public ValueTask<bool> Put(TIn val, bool onCaller)
        {
            var handler = new PutTaskHandler<bool>();
            var (resultType, putResult) = Put(val, handler);

            switch (resultType)
            {
                case AsyncResult.Completed:
                    return new ValueTask<bool>(putResult);
                case AsyncResult.Canceled:
                    return new ValueTask<bool>(PutCancelledTask);
                case AsyncResult.Closed:
                    return new ValueTask<bool>(false);
                case AsyncResult.Enqueued:
                    return new ValueTask<bool>(handler.TaskCompletionSource.Task);
                default:
                    // Should never happen
                    throw new InvalidDataException();
            }
        }


    }
}