unit Myc.Data.Types.Arrays;

interface

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

type
    // Implements the array data type.
    TImplDataArrayType = class(TInterfacedObject, IDataType, IDataArrayType)
    strict private
        // Class-level registry to cache and reuse array type definitions.
        class var
            FArrayTypeRegistry: TDictionary<IDataType, IDataArrayType>;
    private
        FElementType: IDataType;
        FEmptyValue: IDataArrayValue; // Cached empty array value for this type
        function GetElementType: IDataType;
        function GetName: String;
        function GetKind: TDataKind;
    public
        constructor Create(const AElementType: IDataType);

        class constructor CreateClass;
        class destructor DestroyClass;
        // Factory method to get a cached or new array type instance.
        class function GetInstance(const AElementType: IDataType): IDataArrayType; static;

        function CreateValue(const AItems: array of IDataValue): IDataArrayValue;
    end;

implementation

uses
    System.SyncObjs;

type
    // Implements the array data value.
    TImplDataArrayValue = class(TInterfacedObject, IDataValue, IDataArrayValue)
    private
        FArrayType: IDataArrayType;
        FItems: TArray<IDataValue>;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const AArrayType: IDataArrayType; const AItems: array of IDataValue);
    end;

    // Optimized implementation for an array with 0 elements.
    TImplDataArrayValue0 = class(TInterfacedObject, IDataValue, IDataArrayValue)
    private
        FArrayType: IDataArrayType;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const AArrayType: IDataArrayType);
    end;

    // Optimized implementation for an array with 1 element.
    TImplDataArrayValue1 = class(TInterfacedObject, IDataValue, IDataArrayValue)
    private
    private
        FArrayType: IDataArrayType;
        FItem0: IDataValue;
        function GetDataType: IDataType;
        function GetAsString: string;
        function GetElementCount: Integer;
        function GetItem(Idx: Integer): IDataValue;
    public
        constructor Create(const AArrayType: IDataArrayType; const AItems: array of IDataValue);
    end;

{ TImplDataArrayValue (generic implementation for N > 1 elements) }

constructor TImplDataArrayValue.Create(const AArrayType: IDataArrayType; const AItems: array of IDataValue);
var
    i: Integer;
begin
    inherited Create;
    FArrayType := AArrayType;
    SetLength(FItems, Length(AItems));
    for i := 0 to High(AItems) do
        FItems[i] := AItems[i];
end;

function TImplDataArrayValue.GetDataType: IDataType;
begin
    Result := FArrayType;
end;

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

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

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

{ TImplDataArrayValue0 }

constructor TImplDataArrayValue0.Create(const AArrayType: IDataArrayType);
begin
    inherited Create;
    FArrayType := AArrayType;
end;

function TImplDataArrayValue0.GetAsString: string;
begin
    Result := '[]';
end;

function TImplDataArrayValue0.GetDataType: IDataType;
begin
    Result := FArrayType;
end;

function TImplDataArrayValue0.GetElementCount: Integer;
begin
    Result := 0;
end;

function TImplDataArrayValue0.GetItem(Idx: Integer): IDataValue;
begin
    raise EArgumentException.Create('Index out of bounds');
end;

{ TImplDataArrayValue1 }

constructor TImplDataArrayValue1.Create(const AArrayType: IDataArrayType; const AItems: array of IDataValue);
begin
    inherited Create;
    FArrayType := AArrayType;
    FItem0 := AItems[0];
end;

function TImplDataArrayValue1.GetAsString: string;
begin
    Result := '[' + FItem0.AsString + ']';
end;

function TImplDataArrayValue1.GetDataType: IDataType;
begin
    Result := FArrayType;
end;

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

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

{ TImplDataArrayType }

class constructor TImplDataArrayType.CreateClass;
begin
    // Initialize the global registry for array types.
    FArrayTypeRegistry := TDictionary<IDataType, IDataArrayType>.Create;
end;

class destructor TImplDataArrayType.DestroyClass;
begin
    FArrayTypeRegistry.Free;
    FArrayTypeRegistry := nil;
end;

class function TImplDataArrayType.GetInstance(const AElementType: IDataType): IDataArrayType;
var
    res: IDataArrayType;
begin
    if not Assigned(AElementType) then
        raise EArgumentException.Create('Cannot create an array type with a nil element type.');

    TMonitor.Enter(FArrayTypeRegistry);
    try
        if not FArrayTypeRegistry.TryGetValue(AElementType, res) then
        begin
            res := TImplDataArrayType.Create(AElementType);
            FArrayTypeRegistry.Add(AElementType, res);
        end;
    finally
        TMonitor.Exit(FArrayTypeRegistry);
    end;
    Result := res;
end;

constructor TImplDataArrayType.Create(const AElementType: IDataType);
begin
    inherited Create;
    FElementType := AElementType;
    FEmptyValue := TImplDataArrayValue0.Create(Self);
end;

function TImplDataArrayType.CreateValue(const AItems: array of IDataValue): IDataArrayValue;
var
    item: IDataValue;
    i: Integer;
begin
    // Validate that all items match the element type of this array 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 arrays with 0 or 1 elements.
    case Length(AItems) of
        0: Result := FEmptyValue;
        1: Result := TImplDataArrayValue1.Create(Self, AItems);
    else
        // Use the generic implementation for arrays with more than 1 element.
        Result := TImplDataArrayValue.Create(Self, AItems);
    end;
end;

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

function TImplDataArrayType.GetName: String;
begin
    Result := Format('Array<%s>', [FElementType.Name]);
end;

function TImplDataArrayType.GetKind: TDataKind;
begin
    Result := dkArray;
end;

end.
