/*************************************************************************\
  select.c:
        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 (dbugf && ftell(idxf)>critical)
      fprintf(dbugf, "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 if (dbugf)
      fprintf(dbugf, "bin im letzten Deskriptor-Block\n");
  }     
  if (jumpto) {
    fseek(idxf, jumpto, SEEK_SET);
    if (dbugf) fprintf(dbugf, "Deskriptor überquert Blockgrenze\n");
  }     
}


void select(FILE *idxf, UWORD nr, UWORD block)
/* Aus dem Typ4-Block, der in idxf bei 2048*block beginnt, die nr-te */
/* Beschreibung 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);
  if (dbugf) 
    fprintf(dbugf, "Nr. %u/%u (0x%lx): ", block, nr, ftell(idxf));
  mode = fgetc(idxf);
  numwords = getlong(idxf);
  switch (mode) {
    case 0:     /* Beschreibung durch Datensatznummern */
      if (dbugf) 
        fprintf(dbugf, "aufzählender Deskriptor, %ld Worte\n", numwords);
      n = 0;
      while (n<numwords) {
        rec0 = getbytes(idxf, recnumsize); n++; blkskip(idxf, &block, TRUE);
        if ((rec0>>(8*(recnumsize-1)))==0xff) {
          rec0 = getbytes(idxf, recnumsize); n++; blkskip(idxf, &block, TRUE);
          rec1 = getbytes(idxf, recnumsize); n++; blkskip(idxf, &block, TRUE);
          if (dbugf) fprintf(dbugf, "%2lder-Gruppe\n", rec1-rec0+1);
          while (rec0<=rec1)
            selected[rec0++] |= 2;
        } else
          selected[rec0] |= 2;
      } break;
    case 32:
      if (dbugf) fprintf(dbugf, "Bitmuster-Deskriptor\n");
      n = 0; rec0 = 0;
      while (n<numwords) {
        c = fgetc(idxf); n++; blkskip(idxf, &block, TRUE);
        for (k=0x80; k>0; k>>=1, rec0++)
          if (c & k)
            selected[rec0] |= 2;
      } break;
    case 64:
      if (dbugf) fprintf(dbugf, "gepackter Bitmuster-Deskriptor\n");
      if (dbugf) 
        fprintf(dbugf, "auf %ld %% gepackt\n", (800*numwords) / reccnt);
      n = 0; rec0 = 0;
      while (n<numwords) {
        bytes = fgetc(idxf); n++; blkskip(idxf, &block, TRUE);
        for (j=0; j<(bytes & 0x7f); j++) {
          if (j==0 || bytes>0x7f) {
            c = fgetc(idxf); n++; blkskip(idxf, &block, TRUE);
          }
          for (k=0x80; k>0; k>>=1, rec0++)
            if (c & k)
              selected[rec0] |= 2;
        }
      } break;
    default:
      if (dbugf) fprintf(dbugf, "unbekannter Deskriptor-Typ %d!\n", mode);
  }
}


void clr_select(void)
/* 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 */
{
  if (dbugf) fprintf(dbugf, "*** 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;
  
  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;
  if (dbugf) fprintf(dbugf, "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;
  }
  if (dbugf) 
    fprintf(dbugf, "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) {
          selected[getbytes(idxf, recnumsize)] |= 2;
        } 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");
}

