/*========================================================================*\
 |  Datei: select.c                                   Datum: 07 Jun 1998  |
 *------------------------------------------------------------------------*
 |           Entschlüsselung der Indexdateien zum JPC-Programm            |
 |                                                                        |
\*========================================================================*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <exec/types.h>    /* typedef's für UBYTE etc. */
#include "jpc.h"


void check_line(char line[], char splitword[], int packed, int *found)
/* <line> in Wörter zerlegen und untersuchen */
{
  char word[80];
  int i, j, hyphen;

  if (*found==targets) return;
  hyphen = 0; /* Position eines evtl. Trennungsstrichs am Zeilenende */
  i = strlen(line);  while (i>0 && isspace(line[--i])) ;
  if (line[i] == '-') hyphen = i;
  i = 0; j = 0;
  do {
    if (line[i] && (isalnum(line[i]) || strchr("äöüßÄÖÜ", line[i])))
      word[j++] = line[i];
    else if (j>0) {   /* ein Wort zu Ende */
      word[j] = '\0'; civilize(word, FALSE);
      if (*splitword) {
        strcat(splitword, word); strcpy(word, splitword);
        strcpy(splitword, "");
      }
      if (i==hyphen) {
        strcpy(splitword, word);
      }
      else {
        if (match(target[*found], word, TRUE)==0)
          (*found)++;
        else if (packed && *found>0) {
          *found = 0;
          if (match(target[0], word, TRUE)==0) *found = 1;
        }
      }
      j = 0;
    }
    if (line[i]==';'  && *found<targets) *found = 0;
  } while (line[i++] && *found<targets);
  return;
}


int found_in_order(long recnum, int packed)
/* Prüft, ob in dem entsprechenden Datensatz die gesuchten Stichwörter in */
/* der richtigen Reihenfolge bzw. sogar direkt hintereinander vorkommen. */
/* Die Wortfolge darf in keinem Fall von einem Semikolon unterbrochen */
/* werden! */
{  
  FILE *datf, *posf;
  char line[80], splitword[80];
  int c, i, j, len, found = 0;
  long l;

  sprintf(line, "JPC:%s/data/%s.pos", dbpath[thisdb], dbpath[thisdb]);
  posf = fopen(line, "rb");
  fseek(posf, 4*recnum, SEEK_SET);
  l = getlong(posf);
  fclose(posf);
  sprintf(line, "JPC:%s/data/%s.dat", dbpath[thisdb], dbpath[thisdb]);
  datf = fopen(line, "rb");
  fseek(datf, l, SEEK_SET);
  /* ein Word lesen, ausnahmsweise Motorola-Format: */
  len = fgetc(datf)<<8; len |= fgetc(datf);
  j = 0; i = 2;
  strcpy(splitword, "");
  while (i<len) {
    c = fgetc(datf); i++;
    if (c==19) {        /* Zeilenende %-) */
      line[j] = '\0'; j = 0;
      check_line(line, splitword, packed, &found);
      c = fgetc(datf); i++;
    } else
      line[j++] = ibm_decode[c];
  }
  line[j] = '\0';
  check_line(line, splitword, packed, &found);
  fclose(datf);
  return (found==targets);
}


void hand_auslese(int exakt)
/* läßt aus der aktuellen Auswahl nur die Datensätze übrig, die die */
/* Suchwörter target[] in der richtigen Reihenfolge enthalten. */
{
  long hits, lost, l;
  
  if (selcnt==0)
    printf("Erst Vorauswahl durch Index-Suchanfrage treffen!\n");
  else {
    ctrl_c = 0; signal(SIGINT, intercept);
    printf("Suche nach \"");
    for (l=0; l<targets; l++) {
      printf("%s", target[l]);
      if (l<targets-1) printf(" ");
      if (l<targets-1 && !exakt) printf("... ");
    }
    printf("\"\n\n");
    hits = 0; lost = 0;
    for (l=0; l<reccnt; l++)  if (selected[l]) {
      if (found_in_order(l, exakt)) 
        hits++;
      else {
        selected[l] = 0; lost++;
      }
      printf("\e[A%3ld Kandidaten, %3ld ausgeschieden, %3ld Treffer\n",
          selcnt-hits-lost, lost, hits);
      if (ctrl_c) {
        printf("*** Abbruch\n"); break;
      }
    }
    printf("Auswertung ... "); fflush(stdout);
    eval_select(1);
    printf("%ld Datensätze\n", selcnt);
    signal(SIGINT, SIG_DFL);  /* wieder automatische Ctrl-C-Abfrage */
  }
}


UWORD lastblock;        /* um illegale fseek()'s zu vermeiden */


void blkskip( FILE *idxf, UWORD *block, int active )
/* Deskriptoren können länger als ein Block sein. */
/* Für active==FALSE wird festgestellt, wo im nächsten Block dieser */
/* Deskriptor fortgesetzt wird. */
/* Für active==TRUE wird überprüft, ob das letzte Byte im aktuellen */
/* Block gelesen wurde, und wenn ja, ein entsprechender Sprung */
/* ausgeführt. */
    {
    static long critical, resume;
    long l, jumpto = 0;

    if (active)
        {
        if (ftell(idxf)==critical) 
            {
            jumpto = resume; 
            (*block)++;
            }
        else if( ftell(idxf)>critical )
            TRACE0( "Arrgh! Überqueren der Blockgrenze fehlgeschlagen!\n" );
        }
    if( !active || jumpto != 0 )
        {
        l = blksize*(*block); fseek(idxf, l, SEEK_SET);
        critical = l + getword(idxf);   /* letztes Byte+1 dieses Blocks */
        l = blksize*(*block+1) + 10;
        if (*block < lastblock)
            {
            fseek(idxf, l, SEEK_SET);
            resume = l + 2 + 2*getword(idxf);   /* 1. Byte im nächsten Block */
            }
        else
            TRACE0( "bin im letzten Deskriptor-Block\n" );
        }
    if( jumpto )
        {
        fseek( idxf, jumpto, SEEK_SET );
        TRACE0( "Deskriptor überquert Blockgrenze\n" );
        }
    }


void select(FILE *idxf, UWORD nr, UWORD block)
/* Aus dem Typ4-Block, der in idxf bei 2048*block beginnt, den nr-ten */
/* Deskriptor in bit1 von selected[] übertragen. */
    {
    UWORD j, k, numentries, skip;
    long rec0, rec1, numwords, n;
    int mode, bytes, c = 0;  /* Warnung für <c> unterdrücken, s. 'case 64' */

    fseek( idxf, blksize*block + 10, SEEK_SET );
    numentries = getword( idxf );
    skip = 0;
    for( j=0; j<=nr; j++ )
        skip += getword( idxf );
    blkskip( idxf, &block, FALSE );
    /* zum gewünschten Descriptor vorrücken: */
    fseek( idxf, blksize*block + 12 + 2*numentries + skip, SEEK_SET );
    TRACE3( "Nr. %u/%u (0x%lx): ", block, nr, ftell(idxf) );
    mode = fgetc( idxf );
    numwords = getlong( idxf );
    switch( mode )
        {
        case 0:     /* Beschreibung durch Datensatznummern */
            TRACE1( "aufzählender Deskriptor, %ld Worte\n", numwords );
            n = 0;
            while( n<numwords )
                {
                rec0 = getbytes( idxf, recnumsize );
                blkskip( idxf, &block, TRUE );
                n++;
                if( (rec0 >> (8*(recnumsize-1))) == 0xff )
                    {
                    rec0 = getbytes( idxf, recnumsize );
                    blkskip( idxf, &block, TRUE );
                    n++;
                    rec1 = getbytes( idxf, recnumsize );
                    blkskip( idxf, &block, TRUE );
                    n++;
                    TRACE2( "%5ld - %5ld\n", rec0, rec1 );
                    while( rec0<=rec1 )
                        selected[ rec0++ ] |= 2;
                    }
                else
                    {
                    TRACE1( "%5ld\n", rec0 );
                    selected[ rec0 ] |= 2;
                    }
                }
            break;
        case 32:
            TRACE0( "Bitmuster-Deskriptor\n" );
            n = 0;
            rec0 = 0;
            while( n<numwords )
                {
                c = fgetc( idxf );
                blkskip( idxf, &block, TRUE );
                n++;
                for( k=0x80; k>0; k>>=1, rec0++ )
                    if( c & k )
                        selected[ rec0 ] |= 2;
                }
            break;
        case 64:
            TRACE0( "gepackter Bitmuster-Deskriptor\n" );
            TRACE1( "auf %ld %% gepackt\n", (800*numwords) / reccnt );
            n = 0; rec0 = 0;
            while( n<numwords )
                {
                bytes = fgetc( idxf );
                blkskip( idxf, &block, TRUE );
                n++;
                for( j=0; j<(bytes & 0x7f); j++ )
                    {
                    if( j==0 || bytes>0x7f )
                        {
                        c = fgetc( idxf );
                        blkskip( idxf, &block, TRUE );
                        n++;
                        }
                    for( k=0x80; k>0; k>>=1, rec0++ )
                        if( c & k )
                            selected[ rec0 ] |= 2;
                    }
                }
            break;
        default:
            TRACE1( "unbekannter Deskriptor-Typ %d!\n", mode );
        }
    }


void clr_select()
/* Liste der ausgewählten Records löschen, Vorbereitung für Neuauswahl */
/* durch select() */
    {
    long l;

    for( l=0; l<reccnt; l++ )
        selected[ l ] = 0;
    }


void eval_select( int mode )
/* Nach select() Auswahlliste aktualisieren, für mode=0 alte Auswahl */
/* einschränken (AND), für mode=1 erweitern (OR). Wenn bit1 in mode gesetzt */
/* ist, wird die neue Auswahl zuvor negiert. */
    {
    long l;
    int valid[4];

    for( l=0; l<4; l++ )
        valid[ l ] = l % 2;
    valid[ (mode+1) % 4 ] = mode % 2;
    /* (Tja, komisch, kommt aber genau so hin. ;-) */
    
    selcnt = 0;
    for( l=0; l<reccnt; l++ )
        if( valid[ selected[ l ] & 3 ] )
            {
            selected[ l ] = 1;
            selcnt++;
            }
        else
            selected[ l ] = 0;
    }


struct header
    {
    UWORD size;
    ULONG count1;
    ULONG count2;
    ULONG parent;
    UWORD i_parent;
    ULONG prevkeys;   /* nur bei Typ 3 vorhanden! */
    UWORD numkeys;
    };


void getheader( FILE *idxf, UWORD block, int typ3, struct header *h )
/* Auf Typ3-Blöcke zugeschnitten, mit einer Abweichung aber auch für */
/* Typ1 und Typ2 geeignet */
    {
    TRACE1( "*** Block %d\n", block );
    fseek( idxf, blksize*block, SEEK_SET );
    h->size = getword(idxf);
    h->count1 = getlong(idxf);
    h->count2 = getlong(idxf);
    h->parent = getlong(idxf);
    h->i_parent = getword(idxf);
    if( typ3 )
        h->prevkeys = getlong(idxf);   /* <- aha! */
    h->numkeys = getword(idxf);
    }


void hunt(int index, char *key0, char *key1)
/* erstellt eine Selektion, die auf den Bereich key0-key1 paßt. Beispiele: */
/* "*"-"000019,95" "M"-"N" "BOWIE"-"BOWIE" */
    {
    struct header h;
    FILE *idxf;
    char name[80];
    int i, n, levels, cmp;
    int rangemode, in_range;
    UWORD SOT4, block, block0;
    static char buf[MAXBLKSIZE];  /* "static", um Stack zu sparen! */
    static char *keys[256];       /* hier ebenso */
    long hits, seekto, T4num, rec0;

    sprintf(name, "JPC:%s/%s/%s.idx",
       dbpath[thisdb], indpath[index], indpath[index]);
    idxf = fopen(name, "rb"); setvbuf(idxf, NULL, _IOFBF, BUFFERS*blksize);
    fseek(idxf, 0L, SEEK_END); lastblock = ftell(idxf)/blksize - 1;
    getheader(idxf, 0, TRUE, &h);  /* Header vom Rootblock lesen, */
    /* entscheiden, wie tiefe Suchhierarchie vorliegt: */
    if (h.i_parent == 0xffff)
        levels = 3;
    else if (h.prevkeys == 0)
        levels = 1;
    else
        levels = 2;
    TRACE1( "Indexhierarchie über %d Ebenen\n", levels );
    if( levels != 1 )   /* Hoppla, das war gar kein Typ3-Block. */
        h.numkeys = h.prevkeys & 0xffff;
    /* SOT4: wo fangen die Typ4-Blöcke an? */
    /* block: erster zu durchsuchender Typ3-Stichwortblock */
    if (levels == 1)
        {
        /* winzige Datei mit einem einzigen Typ3-Stichwortblock */
        SOT4 = 1; 
        block = 0;
        }
    else
        {
        /* dem Typ2-(Typ1-?)Rootblock folgen numkeys Typ3-(Typ2-?) */
        /* Verzeichnisblöcke */
        SOT4 = h.numkeys + 1;
        /* Den ersten passenden Typ3-Block auswählen */
        fseek( idxf, 18, SEEK_SET );
        for( i=0; i<h.numkeys; i++ )
            keys[i] = buf + getword(idxf);
        fread( buf, sizeof(char), h.size-18-2*h.numkeys, idxf );
        block = 0;
        while( 1==match(key0, keys[block], FALSE) && block<h.numkeys-1 )
            block++;
        block += 1;
        /* Bei levels==2 sind block und SOT4 jetzt bereits korrekt. */
        }
    if (levels==3)
        {
        /* bei levels=3 muß zunächst SOT4 noch korrigiert werden, */
        block0 = SOT4; n = h.numkeys;
        for( i=1; i<=n; i++ )
            {
            getheader( idxf, i, FALSE, &h );
            SOT4 += h.numkeys;
            if( i<block )
            block0 += h.numkeys;
            }
        /* außerdem zeigt <block> erst auf einen Typ2-Verzeichnisblock, den */
        /* es analog zum vorangehenden Typ1-Block zu durchsuchen gilt. */
        getheader(idxf, block, FALSE, &h);
        for( i=0; i<h.numkeys; i++ )
            keys[i] = buf + getword( idxf );
        fread( buf, sizeof(char), h.size-18-2*h.numkeys, idxf );
        block = 0;
        while( 1==match(key0, keys[block], FALSE) && block<h.numkeys-1 )
            block++;
        block += block0;
        }
    TRACE2( "Stichwortsuche in Block %d, Bitmaps ab Block %d\n", block, SOT4 );
    /* im gefundenen Typ3-Block (und ggf. in weiteren Blöcken) das */
    /* Stichwort selbst suchen */
    ctrl_c = 0; signal(SIGINT, intercept);
    rangemode = (strcmp(key0, key1) != 0);
    in_range = FALSE; cmp = 1;
    while( block<SOT4 && cmp != -1 && !ctrl_c )
        {
        getheader( idxf, block, TRUE, &h );
        for( i=0; i<h.numkeys; i++ )
            keys[i] = buf + getword(idxf);
        fread( buf, sizeof(char), h.size-22-2*h.numkeys, idxf );
        i = 0;
        cmp = 1;
        while( i<h.numkeys && cmp != -1 && !ctrl_c )
            {
            cmp = match((in_range) ? key1 : key0, keys[i], TRUE);
            if( rangemode && !in_range && cmp != 1)
                {
                in_range = TRUE;
                cmp = 0;
                }
            if (cmp == 0 || (in_range && cmp != -1))
                {
                seekto = blksize*block+22+2*h.numkeys + (keys[i] - buf)
                    + strlen(keys[i]) + 1;
                fseek( idxf, seekto, SEEK_SET );
                hits = getbytes( idxf, 3 );
                printf( "%5ld-mal %s\n", hits, keys[i] );
                if( hits==1 )
                    {
                    rec0 = getbytes( idxf, recnumsize );
                    selected[ rec0 ] |= 2;
                    TRACE1( "ohne Deskriptor: %ld", rec0 );
                    }
                else
                    {
                    T4num = getbytes(idxf,3);
                    select( idxf, T4num & 0xff, SOT4 + T4num/blksize );
                    }
                }
            i++;
            }
        block++;
        }
    signal(SIGINT, SIG_DFL);  /* wieder automatische Ctrl-C-Abfrage */
    fclose(idxf);
    if (ctrl_c) printf("*** Abbruch\n");
    }

