unit TestMethodCallFromRecordParams;

interface

uses
    System.SysUtils,
    System.Classes,
    System.Rtti,
    Myc.Data.Records,
    Myc.Data.Pipeline,
    Myc.Trade.Indicators;

type
    TMyWorker = class
    public
        type
            TParams = record
                Log: Int64;
                text: String;
            end;

            TArgs = record
                xyz: Double;
            end;

            TResult = record
                val: Int64;
                desc: String;
            end;

        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
    end;

    // SMA indicator for demonstration purposes
    TSmaIndicator = class
    public
        type
            TParams = record
                Period: Integer;
            end;

            TArgs = record
                Value: Double;
            end;

            TResult = record
                Sma: Double;
            end;

        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
    end;

procedure Test1(const Log: TStrings);
implementation

uses
    System.Math;

class function TMyWorker.CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        begin
            var Log := TStrings(Params.Log);
            var text := Params.text;

            Result :=
                function(const Args: TArgs): TResult
                begin
                    // Use Format to avoid locale issues with float conversion
                    Log.Add(Format('Val=%f      (...%s)', [Args.xyz, text]));
                    Result.desc := 'done';
                end;
        end;
end;

procedure Test1(const Log: TStrings);
begin
    var fact := TGenericIndicatorFactory.CreateFromTemplate<TMyWorker>;

    var params: TMyWorker.TParams;
    params.Log := Int64(Log);
    params.text := 'The quick brown fox jumps...';

    var indi := fact.CreateIndicator<TMyWorker.TParams, TMyWorker.TArgs, TMyWorker.TResult>(params);

    var args: TMyWorker.TArgs;
    args.xyz := 3.14159;

    var res := indi(args);

    Log.Add('Final result description: ' + res.desc);
end;

{ TSmaIndicator }

class function TSmaIndicator.CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            // State for the indicator closure
            period: Integer;
            buffer: TArray<Double>;
            sum: Double;
            count: Integer;
            idx: Integer;
        begin
            period := Params.Period;
            if period <= 0 then
                raise Exception.Create('Period must be positive');

            SetLength(buffer, period);
            sum := 0.0;
            count := 0;
            idx := 0;

            Result :=
                function(const Args: TArgs): TResult
                begin
                    // If the buffer is full, subtract the oldest value that is about to be overwritten.
                    if count >= period then
                        sum := sum - buffer[idx];

                    // Add the new value to the buffer and the sum.
                    buffer[idx] := Args.Value;
                    sum := sum + Args.Value;

                    // Advance the index for the circular buffer.
                    idx := (idx + 1) mod period;

                    // Increment the fill count until the buffer is full for the first time.
                    if count < period then
                        inc(count);

                    // Calculate the SMA. The result is the average of the values currently in the buffer.
                    if count > 0 then
                        Result.Sma := sum / count
                    else
                        Result.Sma := 0.0;
                end;
        end;
end;

end.
