#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define BUFLEN 16384
char buf[BUFLEN+2];
char rcl[4];
char rc2[4];

FILE *tp;

unsigned long packit( char x[4] ) {
        return (x[0] & 0x00ff) | 
            ((x[1] << 8) & 0xff00) |
            ((x[2] << 16) & 0xff0000) |
            ((x[3] << 24) & 0xff000000);
}

int read_record( char buf[], unsigned int buf_size, FILE *fd ) {
    int n;
    char tbuf1[4];
    char tbuf2[4];
    int reclen1;
    int reclen2;

    n = fread( tbuf1, sizeof(char), 4, fd );
    if( n == 0 ) {
        return -1;
    }
    if( n < 4 ) {
        fprintf( stderr, "Bad read length %d\n", n );
        exit(1);
    }
    reclen1 = packit( tbuf1 );
    if( reclen1 == 0 ) {
        return 0;
    }
    if( reclen1 > buf_size) {
        fprintf( stderr, "ERROR: tape record too big: %d   max is %s\n",
                 reclen1, buf_size );
        exit(1);
    }
    n = fread( buf, sizeof(char), reclen1, fd );
    if( n != reclen1 ) {
        fprintf( stderr, "ERROR: short read\n" );
        exit(1);
    }
    /*
    ** check end count
    */
    n = fread( tbuf2, sizeof(char), 4, fd );
    if( n != 4 ) {
        fprintf( stderr, "ERROR: end length missing\n" );
        exit(1);
    }
    reclen2 = packit( tbuf2 );
    if( reclen1 != reclen2 ) {
        fprintf( stderr, "ERROR: record lengths do not match [%d,%d]\n",
                 reclen1, reclen2 );
        exit(1);
    }
    return reclen1;
}

void dump_buf( char buf[], int buf_len ) {
    int i,j;

    for( i = 0; i < buf_len; i += 16 ) {
        printf( "   " );
        for( j = 0; j < 16; j++ ) {
            if( (i+j) >= buf_len )
                printf( "-- " );
            else
                printf( "%02x ", (buf[i+j] & 0xff) );
        }
        printf( "   [" );
        for( j = 0; j < 16; j++ ) {
            if( (i + j) >= buf_len )
                printf( " " );
            else if( (buf[i+j] < 32) || ((buf[i+j] & 0xff) >= 127) )
                printf( " " );
            else
                printf( "%c", buf[i+j] );
        }
        printf( "]\n" );
    }
}

int skip_file( FILE *fd ) {
    int reclen;

    while(1) {
        reclen = read_record( buf, BUFLEN, fd );
        if( reclen <= 0 )
            return reclen;
    }
}


int read_logical_record( char buf[], int buf_size, FILE *fd ) {
    int n;
    char xbuf[BUFLEN+2];
    int cksum, ckval;
    int rl;
    int i;
    int cw;

    n = read_record( xbuf, BUFLEN, fd );
    if( n <= 0 )
        return n;
    rl = ((xbuf[0] << 8) & 0xff00) | (xbuf[1] & 0x00ff);
    if( rl > (n-2) ) {
        fprintf( stderr, "FATAL: logical/physical mismatch: phy %d  log %d\n",
                 n, rl );
        skip_file (tp);
        return;
    //
    //    exit(1);
    }
    cksum = 0;
    for( i = 0; i < rl-2; i++ ) {
        buf[i] = xbuf[i+2];
        if( (i & 1) == 0 ) {    /* upper byte */
            cw = (buf[i] << 8) & 0xff00;
        } else {                /* lower byte */
            cw |= ((int) buf[i]) & 0x00ff;
            cksum += cw;
        }
    }

    if( (i & 1) != 0 )
        cksum += cw;

    ckval = ((xbuf[rl] << 8) & 0xff00) | (xbuf[rl+1] & 0x00ff);
    cksum &= 0xffff;
    ckval &= 0xffff;
    if( cksum != ckval ) {
        fprintf( stderr, "FATAL: checksum failed:  %04x  %04x\n", cksum, ckval );
        exit(1);
    }
    return rl-2;
}

void unpack_buffer( char buf[], int bflen, int start ) {
    int bcnt;
    int n;
    int i;

    bcnt = start;
    while( bcnt < bflen ) {
        n = ((buf[bcnt] << 8) & 0xff00) | (buf[bcnt+1] & 0x00ff);
        bcnt += 2;
        printf( "    =%3d= ", n );
        for( i = 0; i < n; i++ )
            printf( "%c", buf[bcnt++] );
        printf( "\n" );
    }
}

int unpack_file( char buf[], int bflen, int start, FILE *fd ) {
    int bcnt, blen, i, n;
    char dl1[32], dl2[32], dl3[32], dl4[32];
    FILE *sd;
    char lbuf[BUFLEN+2];
    char *xbuf;
    int xmax;
    int reclen;
    int rl;
    char fname[2048],pname[2048];
    int l;
    char *rectype;

    bcnt = start;
    n = ((buf[bcnt] << 8) & 0xff00) | (buf[bcnt+1] & 0x00ff);
    bcnt += 2;
    if( n != 16 ) {
        fprintf( stderr, "Phase error in unpack hdr for 1\n" );
	skip_file (tp);
	return;
//exit(1);
    }
    for( i = 0; i < n; i++ )
        dl1[i] = buf[bcnt++];
    dl1[i] = 0;

    n = ((buf[bcnt] << 8) & 0xff00) | (buf[bcnt+1] & 0x00ff);
    bcnt += 2;
    if( n != 10 ) {
        fprintf( stderr, "Phase error in unpack hdr for 2\n" );
        exit(1);
    }
    for( i = 0; i < n; i++ )
        dl2[i] = buf[bcnt++];
    dl2[i] = 0;

    n = ((buf[bcnt] << 8) & 0xff00) | (buf[bcnt+1] & 0x00ff);
    bcnt += 2;
    if( n != 26 ) {
        fprintf( stderr, "Phase error in unpack hdr for 3\n" );
        exit(1);
    }
    for( i = 0; i < n; i++ )
        dl3[i] = buf[bcnt++];
    dl3[i] = 0;

    n = ((buf[bcnt] << 8) & 0xff00) | (buf[bcnt+1] & 0x00ff);
    bcnt += 2;
    if( n != 26 ) {
        fprintf( stderr, "Phase error in unpack hdr for 3\n" );
        exit(1);
    }
    for( i = 0; i < n; i++ )
        dl4[i] = buf[bcnt++];
    dl4[i] = 0;

    /*
    ** figure out type of file
    */

    if( dl2[0] == 'A' )
        rectype = "ABS";
    else if( dl2[0] == 'R' )
        rectype = "REL";
    else if( dl2[0] == 'D' )
        rectype = "DIG";
    else
        rectype = "UNK";

    /*
    ** now, read the rest of the records and save them to a file
    */

    strcpy( pname, dl1 );
#if 0
    for( i = 0; i < strlen(pname); i++ )
        if( pname[i] == ' ' )
            pname[i] = '_';
    l = strlen(pname);
    strcat( pname, "_" );
    strcat( pname, dl3 );
#else
    l = 0;
#endif
    for( i = strlen(pname)-1; i > l; i-- )
        if( pname[i] == ' ' )
            pname[i] = 0;
        else if( pname[i] != ' ' )
            break;
    for( i = l; i < strlen(pname); i++ )
        if( pname[i] == ' ' )
            pname[i] = '_';
        else if( pname[i] == '/' )
            pname[i] = '_';
    sprintf( fname, "tapes/%s", pname );


    if( (sd = fopen( fname, "w" )) == NULL ) {
        fprintf( stderr, "FATAL: Can't open %s for writing\n", fname );
        return 0;
    }
    printf( "Creating file %s\n", fname );

    xbuf = (char *) malloc( sizeof(char) * (BUFLEN * 10) );
    xmax = BUFLEN * 10;
    if( xbuf == NULL ) {
        fprintf( stderr, "FATAL: out of memory\n" );
        exit(1);
    }

    blen = 0;
    while( (reclen = read_logical_record( lbuf, BUFLEN, fd )) > 0 ) {
        if( (blen + reclen) > xmax ) {
            xbuf = realloc( xbuf, xmax + (10 * BUFLEN) );
            xmax += (10 * BUFLEN);
            if( xbuf == NULL ) {
                fprintf( stderr, "FATAL: out of memory\n" );
                exit(1);
            }
        }
        for( i = 0; i < reclen; i++ )
            xbuf[blen++] = lbuf[i];
    }

    bcnt = 0;
    while( bcnt < blen ) {
        rl = ((xbuf[bcnt] << 8) & 0xff00) | (xbuf[bcnt+1] & 0x00ff);
        bcnt += 2;
        if( (rl & 0x8000) != 0 ) /* check for end */
            break;
        fwrite( &(xbuf[bcnt]), sizeof(char), rl, sd );
        bcnt += rl;
    }
    fclose(sd);
    return 1;
}

int main( int argc, char *argv[] ) {
    unsigned int reclen;
    unsigned int reccnt;
    unsigned long byte_cnt;
    char dl1[32], dl2[32], dl3[32], dl4[32];
    char *rectp;
    
    reccnt = 0;
    byte_cnt = 0;

    if( argc < 2 ) {
        fprintf( stderr, "Usage: saveit tape\n" );
        exit(1);
    }

    if( (tp = fopen(argv[1],"r")) == NULL ) {
        fprintf( stderr, "FATAL: Can't open %s\n", argv[1] );
        exit(1);
    }

    skip_file( tp );            /* header? */
    skip_file( tp );            /* prog to read tape? */

    while( 1 ) {
        reclen = read_logical_record( buf, BUFLEN, tp );
        if( reclen < 0 ) {
            printf( "**** End of Tape\n" );
            break;
        } else if( reclen == 0 ) {
            printf( "**** File Mark\n" );
            break;
        }
        byte_cnt += reclen;
        reccnt++;
        /*        printf( "**** record %d  size %d  total size %ld\n",
                  reccnt+1, reclen, byte_cnt ); */
        if( unpack_file( buf, reclen, 4, tp ) == 0 )
            break;              /* saw eof */
    }

    fclose(tp);
    return 0;
}
