/*
 * nuview.c - prints the contents of a NuFX archive
 *
 * By Andy McFadden (fadden@cory.berkeley.edu)
 * NuLib v2.2  April 1990  Freeware (distribute, don't sell)
 */

#include "nudefs.h"
#include <stdio.h>
#ifdef BSD43
# include <strings.h>
#else  /* SYSV, APW, MSC */
# include <string.h>
#endif

#ifdef APW
# include <shell.h>
#endif

#include "nuview.h"
#include "nuread.h"
#include "nuetc.h"


/*
 * String definitions for NuView
 */
/* unknown value msg */
char *unknownStr = "[ unknown ]";

/* weekDay values */
char *WD[8] = { "[ null ]", "Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday" };

/* month values */
char *MO[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
		"Aug", "Sep", "Oct", "Nov", "Dec" };

/* thread_class */
/*#define TCn 4*/
char *TC[TCn] = { "Message_thread", "Control_thread", "Data_thread",
		"Filename_thread" };

/*#define TKn 3  /* max #of thread_kinds in a thread_class */
char *TK[TCn][TKn] = {
		{ "ASCII text", "ASCII text (predef size)", "<undef>" },
		{ "Create directory", "<undef>", "<undef>" },
		{ "File data_fork", "Disk image", "File resource_fork" },
		{ "Generic filename", "<undef>", "<undef>" } };

/* thread_format */
/*#define TFn 6*/
char *TF[TFn] = { "Uncompressed", "SQueezed (SQ/USQ)",
		"Dynamic LZW Type I (ShrinkIt)",
		"Dynamic LZW Type II (ShrinkIt)", "12-bit UNIX compress",
		"16-bit UNIX compress" };

/* quick thread_format */
/*#define QTFn 6*/
char *QTF[QTFn] = { "unc", "squ", "shk", "sh2", "u12", "u16" };

/* file_sys_id */
/*#define FIDn 14*/
char *FID[FIDn] = { "Reserved/unknown ($00)", "ProDOS/SOS", "DOS 3.3",
		"DOS 3.2", "Apple II Pascal", "Macintosh (MFS)",
		"Macintosh (HFS)", "LISA file system", "Apple CP/M",
		"Reserved ($09)", "MS-DOS", "High-Sierra", "ISO 9660",
		"AppleShare" };

/* storage_type */
/*#define STn 14*/
char *ST[STn] = { "Standard file ($00)", "Standard file ($01)",
		"Standard file ($02)", "Standard file ($03)", "??? ($04)",
		"Extended file ($05)", "??? ($06)", "??? ($07)", "??? ($08)",
		"??? ($09)", "??? ($0a)", "??? ($0b)", "??? ($0c)",
		"Subdirectory ($0d)" };

/* file type names */
char *FT[256] = {
	"NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "CHR",
	"PIC", "BA3", "DA3", "WPD", "SOS", "$0D", "$0E", "DIR",
	"RPD", "RPI", "$12", "OUT", "$14", "RPT", "$16", "$17",
	"$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F",
	"$20", "$21", "$22", "$23", "$24", "$25", "$26", "$27",
	"$28", "$29", "$2A", "$2B", "$2C", "$2D", "$2E", "$2F",
	"$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37",
	"$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F",
	"$40", "$41", "$42", "$43", "$44", "$45", "$46", "$47",
	"$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F",
	"$50", "$51", "$52", "$53", "$54", "$55", "$56", "$57",
	"$58", "$59", "$5A", "$5B", "$5C", "$5D", "$5E", "$5F",
	"PRE", "$61", "$62", "$63", "$64", "$65", "$66", "$67",
	"$68", "$69", "$6A", "NIO", "$6C", "DVR", "$6E", "HDV",
	"$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77",
	"$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F",
	"$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87",
	"$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F",
	"$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97",
	"$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F",
	"WPF", "MAC", "HLP", "DAT", "$A4", "LEX", "$A6", "$A7",
	"$A8", "$A9", "$AA", "GSB", "ARC", "$AD", "$AE", "$AF",
	"SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "STR", "TSF",
	"NDA", "CDA", "TOL", "DRV", "$BC", "FST", "$BE", "DOC",
	"PNT", "SCR", "ANI", "$C3", "$C4", "$C5", "$C6", "$C7",
	"FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF",
	"$D0", "$D1", "$D2", "$D3", "$D4", "$D5", "$D6", "$D7",
	"$D8", "$D9", "$DA", "$DB", "$DC", "DDD", "$DE", "$DF",
	"LBR", "$E1", "ATI", "$E3", "$E4", "$E5", "$E6", "$E7",
	"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "$EE", "PAS",
	"CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7",
	"$F8", "IMG", "INT", "IVR", "BAS", "VAR", "REL", "SYS" };


/*
 * NuView program
 */

/* print date from Time structure */
char *PrintDate(tptr, brief)
Time *tptr;
BOOLEAN brief;
{
    static char buf[64];  /* holds final date string; must be static */
    char buf2[64];  /* space to hold string while building it */

    /* check for validity */
    if ( (tptr->day > 30) || (tptr->month > 11) || (tptr->hour > 24) ||
	(tptr->minute > 59) ) {
	strcpy(buf, "   <invalid>   ");
	return (buf);
    }

    if (!tptr->second && !tptr->minute && !tptr->hour && !tptr->day &&
	    !tptr->month && !tptr->year && !tptr->weekDay && !tptr->extra) {
	strcpy(buf, "   [No Date]   ");
	return (buf);
    }

    /* only print weekDay if one was stored and if we're in FULL mode */
    if (!brief && tptr->weekDay) {
	(void) sprintf(buf, "%s, ", WD[tptr->weekDay]);
    } else {
	buf[0] = '\0';
    }
    (void) sprintf(buf2, "%.2d-%s-%.2d %.2d:%.2d",
	(tptr->day)+1, MO[tptr->month], tptr->year,
	tptr->hour, tptr->minute);
    (void) strcat(buf, buf2);
    if (!brief) {  /* add seconds to long output */
	(void) sprintf(buf2, ":%.2d", tptr->second);
	(void) strcat(buf, buf2);
    }
    return (buf);
}


/*
 * Dump contents of the threads (used by FULL view mode)
 */
static void DumpThreads(RNodePtr)
RNode *RNodePtr;
{
    int i;
    fourbyt count = RNodePtr->RHptr->total_threads;
    static char ind[4] = "   ";  /* indentation */
    THblock *THptr;
    TNode *TNodePtr;

    /* go through all threads, printing as we go */
    TNodePtr = RNodePtr->TNodePtr;
    for (i = 0; (fourbyt) i < count; i++) {
	if (TNodePtr == (TNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer threads than expected\n");
	    return;
	}
	THptr = TNodePtr->THptr;

	printf("%s --> Information for thread %d\n", ind, i);
	printf("%s thread_class: %s\n", ind, THptr->thread_class < TCn ?
		TC[THptr->thread_class] : unknownStr);
	printf("%s thread_format: %s\n", ind, THptr->thread_format < TFn ?
		TF[THptr->thread_format] : unknownStr);
	printf("%s thread_kind: %s ($%.2X)\n", ind,
		(THptr->thread_kind < TKn && THptr->thread_class < TCn) ?
		TK[THptr->thread_class][THptr->thread_kind] : unknownStr,
		THptr->thread_kind);
	printf("%s thread_crc: $%.4x\n", ind, THptr->thread_crc);
	printf("%s thread_eof: %lu   ", ind, THptr->thread_eof);
	printf("comp_thread_eof: %lu\n", THptr->comp_thread_eof);
	printf("%s * position within file: %ld\n", ind, TNodePtr->fileposn);

	TNodePtr = TNodePtr->TNext;
    }
    /* after all info printed, show sum total of thread lengths */
    printf("%s * total thread_eof: %lu   ", ind, RNodePtr->unc_len);
    printf("total comp_thread_eof: %lu\n", RNodePtr->comp_len);
}


/*
 * Scan contents of the threads for certain things (for PROSHK view mode)
 * Returns 65535 as error code (-1 in an unsigned short).
 * Places the format, compressed EOF, and uncompressed EOF in the location
 * pointed to by the appropriate variables.
 *
 * This will probably fail if there are > 32767 threads.
 */
static twobyt ScanThreads(RNodePtr, format, dataCEOF, dataEOF)
RNode *RNodePtr;
twobyt *format;  /* format of the data_fork thread */
long *dataCEOF;  /* length of the data_fork thread (compressed) */
long *dataEOF;  /* length of the data_fork thread (uncompressed) */
{
    int i;
    int count;
    THblock *THptr;
    TNode *TNodePtr;

    count = (int) RNodePtr->RHptr->total_threads;
    *format = 65535;  /* default = error */
    *dataCEOF = 0L;
    *dataEOF = 0L;
    TNodePtr = RNodePtr->TNodePtr;
    for (i = 0; i < count; i++) {
	if (TNodePtr == (TNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer threads than expected\n");
	    return (65535);
	}
	THptr = TNodePtr->THptr;

	if (THptr->thread_class == 2) {  /* data thread? */
	    *format = THptr->thread_format;
	    *dataCEOF = THptr->comp_thread_eof;
	    *dataEOF = THptr->thread_eof;
	    return (THptr->thread_kind);
	}
	TNodePtr = TNodePtr->TNext;
    }
    return (65535);  /* no data thread found */
}


/*
 * View archive contents
 *
 * Format types:
 *     T: NAMEONLY - Brief output of filenames only (good for pipes)
 *     V: PROSHK - ProDOS ShrinkIt format
 *     Z: FULL - Fully detailed output
 */
void NuView(filename, options)
char *filename;
char *options;
{
    ListHdr *archive;
    MHblock *MHptr;
    RHblock *RHptr;
    RNode *RNodePtr;
    outtype prtform;
    int rec;
    char tmpbuf[80];  /* temporary buffer for sprintf + printf */
    twobyt format, datakind;  /* PROSHK */
    int percent;  /* PROSHK */
    long dataCEOF, dataEOF;  /* PROSHK */
#ifdef APW  /* kill "not used" messages */
    char *ptr;
#endif
    static char *procName = "NuView";

    /* process options ourselves */
    switch (options[0]) {
    case 't':
	if (INDEX(options+1, 'v')) prtform = PROSHK;  /* -tv is same as -v */
	if (INDEX(options+1, 'z')) prtform = FULL;
	else prtform = NAMEONLY;
	break;
    case 'v':
	prtform = PROSHK;
	break;
    default:
	fprintf(stderr, "%s internal error: unknown output format\n", prgName);
	Quit (-1);
    }

    archive = NuRead(filename);
    MHptr = archive->MHptr;

    /* Print master header info */
    if (prtform == NAMEONLY) {
	/* don't print any info from master header for NAMEONLY */
    } else if (prtform == PROSHK) {
#ifdef APW
	/* strip partial paths from APW filename (if any) */
	ptr = RINDEX(archive->arc_name, '/');
	printf(" %-15.15s ", ptr ? ptr+1 : archive->arc_name);
#else
	printf(" %-15.15s ", archive->arc_name);
#endif
	printf("Created:%s   ", PrintDate(&MHptr->arc_create_when, TRUE));
	printf("Mod:%s     ", PrintDate(&MHptr->arc_mod_when, TRUE));
	printf("Recs:%5lu\n\n", MHptr->total_records);
	printf(" Name                  Kind  Typ  Auxtyp Archived");
	printf("        Fmat Size  Un-Length\n");
	printf("-------------------------------------------------") ;
	printf("----------------------------\n");
    } else if (prtform == FULL) {
	printf("Now processing archive '%s'\n", archive->arc_name);
	printf("---> Master header information:\n");
	printf("master ID: '%.6s'   ", MHptr->ID);
	printf("master_version: $%.4x   ", MHptr->master_version);
	printf("master_crc: $%.4X\n", MHptr->master_crc);
	printf("total_records: %lu    ", MHptr->total_records);
	if (MHptr->master_version >= 0x0001) {
	    printf("master_eof: %lu\n", MHptr->master_eof);
	} else {
	    printf("\n");
	}
	printf("created: %s   ", PrintDate(&MHptr->arc_create_when, FALSE));
	printf("mod: %s\n", PrintDate(&MHptr->arc_mod_when, FALSE));
    } else {
	printf("NuView internal error: undefined output format\n");
	Quit (-1);
    }

    /* Print record info */
    RNodePtr = archive->RNodePtr;
    for (rec = 0; (fourbyt) rec < MHptr->total_records; rec++) {
	if (RNodePtr == (RNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer records than expected\n");
	    return;
	}
	RHptr = RNodePtr->RHptr;

	if (prtform == NAMEONLY) {
	    printf("%.79s\n", RNodePtr->filename);  /* max 79 chars */
	} else if (prtform == PROSHK) {
	    printf("%c", (RHptr->access == 0xE3L || RHptr->access == 0xC3L) ?
		' ' : '+');
	    printf("%-21.21s ", RNodePtr->filename);
	    /* get info on data_fork thread */
	    datakind = ScanThreads(RNodePtr, &format, &dataCEOF, &dataEOF);
	    if (datakind == 65535) {  /* no data thread... */
		printf("????  ");
		printf("%s  ", RHptr->file_type < 256L ? FT[RHptr->file_type] :
			"???");
		printf("$%.4X  ", (twobyt) RHptr->extra_type);
	    } else if (datakind == 1) {  /* disk */
		printf("Disk  ");
		printf("---  ");
		(void) sprintf(tmpbuf, "%dk", (twobyt) RHptr->extra_type / 2);
		printf("%-5s  ", tmpbuf);
	    } else {  /* must be a file */
		printf("File  ");
		printf("%s  ", RHptr->file_type < 256L ? FT[RHptr->file_type] :
			"???");
		printf("$%.4X  ", (twobyt) RHptr->extra_type);
	    }
	    printf("%s  ", PrintDate(&RHptr->archive_when, TRUE));
	    printf("%s  ", format < QTFn ? QTF[format] : "???");

	    /* figure out the percent size, and format it appropriately */
	    /* Note RNodePtr->comp_len corresponds to dataCEOF, and */
	    /*      RNodePtr->unc_len corresponds to dataEOF.       */
	    if (!dataCEOF && !dataEOF) {
		printf("100%%  ");  /* file is 0 bytes long */
	    } else if ((!dataEOF && dataCEOF) || (dataEOF && !dataCEOF)) {
		printf("---   ");  /* something weird happened */
	    } else if (dataEOF < dataCEOF) {
		printf(">100%% ");  /* compression failed?!? */
	    } else {  /* compute from sum of thread lengths (use only data?) */
		percent = (((float) dataCEOF /
			(float) dataEOF) * 100.0) /*+ 0.5*/ ;
		(void) sprintf(tmpbuf, "%.2d%%", percent);
		printf("%-4s  ", tmpbuf);
	    }
	    if (!dataEOF && dataCEOF)  /* weird */
		printf("    ????\n");
	    else
		printf("%8ld\n", dataEOF);
	} else if (prtform == FULL) {
	    printf("\n---> Information for record %d:\n", rec);
	    printf("Filename: (%d) '%s'\n",
				RNodePtr->filename_length, RNodePtr->filename);
	    printf("header ID: '%.4s'   ", RHptr->ID);
	    printf("version_number: $%.4X   ", RHptr->version_number);
	    printf("header_crc: $%.4X\n", RHptr->header_crc);
	    printf("attrib_count: %u   ", RHptr->attrib_count);
	    printf("total_threads: %u\n", RHptr->total_threads);
	    printf("file_sys_id: %s   ", RHptr->file_sys_id < FIDn ?
		FID[RHptr->file_sys_id] : unknownStr);
	    printf("sep: '%c'\n", RHptr->file_sys_info);
	    if (RHptr->file_sys_id == 0x0001) {  /* ProDOS-specific */
		printf("access: %s ($%.8lX)   ", (RHptr->access == 0xE3L ||
		RHptr->access == 0xC3L) ? "Unlocked" : "Locked", RHptr->access);
		printf("file_type: %s ($%.8lX)\n", RHptr->file_type < 256L ?
		    FT[RHptr->file_type] : "???", RHptr->file_type);
	    } else {  /* all other filesystems */
		printf("access: $%.8lX   ", RHptr->access);
		printf("file_type: $%.8lX\n", RHptr->file_type);
	    }
	    printf("extra_type: $%.8lX   ", RHptr->extra_type);
	    printf("storage_type: %s\n", RHptr->storage_type < STn ?
		ST[RHptr->storage_type] : unknownStr);
	    printf("created: %s   ", PrintDate(&RHptr->create_when, FALSE));
	    printf("mod: %s\n", PrintDate(&RHptr->mod_when, FALSE));
	    printf("archived: %s\n", PrintDate(&RHptr->archive_when,
		    FALSE));
	    printf("GS/OS option_size: %.4x\n", RHptr->option_size);
	    /* future expansion... */
	} else {
	    printf("%s internal error: undefined output format\n", prgName);
	    Quit (-1);
	}

	/* Print thread info */
	if (prtform == FULL) DumpThreads(RNodePtr);
	RNodePtr = RNodePtr->RNext;  /* advance to next record */
#ifdef APW
	if (STOP()) Quit (1);  /* check for OA-period */
#endif
    }
    if (prtform == FULL)
	printf("\n*** end of file position: %ld\n", archive->nextposn);
}
