/* -*-ObjC-*-
*******************************************************************************
*
* File:         TransferPanel.m
* RCS:          TransferPanel.m,v 1.23 1998/06/14 01:33:32 tom Exp
* Description:  
* Author:       Tom Hageman <tom@basil.icce.rug.nl>
* Created:      October 1996
* Modified:     
* Language:     Objective-C
* Package:      EnhanceMail
* Status:       Experimental
*
* Copyright (C) 1996, 1997 Tom Hageman, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/
#import "EnhanceMail.h"
#import "TransferPanel.h"
#import "MailboxSummary.h"
#import "MailboxSummaryList.h"
#import "Preferences.h"

#if DEBUG || DEBUG_SUMMARY
#  define DFPRINTF(arglist)	fprintf arglist
#else
#  define DFPRINTF(arglist)
#endif


enum { KIND_UNREAD, KIND_FLAGGED };


@interface NXBrowser (EnhanceTransferPanel)
- (const char *)getPath:(char *)path toCell:(Cell *)cell inColumn:(int)column;
@end


@interface EnhanceMailboxSummary (Mail_app)
- (BOOL)incorporatePendingAppnmailInReader:(MailReader *)reader;
@end


@implementation EnhanceTransferPanel

+ finishLoading:(struct mach_header *)header
{
   [self poseAs:[self superclass]];
   [[TransferPanel new] changeClassTo:[self class]];

   return self;
}

- (void)_updateBrowser:(BOOL)force
{
   Window *w = [browser window];

   if (force && [w isVisible])
   {
      DFPRINTF((stderr, "-[%s %s]\n", [[self class] name], SELNAME(_cmd)));
      [self loadMailSummaries:NO];
   }
   [w disableDisplay];
   [browser validateVisibleColumns];
   [w reenableDisplay];
   [browser displayIfNeeded];
   [w flushWindow];
}

- (void)updateBrowser
{
   [self _updateBrowser:YES];
}

- (void)updateBrowserForMailBoxNamed:(const char *)mailBoxName
{
#if NOTYET
   EnhanceMailboxSummary *summary = [EnhanceMailboxSummary summaryForName:mailBoxName];
   char path[MAXPATHLEN+1];
   // XXX outdated to boot...
   int len = mailboxdir(path);

   mailBoxName = [summary name];
   if (strncmp(path, mailBoxName, len) == 0 && mailBoxName[len] == '/')
   {
      strcpy(path, &mailBoxName[len]);
      len = strlen(path);
      if (len > 5 && strcmp(&path[len-5], ".mbox")==0) path[len-5] = '\0';

      // XXX how to get cell at path without visual artefacts?
      long nUnread = [summary numNewMessages] + [summary numUnreadMessages];
   }
#endif
   [self _updateBrowser:NO];
}

- (BOOL)browser:sender columnIsValid:(int)column
	// delegate method, invoked by [browser validateVisibleColumns].
{
   if (![win isVisible]) return YES;
   return NO; // make this smarter, but it's usually fast enough for now
}

- show
{
   DFPRINTF((stderr, "-[%s %s]\n", [[self class] name], SELNAME(_cmd)));
   [self loadMailSummaries:([[browser window] isVisible] ? NO : YES)];
   return [super show];
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
   DFPRINTF((stderr, "-[%s %s]: column=%d\n", [[self class] name], SELNAME(_cmd), column));
   [[matrix setPrototype:[[EnhanceMailboxBrowserCell allocFromZone:[matrix zone]] init]] free];
   return [super browser:sender fillMatrix:matrix inColumn:column];
}

- browser:sender loadCell:(EnhanceMailboxBrowserCell *)cell atRow:(int)row inColumn:(int)column
{
   char path[MAXPATHLEN+1];
   unsigned long numMessages = 0;
   BOOL pending = NO;
   BOOL newMail = NO;
   int kind = KIND_UNREAD;
   int status = EMS_LOADED;
   int scan = 0;

   if (EnhanceMaxTocCheckSize != 0)
   {
      // XXX Should make Summary and SummaryList polymorphic.
      if ([cell isLeaf])
      {
	 EnhanceMailboxSummary *summary;

	 [sender getPath:path toCell:cell inColumn:column];
  	 // (path+1) to skip leading '/'.
	 summary = [EnhanceMailboxSummary summaryForName:(path+1)];
	 status = [summary loadStatus];
	 if (status == EMS_UNLOADED || [summary needsReload])
	 {
	    status = [summary load];
	 }
	 numMessages = [summary numNewOrUnreadMessages];
	 if (numMessages == 0 && EnhanceShowFlagged)
	 {
	    // Show #flagged messages instead if #unread is zero.
	    numMessages = [summary numFlaggedMessages];
	    kind = KIND_FLAGGED;
	 }
	 pending = [summary hasPendingAppnmail];
	 newMail = [summary hasNewMail];
      }
      else
      {
	 EnhanceMailboxSummaryList *list;

	 [sender getPath:path toCell:cell inColumn:column];
	 list = [EnhanceMailboxSummaryList summaryListForName:(path+1)];
	 status = [list loadStatus];
	 numMessages = [list numMailboxesWithNewOrUnreadMessages];
	 pending = [list hasPendingAppnmail];
	 newMail = [list hasNewMail];
	 scan = ([list doScan] ? 1 : [list dontScan] ? -1 : 0);
      }
   }
   [cell setNewMail:newMail];
   [cell setPending:pending];
   [cell setNumMessages:numMessages];
   [cell setStatus:status];
   [cell setKind:kind];
   [cell setScan:scan];

   return self;
}

// Mailboxes browser target method.
- loadForm:sender
{
   NXBrowserCell *selected = [sender selectedCell];

   /* Get selected cell.  If it is a non-leaf cell, check modifiers, and
	toggle load suppression status accordingly. */

   if (selected && ![selected isLeaf])
   {
      BOOL control = EnhanceControlP();
      BOOL alternate = EnhanceAlternateP();
      BOOL command = EnhanceCommandP();

      if (command || control || alternate)
      {
	 char path[MAXPATHLEN+1];
	 EnhanceMailboxSummaryList *list;

	 [sender getPath:path toCell:selected inColumn:[sender selectedColumn]];

	 list = [EnhanceMailboxSummaryList summaryListForName:(path+1)];

	 if (command)
	 {
	    // Turn both off, thus restoring normal inheritance behiaviour.
	    [list setDoScan:NO];
	    [list setDontScan:NO];
	 }
	 else if (control)
	 {
	    [list setDontScan:YES];
	 }
	 else if (alternate)
	 {
	    [list setDoScan:YES];
	 }

	 /* delayed perform since we're lockfocused here... */
	 [list perform:@selector(iterateLoad:) with:self afterDelay:0 cancelPrevious:YES];
      }
   }
   return [super loadForm:sender];
}


- (void)updateMessageCount:(unsigned)newMessageCount old:(unsigned)oldMessageCount
   forMailBoxNamed:(const char *)mailBoxName isNew:(BOOL)isNew
{
   EnhanceMailboxSummary *summary = [EnhanceMailboxSummary summaryForName:mailBoxName];
   long summaryUnread, summaryNew;

   if ([summary loadStatus] != EMS_LOADED) return;

   summaryUnread = [summary numUnreadMessages];
   summaryNew = [summary numNewMessages];

   // MailReader's messageCounts are totals of summary's (new + unread) counts.
   // Try to compensate for this.
   if (oldMessageCount != summaryUnread + summaryNew)
   {
      oldMessageCount = summaryUnread + summaryNew;
   }
   if (newMessageCount > oldMessageCount)
   {
      if (isNew)
      {
	 [summary setNewMessages:summaryNew + newMessageCount - oldMessageCount];
      }
      else
      {
	 [summary setUnreadMessages:summaryUnread + newMessageCount - oldMessageCount];
      }
   }
   else if (newMessageCount < oldMessageCount)
   {
      // We don't know really at this point if it's a marked-unread or a new message that has been read.  So guess.
      if (oldMessageCount - newMessageCount <= summaryNew)
      {
	 [summary setNewMessages:summaryNew + newMessageCount - oldMessageCount];
      }
      else
      {
	 [summary setNewMessages:0];
	 [summary setUnreadMessages:summaryUnread + newMessageCount - oldMessageCount + summaryNew];
      }
   }
   [self updateBrowserForMailBoxNamed:mailBoxName];
}

- (void)updateFlaggedCountBy:(int)increment forMailBoxNamed:(const char *)mailBoxName
{
   EnhanceMailboxSummary *summary = [EnhanceMailboxSummary summaryForName:mailBoxName];

   if ([summary loadStatus] != EMS_LOADED) return;

   [summary setFlaggedMessages:[summary numFlaggedMessages] + increment];
   [self updateBrowserForMailBoxNamed:mailBoxName];
}

- (void)setNewMailFlagForMailBoxNamed:(const char *)mailBoxName
{
   EnhanceMailboxSummary *summary = [EnhanceMailboxSummary summaryForName:mailBoxName];

   [summary setHasNewMail:YES];
   [self updateBrowserForMailBoxNamed:mailBoxName];
}

- (void)loadMailSummaries:(BOOL)reload
{
   EnhanceMailboxSummaryList *list;

   DFPRINTF((stderr, "-[%s %s]: reload=%d\n", [[self class] name], SELNAME(_cmd), reload));

   list = [EnhanceMailboxSummaryList summaryListForName:""];
   if (!list)
   {
      list = [EnhanceMailboxSummaryList cachedSummaryListForName:""];
      if (!list)
      {
	 list = [[EnhanceMailboxSummaryList alloc] initWithName:""];
      }
      [list setAutoUpdateCache:YES];
      reload = YES;
   }
   if (reload) [list scandir];

   [EnhanceMailboxSummary setDoPendingAppnmailCheck:EnhanceCheckPendingAppnmail];
   [EnhanceMailboxSummaryList setIterateLoadInterval:EnhanceMailboxCheckInterval];
   [list iterateLoad:self];
}

// -iterateLoad callback methods:

- (void)didFinishIterateLoad:(EnhanceMailboxSummaryList *)sender
{
   DFPRINTF((stderr, "-[%s %s]\n", [[self class] name], SELNAME(_cmd)));
   [self _updateBrowser:NO];
}

- (void)newMailForSummary:(EnhanceMailboxSummary *)sender
{
   DFPRINTF((stderr, "-[%s %s] (%s)\n", [[self class] name], SELNAME(_cmd), [sender shortName]));
   [self _updateBrowser:NO];
   if (!EnhanceSilentNewMail) [(MailDriver *)NXApp newMailMsg];
}

- (void)flushSummaryCache
{
   EnhanceMailboxSummaryList *list;

   list = [EnhanceMailboxSummaryList summaryListForName:""];
   [list storeToCache];
}

- (BOOL)checkNewMailForReader:(MailReader *)reader
{
   const char *name = [[reader mailbox] dirname];
   EnhanceMailboxSummary *summary;

   summary = [EnhanceMailboxSummary summaryForName:name];

   if ([summary incorporatePendingAppnmailInReader:reader])
   {
      [self updateBrowserForMailBoxNamed:name];
      return YES;
   }
   return NO;
}

@end // EnhanceTransferPanel


@implementation EnhanceMailboxBrowserCell : NXBrowserCell

static NXImage *loadTiff(const char *fileName, const char *imageName)
{
   char path[MAXPATHLEN+1];
   NXImage *image=nil;

   if ([EnhanceBundle getPath:path forResource:fileName ofType:"tiff"])
   {
      image=[[NXImage alloc] initFromFile:path];
      if (imageName) [image setName:imageName];
   }
   return image;
}

#define EM(constName)		"EM" constName
#define LOAD_TIFF(constName)	loadTiff(constName, EM(constName))
 
+ finishLoading:(struct mach_header *)header
{
   EnhanceBundleInit();
   LOAD_TIFF("disabled");
   LOAD_TIFF("disabledh");
   LOAD_TIFF("dots");
   LOAD_TIFF("dotsh");
   LOAD_TIFF("kaputt");
   LOAD_TIFF("kaputth");
   LOAD_TIFF("disabledarrow");
   LOAD_TIFF("unreadarrow");
   LOAD_TIFF("mbox12");
   LOAD_TIFF("mbox12h");
   LOAD_TIFF("newmail12");
   LOAD_TIFF("newmail12h");
   LOAD_TIFF("doscan");
   LOAD_TIFF("dontscan");
   return self;
}

- (long)numMessages		{ return numMessages; }
- setNumMessages:(long)value	{ numMessages = value; return self; }

- (BOOL)pending			{ return pending; }
- setPending:(BOOL)value	{ pending = value; return self; }

- (BOOL)newMail			{ return newMail; }
- setNewMail:(BOOL)value	{ newMail = value; return self; }

- (int)scan			{ return scan; }
- setScan:(int)value		{ scan = value; return self; }

- (int)status			{ return status; }
- setStatus:(int)value		{ status = value; return self; }

- (int)kind			{ return kind; }
- setKind:(int)value		{ kind = value; return self; }

- (NXCoord)_drawImage:(NXImage *)image inside:(const NXRect *)cellFrame
   rightAlignedAt:(NXCoord)right
{
   NXSize imageSize;
   NXPoint imageOrigin;

   [image getSize:&imageSize];
   imageOrigin.x = right - imageSize.width;
   imageOrigin.y = (NX_Y(cellFrame) + NX_HEIGHT(cellFrame) -
		    (NX_HEIGHT(cellFrame) - imageSize.height) / 2);
   [image composite:NX_SOVER toPoint:&imageOrigin];

   return imageOrigin.x;
}

- drawInside:(const NXRect *)cellFrame inView:controlView
{
   // fudge to line our dot icons up with branch arrow icon.
   static const int DOT_XOFFSET = 2;
   static const int DOT_WIDTH = 11;

   BOOL hilite = (cFlags1.state || cFlags1.highlighted);
   NXCoord rightx = NX_MAXX(cellFrame);
   NXCoord maxx = rightx  - DOT_XOFFSET;
   NXImage *dot;

   [super drawInside:cellFrame inView:controlView];

   if (numMessages > 0)
   {
      // draw number of unread/flagged messages plus bullet to the right
      static id sharedTextCell = nil;
      char numstr[20];
      NXRect rect;

      dot = [NXImage findImageNamed:
	     ![self isLeaf] ? (status == EMS_LOADED ? EM("unreadarrow") : EM("disabledarrow")) :
	     ((kind == KIND_UNREAD) ? (hilite ? "unread" : "unreadh") : "check")];

      maxx = [self _drawImage:dot inside:cellFrame rightAlignedAt:maxx];

      sprintf(numstr, "%lu", numMessages);

      // make cell
      if (!sharedTextCell)
      {
	 sharedTextCell = [[TextFieldCell alloc] init];
	 [sharedTextCell setWrap:NO];
	 [sharedTextCell setFont:[self font]];
	 [sharedTextCell setTextGray:NX_DKGRAY];
      }

      // draw #numMessages, right-aligned.
      [sharedTextCell setStringValueNoCopy:numstr];
      [sharedTextCell calcCellSize:&rect.size];
      NX_HEIGHT(&rect) = NX_HEIGHT(cellFrame);
      NX_X(&rect) = NX_X(cellFrame) + NX_WIDTH(cellFrame) - NX_WIDTH(&rect) - DOT_WIDTH;
      NX_Y(&rect) = NX_Y(cellFrame);
      [sharedTextCell setBackgroundGray:(hilite ? NX_WHITE : NX_LTGRAY)];
      [sharedTextCell drawInside:&rect inView:controlView];
      maxx = NX_X(&rect);
   }
   else if (![self isLeaf])
   {
      if (status != EMS_LOADED)
      {
	 dot = [NXImage findImageNamed:EM("disabledarrow")];
	 [self _drawImage:dot inside:cellFrame rightAlignedAt:maxx];
      }
      maxx -= DOT_WIDTH;
   }
   if (newMail)
   {
      dot = [NXImage findImageNamed:(hilite ? EM("newmail12") : EM("newmail12h"))];
      maxx = [self _drawImage:dot inside:cellFrame rightAlignedAt:maxx];
   }
   if (status != EMS_LOADED && [self isLeaf])
   {
      dot = [NXImage findImageNamed:
	     (status == EMS_LOADSUPPRESSED) ? (hilite ? EM("disabled") : EM("disabledh")) :
	     (status == EMS_LOADFAILED) ? (hilite ? EM("kaputt") : EM("kaputth")) :
	     (hilite ? EM("dots") : EM("dotsh"))];
      maxx = [self _drawImage:dot inside:cellFrame rightAlignedAt:maxx];
   }
   if (pending)
   {
      dot = [NXImage findImageNamed:(hilite ? EM("mbox12") : EM("mbox12h"))];
      maxx = [self _drawImage:dot inside:cellFrame rightAlignedAt:maxx];
   }
   if (scan)
   {
      dot = [NXImage findImageNamed:(scan > 0 ? EM("doscan") : EM("dontscan"))];
      [self _drawImage:dot inside:cellFrame rightAlignedAt:rightx];
   }
   return self;
}

@end // EnhanceMailboxBrowserCell


@implementation NXBrowser (EnhanceTransferPanel)

- (const char *)getPath:(char *)path toCell:(Cell *)aCell inColumn:(int)column
{
   int pathlen;

   [self getPath:path toColumn:column];
   pathlen = strlen(path);
   sprintf(&path[pathlen], "/%s", [aCell stringValue]);
   return path;
}

@end // NXBrowser (EnhanceTransferPanel)


@implementation EnhanceMailboxSummary (EnhanceOverride)

- (BOOL)suppressLoad
{
   return ([self tableOfContentsSize] / 1024 >= (unsigned)EnhanceMaxTocCheckSize);
}

@end // EnhanceMailboxSummary (EnhanceOverride)


// XXX This knows too much about appnmail internals...

#import "mailtoc.h"

#define APPNMAIL_TOC	"appnmail_"MBOX_TOC_FILE
#define APPNMAIL_MBOX	"appnmail_"MBOX_CONTENTS_FILE

#define INCOMING_TOC	"Incoming_Table_of_Contents"
#define INCOMING_MBOX	"Incoming_Mail"

#define APPNMAIL_LOCK	".appnmail.lock"


@implementation EnhanceMailboxSummary (Mail_app)

- (BOOL)_incorporateInReader:(MailReader *)reader
{
   /* Assumes proper locking is in place. */
   char appnmailTocPath[MAXPATHLEN+1], incomingTocPath[MAXPATHLEN+1];
   char appnmailMboxPath[MAXPATHLEN+1], incomingMboxPath[MAXPATHLEN+1];
   int result = NO;

   [self getPath:appnmailTocPath forFile:APPNMAIL_TOC];
   [self getPath:incomingTocPath forFile:INCOMING_TOC];
   [self getPath:appnmailMboxPath forFile:APPNMAIL_MBOX];
   [self getPath:incomingMboxPath forFile:INCOMING_MBOX];

   if (link(appnmailTocPath, incomingTocPath) == 0)
   {
      if (link(appnmailMboxPath, incomingMboxPath) == 0)
      {
	 [reader incorporateMail:self];

	 // This should have removed Incoming_* files.
	 if (access(incomingTocPath, F_OK) != 0 &&
	     access(incomingMboxPath, F_OK) != 0)
	 {
	    unlink(appnmailTocPath);
	    unlink(appnmailMboxPath);
	    result = YES;
	    [self load];
	 }
	 unlink(incomingMboxPath);
      }
      unlink(incomingTocPath);
   }
   return result;
}

- (BOOL)_lockAppnmail
{
   char path[MAXPATHLEN+1];
   char line[BUFSIZ];
   char localhost[MAXHOSTNAMELEN];
   int fd;

   [self getPath:path forFile:APPNMAIL_LOCK];

   if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, 0666)) < 0) return NO;

   gethostname(localhost, sizeof(localhost));
   sprintf(line, "%s,%s.app,%d", localhost, [NXApp appName], getpid());
   write(fd, line, strlen(line));
   close(fd);

   return YES;
}

- (void)_unlockAppnmail
{
   char path[MAXPATHLEN+1];

   [self getPath:path forFile:APPNMAIL_LOCK];
   unlink(path);
}


- (BOOL)incorporatePendingAppnmailInReader:(MailReader *)reader
{
   BOOL result = NO;

   if ([self _lockAppnmail])
   {
      result = [self _incorporateInReader:reader];
      [self _unlockAppnmail];
   }
   return result;
}

@end // EnhanceMailboxSummary (Mail_app)
