unit Aurelius.Mapping.Attributes;

{$I Aurelius.inc}

interface

uses
  Generics.Collections, Rtti, DB,
  Aurelius.Id.AbstractGenerator,
  Aurelius.Mapping.Metadata;

type
  Entity = class(TCustomAttribute)
  end;

  Automapping = class(TCustomAttribute)
  end;

  Model = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    property Name: string read FName;
  end;

  Table = class(TCustomAttribute)
  private
    FName: string;
    FSchema: string;
  public
    constructor Create(const Name: string); overload;
    constructor Create(const Name, Schema: string); overload;
    property Name: string read FName write FName;
    property Schema: string read FSchema write FSchema;
  end;

  TColumnProp = Aurelius.Mapping.Metadata.TColumnProp;
  TColumnProps = Aurelius.Mapping.Metadata.TColumnProps;

  AbstractColumn = class abstract(TCustomAttribute)
  private
    FName: string;
    FProperties: TColumnProps;
  public
    constructor Create(const Name: string); overload; virtual;
    constructor Create(const Name: string; Properties: TColumnProps); overload; virtual;
    property Name: string read FName write FName;
    property Properties: TColumnProps read FProperties write FProperties;
  end;

  Column = class(AbstractColumn)
  private
    FLength: Integer;
    FPrecision: Integer;
    FScale: Integer;
  public
    constructor Create(const Name: string); overload; override;
    constructor Create(const Name: string; Properties: TColumnProps; Length: Integer); overload;
    constructor Create(const Name: string; Properties: TColumnProps; Precision, Scale: Integer); overload;
    property Length: Integer read FLength write FLength;
    property Precision: Integer read FPrecision write FPrecision;
    property Scale: Integer read FScale write FScale;
  end;

  JoinColumn = class(AbstractColumn)
  private
    FReferencedColumnName: string;
  public
    constructor Create(const Name: string; Properties: TColumnProps; const ReferencedColumnName: string); overload;
    property ReferencedColumnName: string read FReferencedColumnName write FReferencedColumnName;
  end;

  ForeignJoinColumn = class(AbstractColumn)
  private
    FReferencedColumnName: string;
  public
    constructor Create(const Name: string; Properties: TColumnProps; const ReferencedColumnName: string); overload;
    property ReferencedColumnName: string read FReferencedColumnName write FReferencedColumnName;
  end;

  PrimaryJoinColumn = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const Name: string);
    property Name: string read FName write FName;
  end;

  TIdGenerator = Aurelius.Mapping.Metadata.TIdGenerator;

  Id = class(TCustomAttribute)
  private
    FMemberName: string;
    FGenerator: TIdGenerator;
    FGeneratorClass: TGeneratorClass;
  public
    constructor Create(const AMemberName: string; AGenerator: TIdGenerator); overload;
    constructor Create(const AMemberName: string; AGeneratorClass: TGeneratorClass); overload;
    property MemberName: string read FMemberName;
    property Generator: TIdGenerator read FGenerator;
    property GeneratorClass: TGeneratorClass read FGeneratorClass;
  end;

  IdUnsavedValue = class(TCustomAttribute)
  private
    FValue: TValue;
  public
    constructor Create(const AValue: Integer); overload;
    constructor Create(const AValue: string); overload;
    property Value: TValue read FValue;
  end;

  UniqueKey = class(TCustomAttribute)
  private
    FColumns: TList<string>;
  public
    constructor Create(const Columns: string); // Specify with comma-separated values
    destructor Destroy; override;
    property Columns: TList<string> read FColumns write FColumns;
  end;

  DBIndex = class(TCustomAttribute)
  private
    FName: string;
    FColumns: TList<string>;
  public
    constructor Create(const AName, AColumns: string); // Specify with comma-separated values
    destructor Destroy; override;
    property Name: string read FName write FName;
    property Columns: TList<string> read FColumns write FColumns;
  end;

  ForeignKey = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    property Name: string read FName write FName;
  end;

  Sequence = class(TCustomAttribute)
  private
    FSequenceName: string;
    FInitialValue: Integer;
    FIncrement: Integer;
  public
    constructor Create(const SequenceName: string); overload;
    constructor Create(const SequenceName: string; InitialValue, Increment: Integer); overload;
    property SequenceName: string read FSequenceName write FSequenceName;
    property InitialValue: Integer read FInitialValue write FInitialValue;
    property Increment: Integer read FIncrement write FIncrement;
  end;

  OrderBy = class(TCustomAttribute)
  strict private
    FMemberNames: string;
  public
    constructor Create(const AMemberNames: string);
    property MemberNames: string read FMemberNames;
  end;

  VersionAttribute = class(TCustomAttribute)
  end;

  TCascadeType = Aurelius.Mapping.Metadata.TCascadeType;
  TCascadeTypes = Aurelius.Mapping.Metadata.TCascadeTypes;
  TAssociationProp = Aurelius.Mapping.Metadata.TAssociationProp;
  TAssociationProps = Aurelius.Mapping.Metadata.TAssociationProps;

const
  CascadeTypeAll = Aurelius.Mapping.Metadata.CascadeTypeAll;
  CascadeTypeAllRemoveOrphan = Aurelius.Mapping.Metadata.CascadeTypeAllRemoveOrphan;
  CascadeTypeAllButRemove = Aurelius.Mapping.Metadata.CascadeTypeAllButRemove;

type
  Association = class(TCustomAttribute)
  private
    FProperties: TAssociationProps;
    FCascade: TCascadeTypes;
  public
    constructor Create; overload;
    constructor Create(AProperties: TAssociationProps); overload;
    constructor Create(AProperties: TAssociationProps; Cascade: TCascadeTypes); overload;
    property Properties: TAssociationProps read FProperties write FProperties;
    property Cascade: TCascadeTypes read FCascade write FCascade;
  end;

  ManyValuedAssociation = class(Association)
  private
    FMappedBy: string;
  public
    constructor Create(AProperties: TAssociationProps; Cascade: TCascadeTypes; const MappedBy: string); overload;
    property MappedBy: string read FMappedBy write FMappedBy;
  end;

  TInheritanceStrategy = Aurelius.Mapping.Metadata.TInheritanceStrategy;

  Inheritance = class(TCustomAttribute)
  private
    FStrategy: TInheritanceStrategy;
  public
    constructor Create(Strategy: TInheritanceStrategy);
    property Strategy: TInheritanceStrategy read FStrategy write FStrategy;
  end;

  TDiscriminatorType = (dtString, dtInteger);

  DiscriminatorColumn = class(TCustomAttribute)
  private
    FName: string;
    FDiscrType: TDiscriminatorType;
    FLength: Integer;
    function GetFieldType: TFieldType;
  public
    constructor Create(const Name: string; DiscriminatorType: TDiscriminatorType); overload;
    constructor Create(const Name: string; DiscriminatorType: TDiscriminatorType; Length: Integer); overload;
    property Name: string read FName write FName;
    property DiscrType: TDiscriminatorType read FDiscrType write FDiscrType;
    property Length: Integer read FLength write FLength;
    property FieldType: TFieldType read GetFieldType;
  end;

  DiscriminatorValue = class(TCustomAttribute)
  private
    FValue: TValue;
  public
    constructor Create(const Value: string); overload;
    constructor Create(Value: Integer); overload;
    property Value: TValue read FValue write FValue;
  end;

  // TODO: Add emEnum and make it work for Postgre databases
  TEnumMappingType = Aurelius.Mapping.Metadata.TEnumMappingType;

  Enumeration = class(TCustomAttribute)
  private
    FMappedType: TEnumMappingType;
    FMappedValues: TList<string>;
    function ValidateEnumValue(const Value: string): string;
  public
    constructor Create(MappedType: TEnumMappingType); overload;
    constructor Create(MappedType: TEnumMappingType; const MappedValues: string); overload;
    destructor Destroy; override;
    property MappedType: TEnumMappingType read FMappedType write FMappedType;
    property MappedValues: TList<string> read FMappedValues write FMappedValues;
  end;

  Transient = class(TCustomAttribute)
  end;

  Description = class(TCustomAttribute)
  private
    FText: string;
  public
    constructor Create(const AText: string);
    property Text: string read FText write FText;
  end;

  DBTypeMemo = class(TCustomAttribute)
  end;

  DBTypeWideMemo = class(TCustomAttribute)
  end;

procedure RegisterEntity(const Clazz: TClass);

implementation

uses
  {$IFDEF NEXTGEN}
  System.Character,
  {$ENDIF}
  Classes, TypInfo, SysUtils, Math,
  Aurelius.Mapping.Exceptions;

procedure RegisterEntity(const Clazz: TClass);
begin
  if Clazz = nil then
    raise Exception.Create('TMS Aurelius entity registration failed - class is nil');
end;

{ Table }

constructor Table.Create(const Name: string);
begin
  FName := Name;
end;

constructor Table.Create(const Name, Schema: string);
begin
  Create(Name);
  FSchema := Schema;
end;

{ Column }

constructor Column.Create(const Name: string; Properties: TColumnProps;
  Length: Integer);
begin
  Create(Name, Properties);
  FLength := Length;
end;

constructor Column.Create(const Name: string; Properties: TColumnProps;
  Precision, Scale: Integer);
begin
  Create(Name, Properties);
  FPrecision := Precision;
  FScale := Scale;
end;

constructor Column.Create(const Name: string);
begin
  FName := Name;
end;

{ UniqueConstraint }

constructor UniqueKey.Create(const Columns: string);
var
  List: TStringList;
  I: Integer;
begin
  FColumns := TList<string>.Create;

  List := TStringList.Create;
  try
    ExtractStrings([',', ';'], [' '], PChar(Columns), List);
    for I := 0 to List.Count - 1 do
      FColumns.Add(List[I]);
  finally
    List.Free;
  end;
end;

destructor UniqueKey.Destroy;
begin
  FColumns.Free;
  inherited;
end;

{ Sequence }

constructor Sequence.Create(const SequenceName: string);
begin
  FSequenceName := SequenceName;
  FInitialValue := 1;
  FIncrement := 1;
end;

constructor Sequence.Create(const SequenceName: string; InitialValue,
  Increment: Integer);
begin
  Create(SequenceName);
  FInitialValue := InitialValue;
  FIncrement := Increment;
end;

{ AbstractColumn }

constructor AbstractColumn.Create(const Name: string);
begin
  FName := Name;
end;

constructor AbstractColumn.Create(const Name: string; Properties: TColumnProps);
begin
  Create(Name);
  FProperties := Properties;
end;

{ Association }

constructor Association.Create;
begin
  FProperties := [];
  FCascade := [];
end;

constructor Association.Create(AProperties: TAssociationProps);
begin
  Create;
  FProperties := AProperties;
end;

constructor Association.Create(AProperties: TAssociationProps; Cascade: TCascadeTypes);
begin
  Create(AProperties);
  FCascade := Cascade;
end;

{ JoinColumn }

constructor JoinColumn.Create(const Name: string; Properties: TColumnProps;
  const ReferencedColumnName: string);
begin
  Create(Name, Properties);
  FReferencedColumnName := ReferencedColumnName;
end;

{ Inheritance }

constructor Inheritance.Create(Strategy: TInheritanceStrategy);
begin
  FStrategy := Strategy;
end;

{ DiscriminatorColumn }

constructor DiscriminatorColumn.Create(const Name: string;
  DiscriminatorType: TDiscriminatorType);
begin
  FName := Name;
  FDiscrType := DiscriminatorType;
  if FDiscrType = TDiscriminatorType.dtString then
    FLength := 30;
end;

constructor DiscriminatorColumn.Create(const Name: string;
  DiscriminatorType: TDiscriminatorType; Length: Integer);
begin
  Create(Name, DiscriminatorType);
  FLength := Length;
end;

function DiscriminatorColumn.GetFieldType: TFieldType;
begin
  case DiscrType of
    TDiscriminatorType.dtString:  Result := ftString;
    TDiscriminatorType.dtInteger: Result := ftInteger;
  else
    raise EUnexpectedDiscriminatorType.Create(
      GetEnumName(TypeInfo(TDiscriminatorType), Ord(DiscrType))
    );
  end;
end;

{ DiscriminatorValue }

constructor DiscriminatorValue.Create(const Value: string);
begin
  FValue := Value;
end;

constructor DiscriminatorValue.Create(Value: Integer);
begin
  FValue := Value;
end;

{ ManyValuedAssociation }

constructor ManyValuedAssociation.Create(AProperties: TAssociationProps;
  Cascade: TCascadeTypes; const MappedBy: string);
begin
  inherited Create(AProperties, Cascade);
  FMappedBy := MappedBy;
end;

{ ForeignJoinColumn }

constructor ForeignJoinColumn.Create(const Name: string;
  Properties: TColumnProps; const ReferencedColumnName: string);
begin
  Create(Name, Properties);
  FReferencedColumnName := ReferencedColumnName;
end;

{ PrimaryJoinColumn }

constructor PrimaryJoinColumn.Create(const Name: string);
begin
  FName := Name;
end;

{ Enumeration }

constructor Enumeration.Create(MappedType: TEnumMappingType);
begin
  Create(MappedType, '');
end;

constructor Enumeration.Create(MappedType: TEnumMappingType; const MappedValues: string);
var
  List: TStringList;
  I: Integer;
begin
  if (MappedType = TEnumMappingType.emChar) and (Trim(MappedValues) = '') then
    raise EInvalidEnumMapping.Create('Mapped values are required for char mapping type.');

  FMappedType := MappedType;
  FMappedValues := TList<string>.Create;

  if FMappedType <> TEnumMappingType.emInteger then
  begin
    List := TStringList.Create;
    try
      List.Duplicates := dupError;
      ExtractStrings([',', ';'], [' '], PChar(MappedValues), List);
      for I := 0 to List.Count - 1 do
        FMappedValues.Add(ValidateEnumValue(List[I]));
    finally
      List.Free;
    end;
  end;
end;

destructor Enumeration.Destroy;
begin
  FMappedValues.Free;
  inherited;
end;

function Enumeration.ValidateEnumValue(const Value: string): string;
begin
  Result := Value;
  if (FMappedType = TEnumMappingType.emChar) then
  begin
    {$IFDEF NEXTGEN}
    if (Value.Length <> 1) or not Value.Chars[0].IsUpper then
    {$ELSE}
    if (Length(Value) <> 1) or not CharInSet(Value[1], ['A'..'Z']) then
    {$ENDIF}
      raise EInvalidEnumMapping.CreateFmt(
        'Enum mapped value "%s" is invalid for type "%s". ' +
        'Tip: For chars, specify in uppercase.', [Value,
         GetEnumName(TypeInfo(TEnumMappingType), Ord(FMappedType))]);
  end;
end;

{ Id }

constructor Id.Create(const AMemberName: string; AGenerator: TIdGenerator);
begin
  FMemberName := AMemberName;
  FGenerator := AGenerator;
end;

constructor Id.Create(const AMemberName: string; AGeneratorClass: TGeneratorClass);
begin
  FMemberName := AMemberName;
  FGeneratorClass := AGeneratorClass;
end;

{ Description }

constructor Description.Create(const AText: string);
begin
  FText := AText;
end;

{ ForeignKey }

constructor ForeignKey.Create(const AName: string);
begin
  FName := AName;
end;

{ OrderBy }

constructor OrderBy.Create(const AMemberNames: string);
begin
  FMemberNames := AMemberNames;
end;

{ Model }

constructor Model.Create(const AName: string);
begin
  FName := AName;
end;

{ IdUnsavedValue }

constructor IdUnsavedValue.Create(const AValue: Integer);
begin
  FValue := AValue;
end;

constructor IdUnsavedValue.Create(const AValue: string);
begin
  FValue := AValue;
end;

{ DBIndex }

constructor DBIndex.Create(const AName, AColumns: string);
var
  List: TStringList;
  I: Integer;
begin
  FName := AName;
  FColumns := TList<string>.Create;

  List := TStringList.Create;
  try
    ExtractStrings([',', ';'], [' '], PChar(AColumns), List);
    for I := 0 to List.Count - 1 do
      FColumns.Add(List[I]);
  finally
    List.Free;
  end;
end;

destructor DBIndex.Destroy;
begin
  FColumns.Free;
  inherited;
end;

end.
