////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// CRW Glossary - thread protected (key,value) dictionary.                    //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20231222 - Created by A.K.                                                 //
////////////////////////////////////////////////////////////////////////////////

unit _crw_gloss; //  CRW Glossary - thread protected (key,value) dictionary.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, strutils, math,
 _crw_alloc, _crw_str, _crw_fio, _crw_hash, _crw_hl;

type
 TGlossary = class(TLatch)
 private
  myGloss : THashList;
  function  GetCount:Integer;
  function  GetKeys(i:Integer):LongString;
  function  GetParams(Key:LongString):LongString;
  procedure SetParams(Key:LongString; Value:LongString);
  function  GetLinks(Key:LongString):Integer;
  procedure SetLinks(Key:LongString; Value:Integer);
  function  GetDatas(Key:LongString):Double;
  procedure SetDatas(Key:LongString; Value:Double);
 public
  constructor Create;
  destructor  Destroy; override;
 protected  // Internal cache to read data faster
  property  Count:Integer                     read GetCount;
  property  Keys[i:Integer]:LongString        read GetKeys;
  property  Params[Key:LongString]:LongString read GetParams write SetParams;
  property  Links[Key:LongString]:Integer     read GetLinks  write SetLinks;
  property  Datas[Key:LongString]:Double      read GetDatas  write SetDatas;
  function  IndexOf(Key:LongString):Integer;
 public     // Read word or String
  function  ReadIniAlpha(IniFile,Section,Key:LongString;
                var aValue:LongString;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
  function  ReadIniString(IniFile,Section,Key:LongString;
                var aValue:LongString;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
 public     // Read String parameter
  function  ReadIniParam(IniFile,Section,Key:LongString;
                var aValue:LongString;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
  function  ReadIniParamDef(IniFile,Section,Key:LongString; Def:LongString;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):LongString;
 public     // Read Integer
  function  ReadIniLink(IniFile,Section,Key:LongString;
                var aValue:Integer;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
  function  ReadIniLinkDef(IniFile,Section,Key:LongString; Def:Integer;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Integer;
 public     // Read Double
  function  ReadIniData(IniFile,Section,Key:LongString;
                var aValue:Double;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
  function  ReadIniDataDef(IniFile,Section,Key:LongString; Def:Double;
                efMode:Integer=efConfigNC; svMode:Integer=svConfig):Double;
 public     // Read file Path
  function  ReadIniPath(IniFile,Section,Key,Home:LongString; var Path:LongString; Url:Boolean=true;
                efMode:Integer=efConfigFN; svMode:Integer=svConfig):Boolean;
  function  ReadIniPathDef(IniFile,Section,Key,Home,Def:LongString; Url:Boolean=true;
                efMode:Integer=efConfigFN; svMode:Integer=svConfig):LongString;
 public     // Internal calculator for mixed (complex) key
  class function ComplexKey(IniFile,Section,Key:LongString; efMode,svMode:Integer):LongString;
 end;

function SysGlossary:TGlossary;

implementation

////////////
// TGlossary
////////////

constructor TGlossary.Create;
begin
 inherited Create;
 myGloss:=NewHashList(false,HashList_DefaultHasher);
 myGloss.Master:=@myGloss;
end;

destructor TGlossary.Destroy;
begin
 Kill(myGloss);
 inherited Destroy;
end;

function  TGlossary.GetCount:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  Lock;
  try
   Result:=myGloss.Count;
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetCount');
 end;
end;

function  TGlossary.GetKeys(i:Integer):LongString;
begin
 Result:='';
 if Assigned(Self) and (i>=0) then
 try
  Lock;
  try
   Result:=myGloss.Keys[i];
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetKeys');
 end;
end;

function  TGlossary.GetParams(Key:LongString):LongString;
begin
 Result:='';
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   Result:=myGloss.KeyedParams[Key];
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetParams');
 end;
end;

procedure TGlossary.SetParams(Key:LongString; Value:LongString);
begin
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   myGloss.KeyedParams[Key]:=Value;
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetParams');
 end;
end;

function  TGlossary.GetLinks(Key:LongString):Integer;
begin
 Result:=0;
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   Result:=myGloss.KeyedLinks[Key];
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetLinks');
 end;
end;

procedure TGlossary.SetLinks(Key:LongString; Value:Integer);
begin
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   myGloss.KeyedLinks[Key]:=Value;
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetLinks');
 end;
end;

function  TGlossary.GetDatas(Key:LongString):Double;
begin
 Result:=0;
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   Result:=myGloss.KeyedDatas[Key];
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetDatas');
 end;
end;

procedure TGlossary.SetDatas(Key:LongString; Value:Double);
begin
 if Assigned(Self) and (Key<>'') then
 try
  Lock;
  try
   myGloss.KeyedDatas[Key]:=Value;
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetDatas');
 end;
end;

function  TGlossary.IndexOf(Key:LongString):Integer;
begin
 Result:=0;
 if Assigned(Self) then
 try
  Lock;
  try
   Result:=myGloss.IndexOf(Key);
  finally
   Unlock;
  end;
 except
  on E:Exception do BugReport(E,Self,'IndexOf');
 end;
end;

class function TGlossary.ComplexKey(IniFile,Section,Key:LongString; efMode,svMode:Integer):LongString;
const Sep='|';
begin
 Result:=Trim(IniFile)+Sep+Trim(Section)+Sep+Trim(Key)+Sep+IntToStr(efMode)+Sep+IntToStr(svMode);
end;

function TGlossary.ReadIniAlpha(IniFile,Section,Key:LongString; var aValue:LongString; efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
var Value,MixKey:LongString;
const Frmt='%a';
begin
 Result:=false; Value:='';
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key+Frmt,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Params[MixKey];
   Result:=True;
  end else begin
   if ReadIniFileAlpha(IniFile,Section,Key+Frmt,Value,efMode,svMode) then begin
    Value:=Trim(Value);
    Params[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then aValue:=Value;
 end;
end;

function TGlossary.ReadIniString(IniFile,Section,Key:LongString; var aValue:LongString; efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
var Value,MixKey:LongString;
const Frmt='%s';
begin
 Result:=false; Value:='';
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key+Frmt,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Params[MixKey];
   Result:=True;
  end else begin
   if ReadIniFileString(IniFile,Section,Key+Frmt,Value,efMode,svMode) then begin
    Value:=Trim(Value);
    Params[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then aValue:=Value;
 end;
end;

function TGlossary.ReadIniParam(IniFile,Section,Key:LongString; var aValue:LongString; efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
var Value,MixKey:LongString;
const Frmt='%s';
begin
 Result:=false; Value:='';
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key+Frmt,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Params[MixKey];
   Result:=True;
  end else begin
   if ReadIniFileString(IniFile,Section,Key+Frmt,Value,efMode,svMode) then begin
    Value:=Trim(Value);
    Params[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then aValue:=Value;
 end;
end;

function TGlossary.ReadIniLink(IniFile,Section,Key:LongString; var aValue:Integer; efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
var Value:Integer; MixKey:LongString;
const Frmt='%i';
begin
 Result:=false; Value:=0;
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key+Frmt,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Links[MixKey];
   Result:=True;
  end else begin
   if ReadIniFileInteger(IniFile,Section,Key+Frmt,Value,efMode,svMode) then begin
    Links[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then aValue:=Value;
 end;
end;

function TGlossary.ReadIniData(IniFile,Section,Key:LongString; var aValue:Double; efMode:Integer=efConfigNC; svMode:Integer=svConfig):Boolean;
var Value:Double; MixKey:LongString;
const Frmt='%f';
begin
 Result:=false; Value:=0;
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key+Frmt,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Datas[MixKey];
   Result:=True;
  end
  else begin
   if ReadIniFileDouble(IniFile,Section,Key+Frmt,Value,efMode,svMode) then begin
    Datas[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then aValue:=Value;
 end;
end;

function TGlossary.ReadIniPath(IniFile,Section,Key,Home:LongString; var Path:LongString; Url:Boolean=true;
              efMode:Integer=efConfigFN; svMode:Integer=svConfig):Boolean;
var Value,MixKey:LongString;
begin
 Result:=false; Value:='';
 if Assigned(Self) and (Key<>'') then begin
  MixKey:=ComplexKey(IniFile,Section,Key,efMode,svMode);
  if (IndexOf(MixKey)>=0) then begin
   Value:=Params[MixKey];
   Result:=True;
  end else begin
   if ReadIniFilePath(IniFile,Section,Key,Home,Value,Url,efMode,svMode) then begin
    Value:=Trim(Value);
    Params[MixKey]:=Value;
    Result:=True;
   end;
  end;
  if Result then Path:=Value;
 end;
end;

function TGlossary.ReadIniPathDef(IniFile,Section,Key,Home,Def:LongString; Url:Boolean=true;
              efMode:Integer=efConfigFN; svMode:Integer=svConfig):LongString;
var Path:LongString;
begin
 Path:='';
 if ReadIniPath(IniFile,Section,Key,Home,Path,Url,efMode,svMode)
 then Result:=TrimDef(Path,Def)
 else Result:=Def;
 Path:='';
end;

function TGlossary.ReadIniParamDef(IniFile,Section,Key:LongString; Def:LongString;
              efMode:Integer=efConfigNC; svMode:Integer=svConfig):LongString;
var Value:LongString;
begin
 Value:='';
 if ReadIniParam(IniFile,Section,Key,Value,efMode,svMode)
 then Result:=TrimDef(Value,Def)
 else Result:=Def;
end;

function TGlossary.ReadIniLinkDef(IniFile,Section,Key:LongString; Def:Integer;
              efMode:Integer=efConfigNC; svMode:Integer=svConfig):Integer;
var Value:Integer;
begin
 Value:=0;
 if ReadIniLink(IniFile,Section,Key,Value,efMode,svMode)
 then Result:=Value
 else Result:=Def;
end;

function TGlossary.ReadIniDataDef(IniFile,Section,Key:LongString; Def:Double;
              efMode:Integer=efConfigNC; svMode:Integer=svConfig):Double;
var Value:Double;
begin
 Value:=0;
 if ReadIniData(IniFile,Section,Key,Value,efMode,svMode)
 then Result:=Value
 else Result:=Def;
end;

//////////////
// SysGlossary
//////////////

const
 TheSysGlossary:TGlossary=nil;

function SysGlossary:TGlossary;
begin
 if not Assigned(TheSysGlossary) then begin
  TheSysGlossary:=TGlossary.Create;
  TheSysGlossary.Master:=@TheSysGlossary;
 end;
 Result:=TheSysGlossary;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_gloss;
begin
 SysGlossary.Ok;
end;

procedure Free_crw_gloss;
begin
 TheSysGlossary.Free;
end;

initialization

 Init_crw_gloss;

finalization

 Free_crw_gloss;

end.

//////////////
// END OF FILE
//////////////

