/*
 * crdprt.c - version 2.1 - November 1st, 1990
 *
 * crdprint - print a Microsoft Windows cardfile
 *
 * This program will print a cardfile in a simple fashion on any ASCII 
 * printer.  It does not bother drawing the cute (and timeconsuming) boxes 
 * around the cards.
 *
 * Art Zemon   July 12, 1986
 *
 * CompuServe:   72406,3275
 * UUCP:   ucbvax!trwrb!felix!zemon
 **************************************************************************
 *
 * Updated: Munjal M. July 21, 1990
 * CompuServe:   76264,602
 **************************************************************************
 *
 * Updated   : Version 2.0 - September 5, 1990
 *           : Rod Hutson... CompuServe 72411,564
 *             (403) 461-2431 (Edmonton, Alberta, Canada)
 * Changes   : couldn't get large cardfiles (90 cards, 19000+ bytes) to print
 *             so changed design of program to use only one file handle and
 *             track file position with a long variable instead... original
 *             version used two file handles open simulataneously to maintain
 *             file position.
 *           : altered variable names to follow Windows-style Hungarian
 *             notation (with some variations due mainly to old habits)
 *           : added copious commenting for those who like such things
 *           : ensured worked with WINDOWS 3.0 Cardfile files
 **************************************************************************
 *
 * Updated   : Version 2.1 - November 1st, 1990
 *           : Rod Hutson... CompuServe 72411,564
 *             (403) 461-2431 (Edmonton, Alberta, Canada)
 * Changes   : modified program to properly handle cardfile cards
 *             containing graphic images; any card containing a standard
 *             cardfile 'picture' graphic as well as standard text is
 *             processed as if the graphic weren't there; if the card
 *             contains only a graphic image or is blank, only the index 
 *             line is processed; if the card contains text only, then 
 *             it is processed normally;
 *           : while modifying previous ver 2.0 for the above functionality,
 *             I discovered a mistake in the card data structures... these 
 *             are now corrected as described below.
 *           : tested the resulting executable with cardfiles issued by
 *             WUGNET, consisting of 100,000+ bytes, over 300 cards, and
 *             many or most with some graphic image(s) on them
 * NOTE      : rather than obsfucate the code to make it 'nice and tight',
 *             I have left it loose and readable, and maybe even somewhat
 *             unstructured (ie. no sub-function calls, etc).
 **************************************************************************
 */

#include <stdio.h>      /* provides NULL, SEEK_SET, etc */
#include <stdlib.h>

#define TEXT_BUF    512     /* size of text buffer for card text */
#define INDEX_TEXT  40      /* max length of index line string   */
#define BYTE        char    /* a convenience, thats all          */

#ifndef NULL                /* lint told me NULL not declared,   */
    #define NULL    0       /*   so I did this, just to be sure  */
#endif

/**********************************************************/
/* layout the data structures for a MicroSoft '.crd' file */
/**********************************************************/
/* file header structure */
typedef struct {
    char    acSigniture[3]; /* 3 bytes ... 'MGC' */
    int     iNumCrds;       /* 2 bytes ... number of cards in cardfile */
} sFILEHDR;              /* structure is 5 bytes long */

/* index data structure for each card */
typedef struct {
    int     aiFuture[3];                /* 6 bytes ... reserved for future   */
    long    lOffset;                    /* 4 bytes ... card offset into file */
    BYTE    bFlag;                      /* 1 byte  ... some kind of flag     */
    char    acIndexText[INDEX_TEXT];    /* INDEX_TEXT bytes ... index line   */
    BYTE    bNull;                      /* 1 byte  ... always '0' or NULL    */
} sINDEXHDR;             /* structure is (12+INDEX_TEXT) bytes long */

/* if card contains text only, this structure applies to card data */
typedef struct {
    int     iLenBMap;       /* 2 bytes ... if no bitmap, always 0X0000     */
    int     iLenText;       /* 2 bytes ... number of bytes in text strings */
} sTEXTONLY;             /* structure is 4 bytes long */

void main (int argc, char **argv)
{
    /* we'll substitute this simple string for the graphics in Windows */ 
    static char acSep[] = "様様様様様様様様様様様様様様様様様様様様";

    FILE            *hFile;             /* handle to current cardfile */

    sFILEHDR        sFileHdr;           /* file header variable */

    sINDEXHDR       sIndexHdr;          /* card index line variable */

    sTEXTONLY       sTextOnly;          /* text-only card body variable */

    unsigned char   acText[TEXT_BUF];   /* card body text buffer */

    unsigned int    iCard,              /* current card count */
                    iCardFile,          /* current cardfile count */
                    iCheck,             /* dummy error variable */
                    iTextLength;        /* dummy text length variable */

    long            lCurrPos,           /* current file position */
                    lTmpPos;            /* dummy file position variable */

    /* if command line did not include cardfile(s) to print out, msg & exit */
    if (argc < 2) {
        fputs ("CrdPrt V2.1, November 1st, 1990\n\n", stderr);
        fputs ("Usage: CrdPrt {card-file} [{card-file-2}...]\n", stderr);
        fputs ("\t{card-file} is any DOS file-spec including wild-cards.\n\n", stderr);
        fputs ("\tCrdPrt will display one or several cardfile(s) in a simple format\n", stderr);
        fputs ("\t  without wasting valuable time doing fancy graphics.\n", stderr);
        exit (1);
    }

    /* do this until no more cardfile files to process */
    iCardFile = 0;
    while (++iCardFile < argc) {

        /* if cannot open cardfile, msg & exit */
        if ((hFile = fopen (argv[iCardFile], "r")) == NULL) {
            perror (argv[iCardFile]);
            exit (1);
        }
        fclose (hFile);

        /* reopen the file for 'read-binary' access */
        hFile = fopen (argv[iCardFile], "rb");

        /* read current cardfile file header into appropriate structure */
        iCheck = fread ((void *) &sFileHdr, sizeof (sFILEHDR), 1, hFile);

        /* if we read incorrect number of records, msg & exit */
        if (iCheck != 1) {
            perror ("Error reading file header");
            fclose (hFile);
            exit (1);
        }

        /* initialize current position in file to origin */
        lCurrPos = ftell (hFile);

        /* for current cardfile, go thru each card & display/print text */
        for (iCard = 1; iCard <= sFileHdr.iNumCrds; ++iCard) {

            /* go to file offset of previous card index info */
            fseek (hFile, lCurrPos, SEEK_SET);

            /* get current index line data */
            iCheck = fread (&sIndexHdr, sizeof (sINDEXHDR), 1, hFile);

            /* if we read incorrect number of records, msg & exit */
            if (iCheck != 1) {
                perror ("Error reading cardfile index text");
                fclose (hFile);
                exit (1);
            }

            /* note current file position for later */
            lCurrPos = ftell (hFile);

            /* go to file offset where this card is located */
            iCheck = fseek (hFile, sIndexHdr.lOffset, SEEK_SET);

            /* if fseek() did not return a 0, msg & exit */
            if (iCheck) {
                perror ("Error seeking card location in file");
                fclose (hFile);
                exit (1);
            }

            /* note start of this card for later */
            lTmpPos = ftell (hFile);

            /* assuming current card is text only, read first four bytes */
            iCheck = fread ((void *) &sTextOnly, sizeof (sTEXTONLY), 1, hFile);

            /********************** debugging stuff ************************/
            /* ******* uncomment if you want to see progress details *******/
            /*
            fprintf (stderr, "iCard=%x iCheck=%x iLenBMap=%x iLenText=%x\n", 
                            iCard, iCheck, sTextOnly.iLenBMap, 
                            sTextOnly.iLenText);
            getchar ();
            */
            /***************************************************************/

            /* if we read incorrect number of records, msg & exit */
            if (iCheck != 1) {
                perror ("Error reading first card body data bytes");
                fclose (hFile);
                exit (1);
            }

            /* if first two bytes are 0, then no bitmap */
            if (sTextOnly.iLenBMap == 0) {

                /* could still be a pure text-bearing card, so check */
                if (sTextOnly.iLenText != 0) {

                    /* okay, there IS some card body text, so process it */
                    if (sTextOnly.iLenText < TEXT_BUF) {

                        /* read designated number of card body text chars */
                        iCheck = fread (acText, 1, sTextOnly.iLenText, hFile);

                        /* if read incorrect number of chars, msg & exit */
                        if (iCheck != sTextOnly.iLenText) {
                            perror ("Error reading card body text");
                            fclose (hFile);
                            exit (1);
                        }

                        /* write separate string to output */
                        puts (acSep);

                        /* write index line to output */
                        puts (sIndexHdr.acIndexText);

                        /* write card body text to output */
                        fwrite (acText, 1, sTextOnly.iLenText, stdout);
                        putchar ('\n');
                    }
                    else
                        /* uh, oh... more than TEXT_BUF chars in card text */
                        fputs ("**Error** Cardfile Text Too Long!\a\n", stderr);
                }
            }
            else {
                /* card contains bitmap of length sTextOnly.iLenBMap bytes */

                /* skip graphic by moving to end of bitmap data for this card */
                iCheck = fseek (hFile, 
                            (lTmpPos + (long) (10 + sTextOnly.iLenBMap)),
                            SEEK_SET);

                /* if fseek() failed, msg & exit */
                if (iCheck) {
                    perror ("Error moving to end of card bitmap data");
                    fclose (hFile);
                    exit (1);
                }

                /* back to card body text: read length of card text in bytes */
                iCheck = fread ((void *) &iTextLength, sizeof (int), 1, hFile);

                /************ debugging stuff ***************/
                /***** uncomment to see progress details ****/
                /*
                fprintf (stderr, "iCheck=%x iTextLength=%x\n", 
                            iCheck, iTextLength);
                getchar ();
                */
                /********************************************/

                /* if we had a problem reading text length, msg & exit */
                if (iCheck != 1) {
                    perror ("Error reading in card body text length");
                    fclose (hFile);
                    exit (1);
                }

                /* check to see how many chars are in card body text */
                if (iTextLength != 0) {

                    /* not a blank card, so do your stuff! */
                    if (iTextLength < TEXT_BUF) {

                        /* read designated number of card body text bytes */
                        iCheck = fread (acText, 1, iTextLength, hFile);

                        /* if read incorrect number of bytes, msg & exit */
                        if (iCheck != iTextLength) {
                            perror ("Error reading card body text");
                            fclose (hFile);
                            exit (1);
                        }

                        /* write separator string to output */
                        puts (acSep);

                        /* write index line to output */
                        puts (sIndexHdr.acIndexText);

                        /* write card body text to output */
                        fwrite (acText, 1, iTextLength, stdout);
                        putchar ('\n');
                    }
                    else
                        /* card body text > TEXT_BUF chars in length */
                        fputs ("**Error** Cardfile Text Too Long!\a\n", stderr);
                }
            }
        }

        /* write final separator string to output, & close current cardfile */
        puts (acSep);
        fclose (hFile);
    }

    /* we're outa here! */
    exit (0);
}

