/*

File: index.c
Author: Neil Cafferkey
Copyright (C) 2000 Neil Cafferkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.

*/

#include "viewer.h"
#include <dos/dos.h>
#include <dos/dostags.h>
#include <exec/memory.h>
#include <string.h>

#include "index_protos.h"
#include "index_record_protos.h"
#include "general_protos.h"
#include <proto/exec.h>


const TEXT FIRST_REC_STRING[]="%FIRST-REC%";
#define FIRST_REC_SIZE 11


static UBYTE *DataSearch(ULONG length_1,ULONG length_2,UBYTE *buf_1,
   UBYTE *buf_2);
static BOOL DataEqual(ULONG length,UBYTE *buf_1,UBYTE *buf_2);


/* Function: CreateIndex
 * =====================
 * Creates an Index.
 */

Index CreateIndex(TEXT *master_file_name,TEXT *index_file_name)
{
   Index index;
   struct FileInfoBlock *temp_fib;
   BPTR master_file;


   if((index=(Index)AllocMem(sizeof(Index_imp),MEMF_CLEAR))!=NULL)
   {

      /* Open the main index file */

      if(!(index->index_file=Open(index_file_name,MODE_OLDFILE)))
         ReportError(NULL,ERROR_REPORT_FILE,index_file_name,0);

      /* Open the master index file */

      master_file=Open(master_file_name,MODE_OLDFILE);

      /* Copy master index file into memory */

      if(master_file)
      {

         if((temp_fib=(struct FileInfoBlock *)AllocDosObjectTags(
            DOS_FIB,TAG_END))!=NULL)
         {
            /* Get master file size */

            ExamineFH(master_file,temp_fib);
            index->block_count=temp_fib->fib_Size/MASTER_REF_SIZE;
            FreeDosObject(DOS_FIB,temp_fib);

            /* Allocate memory for master file data */

            index->master_index=(UBYTE *)AllocMem(index->block_count
               *MASTER_REF_SIZE,MEMF_ANY);

            /* Read link data into memory */

            if(index->master_index!=NULL)
               Read(master_file,index->master_index,index->block_count
                  *MASTER_REF_SIZE);
            else
            {
               ReportError(NULL,ERROR_REPORT_MEM,NULL,0);
            }

         }
         else
         {
            ReportError(NULL,ERROR_REPORT_MEM,NULL,0);
         }

         Close(master_file);
      }
      else
      {
         ReportError(NULL,ERROR_REPORT_FILE,master_file_name,0);
      }

      /* Check if there were any errors */

      if((index->index_file==NULL)||(index->master_index==NULL))
      {
         KillIndex(index);
         index=NULL;
      }

   }
   else
   {
      ReportError(NULL,ERROR_REPORT_MEM,NULL,0);
   }

   return index;
}



/* Function: FindIndexRecords
 * ==========================
 * Searches for records within an index.
 */

struct List *FindIndexRecords(Index index,TEXT *search_string)
{
   IndexRecord index_rec;
   UWORD first_block,last_block,mid;
   WORD low,high;
   UBYTE *p,*q,*index_buffer;
   struct List *rec_list;
   BOOL success=TRUE;

   /* Create a new list to contain the results */

   rec_list=AllocMem(sizeof(struct List),MEMF_PUBLIC);
   if(rec_list!=NULL)
   {
      NewList(rec_list);

      /* Convert search string to uppercase */

      for(p=search_string;*p!='\0';p++)
         *p=toupper(*p);

      /* Do a binary search to find the first block needed */

      p=index->master_index;
      low=-1;
      high=index->block_count-1;

      while(high-low!=1)
      {
         mid=low+(high-low)/2;
         for(q=p+mid*MASTER_REF_SIZE;*q!=0;q-=MASTER_REF_SIZE);

         if(strncmp(search_string,q+1,MASTER_REF_SIZE-1)<=0)
            high=mid;
         else
            low=mid;

      }

      first_block=high;

      /* Do a binary search to find the last block needed */

      low=-1;
      high=index->block_count-1;

      while(high-low!=1)
      {
         mid=low+(high-low)/2;
         for(q=p+mid*MASTER_REF_SIZE;*q!=0;q-=MASTER_REF_SIZE);

         if(strncmp(search_string,q+1,MASTER_REF_SIZE-1)<0)
            high=mid;
         else
            low=mid;

      }

      last_block=high;


      /* Allocate the index buffer */

      index_buffer=AllocMem((last_block-first_block+1)*INDEX_BLOCK_SIZE,
         MEMF_ANY);

      if(index_buffer)
      {

         /* Read relevant index blocks into memory */

         Seek(index->index_file,first_block*INDEX_BLOCK_SIZE,
            OFFSET_BEGINNING);
         Read(index->index_file,index_buffer,(last_block-first_block+1)
            *INDEX_BLOCK_SIZE);

         /* Find first-record marker */

         p=DataSearch(FIRST_REC_SIZE,2*INDEX_BLOCK_SIZE,
            FIRST_REC_STRING,index_buffer);

         /* Find first matching record */

         p+=FIRST_REC_SIZE;
         while(strcmp(p+10,search_string)<0)
            p+=GetLittleEndianUWord(p);

         /* Put all matching records into the list */

         while(strcmp(p+10,search_string)==0)
         {
            index_rec=CreateIndexRecord(p);
            if(index_rec!=NULL)
               AddTail(rec_list,&index_rec->node);
            p+=GetLittleEndianUWord(p);
         }

         FreeMem(index_buffer,(last_block-first_block+1)*INDEX_BLOCK_SIZE);
      }
      else
      {
         ReportError(NULL,ERROR_REPORT_MEM,NULL,0);
         success=FALSE;
      }
   }
   else
   {
      ReportError(NULL,ERROR_REPORT_MEM,NULL,0);
      success=FALSE;
   }

   return rec_list;
}



/* Function: KillIndexResults
 * ==========================
 */

VOID KillIndexResults(struct List *list)
{
   IndexRecord rec;

   while(rec=(IndexRecord)RemHead(list))
      KillIndexRecord(rec);
   FreeMem(list,sizeof(struct List));

   return;
}



/* Function: DataSearch
 * ====================
 * Searches for one sequence within another sequence.
 */

static UBYTE *DataSearch(ULONG length_1,ULONG length_2,UBYTE *buf_1,
   UBYTE *buf_2)
{
   ULONG i,last=length_2-length_1;
   REGISTER UBYTE first=*buf_1;
   BOOL equal=FALSE;

   for(i=0;(i<=last)&&!equal;i++)
      {
      if(first==*buf_2) equal=DataEqual(length_1,buf_1,buf_2);
      buf_2++;
      }

   if(i<=last) return --buf_2;
   return NULL;
}



/* Function: DataEqual
 * ===================
 * Returns TRUE if two data sequences are equal.
 */

static BOOL DataEqual(ULONG length,UBYTE *buf_1,UBYTE *buf_2)
{
   ULONG i;

   for(i=0;(i<length)&&(*(buf_1++)==*(buf_2++));i++);

   return (i==length);
}



/* Function: KillIndex
 * ===================
 * Destroys an index.
 */

VOID KillIndex(Index index)
{
   if(index->index_file!=NULL)
      Close(index->index_file);
   if(index->master_index!=NULL)
      FreeMem(index->master_index,index->block_count*MASTER_REF_SIZE);
   FreeMem(index,sizeof(Index_imp));
}



