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

 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.
