/* mac_mpack.c -- Mac user interface to mpack routines
 *
 * (C) Copyright 1993 by Christopher J. Newman
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Christopher J. Newman not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Christopher J. Newman makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * CHRISTOPHER J. NEWMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL CHRISTOPHER J. NEWMAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "macnapp.h"
#include "macmpack.h"
#include "version.h"

/* save watch cursor */
Cursor watch;

/* flag for active help window */
static WindowPtr helpw = NULL;

typedef struct filelist {
	int vRefNum;
	PCstr fname[65];
} filelist;

typedef struct listwin {
	na_win win;
	int count;
	filelist **hflist;
	ListHandle l;
} listwin;

void warn(char *str);
static void do_decodefiles(listwin *);
static void addfile(listwin *, SFReply *);
static void removefile(listwin *);
static short listclose(na_win *);
static short listmouse(na_win *, Point, short, short);
static short listctrl(na_win *, Point, short, short, ControlHandle);
static short listupdate(na_win *, Boolean);
static short listinit(na_win *,long *);
static short textinit(na_win *,long *);
static void do_decode(void);
static void do_encode(SFReply *);
static short mainmenu(struct na_win*, WORD, WORD);

#define dwin ((listwin *) win)

SFTypeList textList = { 'TEXT', 0, 0, 0 };

/* warn the user
 */
void warn(char *str)
{
	PCstr wstr[257];
	
	CtoPCstrncpy(wstr, str, 255);
	ParamText(P(wstr), NULL, NULL, NULL);
	NAalert(warnALRT);
}

/* yell at the user
 */
void yell(char *str)
{
	PCstr wstr[257];
	
	CtoPCstrncpy(wstr, str, 255);
	ParamText(P(wstr), NULL, NULL, NULL);
	NAalert(errorALRT);
}

/* process the files in the file list
 */
static void do_decodefiles(dw)
	listwin *dw;
{
	int count = dw->count;
	filelist *fl;
	FILE *dfile;
	extern long _ftype, _fcreator;
	
	_ftype = 'TEXT';
	_fcreator = 'mPAK';
	SetCursor(&watch);
	MoveHHi((Handle) dw->hflist);
	HLock((Handle) dw->hflist);
	fl = *dw->hflist;
	while (count--) {
		SetVol(NULL, fl->vRefNum);
		if (dfile = fopen(C(fl->fname), "r")) {
			handleMessage(dfile, "text/plain", 0);
			fclose(dfile);
		}
		++fl;
	}
	HUnlock((Handle) dw->hflist);
	SetCursor(&arrow);
}

/* return non-zero if two filenames have the same prefix
 */
static int fprefixMatch(char *base, PCstr *match)
{
	PCstr temp[257];
	char *scan;
	short prefixlen;
	
	PtoPCstrcpy(temp, base);
	scan = C(temp) + PCstrlen(temp) - 1;
	while (isdigit(*scan) && scan > C(temp)) --scan;
	prefixlen = scan - C(temp) + 1;
	if (strncmp(C(temp), C(match), prefixlen)) return (0);
	scan = C(match) + prefixlen;
	while (isdigit(*scan)) ++scan;
	
	return (!*scan);
}

/* do the add of a file to a list
 */
static void addit(listwin *dw, short vRefNum, char *fname)
{
	long size = GetHandleSize((Handle) dw->hflist) / sizeof (filelist);
	filelist *fl;
	char *bp;
	Cell c;
	PCstr fbuf[42];

	if (size == dw->count) {
		SetHandleSize((Handle) dw->hflist, (++size * sizeof (filelist)));
		if (MemError() != noErr) return;
	}
	MoveHHi((Handle) dw->hflist);
	HLock((Handle) dw->hflist);
	fl = *dw->hflist + dw->count;
	fl->vRefNum = vRefNum;
	PtoPCstrcpy(fl->fname, fname);
	SetPt(&c, 0, dw->count);
	LAddRow(1, ++dw->count, dw->l);
	LSetCell((Ptr) C(fname), (short) Pstrlen(fname), c, dw->l);
	HUnlock((Handle) dw->hflist);
}

/* add file set to file list
 */
static void addfile(dw, reply)
	listwin *dw;
	SFReply *reply;
{
	CInfoPBRec cipbr;
	HFileInfo *fpb = (HFileInfo *)&cipbr;
	PCstr fbuf[42];
	short idx, foundone = 0, wrefnum;
	long dirid, procid;
	
	/* find directory where file is */
	procid = 0;
	GetWDInfo(reply->vRefNum, &wrefnum, &dirid, &procid);
	
	/* loop through directory */
	idx = 1;
	for (;;) {
		fpb->ioVRefNum = wrefnum;
		fpb->ioNamePtr = P(fbuf);
		fpb->ioDirID = dirid;
		fpb->ioFDirIndex = idx;
		if (PBGetCatInfo(&cipbr, FALSE)) break;
		SetClen(fbuf);
		
		if (!(fpb->ioFlAttrib & 16) && fprefixMatch((char *)reply->fName, fbuf)) {
			addit(dw, reply->vRefNum, (char *) P(fbuf));
			foundone = 1;
		}
		++idx;
	}
	if (!foundone) {
		addit(dw, reply->vRefNum, (char *) reply->fName);
	}
}

/* remove file from file list
 */
static void removefile(dw)
	listwin *dw;
{
	filelist *fl;
	int count;
	Cell c;
	
	c.h = c.v = 0;
	if (LGetSelect(TRUE, &c, dw->l)) {
		MoveHHi((Handle) dw->hflist);
		HLock((Handle) dw->hflist);
		fl = *dw->hflist + c.v;
		count = dw->count - c.v;
		while (--count) {
			fl[0] = fl[1];
			++fl;
		}
		HUnlock((Handle) dw->hflist);		
		--dw->count;
		LDelRow(1, c.v, dw->l);
	}
}

/* close list window
 */
static short listclose(win)
	na_win *win;
{
	LDispose(dwin->l);
	DisposHandle((Handle) dwin->hflist);
	
	return (NA_CLOSED);
}

/* mouse procedure
 */
static short listmouse(na_win *win, Point p, short type, short mods)
{
	Cell c;
	
	if (!(type & 1)) {
		LClick(p, mods, dwin->l);
		c.h = c.v = 0;
		NAhiliteDItem((DialogPtr)win->pwin, iRemove, LGetSelect(TRUE, &c, dwin->l) ? 0 : 255);
	}
}

/* control procedure
 */
static short listctrl(na_win *win, Point p, short item, short mods, ControlHandle ctrlh)
{
	SFReply reply;
	Point where;

	switch (item) {
		case iAdd:
			where.h = where.v = 0;
			SFGetFile(where, NULL, NULL, 1, textList, NULL, &reply);
			if (reply.good) {
				if (!dwin->count) {
					NAhiliteDItem((DialogPtr)win->pwin, iOk, 0);
				}
				addfile(dwin, &reply);
			}
			return (NA_PROCESSED);
			
		case iRemove:
			removefile(dwin);
			NAhiliteDItem((DialogPtr)win->pwin, iRemove, 255);
			if (!dwin->count) {
				NAhiliteDItem((DialogPtr)win->pwin, iOk, 255);
			}
			return (NA_PROCESSED);

		case iOk:
			do_decodefiles(dwin);
		case iCancel:
			return (NA_REQCLOSE);
	}
	
	return (NA_NOTPROCESSED);
}

/* update the list window
 */
static short listupdate(na_win *win, Boolean resize)
{
	Rect r;
	Handle hand;
	short type;
	
	GetDItem((DialogPtr)win->pwin, iFileList, &type, &hand, &r);
	FrameRect(&r);
	UpdtDialog(win->pwin, win->pwin->visRgn);
	LUpdate(win->pwin->visRgn, dwin->l);
	
	return (NA_NOTPROCESSED);
}

/* initialize the list window
 */
static short listinit(win, data)
	na_win *win;
	long *data;
{
	SFReply *reply = (SFReply *) data;
	Rect r, zrect;
	Point p;
	Handle hand;
	short type;
	
	GetDItem((DialogPtr)win->pwin, iFileList, &type, &hand, &r);
	InsetRect(&r, 1, 1);
	zrect.top = zrect.bottom = zrect.left = p.h = p.v = 0;\
	zrect.right = 1;
	dwin->l = LNew(&r, &zrect, p, 0, win->pwin, 0, 0, 0, 1);
	if (!dwin->l) return (NA_CLOSED);
	(*dwin->l)->selFlags = lOnlyOne;
	dwin->hflist = (filelist **) NewHandle(sizeof (filelist));
	if (!dwin->hflist) {
		LDispose(dwin->l);
		return (NA_CLOSED);
	}
	dwin->count = 0;
	addfile(dwin, reply);
	win->closep = listclose;
	win->updatep = listupdate;
	win->ctrlp = listctrl;
	win->mousep = listmouse;
	NAhiliteDItem((DialogPtr)win->pwin, iRemove, 255);
	ShowWindow(win->pwin);
	DrawDialog(win->pwin);
	LDoDraw(TRUE, dwin->l);
	
	return (NA_NOTPROCESSED);
}

/* Decode procedure: first get a file, then open decode window
 */
static void do_decode()
{
	SFReply reply;
	Point where;
	
	where.h = where.v = 0;
	SFGetFile(where, NULL, NULL, 1, textList, NULL, &reply);
	if (reply.good) {
		NAwindow(0, NA_DIALOGWINDOW | NA_USERESOURCE | NA_RECENTER | NA_DEFBUTTON |
			NA_HASCONTROLS | NA_CLOSEBOX, NULL, decodeDLOG, (long *) &reply,
			sizeof (listwin), listinit);
	}
}

/* a filter proc which handles Return, Enter, Esc, command-period properly
 */
pascal Boolean mySaveFilterProc(dialog, pevent, item)
	DialogPtr		dialog;
	EventRecord		*pevent;
	short			*item;
{
	int c;

	if (pevent->what == autoKey || pevent->what == keyDown) {
		switch (c = (pevent->message & charCodeMask)) {
			case '\r':
			case '\n':
			case 0x03:
				*item = 1;
				goto HILITE;
			
			case '.':
				if (!(pevent->modifiers & cmdKey)) break;
			case '\033':
				*item = 2;
			HILITE:
				NAflashButton(dialog, *item);
				return (true);
			default:
				if (((DialogPeek)dialog)->editField + 1 == iMaximum
					&& isprint(c) && c != '\t' && !isdigit(c)) {
					pevent->what = nullEvent;
				}
				break;
					
		}
	}
	
	return (false);
}

static long save_max;
static PCstr save_subject[257];
static short save_dflag;

/* custom file dialog procedure
 */
pascal short mySaveDialog(short item, DialogPtr theDialog)
{
	Handle etext;
	PCstr tstr[257];
	ControlHandle	ctrl;
	
	if (item == iDescfile) {
		NAgetDHandle(theDialog, iDescfile, &ctrl);
		SetCtlValue(ctrl, !GetCtlValue(ctrl));
	} else if (item == iOk) {
		NAgetIText(theDialog, iSubject, save_subject);
		NAgetIText(theDialog, iMaximum, tstr);
		save_max = atol(C(tstr));
		NAgetDHandle(theDialog, iDescfile, &ctrl);
		save_dflag = GetCtlValue(ctrl);
	}
	
	return (item);
}

/* Decode procedure: first get a file, then open decode window
 */
static void do_encode(SFReply *reply)
{
	SFReply infile, outfile, descfile;
	Point where;
	PCstr fname[34], mtype[257];
	struct lookupct *ctptr;
	char *scan;
	short mapcount, i;
	Handle h;
	ResType type = 'TyCr';
	extern long _ftype, _fcreator;
	
	where.h = where.v = 0;
	if (!reply) {
		SFGetFile(where, NULL, NULL, -1, NULL, NULL, &infile);
		if (!infile.good) return;
	} else {
		infile = *reply;
	}
	PtoPCstrcpy(fname, (char *) infile.fName);
	if (scan = strrchr(C(fname), '.')) {
		*scan = '\0';
		SetPlen(fname);
	}
	if (PCstrlen(fname) >= 25) {
		*fname = 25;
		SetClen(fname);
	}
	PtoCstr(infile.fName);
  RETRYSFP:
	SFPPutFile(where, "\pMIME part prefix:", P(fname), 
		mySaveDialog, &outfile, encodeDLOG, mySaveFilterProc);
	if (outfile.good) {
		PtoCstr(outfile.fName);
		if (!strcmp((char *)infile.fName, (char *)outfile.fName)) {
			yell("The output filename must be different from the input filename");
			goto RETRYSFP;
		}
		if (save_dflag) {
			SFGetFile(where, NULL, NULL, 1, textList, NULL, &descfile);
			if (!descfile.good) {
				save_dflag = 0;
			} else {
				PtoCstr(descfile.fName);
			}
		}
		mapcount = CountResources(type);
		*mtype = '\0';
		for (i = 1; i <= mapcount; ++i) {
			h = GetIndResource(type, i);
			if (h && **(OSType **)h == infile.fType) {
				GetResInfo(h, &i, &type, P(mtype));
				if (ResError() == noErr) break;
			}
		}
		SetClen(mtype);

		_ftype = 'TEXT';
		_fcreator = 'mPAK';
		SetCursor(&watch);
		encode(infile.fName, save_dflag ? descfile.fName : 0,
			*C(save_subject) ? save_subject : infile.fName, NULL, save_max,
			PCstrlen(mtype) ? C(mtype) : NULL, outfile.fName);
		SetCursor(&arrow);
	}
}

/* open a textedit window & load a file into it
 */
static short textinit(na_win *win, long *data)
{
	Handle h = (Handle) data;
	long size;
	
	TextFont(monaco);
	TextSize(9);
	size = GetHandleSize(h);
	MoveHHi(h);
	HLock(h);
	NATEinit(win, NATE_READONLY, 489, *h, size);
	HUnlock(h);
	ShowWindow(win->pwin);
	
	return (NA_NOTPROCESSED);
}

/* Open a window for a text file
 */
static short textopen(short message, AppFile *afile, FSSpec *fspec)
{
	SFReply reply;
	Point where;
	Handle data;
	long len, saved;
	short fref;
	OSErr err;
	Rect *sr;
	FInfo finfo;
	PCstr fname[257];
	char emsg[512];
	
	if (message != appOpen) return (-1);
	
	/* find & open file */
	if (!afile && !fspec) {
		where.h = where.v = 0;
		SFGetFile(where, NULL, NULL, 1, textList, NULL, &reply);
		if (!reply.good) return (-1);
		PtoPCstrcpy(fname, (char *) reply.fName);
		err = FSOpen(reply.fName, reply.vRefNum, &fref);
	} else if (afile) {
		PtoPCstrcpy(fname, (char *) afile->fName);
		if (afile->fType != 'TEXT') {
			BlockMove(afile->fName, reply.fName, sizeof (reply.fName));
			reply.fType = afile->fType;
			do_encode(&reply);
			return (0);
		}
		err = FSOpen(afile->fName, afile->vRefNum, &fref);
	} else {
		PtoPCstrcpy(fname, (char *) fspec->name);
		if (FSpGetFInfo(fspec, &finfo) == noErr && finfo.fdType != 'TEXT') {
			BlockMove(fspec->name, reply.fName, sizeof (reply.fName));
			reply.fType = finfo.fdType;
			do_encode(&reply);
			return (0);
		}
		err = FSpOpenDF(fspec, fsRdPerm, &fref);
	}
	
	/* read file */
	if (err != noErr) {
		sprintf(emsg, "Could not open file %s\rError %d", C(fname), err);
		yell(emsg);
		return (-1);
	}
	if (GetEOF(fref, &len) < 0 || len >= 32000 || !(data = NewHandle(len))) {
		FSClose(fref);
		sprintf(emsg, "File %s is too long to view.", C(fname));
		yell(emsg);
		return (-1);
	}
	MoveHHi(data);
	HLock(data);
	saved = len;
	err = FSRead(fref, &saved, *data);
	HUnlock(data);
	FSClose(fref);
	if (err != noErr || saved != len) {
		sprintf(emsg, "Could not read file %s\rError %d", C(fname), err);
		yell(emsg);
		return (-1);
	}
	
	/* open NATE window */
	sr = NAscreenrect(NA_TOPSCN | NA_RIGHTSCN | NA_TITLEOFFSET | NA_STACK);
	if (sr->right - sr->left > 512) sr->right = sr->left + 512;
	NAwindow(sr, NA_USERESOURCE | NA_FORCESIZE | NATEflags, C(fname), textWIND,
		(long *) data, sizeof (nate_win), textinit);
	DisposHandle(data);
	
	return (0);
}

#define hwinfo ((nate_win *)win)

/* help close procedure
 */
static short helpclose(na_win *win)
{
	helpw = NULL;

	return (NATEclosep(win));
}

/* help window procedure
 */
static short helpwindow(na_win *win, long *data)
{
	Rect		rtemp, vtemp;
	Handle		h, hs;
	long		len;
	TEHandle	hTE;
	
	rtemp = win->pwin->portRect;
	vtemp = rtemp;
	vtemp.right = vtemp.left + (hwinfo->docwidth = 475);
	win->mousep = NATEmousep;
	win->idlep = NATEidlep;
	win->menup = NATEmenup;
	win->activep = NATEactivep;
	win->updatep = NATEupdatep;
	win->ctrlp = NATEctrlp;
	win->closep = helpclose;
	win->cursorRgn = NewRgn();
	hwinfo->vctrl = hwinfo->hctrl = NULL;
	
	TEAutoView(true, hTE = hwinfo->hTE = TEStylNew(&vtemp, &rtemp));
	h = GetResource('TEXT', helpTEXT);
	hs = GetResource('styl', helpSTYL);
	len = GetHandleSize(h);
	HLock(h);
	TEStylInsert(*h, len, (StScrpHandle) hs, hTE);
	HUnlock(h);
	TESetSelect(0, 0, hTE);
	hwinfo->lheight = TEGetHeight((*hTE)->nLines, 0, hTE) / (*hTE)->nLines;
	ShowWindow(helpw = win->pwin);
	
	return (NA_NOTPROCESSED);
}

/* Main menu procedure
 */
static short mainmenu(na_win *win, WORD menuid, WORD itemno)
{
	short status;
	MenuHandle mh;
	PCstr version[32];

	switch (menuid) {
		case mApple:
			if (itemno == iAbout) {
				CtoPCstrcpy(version, MPACK_VERSION);
				ParamText(P(version), NULL, NULL, NULL);
				return (NAwindow(0, NA_DIALOGWINDOW | NA_USERESOURCE | NA_RECENTER
					| NA_DIALOGUPDATE | NA_MODAL, "About", aboutDLOG, (long *) NULL, 0, NAabout));
			}
			if (!helpw) {
				NAwindow(0, NA_USERESOURCE | NATEflags | NA_SMARTSIZE, NULL, helpWIND,
					(long *) NULL, sizeof (nate_win), helpwindow);
			} else {
				SelectWindow(helpw);
			}
			break;
			
		case mFile:
			switch (itemno) {
				case iEncode:
					do_encode(NULL);
					return (NA_PROCESSED);
					
				case iDecode:
					do_decode();
					return (NA_PROCESSED);
				
				case iOpen:
					textopen(appOpen, NULL, NULL);
					return (NA_PROCESSED);

				case iClose:
					return (NA_NOTPROCESSED);

				case iQuit:
					return (NA_REQCLOSEALL);
			}
			break;

		case mEdit:
			switch (itemno) {
				case iClipboard:
					NAclipboardMenu(menuid, itemno, 'TEXT');
					return (NA_PROCESSED);
			}
			break;
	}
	return (NA_NOTPROCESSED);
}


main()
{
	CursHandle cursH;

	if (NAinit(128, 3, textopen, mainmenu, 2, 0, iClose) == 0) {
		cursH = GetCursor(watchCursor);
		watch = **cursH;
		NAmainloop();
	}
}
