unit Myc.Data.Types.Vector;

interface

uses
    System.Generics.Collections,
    Myc.Data.Types;

type
    // Implements the IDataVectorType interface.
    TImplDataVectorType = class(TInterfacedObject, IDataType, IDataVectorType)
    private
        // Registry for singleton type instances.
        class var
            FVectorTypeRegistry: TDictionary<String, IDataVectorType>;

            FElementType: IDataType;
            FElementCount: Integer;
            FName: string;
        function GetName: String;
        function GetKind: TDataKind;
        function GetElementType: IDataType;
        function GetElementCount: Integer;
        function CreateValue(const AItems: array of IDataValue): IDataVectorValue;

        class constructor Create;
        class destructor Destroy;
    public
        constructor Create(const AElementType: IDataType; const AElementCount: Integer);

        // Gets a cached or new instance for the given element type and count.
        class function GetInstance(const AElementType: IDataType; const AElementCount: Integer): IDataVectorType; static;
    end;

implementation

uses
    System.SysUtils;

type
    // Implements a generic vector value for N > 4 elements.
    TImplDataVectorValue = class(TInterfacedObject, IDataVectorValue)
    private
        FDataType: IDataVectorType;
        FItems: TArray<IDataValue>;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
    end;

    // Optimized implementation for a vector with 1 element.
    TImplDataVectorValue1 = class(TInterfacedObject, IDataValue, IDataVectorValue)
    private
        FDataType: IDataVectorType;
        FItem0: IDataValue;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
    end;

    // Optimized implementation for a vector with 2 elements.
    TImplDataVectorValue2 = class(TInterfacedObject, IDataValue, IDataVectorValue)
    private
        FDataType: IDataVectorType;
        FItem0, FItem1: IDataValue;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
    end;

    // Optimized implementation for a vector with 3 elements.
    TImplDataVectorValue3 = class(TInterfacedObject, IDataValue, IDataVectorValue)
    private
        FDataType: IDataVectorType;
        FItem0, FItem1, FItem2: IDataValue;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
    end;

    // Optimized implementation for a vector with 4 elements.
    TImplDataVectorValue4 = class(TInterfacedObject, IDataValue, IDataVectorValue)
    private
        FDataType: IDataVectorType;
        FItem0, FItem1, FItem2, FItem3: IDataValue;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
    end;

{ TImplDataVectorValue }

constructor TImplDataVectorValue.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
var
    i: Integer;
begin
    inherited Create;
    FDataType := ADataType;
    SetLength(FItems, Length(AItems));
    for i := 0 to High(FItems) do
        FItems[i] := AItems[i];
end;

function TImplDataVectorValue.GetAsString: string;
var
    i: Integer;
    sb: TStringBuilder;
begin
    sb := TStringBuilder.Create;
    try
        sb.Append('(');
        for i := 0 to High(FItems) do
        begin
            if (i > 0) then
                sb.Append(', ');
            sb.Append(FItems[i].AsString);
        end;
        sb.Append(')');
        Result := sb.ToString;
    finally
        sb.Free;
    end;
end;

function TImplDataVectorValue.GetDataType: IDataType;
begin
    Result := FDataType;
end;

function TImplDataVectorValue.GetElementCount: Integer;
begin
    Result := Length(FItems);
end;

function TImplDataVectorValue.GetItem(Idx: Integer): IDataValue;
begin
    Result := FItems[Idx];
end;

{ TImplDataVectorValue1 }

constructor TImplDataVectorValue1.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
    inherited Create;
    FDataType := ADataType;
    FItem0 := AItems[0];
end;

function TImplDataVectorValue1.GetAsString: string;
begin
    Result := '(' + FItem0.AsString + ')';
end;

function TImplDataVectorValue1.GetDataType: IDataType;
begin
    Result := FDataType;
end;

function TImplDataVectorValue1.GetElementCount: Integer;
begin
    Result := 1;
end;

function TImplDataVectorValue1.GetItem(Idx: Integer): IDataValue;
begin
    case Idx of
        0: Result := FItem0;
    else
        raise EArgumentException.Create('Index out of bounds');
    end;
end;

{ TImplDataVectorValue2 }

constructor TImplDataVectorValue2.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
    inherited Create;
    FDataType := ADataType;
    FItem0 := AItems[0];
    FItem1 := AItems[1];
end;

function TImplDataVectorValue2.GetAsString: string;
begin
    Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ')';
end;

function TImplDataVectorValue2.GetDataType: IDataType;
begin
    Result := FDataType;
end;

function TImplDataVectorValue2.GetElementCount: Integer;
begin
    Result := 2;
end;

function TImplDataVectorValue2.GetItem(Idx: Integer): IDataValue;
begin
    case Idx of
        0: Result := FItem0;
        1: Result := FItem1;
    else
        raise EArgumentException.Create('Index out of bounds');
    end;
end;

{ TImplDataVectorValue3 }

constructor TImplDataVectorValue3.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
    inherited Create;
    FDataType := ADataType;
    FItem0 := AItems[0];
    FItem1 := AItems[1];
    FItem2 := AItems[2];
end;

function TImplDataVectorValue3.GetAsString: string;
begin
    Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ', ' + FItem2.AsString + ')';
end;

function TImplDataVectorValue3.GetDataType: IDataType;
begin
    Result := FDataType;
end;

function TImplDataVectorValue3.GetElementCount: Integer;
begin
    Result := 3;
end;

function TImplDataVectorValue3.GetItem(Idx: Integer): IDataValue;
begin
    case Idx of
        0: Result := FItem0;
        1: Result := FItem1;
        2: Result := FItem2;
    else
        raise EArgumentException.Create('Index out of bounds');
    end;
end;

{ TImplDataVectorValue4 }

constructor TImplDataVectorValue4.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
    inherited Create;
    FDataType := ADataType;
    FItem0 := AItems[0];
    FItem1 := AItems[1];
    FItem2 := AItems[2];
    FItem3 := AItems[3];
end;

function TImplDataVectorValue4.GetAsString: string;
begin
    Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ', ' + FItem2.AsString + ', ' + FItem3.AsString + ')';
end;

function TImplDataVectorValue4.GetDataType: IDataType;
begin
    Result := FDataType;
end;

function TImplDataVectorValue4.GetElementCount: Integer;
begin
    Result := 4;
end;

function TImplDataVectorValue4.GetItem(Idx: Integer): IDataValue;
begin
    case Idx of
        0: Result := FItem0;
        1: Result := FItem1;
        2: Result := FItem2;
        3: Result := FItem3;
    else
        raise EArgumentException.Create('Index out of bounds');
    end;
end;

{ TImplDataVectorType }

class constructor TImplDataVectorType.Create;
begin
    // Initialize the class-level type registry.
    FVectorTypeRegistry := TDictionary<String, IDataVectorType>.Create;
end;

class destructor TImplDataVectorType.Destroy;
begin
    // Free the class-level type registry.
    FVectorTypeRegistry.Free;
    FVectorTypeRegistry := nil;
end;

constructor TImplDataVectorType.Create(const AElementType: IDataType; const AElementCount: Integer);
begin
    inherited Create;
    if (AElementCount <= 0) then
        raise EArgumentException.Create('Element count must be greater than zero.');

    FElementType := AElementType;
    FElementCount := AElementCount;
    FName := Format('Vector<%s>[%d]', [FElementType.Name, FElementCount]);
end;

function TImplDataVectorType.CreateValue(const AItems: array of IDataValue): IDataVectorValue;
var
    item: IDataValue;
    i: Integer;
begin
    // Validate that the number of items matches the vector's element count.
    if (Length(AItems) <> FElementCount) then
        raise EArgumentException
            .CreateFmt('Invalid number of elements for vector. Expected %d, but got %d.', [FElementCount, Length(AItems)]);

    // Validate that all items match the element type of this vector type.
    i := 0;
    for item in AItems do
    begin
        if (item.DataType <> FElementType) then
            raise EArgumentException.CreateFmt(
                'Invalid data type for item at index %d. Expected ''%s'', but got ''%s''.',
                [i, FElementType.Name, item.DataType.Name]);
        inc(i);
    end;

    // Use optimized implementations for vectors with 1-4 elements.
    case FElementCount of
        1: Result := TImplDataVectorValue1.Create(Self, AItems);
        2: Result := TImplDataVectorValue2.Create(Self, AItems);
        3: Result := TImplDataVectorValue3.Create(Self, AItems);
        4: Result := TImplDataVectorValue4.Create(Self, AItems);
    else
        // Use the generic implementation for vectors with more than 4 elements.
        Result := TImplDataVectorValue.Create(Self, AItems);
    end;
end;

function TImplDataVectorType.GetElementCount: Integer;
begin
    Result := FElementCount;
end;

function TImplDataVectorType.GetElementType: IDataType;
begin
    Result := FElementType;
end;

function TImplDataVectorType.GetKind: TDataKind;
begin
    Result := dkVector;
end;

function TImplDataVectorType.GetName: String;
begin
    Result := FName;
end;

class function TImplDataVectorType.GetInstance(const AElementType: IDataType; const AElementCount: Integer): IDataVectorType;
var
    key: string;
begin
    if (AElementCount <= 0) then
        raise EArgumentException.Create('Element count must be greater than zero.');

    if not Assigned(AElementType) then
        raise EArgumentException.Create('Cannot create a vector type with a nil element type.');

    // Generate a unique key for the dictionary. Using a separator that is unlikely in a type name.
    key := AElementType.Name + '|' + AElementCount.ToString;

    TMonitor.Enter(FVectorTypeRegistry);
    try
        if not FVectorTypeRegistry.TryGetValue(key, Result) then
        begin
            Result := TImplDataVectorType.Create(AElementType, AElementCount);
            FVectorTypeRegistry.Add(key, Result);
        end;
    finally
        TMonitor.Exit(FVectorTypeRegistry);
    end;
end;

end.
