unit Myc.Futures;

interface

uses
    System.SysUtils,
    Myc.Signals,
    Myc.TaskManager;

type
    // Represents the eventual Value of an asynchronous operation.
    TFuture<T> = record
        type
            IFuture = interface
                {$REGION 'property access'}
                function GetValue: T;
                function GetDone: TState;
                {$ENDREGION}
                // Provides access to the computed Value.
                property Value: T read GetValue;
                // Provides access to the completion state.
                property Done: TState read GetDone;
            end;

            TFuncConst<S, TResult> = reference to function(const Arg1: S): TResult;
            TProcConst<S, TResult> = reference to function(const Arg1: S): TResult;

    {$REGION 'private'}
    strict private
        class var
            FNull: IFuture;

        class constructor CreateClass;
    private
        FFuture: IFuture;
        function GetDone: TState.IState; inline;
        function GetValue: T; inline;
    {$ENDREGION}
    public
        constructor Create(const AFuture: IFuture);

        class operator Initialize(out Dest: TFuture<T>);
        class operator Implicit(const A: IFuture): TFuture<T>; overload;
        class operator Implicit(const A: TFuture<T>): IFuture; overload;

        class function Construct(const Proc: TFunc<T>): TFuture<T>; overload; static;
        class function Construct(const Gate: TState; const Proc: TFunc<T>): TFuture<T>; overload; static;
        class function Construct(const ConstVal: T): TFuture<T>; overload; static;

        class property Null: IFuture read FNull;

        procedure Manage;

        function Chain<S>(const Proc: TFunc<T, S>): TFuture<S>; overload;
        function Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>; overload;
        function Chain(const Proc: TProcConst<T, TState>): TState; overload;
        function WaitFor: T;

        class function WhenAll<S>(const Futures: TArray<IFuture>; const Func: TFuncConst<TArray<T>, S>): TFuture<S>; static; experimental;

        property Done: TState.IState read GetDone;
        property Value: T read GetValue;
    end;

    TFuture = record
    public
        class function FromArray<T>(const Arr: TArray<TFuture<T>>): TFuture<TArray<T>>; static;
    end;

    IObjectRef<T: class> = interface
        function GetObj: T;
        function Pop: T;
        property Obj: T read GetObj;
    end;

    TObjectRef<T: class> = class(TInterfacedObject, IObjectRef<T>)
    private
        FObj: T;
        function GetObj: T;
    public
        constructor Create(const ARef: T);
        destructor Destroy; override;
        function Pop: T;
    end;

implementation

uses
    Myc.Core.Future;

constructor TFuture<T>.Create(const AFuture: IFuture);
begin
    FFuture := AFuture;
    if not Assigned(FFuture) then
        FFuture := FNull;
end;

class constructor TFuture<T>.CreateClass;
begin
    FNull := TMycNullFuture<T>.Create(Default(T));
end;

function TFuture<T>.Chain(const Proc: TProcConst<T, TState>): TState;
begin
    var cFuture := FFuture;

    Result := TaskManager.RunTask(cFuture.Done, function: TState begin Result := Proc(cFuture.Value); end);
end;

function TFuture<T>.Chain<S>(const Proc: TFunc<T, S>): TFuture<S>;
begin
    var cFuture := FFuture;
    Result := TFuture<S>.Construct(cFuture.Done, function: S begin Result := Proc(cFuture.Value); end);
end;

function TFuture<T>.Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>;
begin
    var cFuture := FFuture;
    Result := TFuture<S>.Construct(cFuture.Done, function: S begin Result := Proc(cFuture.Value); end);
end;

class function TFuture<T>.Construct(const Proc: TFunc<T>): TFuture<T>;
begin
    Result := TMycGateFuncFuture<T>.Create(TaskManager, nil, Proc);
end;

class function TFuture<T>.Construct(const Gate: TState; const Proc: TFunc<T>): TFuture<T>;
begin
    Result := TMycGateFuncFuture<T>.Create(TaskManager, Gate, Proc);
end;

class function TFuture<T>.Construct(const ConstVal: T): TFuture<T>;
begin
    Result := TMycNullFuture<T>.Create(ConstVal);
end;

function TFuture<T>.GetDone: TState.IState;
begin
    Result := FFuture.Done;
end;

function TFuture<T>.GetValue: T;
begin
    Result := FFuture.Value;
end;

procedure TFuture<T>.Manage;
begin
    if GetTypeKind(T) = tkClass then
        FFuture := TMycFutureManaged<T>.Create(FFuture);
end;

function TFuture<T>.WaitFor: T;
begin
    TaskManager.WaitFor(FFuture.Done);
    Result := FFuture.Value;
end;

class function TFuture<T>.WhenAll<S>(const Futures: TArray<IFuture>; const Func: TFuncConst<TArray<T>, S>): TFuture<S>;
var
    DoneStates: TArray<TState>;
begin
    SetLength(DoneStates, Length(Futures));
    for var i := 0 to High(DoneStates) do
        DoneStates[i] := Futures[i].Done;
    var cFutures := Futures;
    Result :=
        TFuture<S>.Construct(
            TState.All(DoneStates),
            function: S
            var
                Vals: TArray<T>;
            begin
                SetLength(Vals, Length(cFutures));
                for var i := 0 to High(Vals) do
                    Vals[i] := Futures[i].Value;
                Result := Func(Vals);
            end
        );
end;

class operator TFuture<T>.Implicit(const A: IFuture): TFuture<T>;
begin
    Result.Create(A);
end;

class operator TFuture<T>.Implicit(const A: TFuture<T>): IFuture;
begin
    Result := A.FFuture;
end;

class operator TFuture<T>.Initialize(out Dest: TFuture<T>);
begin
    Dest.FFuture := FNull;
end;

constructor TObjectRef<T>.Create(const ARef: T);
begin
    inherited Create;
    FObj := ARef;
end;

destructor TObjectRef<T>.Destroy;
begin
    FObj.Free;
    inherited Destroy;
end;

function TObjectRef<T>.GetObj: T;
begin
    Result := FObj;
end;

function TObjectRef<T>.Pop: T;
begin
    Result := FObj;
    FreeAndNil(FObj);
end;

class function TFuture.FromArray<T>(const Arr: TArray<TFuture<T>>): TFuture<TArray<T>>;
var
    DoneStates: TArray<TState>;
begin
    var cFutures := Arr;
    SetLength(DoneStates, Length(cFutures));
    for var i := 0 to High(DoneStates) do
        DoneStates[i] := cFutures[i].Done;

    Result :=
        TFuture<TArray<T>>.Construct(
            TState.All(DoneStates),
            function: TArray<T>
            begin
                SetLength(Result, Length(cFutures));
                for var i := 0 to High(Result) do
                    Result[i] := cFutures[i].Value;
            end
        );
end;

end.
