/*+++*
 *  title:	MailboxInspector.m
 *  abstract:	NEXTSTEP Workspace Manager Inspector for Mail.app ".mbox" files.
 *  author:	T.R.Hageman, The Netherlands
 *  created:	May 1996
 *  modified:	(see RCS Log at end)
 *  copyleft:
 *
 *		Copyright (C) 1996,1997  Tom R. Hageman.
 *
 *	This is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This software is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this software; see the file COPYING.  If not, write to
 *	the Free Software Foundation, 59 Temple Place - Suite 330,
 *	Boston, MA 02111-1307, USA.
 *
 *  description:
 *
 *---*/

static const char *const RCSid = ((void)&RCSid, /* to avoid `unused' warning. */
	"@(#)MailboxInspector.m,v 1.5 1997/05/10 15:22:21 tom Exp");

#define VERSION	"1.1"

#ifndef DEBUG
#   define DEBUG 0
#endif

#import "MailboxInspector.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "mailtoc.h"


// Localized strings

#define OPENBUTTON	NXLocalizedStringFromTableInBundle(NULL, bundle, "Open", NULL, okbutton label)

#define NO_NEW_MESSAGES	NXLocalizedStringFromTableInBundle(NULL, bundle, "", NULL, newMailTextField: No new messages)
#define ONE_NEW_MESSAGE	NXLocalizedStringFromTableInBundle(NULL, bundle, "1 new message", NULL, newMailTextField: 1 new message)
#define N_NEW_MESSAGES	NXLocalizedStringFromTableInBundle(NULL, bundle, "%lu new messages", NULL, newMailTextField: %lu new messages format string)
#define CANNOT_READ_TOC	NXLocalizedStringFromTableInBundle(NULL, bundle, "Unable to read Table of Contents", NULL, newMailTextField: Cannot read Table of Contents)
#define INDEX_SIZE	NXLocalizedStringFromTableInBundle(NULL, bundle, "(index: %s)", NULL, indexSizeField: (index: %s))
#define TOC_SIZE	NXLocalizedStringFromTableInBundle(NULL, bundle, "(table of contents: %s)", NULL, tocSizeField: (table of contents: %s))

#define KBYTES		NXLocalizedStringFromTableInBundle(NULL, bundle, "KB", NULL, KiloBytes)
#define MBYTES		NXLocalizedStringFromTableInBundle(NULL, bundle, "MB", NULL, MegaBytes)

#define LOCALIZE(s)	NXLoadLocalized\
StringFromTableInBundle(NULL, bundle, s, NULL)
// (line continuation to keep "genstrings" from breaking.)


@implementation MailboxInspector

- initWithNibName:(const char *)nibname
{
	char path[MAXPATHLEN+1];

	bundle = [NXBundle bundleForClass:[self class]];

	if ([bundle getPath:path forResource:nibname ofType:"nib"] &&
	    [NXApp loadNibFile:path owner:self])
	{
		// Set version field.
		[inspectorVersionField setStringValue:VERSION];
	}
	else
	{
		fprintf(stderr, "Couldn't load %s.nib\n", nibname);
		[self free];
		self = nil;
	}
	return self;
}

+ new
{
	static MailboxInspector *instance;
	
	if (instance == nil)
	{
		instance = [[super new] initWithNibName:[self name]];
	}
	return instance;
}

- showInfo:sender
{
	if (infoPanel == nil)
	{
		char path[MAXPATHLEN+1];

		if ([bundle getPath:path forResource:"Info" ofType:"nib"] &&
		    [NXApp loadNibFile:path owner:self])
		{
			[infoVersionField setStringValue:[inspectorVersionField stringValue]];
		}
	}
	[infoPanel makeKeyAndOrderFront:sender];
	return self;
}

- revert:sender
{
	[super revert:sender];

	if ([self selectionCount] != 1)
	{
		return nil;
	}
	else
	{
		char path[MAXPATHLEN+1];

		[mailbox free];
		[self selectionPathsInto:path separator:'\0'];
		if (!(mailbox = [[NXBundle allocFromZone:[self zone]] initForDirectory:path]))
		{
			return nil;
		}
		if ([self shouldLoad])
		{
			[self load];
		}
	}
	[[[self okButton] setTitle:OPENBUTTON] setEnabled:YES];

	return self;
}

- ok:sender
{
	[self perform:@selector(open:) with:sender afterDelay:0 cancelPrevious:NO];
	[super ok:sender];
	return self;
}

- load
{
	unsigned long counts[NUMTAGS] = {0};
	unsigned long sizes[NUMTAGS] = {0};
	unsigned long attachCounts[NUMTAGS] = {0};
	unsigned long attachSizes[NUMTAGS] = {0};
	BOOL indexed;
	unsigned long indexSize;
	unsigned long tocSize;
	BOOL valid = FALSE;
	char path[MAXPATHLEN+1];
	char sizeBuf[20];
	FILE *tocf;
	int tag;

	/* Quick hack; knows about *.mbox internals.
	   A more OO solution would be to introduce a MailBoxSummary object
	   that does the dirty work, and have us query it. */
	if ((tocf = fopen([self getPath:path forFile:FILE_TOC], "rb")) != NULL)
	{
		struct table_of_contents_header *th;

		if ((th = get_table_of_contents_header(tocf, NO)) != NULL)
		{
			int msgCount = th->num_msgs;

			free(th);

			valid = TRUE;

			// Tally read, new and deleted messages.
			while (--msgCount >= 0)
			{
				struct message_index *mi;
				long size, attachSize;

				if ((mi = get_message_index(tocf)) == NULL)
				{
					valid = FALSE;
					break;
				}
				switch (mi->status)
				{
				case '*':
					tag = TAG_NEW;
					break;
				case 'd': case 'D':
					tag = TAG_DELETED;
					break;
				case 'u': case 'U':
					tag = TAG_UNREAD;
					break;
				case '+':
					tag = TAG_FLAGGED;
					break;
				default:
					tag = TAG_READ;
					break;
				}
				size = mi->mes_length;
				if ((attachSize = message_attachsize(mi)) > 0)
				{
					attachCounts[tag]++;
					attachSizes[tag] += attachSize;
					attachCounts[TAG_TOTAL]++;
					attachSizes[TAG_TOTAL] += attachSize;

					size += attachSize;
				}
				counts[tag]++;
				sizes[tag] += size;
				counts[TAG_TOTAL]++;
				sizes[TAG_TOTAL] += size;

				free(mi);
			}
		}
		fclose(tocf);
	}

	/* Quick&dirty hack to determine if index store file exists. */
	indexed = (stats[STAT_INDEX].st_ino != 0);
	indexSize = stats[STAT_INDEX].st_size;
#if DEBUG & 4
	fprintf(stderr, "MailboxInspector: indexed = %d\n", indexed);
#endif
	tocSize = stats[STAT_TOC].st_size;

	/* Fill in the inspector fields. */

	[[self window] disableFlushWindow];
	[indexTagButton setState:indexed];
	[newMailIconButton setState:(valid && counts[TAG_NEW] > 0)];
	[newMailTextField setStringValue:
	 (valid ? (counts[TAG_NEW] == 0 ? NO_NEW_MESSAGES :
		   counts[TAG_NEW] == 1 ? ONE_NEW_MESSAGE :
		   (sprintf(path, N_NEW_MESSAGES, counts[TAG_NEW]), path)) :
	  CANNOT_READ_TOC)];
	for (tag = 0;  tag < NUMTAGS;  tag++)
	{
#define FORMATCOUNT(count) \
		(valid ? ((count) ? (sprintf(sizeBuf, "%lu", (count)), sizeBuf) : \
			  "Ð") : "?")
#define FORMATSIZE(size) \
		(valid ? [self formatSize:(size) inBuf:sizeBuf] : "")

		[[messageCountMatrix findCellWithTag:tag]
		 setStringValue:FORMATCOUNT(counts[tag])];
		[[messageSizeMatrix findCellWithTag:tag]
		 setStringValue:FORMATSIZE(sizes[tag])];
		[[attachmentCountMatrix findCellWithTag:tag]
		 setStringValue:FORMATCOUNT(attachCounts[tag])];
		[[attachmentSizeMatrix findCellWithTag:tag]
		 setStringValue:FORMATSIZE(attachSizes[tag])];
#undef FORMATCOUNT
#undef FORMATSIZE
	}
	[indexSizeField setStringValue:
	 (indexed ? (sprintf(path, INDEX_SIZE, [self formatSize:indexSize
						inBuf:sizeBuf]), path) : "")];
	[tocSizeField setStringValue:
	 (sprintf(path, TOC_SIZE, [self formatSize:tocSize inBuf:sizeBuf]), path)];

	[[self window] reenableFlushWindow];
	[[self window] flushWindowIfNeeded];

	return self;
}

#define STAT_EQ(s1, s2)	((s1)->st_ino == (s2)->st_ino && \
			 (s1)->st_dev == (s2)->st_dev && \
			 (s1)->st_mtime == (s2)->st_mtime && \
			 (s1)->st_size == (s2)->st_size)

- (BOOL)shouldLoad
{
	char path[MAXPATHLEN+1];
	struct stat newstats[NUMSTATS];
	static const char * const filesToStat[NUMSTATS] = { FILESTOSTAT };
	BOOL result = NO;
	int i;

	for (i = 0;  i < NUMSTATS;  i++)
	{
		memset(&newstats[i], 0, sizeof(struct stat));
		stat([self getPath:path forFile:filesToStat[i]], &newstats[i]);
		if (!STAT_EQ(&newstats[i], &stats[i]))
		{
			result = YES;
			///break; // NOT!!! must stat all for accurate cache.
		}
		stats[i] = newstats[i];
	}

	return result;
}


// Support methods

- (const char *)getPath:(char *)buf forFile:(const char *)file
{
	sprintf(buf, "%s/%s", [mailbox directory], file);
#if DEBUG & 2
	fprintf(stderr, "MailboxInspector: file=\"%s\" path=\"%s\"\n",
		file, buf);
#endif
	return buf;
}


- (const char *)formatSize:(unsigned long)size inBuf:(char *)buf
{
#define KB	1024
#define MB	(KB * 1024)

	if (size == 0)
	{
		strcpy(buf, "");
	}
	else if (size < 1000*KB - KB/2)
	{
		sprintf(buf, "%.*f%s",
			//(size < 10*KB - KB/2 ? 2 : size < 100*KB - KB/2 ? 1 : 0),
			(size < 10*KB - KB/2 ? 1 : 0),
			(double) size / KB, KBYTES);
	}
	else
	{
		sprintf(buf, "%.*f%s",
			//(size < 10*MB - MB/2 ? 2 : size < 100*MB - MB/2 ? 1 : 0),
			(size < 10*MB - MB/2 ? 1 : 0),
			(double) size / MB, MBYTES);
	}
	return buf;
#undef KB
#undef MB
}


static void openInWorkspace(const char *filename)
{
	// Indirect approach to circumvent Workspace deadlock/timeout.
	char command[14+3*MAXPATHLEN+1];
	const char *s;
	char *d = command;

	for (s = "exec open '"; *s; ) *d++ = *s++;
	// Escape single quote characters.
	for (s = filename; *s; )
	{
		if ((*d++ = *s++) == '\'')
		{
			*d++ = '\\', *d++ = '\'', *d++ = '\'';
		}
	}
	for (s = "'&"; *d++ = *s++; ) ;
	system(command);
}

- open:sender
{
	openInWorkspace([mailbox directory]);
	return self;
}

@end // MailboxInspector

//======================================================================
// MailboxInspector.m,v
// Revision 1.5  1997/05/10 15:22:21  tom
// MailboxInspector:
// (tocSizeField): new outlet.
// (VERSION): bumped to 1.1.
// (TOC_SIZE): new localizable string.
// (LOCALIZE): hack to keep "genstrings" from breaking.
// (-load): show toc size.
//
// Makefile.preamble:
// (GENSTRINGS,LANGUAGE_DIRECTORIES): new variables.
//
// Makefile.postamble:
// (clean::,resources::,genstrings:): new targets.
//
// README.rtf:
// - updated for 1.1.
//
// Revision 1.4  1997/04/19 18:14:56  tom
// (VERSION): upped to 1.0.
// (-load): adapt to mailtoc API changes.
//
// Revision 1.3  1996/06/27 22:55:52  tom
// Add sccs `what' marker in ident.
//
// Revision 1.2  1996/06/27 20:04:40  tom
// (VERSION): bumped to 0.9.
// (INDEX_SIZE): new localizable.
// (-load): show size of index store.
//
// Revision 1.1  1996/06/26 14:40:42  tom
// Initial revision
//
//======================================================================
