unit ASAttributes;

{ **************************************************************************** }
{ Rejbrand AlgoSim attribute types and functions                               }
{ Copyright © 2017 Andreas Rejbrand                                            }
{ https://english.rejbrand.se/                                                 }
{ **************************************************************************** }

interface

uses
  SysUtils, Types, Classes;

type
  EAttributeException = class(Exception);

  TASAttribute = record
    Name, Value: string;
    constructor Create(const AName, AValue: string);
    class operator Implicit(const AAttribute: TASAttribute): string;
    function MemorySize: Int64; inline;
  end;

  TASAttributes = record
  strict private
    function GetValue(const AName: string): string;
    function GetValueDef(const AName: string): string;
    function GetIsActive: boolean; inline;
    function GetIsEmpty: boolean; inline;
  public
    Data: array of TASAttribute;
    constructor Create(const AData: array of string); overload;
    constructor Create(const AData: string); overload;
    procedure SetProp(const AName, AValue: string); overload; inline;
    procedure SetProp(const AAttribute: TASAttribute); overload;
    property Value[const Name: string]: string read GetValue;
    property ValueDef[const Name: string]: string read GetValueDef write SetProp; default;
    property IsEmpty: boolean read GetIsEmpty;
    property IsActive: boolean read GetIsActive;
    function TryGetValue(const AName: string; out AValue: string): boolean;
    procedure Clear;
    procedure Remove(const Name: string);
    function Clone: TASAttributes;
    function MemorySize: Int64;
    class operator Multiply(const Left, Right: TASAttributes): TASAttributes;
    class operator Implicit(const AAttributes: TASAttributes): string;
  end;

const
  EmptyAttributes: TASAttributes = (Data: nil);

implementation

{ TASAttribute }

constructor TASAttribute.Create(const AName, AValue: string);
begin
  Name := AName;
  Value := AValue;
end;

class operator TASAttribute.Implicit(const AAttribute: TASAttribute): string;
begin
  result := Format('%s: %s', [AAttribute.Name, AAttribute.Value]);
end;

function TASAttribute.MemorySize: Int64;
begin
  result := sizeof(Self) + (Length(Name) + Length(Value)) * sizeof(char);
end;

{ TASAttributes }

procedure TASAttributes.SetProp(const AName, AValue: string);
begin
  SetProp(TASAttribute.Create(AName, AValue));
end;

procedure TASAttributes.SetProp(const AAttribute: TASAttribute);
var
  i: Integer;
begin
  for i := 0 to high(Data) do
    if SameText(AAttribute.Name, Data[i].Name) then
    begin
      Data[i].Value := AAttribute.Value;
      Exit;
    end;
  SetLength(Data, Length(Data) + 1);
  Data[high(Data)] := AAttribute;
end;

function TASAttributes.TryGetValue(const AName: string; out AValue: string): boolean;
var
  i: Integer;
begin
  for i := 0 to high(Data) do
    if SameText(Data[i].Name, AName) then
    begin
      AValue := Data[i].Value;
      Exit(true);
    end;
  result := false;
end;

constructor TASAttributes.Create(const AData: array of string);
var
  i: Integer;
begin
  if Odd(Length(AData)) then
    raise EAttributeException.Create('Odd length of attribute name-value list.');
  SetLength(Self.Data, Length(AData) div 2);
  for i := 0 to high(Self.Data) do
    Self.Data[i] := TASAttribute.Create(AData[2*i], AData[2*i + 1]);
end;

procedure TASAttributes.Clear;
begin
  SetLength(Data, 0);
end;

function TASAttributes.Clone: TASAttributes;
begin
  result.Data := Copy(Self.Data);
end;

constructor TASAttributes.Create(const AData: string);
begin
  // TODO: implement
end;

function TASAttributes.GetIsActive: boolean;
begin
  result := Length(Data) > 0;
end;

function TASAttributes.GetIsEmpty: boolean;
begin
  result := Length(Data) = 0;
end;

function TASAttributes.GetValue(const AName: string): string;
begin
  if not TryGetValue(AName, result) then
    raise EAttributeException.CreateFmt('There is no attribute named "%s".', [AName]);
end;

function TASAttributes.GetValueDef(const AName: string): string;
begin
  if not TryGetValue(AName, result) then
    result := '';
end;

class operator TASAttributes.Implicit(const AAttributes: TASAttributes): string;
var
  i: Integer;
begin
  if AAttributes.IsActive then
    result := AAttributes.Data[0];
  for i := 1 to high(AAttributes.Data) do
    result := result + '; ' + AAttributes.Data[i];
end;

function TASAttributes.MemorySize: Int64;
var
  i: Integer;
begin
  result := 0;
  for i := 0 to high(Data) do
    inc(result, Data[i].MemorySize);
end;

class operator TASAttributes.Multiply(const Left, Right: TASAttributes): TASAttributes;
var
  i: Integer;
begin
  SetLength(result.Data, 0);
  for i := 0 to high(Left.Data) do
    result.SetProp(Left.Data[i]);
  for i := 0 to high(Right.Data) do
    result.SetProp(Right.Data[i]);
end;

procedure TASAttributes.Remove(const Name: string);
var
  i: Integer;
  j: Integer;
begin
  for i := 0 to high(Data) do
    if SameText(Data[i].Name, Name) then
    begin
      for j := i to high(Data) - 1 do
        Data[j] := Data[j + 1];
      SetLength(Data, high(Data));
      Exit;
    end;
end;

end.
