/*
 * nupak.c - interface to the compression routines
 *
 * By Andy McFadden (fadden@cory.berkeley.edu)
 * NuLib v2.2  April 1990  Freeware (distribute, don't sell)
 */

#include "nudefs.h"
#include "nuread.h"    /* need THblock */
#include <stdio.h>
#include <fcntl.h>

#ifdef MSDOS  /* for file I/O */
# include <io.h>
# include <sys\types.h>
# include <sys\stat.h>
# include <errno.h>
#endif

#include "nupak.h"
#include "nuetc.h"

#define CONVSIZ    1024

long packedSize;  /* global - size of file after packing */
onebyt lastseen;  /* used in crlf(); must be set by caller */


/*
 * Make a little spinning thing.
 *
 * This just lets the user know that the whole thing hasn't stalled on him.
 * Prints a character, then backspaces so that the next thing will overwrite
 * it.
 *
 * Currently called by FCopy(), unpak_SHK(), pak_SHK()
 */
void Spin()
{
    static char *sp = "/-\\|";
    static int posn = 0;

    posn++;
    if ((posn < 0) || (posn > 3))
    posn = 0;
    putchar(sp[posn]);
    putchar('\b');
    fflush(stdout);
}


/*
 * Convert the end-of-line terminator between systems.
 *
 * Compile-time defines determine the value that things are translated to;
 * the value of "translate" determines what they are translated from.  This
 * will write the contents of the buffer to the passed file descriptor,
 * altering bytes as necessary.  Max buffer size is 64K.  Note that the
 * syntax is the same as for write();
 *
 * This would have been a lot easier without IBM...  lastseen is the last
 * character seen (used only in CRLF translations).  This needs to be set
 * by the caller (FCopy(), extract_files()).
 *
 * The putc_ncr() procedure in nusq.c does its own processing; this was
 * somewhat unavoidable.
 *
 * BUGS: This proc will have to be re-written.  It would be nice to be
 * able to pack files with a CRLF translation, not just unpack... but you
 * can't just do buffer writes for that.  It'll take some work, and will
 * probably appear in the next version.
 */
unsigned int crlf(dstfd, buffer, length)
int dstfd;
onebyt *buffer;
unsigned int length;
{
    register BOOLEAN doconv;
    register onebyt *bptr = buffer;
    register unsigned int idx;
    static char *procName = "crlf";
    unsigned int partial;   /* size for partial read/write */
    onebyt tobuf[2048];
    onebyt *toptr;
    int conv;
    unsigned int origlength = length;

    if ((transfrom == -1) && (transto == -1)) {  /* no translation necessary */
    return (write(dstfd, buffer, length));
    }
    if (transfrom < -1 || transfrom > 2) {
    fprintf(stderr, "%s: unknown translation type %d\n",
                            prgName, transfrom);
    fprintf(stderr, "%s: assuming conversion 0 (from CR)\n", prgName);
    transfrom = 0;
    }
    if (transto < -1 || transto > 2) {
    fprintf(stderr, "%s: unknown translation type %d\n",
                            prgName, transto);
    fprintf(stderr, "%s: assuming conversion 0 (to CR)\n", prgName);
    transto = 0;
    }

    /* macro defs for system-dependent actions */
#ifdef UNIX
# define DEFCONVFROM    if (*bptr == 0x0a)  /* LF */ \
                doconv = TRUE
#  define DEFCONVTO    *(toptr++) = 0x0a
#else
# ifdef APW
#  define DEFCONVFROM    if (*bptr == 0x0d)  /* CR */ \
                doconv = TRUE
#  define DEFCONVTO    *(toptr++) = 0x0d
# endif
# ifdef MSDOS
#  define DEFCONVFROM    if ((*bptr == 0x0a) && (lastseen == 0x0d)) { \
                doconv = TRUE; \
                toptr--;  /*already put CR; back up over it*/ \
            } \
            lastseen = *bptr
#  define DEFCONVTO    *(toptr++) = 0x0d; \
            *(toptr++) = 0x0a
# endif
# ifndef APW
# ifndef MSDOS
#  define DEFCONVFROM    if (*bptr == 0x0a)  /* LF */ \
                doconv = TRUE
# endif /* none2 */
# endif /* none1 */
#endif /* UNIX */

    while (length != 0) {
    if (length > CONVSIZ) {
        partial = CONVSIZ;
        length -= CONVSIZ;
    } else {
        partial = length;
        length = 0;
    }

    /* uses an explicit flag rather than "continue" for clarity... */
    toptr = tobuf;
    for (idx = partial; idx > 0; idx--, bptr++) {
        doconv = FALSE;
        switch (transfrom) {
        case -1:  /* convert from current system's terminator */
        DEFCONVFROM;
        break;
        case 0:
        if (*bptr == 0x0d)  /* CR */
            doconv = TRUE;
        break;
        case 1:
        if (*bptr == 0x0a)  /* LF */
            doconv = TRUE;
        break;
        case 2:
        if ((*bptr == 0x0a) && (lastseen == 0x0d)) {
            doconv = TRUE;
            toptr--;  /*already outputed CR; back up over it*/
        }
        lastseen = *bptr;
        break;
        }


        if (doconv) {
        switch (transto) {
        case -1:  /* convert to current system's terminator */
            DEFCONVTO;
            break;
        case 0:
            *(toptr++) = 0x0d;
            break;
        case 1:
            *(toptr++) = 0x0a;
            break;
        case 2:
            *(toptr++) = 0x0d;
            *(toptr++) = 0x0a;
            break;
        }
        } else {
        *(toptr++) = *bptr;
        }
    } /* for loop */
            
    if (write(dstfd, tobuf, (toptr-tobuf)) != (toptr-tobuf))
        Fatal("Dest write failed", procName);
    }  /* while loop */
    return (origlength);
}


/*
 * Read a file, and place in another file at current posn.  We can't read more
 * than PAKBUFSIZ at a time, so for some files it will have to be broken down
 * into pieces.  Note PAKBUFSIZ is expected to be an int (defined in nupak.h),
 * and can't be any larger than read() can handle (64K... unsigned 16-bit int).
 *
 * The transl option is present for NuUpdate and NuDelete, which have to
 * copy old records to a new archive w/o performing translation.
 */
void FCopy(srcfd, dstfd, length, copybuf, transl)
int srcfd;    /* source file descriptor (must be open & seek()ed) */
int dstfd;    /* destination file descriptor (must be open & seek()ed) */
fourbyt length; /* number of bytes to copy */
onebyt *copybuf;
BOOLEAN transl;  /* maybe do text translation? */
{
    unsigned int partial;   /* size for partial read/write */
    static char *procName = "FCopy";

    if (transl) lastseen = '\0';
    while (length != 0L) {
    if (length > (long) PAKBUFSIZ) {
        partial = (unsigned int) PAKBUFSIZ;
        length -= (long) PAKBUFSIZ;
        if (verbose) Spin();
    } else {
        partial = (unsigned int) length;
        length = 0L;
    }

    if (read(srcfd, copybuf, partial) != partial)
        Fatal("Source read failed", procName);
    if (transl) {  /* do text translation if user wants it */
        if (crlf(dstfd, copybuf, partial) != partial)
        Fatal("Dest write failed (c)", procName);
    } else {  /* NEVER do translation */
        if (write(dstfd, copybuf, partial) != partial)
        Fatal("Dest write failed (w)", procName);
    }
    }
}


/*
 * Add a range of bytes from one file into another, packing them.
 *
 * Set up stuff, then call the appropriate pack routine.  Returns the actual
 * algorithm used (thread_format), since the compression algorithm could
 * fail, storing the file in uncompressed format instead.  The packed length
 * is stored in a global variable.
 *
 * Since we're only using version 0 records, we don't need to propagate the
 * thread_crc.
 */
twobyt PackFile(srcfd, dstfd, thread_eof, thread_format, buffer)
int srcfd;    /* source file descriptor (must be open & seek()ed) */
int dstfd;    /* destination file descriptor (must be open & seek()ed) */
fourbyt thread_eof; /* size of input */
int thread_format; /* how to pack the bytes */
onebyt *buffer; /* alloc in main prog so we don't have to each time */
{
    long length = (long) thread_eof;
    twobyt retval = thread_format;  /* default = successful pack */
    static char *procName = "PackFile";

    switch (thread_format) {
    case 0x0000:  /* uncompressed */
    if (verbose) { printf("storing...", thread_format);  fflush(stdout); }
    FCopy(srcfd, dstfd, length, buffer, TRUE);
    packedSize = length;
    break;

    case 0x0001:  /* SQUeeze */
    if (verbose) {
        printf("[can't squeeze; storing]...");
        fflush(stdout);
    } else {
        printf("WARNING: can't squeeze; files stored uncompressed\n");
    }
    FCopy(srcfd, dstfd, length, buffer, TRUE);
    packedSize = length;
    retval = 0x0000;  /* uncompressed */
    break;

    case 0x0002:  /* LZW (ShrinkIt) */
    if (verbose) { printf("shrinking...");  fflush(stdout); }
    /* packedSize set by pak_SHK */
    retval = pak_SHK(srcfd, dstfd, length, buffer);
    break;
    case 0x0003:  /* LZW II (ShrinkIt) */
    if (verbose) {
        printf("[can't do LZW II; storing]...");
        fflush(stdout);
    } else {
        printf("WARNING: can't do LZW II; files stored uncompressed\n");
    }
    FCopy(srcfd, dstfd, length, buffer, TRUE);
    packedSize = length;
    retval = 0x0000;  /* uncompressed */
    break;
    case 0x0004:  /* UNIX 12-bit compress */
    if (verbose) {
        printf("[can't do 12-bit UNIX compress; storing]...");
        fflush(stdout);
    } else {
        printf(
        "WARNING: can't do 12-bit compress; files stored uncompressed\n");
    }
    FCopy(srcfd, dstfd, length, buffer, TRUE);
    packedSize = length;
    retval = 0x0000;  /* uncompressed */
    break;
    default:
    fprintf(stderr, "\nUnknown compression method %d\n", thread_format);
    fprintf(stderr, "Aborting.\n");
    Quit(-1);
    }

    return (retval);
}


/*
 * Extract a range of bytes from one file into another, unpacking them.
 *
 * (hacked to unpack disks, also.  Forces the thread_eof to be the total
 *  size of the disk, since ShrinkIt doesn't really define it, esp for DOS 3.3
 *  disks).
 *
 * Set up stuff, then call the appropriate unpack routine.  Leaves the srcfd
 * positioned past the data to be unpacked; the calling routine should not
 * have to do any seeks.
 *
 * Returns TRUE if able to unpack, FALSE if not able to.  Note that srcfd
 * WILL be seeked even if the compression method is not handled.
 */
int UnpackFile(srcfd, dstfd, THptr, thread_format, buffer)
int srcfd;    /* source file descriptor (must be open & lseek()ed) */
int dstfd;    /* destination file descriptor (must be open & lseek()ed) */
THblock *THptr;    /* pointer to thread structure */
int thread_format; /* how to unpack the bytes (NOT THptr->thread_format) */
onebyt *buffer;
{
    long length;
    fourbyt thread_eof,        /* #of bytes to output */
        comp_thread_eof;    /* #of bytes in source */
    twobyt thread_crc;
    BOOLEAN retval = TRUE;  /* default to success */
    static char *procName = "UnpackFile";

    thread_eof = THptr->thread_eof;
    comp_thread_eof = THptr->comp_thread_eof;
    thread_crc = THptr->thread_crc;    /* never used? */
    length = (long) comp_thread_eof;    /* type checking goes easier */

    switch (thread_format) {
    case 0x0000:  /* uncompressed */
    if (verbose) { printf("extracting...", thread_format); fflush(stdout);}
    FCopy(srcfd, dstfd, length, buffer, TRUE);
    break;

    case 0x0001:  /* unSQUeeze */
#ifdef NO_BLU
    if (verbose) {
        printf("[can't unsqueeze - aborting]...");
        fflush(stdout);
    } else {
        printf("ERROR: can't unsqueeze; 'squ' files not extracted\n");
    }
    lseek(srcfd, length, S_REL);  /* set file posn */
    retval = FALSE;
#else
    if (verbose) { printf("unsqueezing..."); fflush(stdout); }
    unpak_SQU(srcfd, dstfd, length);  /* thread_eof not needed */
#endif
    break;

    case 0x0002:  /* LZW (ShrinkIt) */
    if (verbose) { printf("unshrinking (I)..."); fflush(stdout); }
    unpak_SHK(srcfd, dstfd, comp_thread_eof, thread_eof, buffer, FALSE);
    break;

    case 0x0003:  /* LZW II (ShrinkIt) */
#ifdef TRY_II
    if (verbose) { printf("unshrinking (II)..."); fflush(stdout); }
    unpak_SHK(srcfd, dstfd, comp_thread_eof, thread_eof, buffer, TRUE);
#else
    if (verbose) {
        printf("[can't unshrink type II - aborting]...");
        fflush(stdout);
    } else {
        printf(
        "ERROR: can't unshrink type II; 'sh2' files not extracted\n");
    }
    lseek(srcfd, length, S_REL);  /* set file posn */
    retval = FALSE;
#endif
    break;

    case 0x0004:  /* 12-bit UNIX compress */
    if (verbose) {
        printf("[can't undo 12-bit UNIX compress - aborting]...");
        fflush(stdout);
    } else {
        printf(
    "ERROR: can't undo 12-bit UNIX compress; 'u12' files not extracted\n");
    }
    lseek(srcfd, length, S_REL);  /* set file posn */
    retval = FALSE;
    break;

    default:
    fprintf(stderr, "Unknown uncompression method %d\n", thread_format);
    lseek(srcfd, length, S_REL);  /* set file posn */
    retval = FALSE;
    break;
    }

    return (retval);
}

