unit XData.Types.Converters;

{$I XData.Inc}

interface

uses
  SysUtils,
  Bcl.Rtti.Common,
  {$IFDEF PAS2JS}
  JS,
  {$ENDIF}
  XData.Model.Classes;

type
  TXDataTypeBaseConverter = class(TXDataTypeConverter)
  end;

  TXDataInt32Converter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataInt16Converter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  {$IFDEF PAS2JS}
  TXDataInt64Converter = TXDataInt32Converter;
  {$ELSE}
  TXDataInt64Converter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;
  {$ENDIF}

  TXDataBooleanConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataDoubleConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  {$IFNDEF PAS2JS}
  TXDataCurrencyConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;
  {$ENDIF}

  TXDataDateTimeConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataDateConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataTimeConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataBinaryConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataStringConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataCharConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataGuidConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataVariantConverter = class(TXDataTypeBaseConverter)
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  end;

  TXDataEnumConverter = class(TXDataTypeConverter)
  strict private
    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}
    FEnumType: TXDataEnumType;
  strict protected
    function GetXDataType: TXDataScalarType; override;
  strict protected
    function TryValueToUrl(const Value: TValue; out Literal: string; Prop: TXDataSimpleProperty): boolean; override;
    function TryUrlToValue(const Literal: string; out Value: TValue; Prop: TXDataSimpleProperty): boolean; override;
  public
    constructor Create(AEnumType: TXDataEnumType); reintroduce;
  end;

procedure AddDefaultUrlConverters(Converters: TXDataUrlConverters);

implementation

uses
  XData.Model.Types;

procedure AddDefaultUrlConverters(Converters: TXDataUrlConverters);
begin
  // Integer types
  Converters.RegisterConverter('Int32', TXDataInt32Converter.Create);
  Converters.RegisterConverter('Int16', TXDataInt16Converter.Create);
  Converters.RegisterConverter('Int64', TXDataInt64Converter.Create);

  // Boolean types
  Converters.RegisterConverter('Boolean', TXDataBooleanConverter.Create);

  // Float types
  Converters.RegisterConverter('Double', TXDataDoubleConverter.Create);
  {$IFDEF PAS2JS}
  Converters.RegisterConverter('Currency', TXDataDoubleConverter.Create);
  {$ELSE}
  Converters.RegisterConverter('Currency', TXDataCurrencyConverter.Create);
  {$ENDIF}

  // Date time types
  Converters.RegisterConverter('DateTime', TXDataDateTimeConverter.Create);
  Converters.RegisterConverter('Date', TXDataDateConverter.Create);
  Converters.RegisterConverter('Time', TXDataTimeConverter.Create);

  // Blob types
  Converters.RegisterConverter('Binary', TXDataBinaryConverter.Create);
  Converters.RegisterConverter('Stream', TXDataBinaryConverter.Create);

  // String types
  Converters.RegisterConverter('String', TXDataStringConverter.Create);

  // Char types
  Converters.RegisterConverter('Char', TXDataCharConverter.Create);

  // Guid types
  Converters.RegisterConverter('Guid', TXDataGuidConverter.Create);

  // Variant
  Converters.RegisterConverter('Variant', TXDataVariantConverter.Create);
end;

{ TXDataIntegerConverter }

function TXDataInt32Converter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.Int32Type;
end;

function TXDataInt32Converter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  IntValue: integer;
begin
  Result := TXDataPrimitiveTypes.Instance.Int32Type.LiteralToValue(Literal, IntValue);
  Value := IntValue;
end;

function TXDataInt32Converter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.Int32Type.ValueToLiteral(
    TValue_AsInteger(Value), Literal);
end;

{ TXDataInt16Converter }

function TXDataInt16Converter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.Int16Type;
end;

function TXDataInt16Converter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  IntValue: integer;
begin
  Result := TXDataPrimitiveTypes.Instance.Int16Type.LiteralToValue(Literal, IntValue);
  Value := IntValue;
end;

function TXDataInt16Converter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.Int16Type.ValueToLiteral(
    TValue_AsInteger(Value), Literal);
end;

{ TXDataInt64Converter }

{$IFNDEF PAS2JS}
function TXDataInt64Converter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.Int64Type;
end;

function TXDataInt64Converter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  IntValue: Int64;
begin
  Result := TXDataPrimitiveTypes.Instance.Int64Type.LiteralToValue(Literal, IntValue);
  Value := IntValue;
end;

function TXDataInt64Converter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.Int64Type.ValueToLiteral(
    TValue_AsOrdinal(Value),
    Literal);
end;
{$ENDIF}

{ TXDataBooleanConverter }

function TXDataBooleanConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.BooleanType;
end;

function TXDataBooleanConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  BoolValue: boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.BooleanType.LiteralToValue(Literal, BoolValue);
  if Result then
    Value := BoolValue;
end;

function TXDataBooleanConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.BooleanType.ValueToLiteral(
    TValue_AsBoolean(Value), Literal);
end;

{ TXDataDoubleConverter }

function TXDataDoubleConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.DoubleType;
end;

function TXDataDoubleConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  DoubleValue: Double;
begin
  Result := TXDataPrimitiveTypes.Instance.DoubleType.LiteralToValue(Literal, DoubleValue);
  if Result then
    Value := DoubleValue;
end;

function TXDataDoubleConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.DoubleType.ValueToLiteral(
    TValue_AsExtended(Value), Literal);
end;

{ TXDataDateTimeConverter }

function TXDataDateTimeConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.DateTimeType;
end;

function TXDataDateTimeConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  DateValue: TDateTime;
begin
  Result := TXDataPrimitiveTypes.Instance.DateTimeType.LiteralToValue(Literal, DateValue);
  if Result then
    Value := TValue_FromDateTime(DateValue);
end;

function TXDataDateTimeConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  {$IFDEF PAS2JS}
  // for Pas2JS, value can be string thus we just bypass the value (todo: test format yyyy-mm-dd)
  if JS.IsString(Value) then
  begin
    Result := True;
    Literal := string(value);
  end
  else
  {$ENDIF}
  Result := TXDataPrimitiveTypes.Instance.DateTimeType.ValueToLiteral(
    TDateTime(TValue_AsDouble(Value)), Literal);
end;

{ TXDataTimeConverter }

function TXDataTimeConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.TimeType;
end;

function TXDataTimeConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  TimeValue: TTime;
begin
  Result := TXDataPrimitiveTypes.Instance.TimeType.LiteralToValue(Literal, TimeValue);
  if Result then
    Value := TValue_FromTime(TimeValue);
end;

function TXDataTimeConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  {$IFDEF PAS2JS}
  // for Pas2JS, value can be string thus we just bypass the value (todo: test format yyyy-mm-dd)
  if JS.IsString(Value) then
  begin
    Result := True;
    Literal := string(value);
  end
  else
  {$ENDIF}
  Result := TXDataPrimitiveTypes.Instance.TimeType.ValueToLiteral(
    TTime(TValue_AsDouble(Value)), Literal);
end;

{ TXDataBinaryConverter }

function TXDataBinaryConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.BinaryType;
end;

function TXDataBinaryConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  Bytes: TBytes;
  StringValue: string;
begin
  StringValue := Literal;
  LiteralUnquote(StringValue);
  Result := TXDataPrimitiveTypes.Instance.BinaryType.LiteralToValue(StringValue, Bytes);
  if Result then
    Value := TValue_FromBytes(Bytes);
end;

function TXDataBinaryConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
var
  Bytes: TBytes;
begin
  Bytes := TValue_AsBytes(Value);
  Result := TXDataPrimitiveTypes.Instance.BinaryType.ValueToLiteral(Bytes, Literal);
  Literal := LiteralQuote(Literal);
end;

{ TXDataStringConverter<T>  }

function TXDataStringConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType;
end;

function TXDataStringConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  StringValue: string;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType.LiteralToValue(Literal, StringValue)
    and LiteralUnquote(StringValue);
  if Result then
    Value := StringValue;
end;

function TXDataStringConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType.ValueToLiteral(
    LiteralQuote(TValue_AsString(Value)), Literal);
end;

{ TXDataCharConverter<T> }

function TXDataCharConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType;
end;

function TXDataCharConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  StringValue: string;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType.LiteralToValue(Literal, StringValue)
    and LiteralUnquote(StringValue);
  if Result then
    Value := StringValue;
end;

function TXDataCharConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.StringType.ValueToLiteral(
    LiteralQuote(TValue_AsString(Value)), Literal);
end;

{ TXDataGuidConverter }

function TXDataGuidConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.GuidType;
end;

{$IFDEF PAS2JS}
type TGuid = string;
{$ENDIF}

function TXDataGuidConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  GuidValue: TGuid;
begin
  Result := TXDataPrimitiveTypes.Instance.GuidType.LiteralToValue(Literal, GuidValue);
  if Result then
    {$IFDEF PAS2JS}
    Value := GuidValue;
    {$ELSE}
    Value := TValue.From<TGuid>(GuidValue);
    {$ENDIF}
end;

function TXDataGuidConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  {$IFDEF PAS2JS}
  Result := TXDataPrimitiveTypes.Instance.GuidType.ValueToLiteral(TValue_AsString(Value), Literal);
  {$ELSE}
  Result := TXDataPrimitiveTypes.Instance.GuidType.ValueToLiteral(Value.AsType<TGuid>, Literal);
  {$ENDIF}
end;

{ TXDataCurrencyConverter }

{$IFNDEF PAS2JS}
function TXDataCurrencyConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.CurrencyType;
end;
{$ENDIF}

{$IFNDEF PAS2JS}
function TXDataCurrencyConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  Curr: Currency;
begin
  Result := TXDataPrimitiveTypes.Instance.CurrencyType.LiteralToValue(Literal, Curr);
  if Result then
    Value := Curr;
end;
{$ENDIF}

{$IFNDEF PAS2JS}
function TXDataCurrencyConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.CurrencyType.ValueToLiteral(Value.AsCurrency, Literal);
end;
{$ENDIF}

{ TXDataEnumConverter }

constructor TXDataEnumConverter.Create(AEnumType: TXDataEnumType);
begin
  FEnumType := AEnumType;
end;

function TXDataEnumConverter.GetXDataType: TXDataScalarType;
begin
  Result := FEnumType;
end;

function TXDataEnumConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  I: integer;
  StringValue: string;
begin
  StringValue := Literal;
  Result := False;
  if LiteralUnquote(StringValue) then
    for I := 0 to FEnumType.Members.Count - 1 do
      if StringValue = FEnumType.Members[I].Name then
      begin
        Value := FEnumType.Members[I].Value;
        Exit(True);
      end;
end;

function TXDataEnumConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
var
  I: Integer;
  EnumValue: Integer;
begin
  EnumValue := TValue_AsOrdinal(Value);
  Result := False;
  for I := 0 to FEnumType.Members.Count - 1 do
    if EnumValue = FEnumType.Members[I].Value then
    begin
      Result := TXDataPrimitiveTypes.Instance.StringType.ValueToLiteral(
        LiteralQuote(FEnumType.Members[I].Name),
        Literal);
      Exit;
    end;
end;

{ TXDataDateConverter }

function TXDataDateConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.DateType;
end;

function TXDataDateConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  DateValue: TDate;
begin
  Result := TXDataPrimitiveTypes.Instance.DateType.LiteralToValue(Literal, DateValue);
  if Result then
    Value := TValue_FromDate(DateValue);
end;

function TXDataDateConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  {$IFDEF PAS2JS}
  // for Pas2JS, value can be string thus we just bypass the value (todo: test format yyyy-mm-dd)
  if JS.IsString(Value) then
  begin
    Result := True;
    Literal := string(value);
  end
  else
  {$ENDIF}
  Result := TXDataPrimitiveTypes.Instance.DateType.ValueToLiteral(
    TDate(TValue_AsDouble(Value)), Literal);
end;

{ TXDataVariantConverter }

function TXDataVariantConverter.GetXDataType: TXDataScalarType;
begin
  Result := TXDataPrimitiveTypes.Instance.VariantType;
end;

{$IFDEF PAS2JS}
type
  Variant = JSValue;
{$ENDIF}

function TXDataVariantConverter.TryUrlToValue(const Literal: string; out Value: TValue;
  Prop: TXDataSimpleProperty): boolean;
var
  VariantValue: Variant;
begin
  Result := TXDataPrimitiveTypes.Instance.VariantType.LiteralToValue(Literal, VariantValue);
  {$IFNDEF PAS2JS}
  if Result then
    Value := TValue.From<Variant>(VariantValue);
  {$ENDIF}
end;

function TXDataVariantConverter.TryValueToUrl(const Value: TValue; out Literal: string;
  Prop: TXDataSimpleProperty): boolean;
begin
  Result := TXDataPrimitiveTypes.Instance.VariantType.ValueToLiteral(
    {$IFDEF PAS2JS}
    Value,
    {$ELSE}
    Value.AsType<Variant>,
    {$ENDIF}
    Literal);
end;

end.

