unit wStringDataLink2;

{ Feel free to use this unit as you want.
  First developped by Bernard Gisin
  This unit uses object programming to simplify the developpment
  of programs.

  This unit Creates helps associating (=linking) some information with a string.
  And this association is saved in a file.
  Three things are possible.
  1) Create a new link  string --> Data
  2) Modifiy the data linked to a string
  3) From a string, get the linked data.
  We will suppose that only few such links are wanted ( < 200 ),
  but its not important, and it works with more also.

  For example, if we give a name to a main Window or a dialog box,
  its position (and size) can be associated to this name and each time
  they are reopened, they come to the place they where last time.

  Two kind of information (=Data) can be linked.
  1) Five word32 numbers.
  2) Another string., less that 80 characters.

  Remark :  the  '#'  character is not allowed in the strings.

  - Deleting a string is not possible.
  --------------------------------------------}

// To tell FreePascal that the application is a Graphical User Interface
// i.e. is running in windows, not in text mode.
{$APPTYPE GUI}

INTERFACE
//"""""""

Uses wHashTable2, wUtiles0, wDebug0, wGlobals2;

const LengthStringDataLink = 95;
type  STRDataLink = string[LengthStringDataLink];

type
//  PStringDataLink = ^TStringDataLink;
  TStringDataLink = object
  //""""""""""""""""""""""
  oHT : THashTableW2;
  // To memorise a link between the string and its associated data.

  wHashTableSize : word;
  // Size of the associated HashTable. Set to (about) 307 in the Init constr.,
  // it can be modified just after, if wanted. But it is unusual.

  strPathName : ShortString;
  // Pathname of the file containing the strings - Data link information
  // By default it is the same as the parameter's file name, but
  // with  '_Prm.msg'  instead of  '.prm'
  // This can be changed just after the constructor has been called.

  fFileData : file of STRDataLink;

  wNbStrDataLink : word32;
  // Number of links between strings and data.
  // Do not modify.

  constructor Init(strPathNameP:ShortString);
  // strPathNameP is the filename where the linking data are stored.

  destructor Done; virtual;

  function GetString(wNum:Word32):STRDataLink; virtual;
  // return the string at the line number wNum
  // wNum = 0 => first line. (The message at the begining of the file
  // wNum = 1 => General information associated to this file
  // wNum = 2 => first string associated to data
  // wNum = 3 => first data

  function IndexCompute(strS:STRDataLink; var wIndex1V,wIndex2V:Word32):Word32; virtual;
  // From a string, using the hash table, return :
  // zero if the string is not in the table.
  // The Sub index where the string is,
  // i.e. strS = GetMsgSub(CTEMSGLink, returned value)
  // I will use the trick form Schrage, See "Numerical recipes in ..."
  // "The Art of Scientific Compution", Second Edition, from
  // William H. Press, Saul A. Teukolsky, William T. Vetterling
  // Brian P. Flannery, ISBN: 0 521 43108 5
  // page 278 in my book.

  procedure UpdateHashTable; virtual;
  // This method is called to create the links between the
  // strings and the associated information.
  // It is automatically called by  SetupStringDataLink;
  // Create the links  string - data.
  // It uses a hash to create a table :
  // To a string is associated a line number in the  .msg  file where
  // the string is strored.
  // The line just after contains the data.

  procedure SetupStringDataLink(var wError:Word32); virtual;
  // Just after calling the constructor, this method must be called.
  // Modification of  strPathName  can be made after calling the constructor
  // and before calling this method.
  // nError = 0 is OK
  // nError > 0 if the data file could not be opened or created.

  procedure SetStringDataW5(strS:STRDataLink; wD1,wD2,wD3,wD4,wD5:Word32; var wError:Word32); virtual;
  // Set the information associated to the string
  // The information is composed of five word32

  procedure SetStringDataStr(strS,strDataP:STRDataLink; var wError:Word32); virtual;
  // Set the information associated to the string
  // The information is composed of a string, at most 80 characters.

  procedure GetStringDataW5(strS:STRDataLink; var wD1,wD2,wD3,wD4,wD5,wError:Word32); virtual;
  // Get the information associated to the string
  // The information is composed of five word32

  procedure GetStringDataStr(strS:STRDataLink; var strDataV:STRDataLink; var wError:Word32); virtual;
  // Get the information associated to the string
  // The information is composed of a string, at most 80 characters.

  function GetNumberStrings:Word32; virtual;
  // Get the number of strings having an association

  procedure GetStringNumber(wNumP:Word32; var strS,strDataV:STRDataLink); virtual;
  // Get the strings and data from a number between 1 and  GetNumberStrings

  // Deleting a string and its data will come later, maybe.

  end; // TStringDataLink = object


IMPLEMENTATION
//""""""""""""

var nNotUsed : integer; // only to avoid an error message while compliling in DevPascal 1.9

constructor TStringDataLink.Init(strPathNameP:ShortString);
//=========================================================
begin
wHashTableSize:=307;
wNbStrDataLink:=0;
strPathName:=strPathNameP;
end;  // TStringDataLink.Init

destructor TStringDataLink.Done;
//==============================
begin
{$i-}
// Close the associated message file.
Close(fFileData);  IOResult; // clear IO errors.

oHT.Done;
end;  // TStringDataLink.Done

function TStringDataLink.GetString(wNum:Word32):STRDataLink;
//==========================================================
// return the string at the line number wNum
// wNum = 0 => first line. (The message at the begining of the file
// wNum = 1 => General information associated to this file
// wNum = 2 => first string associated to data
// wNum = 3 => first data
var
  strS        : STRDataLink;
  ww          : Word;
begin
// Go to the wanted line number
seek(fFileData, wNum);

// Read the line
read(fFileData, strS);
if (IOResult <> 0) then begin GetString:='#'; exit; end;

// Remove what follows the #
ww:=1;
while (ww <= length(strS)) and (strS[ww] <> '#') do inc(ww);

GetString:=Copy(strS,1,ww-1);
end;  // TStringDataLink.GetString

function TStringDataLink.IndexCompute(strS:STRDataLink; var wIndex1V,wIndex2V:Word32):Word32;
//===========================================================================================
// From a string, using the hash table, return :
// zero if the string is not in the table.
// The Sub index where the string is,
// i.e. strS = GetMsgSub(CTEMSGLink, returned value)
// I will use the trick form Schrage, See "Numerical recipes in ..."
// "The Art of Scientific Compution", Second Edition, from
// William H. Press, Saul A. Teukolsky, William T. Vetterling
// Brian P. Flannery, ISBN: 0 521 43108 5
// page 278 in my book.
const
  wM1 = word32(4294967029); // A large prime number smaller than 2^32 - 255
  wA1 =        128; // arbitrary
  wQ1 =   33554429; // = wM1 div wA1;
  wR1 =        117; // = wM1 mod wA1;
  wM2 = 4294966997; // A largest prime number smaller than wM1
  wA2 =        128; // arbitrary
  wQ2 =   33554429; // = wM2 div wA2;
  wR2 =        85; // = wM2 mod wA2;

var
  wDivQ,      // To compute a pair of index from the string strS
  wModQ,
  wAMod,
  wRDiv,
  ww,         // To loop
  wInfoL,
  wIndex1L,
  wIndex2L,
  wLinePosL // The line position in the  .sdl  file where the string is.
              : Word32;
  strVerif,   // to verify if the string has been found
  strOriginal // The original string.
              : STRDataLink;

begin
// Search the string  strS  in the  .msg  file
// using the hash table to be quicker.
// probability of repeating < 10^(-19);  thus the repeat is quite useless :-)
strOriginal:=strS;
repeat
   wIndex1L:=0;
   wIndex2L:=0;

   // Computing indexes from the string
   for ww:=1 to length(strS) do begin
      // Compute modulo a big prime
      // wIndex1L := (128* wIndex1L + ord(strS[ww])) mod wM1
      wDivQ  :=wIndex1L div wQ1;
      wModQ  :=wIndex1L mod wQ1; // < wQ1
      wAMod  :=wA1 * wModQ;      // < wA1 * wQ1 = wM1 - wR1 < 2^32
      wRDiv  :=wR1 * wDivQ;      // < wQ1 * 2^32/wQ1  < 2^32

      if (wAMod >= wRDiv) then wIndex1L:=wAMod - wRDiv + ord(strS[ww])
      else                     wIndex1L:=Word32(wM1) - wRDiv + wAMod + Word32(strS[ww]);

      // Compute modulo a big prime
      // wIndex2L := (128* wIndex2L + ord(strS[ww])) mod wM2
      wDivQ  :=wIndex2L div wQ2;
      wModQ  :=wIndex2L mod wQ2; // < wQ2
      wAMod  :=wA2 * wModQ;      // < wA2 * wQ2 = wM2 - wR2 < 2^32
      wRDiv  :=wR2 * wDivQ;      // < wQ2 * 2^32/wQ2  < 2^32

      if (wAMod >= wRDiv) then wIndex2L:=wAMod - wRDiv + ord(strS[ww])
      else                     wIndex2L:=Word32(wM2) - wRDiv + wAMod + Word32(strS[ww]);
      end; // for ww:=1 to length(strS) do begin

   // If the string is in the table, it must return a non zero number
   wLinePosL:=Word32(oHT.GetPointerW2(wIndex1L, wIndex2L, wInfoL));

   if (wLinePosL > 0) then begin
      // Verify that the indexes correspond to the right string
      strVerif:=GetString(wLinePosL);
      end;

   strS:=strS + '*'; // in case of string collision, (very very very unlikely)
   until (wLinePosL = 0) // The string doesn't exist in the table
      or (strVerif = strOriginal); // the string has been found

wIndex1V:=wIndex1L;
wIndex2V:=wIndex2L;
IndexCompute:=wLinePosL;
end;  // TStringDataLink.IndexCompute

procedure TStringDataLink.UpdateHashTable;
//========================================
// Create the links  string - data.
// It uses a hash to create a table :
// To a string is associated a line number in the  .msg  file where
// the string is strored.
// The line just after contains the data.
var
  strS        : STRDataLink;
  wIndex1L,
  wIndex2L,
  ww          : Word32;
begin
FileMode:=0;  // read only
reset(fFileData);
FileMode:=2;  // read / write

ww:=2;
wNbStrDataLink:=0;
strS:=GetString(2); // first line of data
while (strS <> '#') and (ww < 20) do begin
   inc(wNbStrDataLink);  // = ww/2
   IndexCompute(strS, wIndex1L, wIndex2L);
   oHT.AddPointerW2(wIndex1L, wIndex2L, 0, Pointer(2*wNbStrDataLink));

   inc(ww,2);
   strS:=GetString(ww); // ww = 2*wNbStrDataLink
   end;

IOResult; // Clear the IO errors
end;  // TStringDataLink.UpdateHashTable

procedure TStringDataLink.SetupStringDataLink(var wError:Word32);
//===============================================================
var
  strS        : STRDataLink;
begin
oHT.Init(wHashTableSize);

// a name must be given if you want to use a filename
if (strPathName = '') then begin wError:=1001; exit; end;

// Opens the parameter message file, at the bottom
// (100 files down, if 100 .msg files are opened)
// Remember a pointer used to close the file at destruction.
{$i-}
assign(fFileData, strPathName);
FileMode:=2;
reset(fFileData);

// Test if the file exist.
seek(fFileData, 1);  wError:=IOResult;

if (wError <> 0) then begin
   // The file doesn't exist, thus create it.
   rewrite(fFileData);
   strS:='This is a starting list of string - Data links. Do NOT MODIFY !#-------------------------------';
   strS[LengthStringDataLink-1]:=#13;
   strS[LengthStringDataLink  ]:=#10;
   write(fFileData, strS);

   strS:='100 = Version; Data could follow here. #--------------------------------------------------------';
   strS[LengthStringDataLink-1]:=#13;
   strS[LengthStringDataLink  ]:=#10;
   write(fFileData, strS);

   FileMode:=0;  // read only
   reset(fFileData);
   FileMode:=2;  // read / write

   wError:=IOResult;
   exit;
   end
else begin
   UpdateHashTable; // Create the links String - Data.
                   // In the future, it could be memorised in a file.
   end;
end;  // TStringDataLink.SetupStringDataLink

procedure TStringDataLink.SetStringDataW5(strS:STRDataLink; wD1,wD2,wD3,wD4,wD5:Word32; var wError:Word32);
//=========================================================================================================
// To a given string, I want to associate 5 word32, and remember it.
var
  wLinePosL,
  wIndex1L,
  wIndex2L
              : Word32;
  strMore     : STRDataLink;

begin
wError:=0;
// Search the string in the .msg file corresponding,
wLinePosL:=IndexCompute(strS, wIndex1L, wIndex2L);
strMore:='#----------------------------------------------------------------------------------------------';

FileMode:=2;  // read / write
reset(fFileData);

if (wLinePosL = 0) then begin
   // string  strS  doesn't exist in the file. Add it
   // Append a line beggining with the wanted number (0)
   // being bellow the number CTEMSGLink
   inc(wNbStrDataLink);
   strS:=strS + strMore;
   strS[LengthStringDataLink-1]:=#13;
   strS[LengthStringDataLink  ]:=#10;
   Seek(fFileData, 2*wNbStrDataLink); // go to the end of file, to add a new string
   write(fFileData, strS);

   // Memorise the link in the hash table
   oHT.AddPointerW2(wIndex1L, wIndex2L, 0, Pointer(2*wNbStrDataLink));
   end
else begin
   // Here we found the string, update the associated data
   Seek(fFileData, wLinePosL+1); // go to line after the string reference line, to modify it.
   end;

// one line below, put the associated data.
strS:= IntToStr(wD1,0) + ' ' + IntToStr(wD2,0) + ' ' + IntToStr(wD3,0) + ' '
     + IntToStr(wD4,0) + ' ' + IntToStr(wD5,0);
strS:=strS + strMore;
strS[LengthStringDataLink-1]:=#13;
strS[LengthStringDataLink  ]:=#10;
write(fFileData, strS);

FileMode:=0;  // read only
reset(fFileData);
FileMode:=2;  // read / write

wError:=IOResult;
end;  // TStringDataLinkW5.SetStringDataW5

procedure TStringDataLink.SetStringDataStr(strS,strDataP:STRDataLink; var wError:Word32);
//=======================================================================================
// To a given string, I want to associate a string, and remember it.
var
  wLinePosL,
  wIndex1L,
  wIndex2L
              : Word32;
  strMore     : STRDataLink;

begin
// Search the string in the .msg file corresponding,
wLinePosL:=IndexCompute(strS, wIndex1L, wIndex2L);
strMore:='#----------------------------------------------------------------------------------------------';

FileMode:=2;  // read / write
reset(fFileData);

if (wLinePosL = 0) then begin
   // string  strS  doesn't exist in the file. Add it
   // Append a line beggining with the wanted number (0)
   // being bellow the number CTEMSGLink
   inc(wNbStrDataLink);
   strS:=strS + strMore;
   strS[LengthStringDataLink-1]:=#13;
   strS[LengthStringDataLink  ]:=#10;
   Seek(fFileData, 2*wNbStrDataLink); // go to the end of file, to add a new string
   write(fFileData, strS);

   // Memorise the link in the hash table
   oHT.AddPointerW2(wIndex1L, wIndex2L, 0, Pointer(2*wNbStrDataLink));
   end
else begin
   // Here we found the string, update the associated data
   Seek(fFileData, wLinePosL+1); // go to right line, to modify it.
   end;

// Add a new line, or update an existing one.
strDataP:=strDataP + strMore;
strDataP[LengthStringDataLink-1]:=#13;
strDataP[LengthStringDataLink  ]:=#10;
write(fFileData, strDataP);

FileMode:=0;  // read only
reset(fFileData);
FileMode:=2;  // read / write

wError:=IOResult;
end;  // TStringDataLinkW5.SetStringDataStr

procedure TStringDataLink.GetStringDataW5(strS:STRDataLink; var wD1,wD2,wD3,wD4,wD5,wError:Word32);
//=================================================================================================
// wError = 1 if not found
var
  wLinePosL,
  wIndex1L,
  wIndex2L    : Word32;
  nEnd        : integer;

begin
// Compute indexes from the string.
wLinePosL:=IndexCompute(strS, wIndex1L, wIndex2L);

if (wLinePosL = 0) then begin
   // String not found
   wError:=1;
   exit;
   end;

// Get the associated data
strS:=GetString(wLinePosL+1);

if (strS = '#') then begin wError:=2; exit; end; // not normal, since the ref. string
                                                 // has been found
wD1:=GetIntValue(   1, strS, nEnd);
wD2:=GetIntValue(nEnd, strS, nEnd);
wD3:=GetIntValue(nEnd, strS, nEnd);
wD4:=GetIntValue(nEnd, strS, nEnd);
wD5:=GetIntValue(nEnd, strS, nEnd);
wError:=0;
end;  // TStringDataLink.GetStringDataW5

procedure TStringDataLink.GetStringDataStr(strS:STRDataLink; var strDataV:STRDataLink; var wError:Word32);
//========================================================================================================
var
  wLinePosL,
  wIndex1L,
  wIndex2L    : Word32;

begin
// Compute indexes from the string.
wLinePosL:=IndexCompute(strS, wIndex1L, wIndex2L);

if (wLinePosL = 0) then begin
   // String not found
   wError:=1;
   exit;
   end;

// Get the associated data
strDataV:=GetString(wLinePosL+1);

if (strDataV = '#') then begin wError:=2; exit; end; // not normal, since the ref. string
                                                    // has been found
wError:=0;
end;  // TStringDataLink.GetStringDataW5

function TStringDataLink.GetNumberStrings:Word32;
//===============================================
begin
GetNumberStrings:=wNbStrDataLink;
end;  // TStringDataLink.GetNumberStrings

procedure TStringDataLink.GetStringNumber(wNumP:Word32; var strS,strDataV:STRDataLink);
//=====================================================================================
// If the number  wNumP  is bigger than  wNbStrDataLink, then
// an '#' will be returned in both strings.
begin
strS     :=GetString(2*wNumP);
strDataV :=GetString(2*wNumP+1);
end;  // TStringDataLink.GetStringNumber

end. // unit wStringDataLink2.pp

