unit uModels;

interface

uses
    Aurelius.Mapping.Attributes
  , Aurelius.Types.Proxy
  , System.Generics.Collections
  ;

type
  TNode       = class;
  TRootNode   = class;
  TBranchNode = class;
  TLeafNode   = class;
  TDivision   = class;
  TDepartment = class;
  TFunction   = class;

  TNodeList = TList<TNode>;
  TNodeArray = TArray<TNode>;

  IParentNode = interface;

  IParentNode = interface
    ['{9A93DAF0-70B1-420E-B9A0-C2C2648DAFAC}']
    function GetNodes: TNodeArray;
    property Nodes: TNodeArray read GetNodes;
  end;

  [Entity, Automapping]
  [Inheritance(TInheritanceStrategy.SingleTable)]
  [DiscriminatorColumn('Kind', TDiscriminatorType.dtString, 11)]
  [Id('FId', TIdGenerator.IdentityOrSequence)]
  TNode = class(TNoRefCountObject)
  strict private
    FId: Integer;
    FParent: Proxy<TNode>;
    FName: string;
    FOrdinalValue: Integer;

    [ManyValuedAssociation([TAssociationProp.Lazy], CascadeTypeAllButRemove, 'FParent')]
    FNodes: Proxy<TNodeList>;

    function NodeObject: TNode;
  private
    function GetParent: TNode;

  strict protected
    function GetNodes: TNodeArray;
    function GetLevel: Integer; virtual;
    procedure Add(ANode: TNode);
    procedure Remove(ANode: TNode);
    procedure SetParent(ANode: TNode);
    property Parent: TNode read GetParent write SetParent;
    property Nodes: TNodeArray read GetNodes;
    property OrdinalValue: Integer read FOrdinalValue write FOrdinalValue;
  public
    constructor Create;
    destructor Destroy; override;
    function IsParent: Boolean; virtual;
    property Id: Integer read FId;
    property Level: Integer read GetLevel;
    property Name: string read FName write FName;
  end;

  [Entity, Automapping]
  [DiscriminatorValue('root')]
  TRootNode = class(TNode, IParentNode)
  strict protected
    function GetLevel: Integer; override;
  public
    procedure AddFunction(AFunction: TFunction);
    procedure AddDivision(ADivision: TDivision);
    procedure AddDepartment(ADepartment: TDepartment);
    procedure AddLeaf(ALeaf: TLeafNode);
    property Nodes;
  end;

  [Entity, Automapping]
  [DiscriminatorValue('branch')]
  TBranchNode = class(TNode, IParentNode)
  public
    procedure AddLeaf(ALeaf: TLeafNode);
    property Nodes;
    property Parent;
    property OrdinalValue;
  end;

  [Entity, Automapping]
  [DiscriminatorValue('leaf')]
  TLeafNode = class(TNode)
  public
    function IsParent: Boolean; override;
    property Parent;
    property OrdinalValue;
  end;

  [Entity, Automapping]
  [DiscriminatorValue('division')]
  TFunction = class(TBranchNode)
  public
    procedure AddDivision(ADivision: TDivision);
    procedure AddDepartment(ADepartment: TDepartment);
  end;

  [Entity, Automapping]
  [DiscriminatorValue('division')]
  TDivision = class(TBranchNode)
  public
    procedure AddDepartment(ADepartment: TDepartment);
  end;

  [Entity, Automapping]
  [DiscriminatorValue('department')]
  TDepartment = class(TBranchNode)
  public
  end;

implementation

constructor TNode.Create;
begin
  inherited Create;
  FNodes.SetInitialValue(TNodeList.Create());
end;

destructor TNode.Destroy;
begin
  FNodes.DestroyValue;
  inherited Destroy;
end;

{ TNode }

procedure TNode.Add(ANode: TNode);
begin
  FNodes.Value.Add(ANode);
end;

function TNode.GetLevel: Integer;
begin
  Result := Parent.Level + 1;
end;

function TNode.GetNodes: TNodeArray;
begin
  Result := FNodes.Value.ToArray;
end;

function TNode.GetParent: TNode;
begin
  Result := FParent.Value;
end;

function TNode.IsParent: Boolean;
begin
  Result := True;
end;

function TNode.NodeObject: TNode;
begin
  Result := Self;
end;

procedure TNode.Remove(ANode: TNode);
begin
  FNodes.Value.Remove(ANode);
end;

procedure TNode.SetParent(ANode: TNode);
begin
  FParent.Value := ANode;
end;

{ TRootNode }

procedure TRootNode.AddDepartment(ADepartment: TDepartment);
begin
  Add(ADepartment);
  ADepartment.Parent := Self;
  ADepartment.OrdinalValue := Length(Nodes);
end;

procedure TRootNode.AddDivision(ADivision: TDivision);
begin
  Add(ADivision);
  ADivision.Parent := Self;
  ADivision.OrdinalValue := Length(Nodes);
end;

procedure TRootNode.AddFunction(AFunction: TFunction);
begin
  Add(AFunction);
  AFunction.Parent := Self;
  AFunction.OrdinalValue := Length(Nodes);
end;

procedure TRootNode.AddLeaf(ALeaf: TLeafNode);
begin
  Add(ALeaf);
  ALeaf.Parent := Self;
  ALeaf.OrdinalValue := Length(Nodes);
end;

function TRootNode.GetLevel: Integer;
begin
  Result := 0;
end;

procedure TBranchNode.AddLeaf(ALeaf: TLeafNode);
begin
  Add(ALeaf);
  ALeaf.Parent := Self;
  ALeaf.OrdinalValue := Length(Nodes);
end;

{ TLeafNode }

function TLeafNode.IsParent: Boolean;
begin
  Result := False;
end;

{ TDivision }

procedure TDivision.AddDepartment(ADepartment: TDepartment);
begin
  Add(ADepartment);
  ADepartment.Parent := Self;
  ADepartment.OrdinalValue := Length(Nodes);
end;

{ TFunction }

procedure TFunction.AddDepartment(ADepartment: TDepartment);
begin
  Add(ADepartment);
  ADepartment.Parent := Self;
  ADepartment.OrdinalValue := Length(Nodes);
end;

procedure TFunction.AddDivision(ADivision: TDivision);
begin
  Add(ADivision);
  ADivision.Parent := Self;
  ADivision.OrdinalValue := Length(Nodes);
end;

initialization
  RegisterEntity(TNode);
  RegisterEntity(TRootNode);
  RegisterEntity(TBranchNode);
  RegisterEntity(TLeafNode);
  RegisterEntity(TDivision);
  RegisterEntity(TDepartment);
end.
