unit Foo;
// Check Fpc version
{$ifdef VER1}
   {$fatal You need at least FPC 2.0.0}
{$endif}


// Pascal object
{$mode objfpc}

{$H+}

// Link
{$smartlink on}

// Inline
{$inline on}

// Allow macros
{$macro on}
// All C operators style
{$coperators on}

{$debuginfo on}

// Assertions
{$assertions on}
// Overflow checks
{$Q+}
// IO Checking
{$I+}
// Range check error
{$rangechecks on}
// Checks that Strings types are the same
// {$V+}
// Check if self is nil
{$objectchecks on}

interface
uses
  Contnrs;



type

  IMCollection = Interface
  end;

  TMCollectionItem = class(TObject)
  private
    FParent: IMCollection;
    FEnabled: Boolean;
    FID: UTF8String;
    FDefaultCaption: UTF8String;
    FDefault: Boolean;
    procedure DoInit(AParent: IMCollection; const AID: UTF8String);inline;
    procedure SetDefault(const Value: Boolean);
  public
    constructor Create(AParent: IMCollection);
    constructor Create(AParent: IMCollection; const AID: UTF8String);

    property Parent: IMCollection read FParent;
    property Enabled: boolean read FEnabled write FEnabled;
    property DefaultCaption: UTF8String read FDefaultCaption write FDefaultCaption;
    property ItemByDefault: Boolean read FDefault write SetDefault;
  end;




  // Warning : _T must a descendant of TMCollectionItem
  generic TMCollection<_T> = class(TObject,IMCollection)
  private
    //FItems: TObjectList;
    FDefaultItem: _T;
  private
    procedure SetDefaultToItem(Item: TMCollectionItem);
    procedure SetDefaultToNil;
    procedure SetDefault(Item: _T);
  public
    constructor Create;
    destructor Destroy;override;

    function Add(const ID: UTF8String; const AllowDupplicates: boolean = false): _T;

    function Find(const i: Integer): _T;inline;
    function Find(const id: UTF8String): _T;
    property Item[const id: UTF8String] : _T read Find; 

    property ItemByDefault: _T read FDefaultItem write FDefaultItem;

  end;

  TTestMCollection = specialize TMCollection<TMCollectionItem>;



  //TMCollection = class;
implementation

procedure TMCollectionItem.DoInit(AParent: IMCollection; const AID: UTF8String);inline;
begin
  Assert(Assigned(AParent));
  FParent := AParent;
  FID := AID;
  FEnabled := True;
  FDefault := False;
  FDefaultCaption := '';
end;

constructor TMCollectionItem.Create(AParent: IMCollectionItems);
begin
  DoInit(AParent, '');
end;

constructor TMCollectionItem.Create(AParent: IMCollectionItems; const AID: UTF8String);
begin
  DoInit(AParent, AID);
end;

destructor TMCollectionItem.Destroy;
begin
  inherited Destroy;
end;

procedure TMCollectionItem.SetDefault(const Value: Boolean);
begin
  if Value then
    FParent.ItemByDefault := Self
  else begin
    if FParent.ItemByDefault = Self then
      FParent.ItemByDefault := nil;
  end;
end;



constructor TMCollection.Create;
begin
  FItems := TObjectList.Create(true);
  FDefaultLng := nil;
end;

destructor TMCollection.Destroy;
begin
  FItems.Free;
  inherited Destroy;
end;

function TMCollection.Find(const i: Integer): _T;inline;
begin
  result := _T(FItems[i]);
end;
    
function TMCollection.Find(const id: UTF8String): _T;
var i: Integer;
begin
  // TODO Use a Hash table instead
  for i := 0 to FItems.Count - 1 do
  begin
    result := _T(FItems[i]);
    if result.ID = id then
      Exit;
  end;
  result := nil;
end;

function TMCollection.Add(const ID: UTF8String; const AllowDupplicates: boolean): _T;
begin
  if not AllowDupplicates  then
  begin
    result := Find(ID);
    if Assigned(result) then
      Exit;
  end;
  result := TMCollectionItem.Create(Self, ID);
  FItems.Add(result);
end;

procedure TMCollection.SetDefaultToItem(Item: TMCollectionItem);
var i: Integer;
    o: FDefaultItem;
begin
  FDefaultItem := nil;
  for i := 0 to FItems.Count - 1 do
  begin
    o := TMCollectionItem(FItems[i]);
    if o = Item then begin
      o.FDefault := True;
      FDefaultItem := o;
    end else
      o.FDefault := False;
  end;
end;

procedure TMCollection.SetDefaultToNil;
var i: Integer;
begin
  FDefaultItem := nil;
  for i := 0 to FItems.Count - 1 do
    TMCollectionItem(FItems[i]).FDefault := False;
end;


procedure TMCollection.SetDefault(Item: T);
begin
  if Assigned(Item) and ((Item as TMCollectionItem).Parent = Self) then
    SetDefaultToItem(Item as TMCollectionItem)
  else
    SetDefaultToNil;
end;



end.
