/*========================================================================*\
 |  Datei: output.c                                   Datum: 02 Jan 1998  |
 *------------------------------------------------------------------------*
 |              Klartextausgaben aus der JPC-Hauptdatenbank               |
 |                                                                        |
\*========================================================================*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <assert.h>
#include "jpc.h"

char out1[80], out2[80];        /* Namen der Ergebnisdateien */
char win1[80], win2[80], win3[80];      /* drei RAW:-Fenster */
/* große Felder, werden dynamisch angelegt: */
long *recnums;                  /* phys. Datensatznummern der Auswahl */
char **records = NULL;          /* Zeiger auf die Kurztexte im Cache */
char *reccache = NULL;          /* riesiger Zwischenspeicher */
long cached;
/* Dateien, die aus Effizienzgründen über mehrere Aufrufe der Funktionen,
 * die mit ihnen arbeiten, hinweg offen bleiben sollen. Die betreffenden
 * Funktionen öffnen sie bei Bedarf, schließen sie aber nicht wieder. */
FILE *prtFile = NULL;           /* für printer() */
FILE *txtFile = NULL;           /* für short_entry() */


int waitkey_or_resize( FILE *f )
/* Kehrt zurück, sobald aus <f> eine Taste gelesen wurde, */
/* wobei ESC-Sequenzen erst komplett ausgelesen werden. */
/* Rückgabewert ist normalerweise 0; 1 bedeutet, daß ein Resize erkannt */
/* wurde und außerdem jetzt ein ESC-String mit den Dimensionen in der Eingabe */
/* bereitsteht */
{
  int c;

  c = fgetc( f );
  if( c != 155 )
    return 0;
  do
    c = fgetc( f );
  while( c < '@' );
  if( c == '|' )
    {
      snapshot();
      fprintf( f, "\e[ q");
      return 1;
    }
  if( c == 'r' )
    return 1;
  return 0;
}


void get_winsize( char *esccode, int *pw, int *ph )
/* aus einem CON-Dimensionsstring Breite und Höhe ermitteln */
{
  int i, x0, y0;

  for( i = 0; esccode[i]; i++ )
    if( !isdigit( esccode[ i ] ) )
      esccode[ i ] = ' ';
  sscanf( esccode, "%d%d%d%d", &y0, &x0, ph, pw );
  *pw -= x0;
  *ph -= y0;
}


void write_line( char line[], char splitword[], FILE * f, int style )
/* <line> nach <f> ausgeben, dabei auftretende target[]'s markieren */
/* <splitword> hilft, diese auch über Zeilengrenzen hinweg zu finden */
{
  char word[80];
  int i, j, k, sow, sol, hyphen;
  int found;

  hyphen = 0;                   /* Position eines evtl. Trennungsstrichs am Zeilenende */
  i = strlen (line);
  while (i > 0 && isspace (line[--i]));
  if (line[i] == '-')
    hyphen = i;
  i = 0;
  sol = 0;
  j = 0;
  sow = 0;
  do
    {
      if (line[i] && (isalnum (line[i]) || strchr ("äöüßÄÖÜ", line[i])))
        {
          if (j == 0)
            sow = 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);
          found = FALSE;
          for (k = 0; k < targets; k++)
            if (match (target[k], word, TRUE) == 0)
              found = TRUE;
          if (found)
            {
              fprintf (f, "%.*s\e[%dm", sow - sol, line + sol, style);
              fprintf (f, "%.*s\e[0m", i - sow, line + sow);
              sol = i;
            }
          j = 0;
        }
    }
  while (line[i++]);
  fprintf (f, "%s\n", line + sol);
}


void long_entry( long recnum, FILE *f, int tagstyle, int hitstyle, int pagesize )
/* Eintrag aus der Hauptdatenbank ausgeben, */
/* für hitstyle!=PLAIN Stichwörter suchen und markieren */
/* alle <pagesize> Zeilen anhalten und auf Tastendruck warten */
{
  FILE *datf, *posf;
  char line[80], splitword[80];
  int c, i, j, len, onpage = 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;
          if (hitstyle != PLAIN && targets > 0)
            write_line (line, splitword, f, hitstyle);
          else
            fprintf( f, "%s\n", line );
          if( ++onpage == pagesize )
            {
              fprintf( f, "\e[7;2m-- Taste --\e[0m" );
              if( waitkey_or_resize( f ) )
                break;          /* Resize beendet */
              fprintf( f, "\r\e[K" );
            }
          c = fgetc( datf );
          i++;
          if( *fieldname[c] )
            {
              if (tagstyle != PLAIN)
                fprintf (f, "\e[%dm%s\e[0m ", tagstyle, fieldname[c]);
              else
                fprintf (f, "%s ", fieldname[c]);
            }
        }
      else
        line[j++] = ibm_decode[c];
    }
  line[j] = '\0';
  if (hitstyle != PLAIN && targets > 0)
    write_line (line, splitword, f, hitstyle);
  else
    fprintf (f, "%s\n", line);
  fclose (datf);
}


STRPTR short_entry( long recnum )
/* Eintrag aus der Klartext-Liste auslesen (einzeiliger String) */
{
  UBYTE *s, sbuf[80];
  static UBYTE line[80];

  sprintf (sbuf, "JPC:%s/data/%s.txt", dbpath[thisdb], dbpath[thisdb]);
  if( txtFile == NULL )
    txtFile = fopen (sbuf, "rb");
  fseek (txtFile, linelen * recnum, SEEK_SET);
  fgets (line, 80, txtFile);
  for (s = line; *s; s++)
    *s = ibm_decode[*s];
  while (isspace (*--s))
    *s = '\0';
  return line;
}


STRPTR cached_short_entry( long cachenum )
/* Variante zu short_entry(), die wenn möglich den großen Zeilencache 
 * benutzt. Der übergebene Parameter ist hierbei aber nur ein Index auf 
 * recnums[], nicht die Datensatznummer selbst!
 */
{
  if( reccache == NULL )        /* kein Cache, verm. Speichermangel  */
    return short_entry( recnums[ cachenum ] );
  if( records[ cachenum ] == NULL )
    {
      assert( cached < selcnt );
      records[ cachenum ] = reccache + linelen * cached++;
      strcpy( records[ cachenum ], short_entry( recnums[ cachenum ] ) );
    }
  return records[ cachenum ];
}                          

void saveall( int full )
/* Speichert die in selected[] markierten Datensätze. */
/* Bei full==TRUE werden Volltexte ausgegeben, sonst Kurzbeschreibungen */
{                                                            
  char *name;
  FILE *datei;
  long l;

  name = (full) ? out2 : out1;
  printf ("Speichern de%s, Ausgabedatei:\n%s\n",
        (full) ? "s vollen Katalogtextes" : "r Bestellinformationen", name);
  datei = fopen (name, "a");
  if (!datei)
    {
      printf ("Fehler - Datei konnte nicht geöffnet werden!\n");
      return;
    }
  ctrl_c = 0;
  signal (SIGINT, intercept);
  printf ("Datensatz Nr. ");
  for (l = 0; l < reccnt; l++)
    if (selected[l])
      {
        printf ("%5ld\b\b\b\b\b", l);
        if (full)
          long_entry( l, datei, PLAIN, PLAIN, 0 );
        else
          fprintf( datei, "%s\n", short_entry( l ) );
        if (ctrl_c)
          {
            printf ("\n*** Abbruch");
            break;
          }
      }
  fclose( datei );
  if( txtFile )
    fclose( txtFile );
  txtFile = NULL;
  printf ("\n");
  signal (SIGINT, SIG_DFL);     /* wieder automatische Ctrl-C-Abfrage */
}


void saveone( long rec, int full )
/* Bei full==TRUE wird ein Volltext ausgegeben, sonst eine Kurzbeschreibung */
{
  char *name;
  FILE *datei, *fenster;

  fenster = fopen (win3, "r+");
  fprintf( fenster, "\e[12{" );
  name = (full) ? out2 : out1;
  fprintf (fenster, "\fSpeichern de%s, Ausgabedatei:\n%s\n",
        (full) ? "s vollen Katalogtextes" : "r Bestellinformationen", name);
  datei = fopen (name, "a");
  if (!datei)
    fprintf (fenster, "Fehler - Datei konnte nicht geöffnet werden!\n");
  else
    {
      if (full)
        long_entry( rec, datei, PLAIN, PLAIN, 0 );
      else
        fprintf( datei, "%s\n", short_entry( rec ) );
      fclose (datei);
      fprintf (fenster, "fertig, ");
    }
  fprintf( fenster, "bitte eine Taste drücken -> " );
  while( waitkey_or_resize( fenster ) );
  fclose( fenster );
}


void printer( long rec, int full )
/* ähnlich saveone(), gibt aber immer nach "PRT:" aus */
/* öffnet prtFile, falls nötig, schließt ihn aber *nicht* wieder */
{
  FILE *fenster;

  fenster = fopen( win3, "r+" );
  fprintf( fenster, "\fDrucken de%s nach PRT:\n",
           (full) ? "s vollen Katalogtextes" : "r Bestellinformationen" );
  if( prtFile == NULL )
    prtFile = fopen( "PRT:", "a" );
  if( prtFile == NULL )
    fprintf( fenster, "Fehler - Drucker nicht ansprechbar!\n" );
  else
    {
      if( full )
        long_entry( rec, prtFile, UNDERLINE, PLAIN, 0 );
      else
        fprintf( prtFile, "%s\n", short_entry( rec ) );
    }
  fclose( fenster );
}


void keyhelp()
/* Fenster mit Informationen zur Tastaturbelegung öffnen */
{
  FILE *fenster;

  fenster = fopen( win3, "r+" );
  fprintf( fenster, "\e[12{" );
  fprintf( fenster, " \n\e[7mTastaturbelegung:\e[0m\n");
  fprintf( fenster, "\e[2mCrsr, F1 - F10:\e[0m Datensätze durchblättern\n");
  fprintf( fenster, "\e[2mReturn:\e[0m Volltext-Fenster öffnen/schließen\n");
  fprintf( fenster, "\e[2ms/S:\e[0m Datensatz als Kurzinfo/Volltext speichern\n");
  fprintf( fenster, "\e[2mp/P:\e[0m Datensatz als Kurzinfo/Volltext drucken\n");
  fprintf( fenster, "\e[2m1 - 5:\e[0m nach Spalten sortieren\n");
  fprintf( fenster, "\e[2mEsc:\e[0m Betrachten der Datensätze beenden\n");
  while( waitkey_or_resize( fenster ) );
  fclose( fenster );
}


void markieren( FILE *win, long index, long offset, int width, int really )
/* wenn Cache aktiv ist, Zeile farbig markieren, sonst nur durch '>' */
{
  fprintf( win, "\e[%ld;1H", index - offset + 1 );
  if( reccache == NULL )
    fputc( really ? '>' : ' ', win );
  else
    fprintf( win, "\e[4%dm %-*.*s\e[40m", really ? 3 : 0,
        width, width, cached_short_entry( index ) );
}


long sortieren( long marker, int spalte )
/* Rückgabewert ist die aktualiserte Position der Auswahlmarke in der Liste */
{
  int offset = 0;
  long i, j, marked, gap, temp;
  char *s, *s0;
  FILE *fenster;

  if( reccache == NULL )        /* ohne Cache lieber nicht sortieren */
    return marker;
  fenster = fopen (win3, "r+");
  fprintf( fenster, " \nnach Spalte %d sortieren:\n", spalte + 1 );
  marked = recnums[ marker ];
  if( cached < selcnt )         /* alle Datensätze in den Cache holen */
    {
      fprintf( fenster, "Datensätze laden...\n");
      for( i = 0; i < selcnt; i++ )
        cached_short_entry( i );
      assert( cached == selcnt );
    }
  fprintf( fenster, "Shellsort läuft...\n");
  s0 = s = cached_short_entry( 0 );
  while( spalte-- >= 0 && s != NULL )
    {
      offset = s - s0;
      s = strchr( s + 1, separator );
    }
  gap = selcnt;                 /* mit Shellsort sortieren */
  while( (gap /= 2) > 0 )
    for( i = gap; i < selcnt; i++ )
      {
        j = i;
        while( (j -= gap) >= 0 )
          if( strcmp( cached_short_entry( j )     + offset,
                      cached_short_entry( j+gap ) + offset ) > 0 )
            {                   /* vertauschen */
              temp = recnums[ j ];
              recnums[ j ] = recnums[ j + gap ];
              recnums[ j + gap ] = temp;
              s = records[ j ];
              records[ j ] = records[ j + gap ];
              records[ j + gap ] = s;
            }
          else
            break;
      }
  for( i = 0; i < selcnt; i++ )
    if( recnums[ i ] == marked )
      marker = i;
  fclose( fenster);
  return marker;
}


void browse()
/* Ausgewählte Liste anzeigen */
{
  FILE *raw, *raw2 = NULL;
  char s[80];
  long k, l, delta, move, dest, akt = 0, offset = 0;
  int i, j, lines, cols, pageht;
  int c;
  int done = 0;

  recnums = (long *)calloc( selcnt, sizeof( long ));
  if( recnums == NULL )
    {
      printf( "Auswahl zu groß, kein Speicher für Datensatzliste (%ld Byte)\n",
        selcnt * sizeof( long ) );
      return;
    }
  records = (char **)calloc( selcnt, sizeof( char * ));
  reccache = (char *)malloc( selcnt * linelen );
  cached = 0;
  if( records == NULL || reccache == NULL )
    {
      printf( "kein Speicher für Kurztext-Cache (%ld KByte benötigt)\n",
        (selcnt * (linelen + sizeof( char * ))) / 1024 );
      free( records );
      free( reccache );
      records = NULL;
      reccache = NULL;
    }
  for (l = 0, k = 0; l < reccnt && k < selcnt; l++)
    if (selected[l])
      recnums[k++] = l;
  raw = fopen (win1, "r+");
  fprintf (raw, "\e[0 p");      /* Cursor unsichtbar */
  fprintf (raw, "\e[12{");      /* über neue Fenstergröße benachrichtigen */
  fprintf (raw, "\e[ q");       /* und Fensterausmaße anfordern */
  do
    {
      dest = akt;
      *s = '\0';
      c = fgetc( raw2 ? raw2 : raw );
      switch (c)
        {
        case 13:                /* Return: Volltext anzeigen/schließen */
          if( raw2 )
            {
              fclose (raw2);
              raw2 = NULL;
            }
          else
            {
              raw2 = fopen( win2, "r+" );
              fprintf( raw2, "\e[0 p\e[12{\e[ q" );
            }
          break;
        case 27:                /* ESC: ein Fenster schließen */
          if( raw2 )
            {
              fclose( raw2 );   /* Volltext */
              raw2 = NULL;
            }
          else
            done = 1;           /* sonst die ganze Auswahl */
          break;
        case 's':
          saveone( recnums[akt], FALSE );
          break;
        case 'S':
          saveone( recnums[akt], TRUE );
          break;
        case 'p':
          printer( recnums[akt], FALSE );
          break;
        case 'P':
          printer( recnums[akt], TRUE );
          break;
        case 155:               /* ESC-Sequenz */
          i = 0;
          do
            s[i++] = c = fgetc( raw2 ? raw2 : raw );
          while (c < '@');
          s[i] = '\0';
          break;
        default:                /* diverse Tasten */
          if( isdigit( c ) )    /* Zifferntaste: Sortieren */
            {
              dest = sortieren( akt, (c - '0' + 9) % 10 );
              akt = -1;         /* Neuaufbau nötig! */
            }
          else                  /* irgendeine Taste */
            keyhelp();
        }
      if( *s ) switch( c )      /* ESC-Sequenzen analysieren */
        {
        case '|':               /* Fenstergröße wurde verändert */
          snapshot();
          fprintf( raw2 ? raw2 : raw, "\e[ q");
          break;
        case 'r':               /* neue Fensterabmessungen lesen */
          if( raw2 )
            {
              get_winsize( s, &i, &pageht );
              fprintf( raw2, "\f" );
              long_entry( recnums[akt], raw2, INVERSE, HILITE, pageht );
            }
          else
            {
              get_winsize( s, &cols, &lines );
              akt = -1;         /* erzwingt Neuaufbau */
            }
          break;
        case '~':
          if (s[0] == '?')      /* Help-Taste */
            keyhelp ();
          else                  /* F-Taste: prozentualer Sprung */
            dest = ((s[0] - '0') * (selcnt - 1)) / 9;
          break;
        case 'A':               /* Cursor hoch */
          dest = akt - 1;
          break;
        case 'B':               /* Cursor runter */
          dest = akt + 1;
          break;
        case 'C':               /* Cursor rechts */
          dest = akt + lines - 1;
          break;
        case 'D':               /* Cursor links */
          dest = akt - lines + 1;
          break;
        case 'S':               /* Shift-Cursor hoch */
          dest = akt + 5;
          break;
        case 'T':               /* Shift-Cursor runter */
          dest = akt - 5;
          break;
        }
      /* Aktionen ausführen */
      if (dest < 0)
        dest = 0;
      if (dest >= selcnt)
        dest = selcnt - 1;
      delta = dest - akt;
      /* Wie weit muß der Bildschirmausschnitt verschoben werden? */
      move = 0;
      if (dest < offset)
        move = dest - offset;
      if (dest >= offset + lines)
        move = dest - (offset + lines - 1);
      if (akt >= offset)        /* Markierung aufheben */
        markieren( raw, akt, offset, cols, FALSE );
      if (move > 0)             /* Statuszeile löschen */
        fprintf (raw, "\e[%d;1H\e[K", lines + 1);
      offset += move;
      if (akt < 0)
        move = lines;           /* naja, tut halt was es soll */
      akt = dest;
      if (move >= lines || -move >= lines)
        {                       /* kompletter Neuaufbau */
          fprintf (raw, "\f");
          i = 1;
          j = lines;
        }
      else if (move < 0)
        {                       /* abwärts scrollen */
          fprintf (raw, "\e[%ldT", -move);
          i = 1;
          j = -move;
        }
      else if (move > 0)
        {                       /* aufwärts scrollen */
          fprintf (raw, "\e[%ldS", move);
          i = lines - move + 1;
          j = lines;
        }
      else
        {                       /* nur neu markieren */
          markieren( raw, akt, offset, cols, TRUE );
          i = 1;
          j = 0;
        }
      fprintf( raw, "\e[2m\e[%d;1HNr. %ld/%ld\e[0m\e[K",
          lines + 1, akt + 1, selcnt );
      fflush (raw);
      /* Zeilen neu aufbauen: */
      while (i <= j)
        {
          k = i + offset - 1;
          if (k < selcnt)
            fprintf( raw, "\e[%d;1H %.*s", i, cols, cached_short_entry( k ) );
          if( k == akt )
            markieren( raw, akt, offset, cols, TRUE );
          i++;
        }
      if (raw2 && delta != 0)
        {
          /* beim Blättern Volltext aktualisieren */
          fprintf( raw2, "\f" );
          long_entry( recnums[akt], raw2, INVERSE, HILITE, pageht );
        }
    }
  while( !done );
  fclose( raw );
  if( prtFile )
    fclose( prtFile );
  if( txtFile )
    fclose( txtFile );
  prtFile = NULL;
  txtFile = NULL;
  free( recnums );
  free( records );              /* dran denken: */
  free( reccache );             /* free( NULL ) ist erlaubt! */
  records = NULL;
  reccache = NULL;
}

