unit Myc.Data.Types.Decimal;

interface

uses
    Myc.Data.Types;

type
    // Concrete implementation of the IDataDecimalType interface.
    TImplDataDecimalType = class(TInterfacedObject, IDataDecimalType)
    private
        FScale: Integer;
    strict private
        class var
            // Pre-cached instances for scales 0-18.
            FDecimalTypes: TArray<IDataDecimalType>;
    public
        constructor Create(const AScale: Integer);

        class constructor CreateClass;
        class destructor DestroyClass;

        // Returns a cached instance for the specified scale.
        class function GetInstance(const AScale: Integer): IDataDecimalType; static;

        // IDataType
        function GetName: String;
        function GetKind: TDataKind;

        // IDataDecimalType
        function GetScale: Integer;
        function CreateValue(const AValue: Int64): IDataDecimalValue;
    end;

implementation

uses
    System.SysUtils,
    System.Math;

type
    TDataDecimalValue = class(TInterfacedObject, IDataDecimalValue)
    private
        FDataType: IDataDecimalType;
        FValue: Int64;
    public
        constructor Create(const AValue: Int64; const ADataType: IDataDecimalType);

        // IDataValue
        function GetDataType: IDataType;
        function GetAsString: string;

        // IDataDecimalValue
        function GetValue: Int64;
    end;

{ TImplDataDecimalType }

constructor TImplDataDecimalType.Create(const AScale: Integer);
begin
    inherited Create;
    FScale := AScale;
end;

class constructor TImplDataDecimalType.CreateClass;
var
    i: Integer;
begin
    // Create and cache singleton instances for each supported scale.
    SetLength(FDecimalTypes, 19);
    for i := 0 to High(FDecimalTypes) do
        FDecimalTypes[i] := TImplDataDecimalType.Create(i);
end;

class destructor TImplDataDecimalType.DestroyClass;
begin
    FDecimalTypes := nil;
end;

class function TImplDataDecimalType.GetInstance(const AScale: Integer): IDataDecimalType;
begin
    if (AScale < 0) or (AScale > High(FDecimalTypes)) then
        raise EArgumentException.CreateFmt('Scale for decimal must be between 0 and %d.', [High(FDecimalTypes)]);
    Result := FDecimalTypes[AScale];
end;

function TImplDataDecimalType.CreateValue(const AValue: Int64): IDataDecimalValue;
begin
    Result := TDataDecimalValue.Create(AValue, Self);
end;

function TImplDataDecimalType.GetKind: TDataKind;
begin
    Result := dkDecimal;
end;

function TImplDataDecimalType.GetName: String;
begin
    Result := Format('Decimal(18, %d)', [FScale]);
end;

function TImplDataDecimalType.GetScale: Integer;
begin
    Result := FScale;
end;

{ TDataDecimalValue }

constructor TDataDecimalValue.Create(const AValue: Int64; const ADataType: IDataDecimalType);
begin
    inherited Create;
    FValue := AValue;
    FDataType := ADataType;
end;

function TDataDecimalValue.GetAsString: string;
var
    s: string;
    scale: Integer;
    insertPos: Integer;
begin
    s := IntToStr(abs(FValue));
    scale := FDataType.Scale;

    if scale > 0 then
    begin
        if Length(s) <= scale then
            s := StringOfChar('0', scale - Length(s) + 1) + s;

        insertPos := Length(s) - scale;
        Result := Copy(s, 1, insertPos) + '.' + Copy(s, insertPos + 1, MaxInt);
    end
    else
    begin
        Result := s;
    end;

    if FValue < 0 then
        Result := '-' + Result;
end;

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

function TDataDecimalValue.GetValue: Int64;
begin
    Result := FValue;
end;

end.
