{$MODE OBJFPC}
{$MODESWITCH AUTODEREF}
unit dresman;

//
//  TODO
//      Take away deprecated parts to deprecated subdirectory.
//

//
// TParameters — набор параметров, задающих создание ресурса
// THolder     — то, что хранит ресурс и счётчик ссылок
// TManager    — набор THolder'ов
// TResource   — то, с чем работает пользователь, удобные функции
//
// Parameters.GetKey: AnsiString;
// Parameters.Equal(OtherPatameters): Boolean;
// Holder.Init(Manager, Patameters);
// Holder.Done;
// Holder.IncRef
// Holder.DelRef
// Holder.Parameters: PParameters
// Holder.GetKey
// Manager.Init
// Manager.GetHolder(Parameters): PHolder
// Resource.Init(PManager, ...) <- первый параметр может быть nil, будет
//                                 использован какой-то глобальный менеджер
// Resource.Done
//
//
// Описанное выше -- слишком сложно! И требует много дублирующего кода
// на подсчёт ссылок холдера и логику IncRef / DelRef.
//
// Что хочется:
//   1. Один универсальный менеджер TManager на все ресурсы,
//      с мапой String->Pointer внутри
//   2. Базовый класс TResourceBase. В наследниках требуется только
//      грамотно объявить конструкторы, методы
//   3. Базовый класс TResourceHolder
// Требует примерно 60 строк кода обвзяки на каждый новый тип ресурса
//

interface

uses
  dstrnum,
  dobjects,
  dstrings,
  dhashmap_oldstyle,
  dhashfuncs;

type
PManager = ^TManager;
TManager = object
public type
  TIterator = THashMap.TIterator;
protected
  FHashMap: THashMap;
public
  constructor Init;
  destructor Done; virtual;
  // if returns nil -- we should load resource and add it via AddResource
  // blocks execution until resource not added
  function GetResource(const Key: AnsiString): Pointer;
  procedure AddResource(const Key: AnsiString; Resource: Pointer);
end;

PResourceHolder = ^TResourceHolder;
TResourceHolder = object
public const
  PRIORITY_REQUIRE = 0;
  PRIORITY_NORMAL = 1;
  PRIORITY_BACKGROUND = 2;
  PRIORITIES_COUNT = 3;
  PRIORITY_DEFAULT = PRIORITY_REQUIRE;
private
  FRefCount: array[0 .. PRIORITIES_COUNT - 1] of LongInt;
public
  constructor Init;
  destructor Done; virtual;
  procedure IncRef(Priority: LongInt = PRIORITY_DEFAULT); inline;
  procedure DelRef(Priority: LongInt = PRIORITY_DEFAULT); inline;
  procedure ChangeRef(PriorityBefore, PriorityAfter: LongInt); inline;
  function GetPriority: LongInt;
end;

// deprecated?
generic TResourceBase<TParameters, THolder> = object(TObject)
public type
  TPtr = ^TResourceBase;
  PHolder = ^THolder;
  PParameters = ^TParameters;
  TCreateHolder = function (Man: PManager; Parameters: PParameters): PHolder;
  constructor InitBase(Man: PManager; Parameters: PParameters; CreateHolder: TCreateHolder);
  destructor Done; virtual;
  procedure LinkToHolder(Holder: PHolder); virtual;
  procedure UnlinkFromHolder; virtual;
end;

// deprecated
generic TResMan<TParameters, THolder> = object
public type
  TPtr = ^TResMan;
  PHolder = ^THolder;
  PParameters = ^TParameters;
private type
  PList = ^TList;
  TList = record
    Holder: THolder;
    Prev: PList;
  end;
private
  // TODO use HashMap here
  FHolders: PList;
  //FHolders: array of THolder;
public
  constructor Init;
  destructor Done; virtual;
  function LoadHolder(const Parameters: TParameters): PHolder;
  procedure FreeHolder(Holder: PHolder);
end;

implementation

constructor TManager.Init;
begin
  FHashMap.Init(SizeOf(Pointer), @HashPjw32);
end;

destructor TManager.Done;
begin
  // TODO upgrade DeleteElement for HashMap
  FHashMap.Done;
end;

function TManager.GetResource(const Key: AnsiString): Pointer;
var
  Element: Pointer;
begin
  if FHashMap.Find(Key, Element) then begin
    Result := Pointer(Element^);
  end else
    Result := nil;
end;

procedure TManager.AddResource(const Key: AnsiString; Resource: Pointer);
begin
  FHashMap.Add(Key, @Resource);
end;

constructor TResourceHolder.Init;
var
  I: LongInt;
begin
  for I := 0 to PRIORITIES_COUNT - 1 do
    FRefCount[I] := 0;
end;

destructor TResourceHolder.Done;
begin
end;

procedure TResourceHolder.IncRef(Priority: LongInt = PRIORITY_DEFAULT);
begin
  Inc(FRefCount[Priority]);
end;

procedure TResourceHolder.DelRef(Priority: LongInt = PRIORITY_DEFAULT);
begin
  Dec(FRefCount[Priority]);
  // TODO this should be called by the manager, not by the holder itself
  if GetPriority = -1 then begin
    Dispose(PResourceHolder(@Self), Done);
  end;
end;

procedure TResourceHolder.ChangeRef(PriorityBefore, PriorityAfter: LongInt);
begin
  IncRef(PriorityAfter);
  DelRef(PriorityBefore);
end;

function TResourceHolder.GetPriority: LongInt;
var
  I: LongInt;
begin
  for I := 0 to PRIORITIES_COUNT - 1 do begin
    if FRefCount[I] > 0 then
      Exit(I);
  end;
  Exit(-1);
end;

constructor TResourceBase.InitBase(Man: PManager; Parameters: PParameters; CreateHolder: TCreateHolder);
var
  Holder: PHolder;
begin
  Holder := PHolder(Man^.GetResource(Parameters.Key));
  if Holder = nil then begin
    Holder := CreateHolder(Man, Parameters);
    Man^.AddResource(Parameters.Key, Pointer(Holder));
  end;
  LinkToHolder(Holder);
end;

destructor TResourceBase.Done;
begin
  UnlinkFromHolder;
end;

procedure TResourceBase.LinkToHolder(Holder: PHolder);
begin
end;

procedure TResourceBase.UnlinkFromHolder;
begin
end;

constructor TResMan.Init;
begin
  FHolders := nil;
end;

destructor TResMan.Done;
var
  Prev: PList;
begin
  while FHolders <> nil do begin
    Prev := FHolders^.Prev;
    FHolders^.Holder.Done;
    Dispose(FHolders);
    FHolders := Prev;
  end;
end;

function TResMan.LoadHolder(const Parameters: TParameters): PHolder;
var
  I: LongInt;
  List: PList;
begin
  List := FHolders;
  while List <> nil do
    if List.Holder.GetKey = Parameters.GetKey then begin
      List.Holder.AddRef;
      Exit(@List.Holder);
    end;
  New(List);
  List.Holder.Init(@Self, Parameters);
  List.Prev := FHolders;
  FHolders := List;
  Result := @List.Holder;
end;

procedure TResMan.FreeHolder(Holder: PHolder);
begin
  Holder^.DelRef;
end;

end.
