/*========================================================================*\
 |  Datei: output.c                                   Datum: 07 Jun 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 */
short *lastsort = NULL;         /* für "stabile" Sortierung */
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 ];

    TRACE1( "short_entry( %ld )... ", recnum );
    sprintf( sbuf, "JPC:%s/data/%s.txt", dbpath[ thisdb ], dbpath[ thisdb ] );
    if( txtFile == NULL )
        {
        TRACE1( "öffne \"%s\"\n", sbuf );
        txtFile = fopen (sbuf, "rb");
        }
    else
        TRACE0( "(Datei offen)... " );
    fseek( txtFile, linelen * recnum, SEEK_SET );
    fgets( line, 80, txtFile );
    for( s = line; *s; s++ )
        *s = ibm_decode[ *s ];
    while( isspace( *--s ) )
        *s = '\0';
    TRACE0( "OK\n" );
    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 );
            fflush( stdout );
            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 diff, offset, width;
    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 );
    TRACE1( "Spalte %d, ", 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 );
        }
    s0 = s = cached_short_entry( 0 );
    offset = 0;
    while( spalte-- >= 0 )
        {
        offset = s - s0;
        s = strchr( s + 1, separator );
        if( s == NULL )
            break;
        }
    if( offset > 0 )
        offset++;
    width = ( s != NULL ) ? s - s0 : linelen;
    width -= offset;
    TRACE2( "Offset %d, Breite %d\n", offset, width );

    fprintf( fenster, "Shellsort läuft...\n");
    gap = selcnt;               /* mit Shellsort sortieren */
    while( (gap /= 2) > 0 )
        for( i = gap; i < selcnt; i++ )
            {
            j = i;
            while( (j -= gap) >= 0 )
                {
                s0 = cached_short_entry( j )     + offset;
                s  = cached_short_entry( j+gap ) + offset;
                if( isdigit( *s0 ) && isdigit( *s ) )
                    diff = atoi( s ) - atoi( s0 );
                else
                    diff = strncmp( s, s0, width );
                /* "stabile" Sortierung erzwingen: bei Gleichheit */
                /* entscheidet das Ergebnis des letzten Sortiervorgangs */
                if( diff == 0 )
                    diff = lastsort[ j + gap ] - lastsort[ j ];
                if( diff < 0 )
                    {           /* vertauschen */
                    temp = recnums[ j ];
                    recnums[ j ] = recnums[ j + gap ];
                    recnums[ j + gap ] = temp;
                    temp = lastsort[ j ];
                    lastsort[ j ] = lastsort[ j + gap ];
                    lastsort[ j + gap ] = temp;
                    s = records[ j ];
                    records[ j ] = records[ j + gap ];
                    records[ j + gap ] = s;
                    }
                else
                    break;
                }
            }
    for( i = 0; i < selcnt; i++ )
        {
        lastsort[ i ] = 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;

    TRACE0( "Alloziere... " );
    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* ) );
    lastsort = (short *)calloc( selcnt, sizeof( short ) );
    reccache = (char *)malloc( selcnt*linelen );
    TRACE0( "OK\n" );
    cached = 0;
    if( records == NULL || lastsort == NULL || reccache == NULL )
        {
        printf( "kein Speicher für Kurztext-Cache (%ld KByte benötigt)\n",
            (selcnt * (sizeof( char* ) + sizeof( short ) + linelen)) / 1024 );
        free( records );
        free( lastsort );
        free( reccache );
        records = NULL;
        lastsort = NULL;
        reccache = NULL;
        }
    for( l = 0, k = 0; l < reccnt && k < selcnt; l++ )
        if( selected[ l ] )
            {
            if( lastsort != NULL )
                lastsort[ k ] = k;
            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  {
                    c = fgetc( raw2 ? raw2 : raw );
                    s[ i++ ] = c;
                    }
                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] == '?' )
                    keyhelp (); /* Help-Taste */
                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! */
    free( lastsort );
    records = NULL;
    lastsort = NULL;
    reccache = NULL;
    }

