#import <appkit/OpenPanel.h>
#import <appkit/publicWraps.h>
#import <appkit/Application.h>
#import <appkit/Listener.h>
#import <appkit/NXImage.h>
#import <appkit/PrintInfo.h>
#import <appkit/Pasteboard.h>
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/file.h>
#import "TVController.h"
#import "PrefControl.h"
#import "ToyWin.h"
#import "ToyWinPPM.h"
#import "ToyWinEPS.h"
#import "ToyWinBMP.h"
#import "ToyWinGIF.h"
#import "ToyWinPCD.h"
#import "DirList.h"
#import "ADController.h"
#import "strfunc.h"
#import "Exttab.h"
#import "common.h"

TVController *theController = NULL;

@implementation TVController

#define  typeNumber	18

/* ToyViewer treats these extensions as pre-defined */
static const char *const fileType[typeNumber + 1] = {
	"tiff", "tif", "TIFF", "TIF",
	"eps", "EPS",
	"gif", "GIF",
	"bmp", "BMP", "dib", "DIB",
	"ppm", "pgm", "pbm", "pnm",
	"pcd", "PCD",
	NULL };
static const char fileTypeID[typeNumber] = {
	Type_tiff, Type_tiff, Type_tiff, Type_tiff,
	Type_eps, Type_eps,
	Type_gif, Type_gif,
	Type_bmp, Type_bmp, Type_bmp, Type_bmp,
	Type_ppm, Type_ppm, Type_ppm, Type_ppm,
	Type_pcd, Type_pcd };
/* extern */ const char **fileTypeBuffer;
/* extern */ short *fileTypeIDBuffer;
static char odir[MAXFILENAMELEN];	/* Last Opened Directory */
static Exttab *extTable;

/* If file has no extension, this func. recognize its file type */
static int discriminate(const char *fn)
{
	int maybe = Type_none;
	int cc;
	FILE *fp;

	if ((fp = fopen(fn, "r")) == NULL)
		return Type_none;
	switch (cc = getc(fp)) {
	case 0x0a: maybe = Type_pcx;  break;
	case '%':  maybe = Type_eps;  break;
	case 'B':  maybe = Type_bmp;  break;
	case 'G':  maybe = Type_gif;  break;
	case 'I':  maybe = Type_tiff;  break;
	case 'M':  maybe = Type_mag;  break;
	case 'P':  maybe = Type_ppm;  break;
	case 0x89:  maybe = Type_png;  break;
	case 0xff:
		if (getc(fp) == 0xd8)
			maybe = Type_jpg;
		break;
	default: break;
	}
	fclose(fp);
	return maybe;
}


- appDidInit:sender
{
	int i, j, n;
	char buf[MAXFILENAMELEN], *p, **q;
	NXCoord	lmg, rmg, tmg, bmg;
	const char *sendTypes[3];
	const char *returnTypes[3];
	const char *const *types = [NXImage imageFileTypes];

	// [preference init];
	if (thePreference == NULL) thePreference = preference;
	theController = self;

	extTable = [[Exttab alloc] init];
	n = dircopy(buf, NXArgv[0], YES);
	strcpy(&buf[n], toyviewerTAB);
	[extTable readExtData: buf];
	p = getenv("HOME");
	sprintf(buf, "%s/%s", p, toyviewerRC);
	[extTable readExtData: buf];
	sprintf(buf, "%s/Library/ToyViewer/rc.%s", p, __ARCHITECTURE__);
	[extTable readExtData: buf];
	n = [extTable entry];

	for (i = 0; types[i]; i++) ;
	n += i + typeNumber + 1;
	fileTypeBuffer = (const char **)malloc(sizeof(char *const *) * n);
	fileTypeIDBuffer = (short *)malloc(sizeof(short) * n);
	i = 0;
	if ((q = [extTable table]) != NULL)
		for (j = 0; q[j]; j++) {
			fileTypeBuffer[i] = q[j];
			fileTypeIDBuffer[i++] = Type_user;
		}
	for (j = 0; fileType[j]; j++) {
		fileTypeBuffer[i] = fileType[j];
		fileTypeIDBuffer[i++] = fileTypeID[j];
	}
	for (j = 0; types[j]; j++) {
		fileTypeBuffer[i] = types[j];
		fileTypeIDBuffer[i++] = Type_other;
	}
	fileTypeBuffer[i] = NULL;
	[DirList setExtList: fileTypeBuffer];

	if ([thePreference isUpdatedServices])
		NXUpdateDynamicServices();	// service re-providing
	[[NXApp appListener] setServicesDelegate: self];
	sendTypes[0] = NULL;
	returnTypes[0] = NXPostScriptPboardType;
	returnTypes[1] = NXTIFFPboardType;
	returnTypes[2] = NULL;
	[NXApp registerServicesMenuSendTypes:sendTypes
			andReturnTypes:returnTypes];

	printInfo = [NXApp printInfo];
	[printInfo setOrientation:NX_LANDSCAPE andAdjust:YES];
	[printInfo getMarginLeft:&lmg right:&rmg top:&tmg bottom:&bmg];
	lmg = (lmg + rmg) * 0.3;
	tmg = (tmg + bmg) * 0.3;
	[printInfo setMarginLeft:lmg right:lmg top:tmg bottom:tmg];

	[self initWallpaper];
	return self;
}

- (int)getFTypeID: (const char *)aType
{
	int	i;

	if (aType && *aType) {
		for (i = 0; fileTypeBuffer[i]; i++)
			if (strcmp(aType, fileTypeBuffer[i]) == 0)
				return fileTypeIDBuffer[i];
	}
	return Type_none;
}


/* Local Method */
- allocWinFromFile: (const char *)fn :(const char *)aType
		type:(int *)ftype display:(BOOL)display
	/* Return Value:  nil: Error,  id: New ToyWin */
{
	id twtmp = nil;
	int itype = Type_none;
	const char *key = NULL;

	itype = [self getFTypeID: aType];
	if (itype == Type_none) { /* Unknown Extension */
		if ((itype = discriminate(fn)) == Type_none)
			return nil;
		key = NULL;
	}else
		key = aType;

	if (viaPipe(itype)) { /* Type_user and ... */
		if (key == NULL)
			switch (itype) {
			case Type_pcx:	key = "pcx"; break;
			case Type_mag:	key = "mag"; break;
			case Type_jpg:	key = "jpg"; break;
			case Type_xbm:	key = "xbm"; break;
			case Type_jbg:	key = "bie"; break;
			case Type_png:	key = "png"; break;
			}
		twtmp = [ToyWinPPM alloc];
		if (display) [twtmp init:nil by:0];
		else [twtmp initMapOnly];
		[twtmp setExecList:
			[extTable execListAlloc: key with: fn] ext: key]; 
	}else if (itype == Type_pcd) {
		twtmp = [ToyWinPCD alloc];
		if (display) [twtmp init:nil by:0];
		else [twtmp initMapOnly];
		[twtmp setting];
	}else {
		switch (itype) {
		case Type_bmp:
			twtmp = [ToyWinBMP alloc]; break;
		case Type_gif:
			twtmp = [ToyWinGIF alloc]; break;
		case Type_eps:
			twtmp = [ToyWinEPS alloc];
			display = YES;
			break;
		case Type_ppm:
			twtmp = [ToyWinPPM alloc]; break;
		case Type_tiff:
		case Type_other:
		default:
			twtmp = [ToyWin alloc];
			display = YES;
			break;
		}
		if (display) [twtmp init:nil by:0];
		else [twtmp initMapOnly];
	}
	*ftype = itype;
	return twtmp;	// return New ToyWin
}

- drawFile: (const char *)fn :(const char *)aType
	/* Return Value:  nil: Error,  id: New ToyWin */
{
	unsigned char *map[MAXPLANE];
	id newtw;
	commonInfo *cinf;
	int itype, err = 0;

	newtw = [self allocWinFromFile:fn : aType type:&itype display:YES];
	if (!newtw)
		return nil;
	cinf = [newtw drawToyWin:(const char *)fn type:itype map:map err:&err];
	if (cinf == NULL && err) {
		if (err > 0) errAlert(fn, err);
		[newtw free];
		return nil;
	}
	[self newWindow: newtw];
	return newtw;
}

/* Local Method */
/* This method is NOT used for EPS and TIFF */
- (NXBitmapImageRep *)imageFromFile: (const char *)fn :(const char *)aType map:(unsigned char **)map
{
	id newtw;
	commonInfo *cinf;
	NXBitmapImageRep *img;
	int itype, err = 0, spp;

	map[0] = NULL;
	newtw = [self allocWinFromFile:fn : aType type:&itype display:NO];
	if (!newtw)
		return NULL;
	cinf = [newtw drawToyWin:(const char *)fn type:itype map:map err:&err];
	if (cinf == NULL && err) {
		if (err > 0) errAlert(fn, err);
		[newtw free];
		return NULL;
	}

	if ((img = [NXBitmapImageRep alloc]) != NULL) {
	    spp = cinf->numcolors;
	    if (cinf->alpha) spp++;
	    [img initDataPlanes: map	pixelsWide: cinf->width
		pixelsHigh: cinf->height bitsPerSample: cinf->bits
		samplesPerPixel: spp	hasAlpha: cinf->alpha
		isPlanar: YES		colorSpace: cinf->cspace
		bytesPerRow: cinf->xbytes 
		bitsPerPixel: cinf->bits];
	}
	[newtw free];
	if (cinf->palette)
		free((void *)cinf->palette);
	free((void *)cinf);
		/* An instance of ToyWin initialized by "initMapOnly"
			does not have instances of ToyView in it.
			So, "cinf" must be freed here. */
	return img;
}

/* Get stream from file without actual window.
   This method is used to provide Pasteboard-Services and Initial Wallpaper.
 */
- (NXStream *)openStreamFromFile: (const char *)fn :(const char *)aType
{
	unsigned char *map[MAXPLANE];
	NXBitmapImageRep *img;
	NXStream *pbStream = NULL;
	ToyWinEPS *tweps;
	int	itype, err;

	if ((pbStream = NXOpenMemory(NULL, 0, NX_READWRITE)) == NULL)
		return NULL;

	itype = [self getFTypeID: aType];
	if (itype == Type_eps) {
		tweps = [[ToyWinEPS alloc] initMapOnly];
		pbStream = [tweps openStreamFromFile:fn err:&err];
		[tweps free];
		if (pbStream == NULL && err > 0) {
			errAlert(fn, err);
			if (pbStream)
				NXCloseMemory(pbStream, NX_FREEBUFFER);
			return NULL;
		}
		return pbStream;
	}
	map[0] = NULL;
	img = (itype == Type_tiff)
		? [[NXBitmapImageRep alloc] initFromFile: fn]
		: [self imageFromFile: fn : aType map: map];
	if (img) {
		[img writeTIFF: pbStream];
		NXSeek(pbStream, 0L, NX_FROMSTART);
		[img free];
	}else {
		NXCloseMemory(pbStream, NX_FREEBUFFER);
		pbStream = NULL;
	}
	if (map[0])
		free((void *)map[0]);
	return pbStream;
}

- openFile:sender
{
	char fn[MAXFILENAMELEN];
	id openPanel;
    
	if (!odir[0])
		strcpy(odir, getenv("HOME"));
	openPanel = [OpenPanel new];
	[[openPanel chooseDirectories: NO] allowMultipleFiles:YES];
	if ([openPanel runModalForDirectory:odir
			file:NULL types:fileTypeBuffer]) {
		const char *const *files = [openPanel filenames];
		strcpy(odir, [openPanel directory]);
		for ( ; files && *files; files++) {
			sprintf(fn, "%s/%s", odir, *files);
			if (! [self isOpened: fn]) {
				int j;
				if ((j = getExtension(fn)) == 0)
					return self; /* No Extension */
				[self drawFile: fn : &fn[j]];
			}
		}
	}
	return self;
}

#define  AutoThreshold	4

/* Local Method */
- openDirectory: (const char *)dir
{
	char fn[MAXFILENAMELEN];
	const char *p;
	int	n, i;
	id	dirlist;
	static id autoDisplayCtr = nil;

	dirlist = [[DirList alloc] init];
	n = [dirlist getDirList: dir];
	if (n <= 0) {
		errAlert(dir, Err_NOFILE);
		[dirlist free];
	}else if (n < AutoThreshold) {
		for (i = 0; i < n; i++) {
			p = [dirlist filenameAt: i];
			sprintf(fn, "%s/%s", dir, p);
			if (! [self isOpened: fn]) {
				int j = getExtension(p);
				(void)[self drawFile: fn : &p[j]];
			}
		}
		[dirlist free];
	}else { /* Auto Display */
		char bdir[MAXFILENAMELEN];
		id bundle, ad;
		if (autoDisplayCtr == nil) {
			n = dircopy(bdir, NXArgv[0], YES);
			strcpy(&bdir[n], "ADController.bundle");
			bundle = [[NXBundle alloc] initForDirectory: bdir];
			if (bundle == nil) /* ERROR */
				return nil;
			autoDisplayCtr = [bundle classNamed:"ADController"];
			if (autoDisplayCtr == nil) /* ERROR */
				return nil;
		}
		ad = [autoDisplayCtr alloc];
		[ad init:self dir:dir with:dirlist];
	}

	return self;
}

- autoDisplay:sender
{
	char fn[MAXFILENAMELEN];
	id openPanel;
	const char *const *files;

	if (!odir[0])
		strcpy(odir, getenv("HOME"));
	openPanel = [OpenPanel new];
	[[openPanel chooseDirectories: YES] allowMultipleFiles:NO];
	if (![openPanel runModalForDirectory:odir file:NULL])
		return self;

	files = [openPanel filenames];
	strcpy(odir, [openPanel directory]);
	sprintf(fn, "%s/%s", odir, *files);
	[self openDirectory: fn];
	return self;
}


- readSelectionFromPasteboard:pb
{
	const NXAtom	*cont;
	NXStream	*st;
	id   twtmp = nil;
	char *ext, fn[256];
	int  err;
	static int untitledCount = 0;

	ext = NULL;
	for (cont = [pb types]; cont && *cont; cont++) {
		if (*cont == NXTIFFPboardType) {
			twtmp = [[ToyWin alloc] init:nil by:0];
			ext = "tiff";
			break;
		}
		if (*cont == NXPostScriptPboardType) {
			twtmp = [[ToyWinEPS alloc] init:nil by:0];
			ext = "eps";
			break;
		}
	}
	if (ext == NULL) {
		NXBeep();
		return self;
	}
	st = [pb readTypeToStream: *cont];
	sprintf(fn, "%s/Untitled%d.%s", getenv("HOME"), ++untitledCount, ext);

	err = [twtmp drawFromFile:(const char *)fn or:st];
	if (err == 0)
		[self newWindow: twtmp];
	else {
		if (err > 0)
			errAlert(fn, err);
		[twtmp free];
	}
	NXCloseMemory(st, NX_FREEBUFFER);
	return self;
}

- openPasteBoard:sender
{
	Pasteboard  *pb = [Pasteboard new];  // don't free it
	return [self readSelectionFromPasteboard:pb];
}

- (int)app:sender openFile:(const char *)fn type:(const char *)aType
{
	struct stat  buf;
	int	mode;
	id	res = nil;

	if (printInfo == nil)
		[self appDidInit:sender];
	if (stat((char *)fn, &buf) != 0) return NO;
	mode = (buf.st_mode & S_IFMT);
	if (mode == S_IFDIR) {
		if (access(fn, X_OK) != 0) return NO;
		res = [self openDirectory: fn];
	}else {
		if ([self isOpened: fn]) return YES;
		res = [self drawFile: fn : aType];
	}
	return (res != nil);
}

- (BOOL)appAcceptsAnotherFile:sender
{
	return YES;
}

- messageDisplay:(const char *)msg
{
	if (msg) {
		[messagePanel makeKeyAndOrderFront: self];
		[messageText setStringValue: msg];
		[messagePanel setFloatingPanel: YES];
		NXPing();
	}else {
		[messagePanel setFloatingPanel: NO];
		[messagePanel close];
	}
	return messageText;
}

- validRequestorForSendType:(NXAtom)typeSent
                        andReturnType:(NXAtom)typeReturned
{
	if (typeReturned == NXPostScriptPboardType
			|| typeReturned == NXTIFFPboardType)
		return self;
	return nil;
}

@end
