{*********************************************************

 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: keywords2

 TKeyword, TKeywordItem

*********************************************************}
unit keywords2;

interface

uses
 Windows, SysUtils, share, constants, stypes;

{$I defines.pas}

const
 ITEMS_PER_KEYWORD         = 29; // *4 + 12 = 256 bytes
 KEYWORD_MOVE_ITEMS        = 10; // if number of items in TKeywordItem is less than this all items are moved to another incomplete TKeywordItem - to make lower memory usage.

type
 PKeywordItem = ^TKeywordItem;
 TKeywordItem = packed record // one item in TKeyword
   share       : Array[0..ITEMS_PER_KEYWORD-1] of PShare;
   used        : Integer; // number of used items
   prev, next  : PKeywordItem;
 end;
 PKeyword = ^TKeyword;
 TKeyword = packed record // structure that manages one keyword
   count       : Integer; // number of items in list
   total       : Integer; // number of files matching keyword (might not be all in this list)
   available   : Integer; // number of available slots in incomplete list
   keyword     : String; // keyword
   kwlength    : Byte; // keyword length. for faster comparison
   id          : Byte; // file ID. for faster detection of list
   complete    : PKeywordItem; // pointer to first full item
   incomplete  : PKeywordItem; // pointer to first incomplete item
   prev, next  : PKeyword; // pointer to previous and next PKeyword items in global list
 end;
 PKeywordList = ^TKeywordList;
 TKeywordList = packed record // global list of all keywords
   count       : Integer; // number of keywords in list
   first       : PKeyword; // pointer to first item
 end;

function  KWList_CreateList: PKeywordList;
procedure KWList_FreeList(list: PKeywordList);
procedure KWList_ClearList(list: PKeywordList);
function  KWList_AddItem(list: PKeywordList; id: Byte; keyword: String): PKeyword;
function  KWList_FindItem(list: PKeywordList; keyword: String): PKeyword;
procedure KWList_DeleteItem(list: PKeywordList; keyword: PKeyword);
procedure KWList_ClearItem(list: PKeywordList; keyword: PKeyword);
function  KWList_AddShare(keyword: PKeyword; share: PShare): PKeywordItem;
procedure KWList_DeleteShare(keyword: PKeyword; item: PKeywordItem; share: PShare);

implementation

uses
 keywords, handler, vars;

function  KWList_CreateList: PKeywordList;
begin
 result:=AllocMem(sizeof(TKeywordList));
 result^.first:=nil;
 result^.count:=0;
end;

procedure KWList_FreeList(list: PKeywordList);
begin
 KWList_ClearList(list);
 FreeMem(list,sizeof(TKeywordList));
end;

procedure KWList_ClearList(list: PKeywordList);
var
 item, next: PKeyword;
 item2, next2: PKeywordItem;
begin
 tmp_pos:=1282;
 item:=list^.first;
 while item<>nil do
 begin
   next:=item^.next;
   tmp_pos:=1283;
   item2:=item^.complete;
   while item2<>nil do
   begin
     next2:=item2^.next;
     FreeMem(item2,sizeof(TKeywordItem));
     item2:=next2;
   end;
   tmp_pos:=1284;
   item2:=item^.incomplete;
   while item2<>nil do
   begin
     next2:=item2^.next;
     FreeMem(item2,sizeof(TKeywordItem));
     item2:=next2;
   end;
   item^.keyword:='';
   tmp_pos:=1285;
   FreeMem(item,sizeof(TKeyword));
   item:=next;
 end;
 list^.count:=0;
 list^.first:=nil;
 tmp_pos:=1286;
end;

function  KWList_AddItem(list: PKeywordList; id: Byte; keyword: String): PKeyword;
begin
 tmp_pos:=1287;
 result:=AllocMem(sizeof(TKeyword));
 result^.count:=0;
 result^.total:=0;
 Pointer(result^.keyword):=nil;
 result^.keyword:=keyword;
 result^.kwlength:=Length(keyword);
 result^.id:=id;
 result^.complete:=nil;
 result^.incomplete:=nil;
 result^.available:=0;
 result^.next:=list^.first;
 if list^.first<>nil then
  list^.first^.prev:=result;
 result^.prev:=nil;
 list^.first:=result;
 tmp_pos:=1288;
end;

function  KWList_FindItem(list: PKeywordList; keyword: String): PKeyword;
var
 len: Byte;
begin
 tmp_pos:=1289;
 result:=list^.first;
 len:=Length(keyword);
 while result<>nil do
 begin
   if result^.kwlength=len then
    if result^.keyword=keyword then
     exit;
   result:=result^.next;
 end;
 tmp_pos:=1290;
end;

procedure KWList_DeleteItem(list: PKeywordList; keyword: PKeyword);
var
 item, next: PKeywordItem;
begin
 tmp_pos:=1291;
 if keyword^.prev=nil then list^.first:=keyword^.next
 else keyword^.prev^.next:=keyword^.next;
 if keyword^.next<>nil then keyword^.next^.prev:=keyword^.prev;
 dec(list^.count);
 tmp_pos:=1292;
 item:=keyword^.complete;
 while item<>nil do
 begin
   next:=item^.next;
   FreeMem(item,sizeof(TKeywordItem));
   item:=next;
 end;
 tmp_pos:=1293;
 item:=keyword^.incomplete;
 while item<>nil do
 begin
   next:=item^.next;
   FreeMem(item,sizeof(TKeywordItem));
   item:=next;
 end;
 tmp_pos:=1294;
 keyword^.keyword:='';
 FreeMem(keyword,sizeof(TKeyword));
 tmp_pos:=1295;
end;

procedure KWList_ClearItem(list: PKeywordList; keyword: PKeyword);
var
 item, next: PKeywordItem;
begin
 tmp_pos:=1296;
 if not running then
 begin // if server is shutting down - quickly freing variables
  keyword^.count:=0;
  keyword^.complete:=nil;
  keyword^.incomplete:=nil;
  keyword^.total:=0;
  keyword^.available:=0;
  exit;
 end;
 tmp_pos:=1297;
 item:=keyword^.complete;
 while item<>nil do
 begin
   next:=item^.next;
   FreeMem(item,sizeof(TKeywordItem));
   item:=next;
 end;
 tmp_pos:=1298;
 item:=keyword^.incomplete;
 while item<>nil do
 begin
   next:=item^.next;
   FreeMem(item,sizeof(TKeywordItem));
   item:=next;
 end;
 tmp_pos:=1299;
 keyword^.total:=0;
 keyword^.count:=0;
 keyword^.available:=0;
 keyword^.complete:=nil;
 keyword^.incomplete:=nil;
end;

function  KWList_AddShare(keyword: PKeyword; share: PShare): PKeywordItem;
var
 i: Integer;
begin
 tmp_pos:=1300;
//DebugLog(' Adding PShare to ('+IntToStr(keyword^.id)+') '+keyword^.keyword+' count='+IntToStr(keyword^.count)+' total='+IntToStr(keyword^.total)+' available='+IntToStr(keyword^.available),true);
 if (keyword^.count>=MAX_KEYWORD_ITEMS) and (keyword^.available=0) then
 begin // adding only reference
   inc(keyword^.total);
   result:=nil;
//DebugLog(' added only reference',true);
 end
 else
 begin
   if keyword^.available>0 then
   begin // adding to current item
//DebugLog(' adding to existing record. available='+IntToStr(keyword^.available),true);
     tmp_pos:=1301;
     result:=keyword^.incomplete;
     if result=nil then
     begin
       DebugLog('Warning: KWList_AddShare(): result->incomplete==NULL while keyword->availabe='+IntToStr(keyword^.available)+'. Please report this error to developers!!!');
       exit;
     end;
     for i:=0 to ITEMS_PER_KEYWORD-1 do
     if result^.share[i]=nil then
     begin
       tmp_pos:=1302;
       result^.share[i]:=share;
       dec(keyword^.available);
       inc(result^.used);
       inc(keyword^.total);
       inc(keyword^.count);
       if result^.used=ITEMS_PER_KEYWORD then
       begin // all slots in result are taken - move to complete list
         tmp_pos:=1303;
         keyword^.incomplete:=result^.next;
         if result^.next<>nil then result^.next^.prev:=nil;
         result^.next:=keyword^.complete;
         if result^.next<>nil then result^.next^.prev:=result;
         keyword^.complete:=result;
       end;
       exit;
     end;
     // if program got here then some weird error occured when .available<>0
     tmp_pos:=1304;
     DebugLog('Warning: KWList_AddShare(): cannot find empty item. Please report this error to developers!!!');
   end;
   tmp_pos:=1305;
//DebugLog(' creating new record',true);
   result:=AllocMem(sizeof(TKeywordItem));
   result^.next:=keyword^.incomplete;
   result^.prev:=nil;
   result^.share[0]:=share;
   for i:=1 to ITEMS_PER_KEYWORD-1 do
    result^.share[i]:=nil;
   result^.used:=1;
   result^.next:=nil;
   result^.prev:=nil;
   keyword^.incomplete:=result;
   keyword^.available:=ITEMS_PER_KEYWORD-1;
   inc(keyword^.total);
   inc(keyword^.count);
 end;
 tmp_pos:=1306;
end;

procedure KWList_DeleteShare(keyword: PKeyword; item: PKeywordItem; share: PShare);
var
 i, j, k, l, pos, oldcount, words: Integer;
 p: PKeywordItem;
 found: Boolean;
begin
 tmp_pos:=1307;
 if item=nil then
 begin
   dec(keyword.total);
   if (keyword^.count=0) and (keyword^.total=0) then
    KWList_DeleteItem(db_keywords[keyword^.id,GetKeywordIndex(keyword^.keyword),keyword^.kwlength],keyword);
   exit;
 end;
 oldcount:=item^.used;
 tmp_pos:=1308;
 for i:=0 to ITEMS_PER_KEYWORD-1 do
 if item^.share[i]=share then
 begin
   tmp_pos:=1309;
   item^.share[i]:=nil;
   dec(item^.used);
   dec(keyword^.total);
   dec(keyword^.count);
   inc(keyword^.available);
   if oldcount=ITEMS_PER_KEYWORD then
   begin // move from complete list to incomplete list
     tmp_pos:=1310;
     if item^.prev<>nil then item^.prev^.next:=item^.next
     else keyword^.complete:=item^.next;
     if item^.next<>nil then item^.next^.prev:=item^.prev;
     item^.prev:=nil;
     item^.next:=keyword^.incomplete;
     if keyword^.incomplete<>nil then keyword^.incomplete^.prev:=item;
     keyword^.incomplete:=item;
     exit;
   end;
   if item^.used=0 then
   begin // delete item
     tmp_pos:=1311;
     dec(keyword^.available,ITEMS_PER_KEYWORD);
     if item^.prev<>nil then item^.prev^.next:=item^.next
     else keyword^.incomplete:=item^.next;
     if item^.next<>nil then item^.next^.prev:=item^.prev;
     FreeMem(item,sizeof(TKeywordItem));
     if (keyword^.count=0) and (keyword^.total=0) then
      KWList_DeleteItem(db_keywords[keyword^.id,GetKeywordIndex(keyword^.keyword),keyword^.kwlength],keyword);
     exit;
   end;
   if (item^.used<=KEYWORD_MOVE_ITEMS) and
    (keyword^.available>ITEMS_PER_KEYWORD) then
   begin // moving to another items
    for l:=0 to ITEMS_PER_KEYWORD-1 do
     if item^.share[l]<>nil then
     begin
       // finding other free item
       p:=keyword^.incomplete;
       found:=false;
       while not found do
       begin
        if p=nil then found:=true
        else if p=item then p:=p^.next
        else found:=true;
       end;
       if p=nil then exit; // some kind of error - cannot find empty item
       pos:=-1;
       for j:=0 to ITEMS_PER_KEYWORD-1 do
        if pos=-1 then
         if p^.share[j]=nil then pos:=j;
       if pos=-1 then exit; // some kind of weird error - cannot find empty slot
       // moving item
       p^.share[pos]:=item^.share[l];
       inc(p^.used);
       item^.share[l]:=nil;
       dec(item^.used);
       // moving associated TShare
       share:=p^.share[pos];
       words:=opNumWords(share^.options);
       for k:=0 to words-1 do
        if share^.keywords^[k*2+1]=item then
         share^.keywords^[k*2+1]:=p;
       // if p is full moving to complete list
       if p^.used=ITEMS_PER_KEYWORD then
       begin
         if p^.prev=nil then
          keyword^.incomplete:=p^.next
         else
          p^.prev^.next:=p^.next;
         if p^.next<>nil then
          p^.next^.prev:=p^.prev;
         p^.next:=keyword^.complete;
         if keyword^.complete<>nil then
          keyword^.complete^.prev:=p;
         keyword^.complete:=p;
         p^.prev:=nil;
       end;
       // if item is empty freing it
       if item^.used=0 then
       begin
         dec(keyword^.available,ITEMS_PER_KEYWORD);
         if item^.prev<>nil then item^.prev^.next:=item^.next
         else keyword^.incomplete:=item^.next;
         if item^.next<>nil then item^.next^.prev:=item^.prev;
         FreeMem(item,sizeof(TKeywordItem));
         if (keyword^.count=0) and (keyword^.total=0) then
          KWList_DeleteItem(db_keywords[keyword^.id,GetKeywordIndex(keyword^.keyword),keyword^.kwlength],keyword);
        exit;
       end;
     end;
   end;
   exit;
 end;
 DebugLog('Warning: KWList_DeleteShare(): cannot find item. Please report this error to developers!!!');
end;


end.
