/* -*-C-*-
*******************************************************************************
*
* File:         EnhanceMail.m
* RCS:          /usr/local/sources/CVS/EnhanceMail/EnhanceMail.m,v 1.17 1998/07/07 22:25:03 tom Exp
* Description:  
* Author:       Carl Edman
* Created:      Fri Oct 13 11:48:05 1995
* Modified:     Sun Apr 13 22:30:48 1997 Tom Hageman <tom@basil.icce.rug.nl>
* Language:     C
* Package:      N/A
* Status:       Experimental (Do Not Distribute)
*
* (C) Copyright 1995, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import "EnhanceMail.h"
#import "Preferences.h"
#import "TransferPanel.h"
#import <ctype.h>
#import <sys/wait.h>
#import <objc/objc-runtime.h>
#import "regexp.h"

#ifndef EMBEDDEDCELLFIX
#  define EMBEDDEDCELLFIX 1
#endif

NXBundle *EnhanceBundle=0;
char *EnhanceVersion="X";
char *regerrval=0;

static const char *CVSName="EMrelease2_2p1";
static BOOL windowsOnUnhide=NO;

void EnhanceBundleInit()
{
   if (!EnhanceBundle)
   {
      EnhanceBundle = [NXBundle bundleForClass:[EnhanceMail class]];
   }
}

NXZone *EnhanceScratchZone()
{
   static NXZone *zone;

   if (zone) return zone;

   if ((zone = NXCreateZone(getpagesize(), getpagesize(), YES)) == NULL)
   {
      return NXDefaultMallocZone();
   }
   NXNameZone(zone, "EnhanceScratch");
   return zone;
}

BOOL EnhanceControlP(void)
{
   NXEvent *ev=[NXApp currentEvent];
   return ev && (ev->flags & NX_CONTROLMASK);
}

BOOL EnhanceShiftP(void)
{
   NXEvent *ev=[NXApp currentEvent];
   return ev && (ev->flags & NX_SHIFTMASK);
}

BOOL EnhanceAlternateP(void)
{
   NXEvent *ev=[NXApp currentEvent];
   return ev && (ev->flags & NX_ALTERNATEMASK);
}

BOOL EnhanceCommandP(void)
{
   NXEvent *ev=[NXApp currentEvent];
   return ev && (ev->flags & NX_COMMANDMASK);
}

BOOL EnhancePaths(const char *paths[])
{
   int i;
   char path[MAXPATHLEN+1];
   
   EnhanceBundleInit();
   
   for(i=0;paths[i];i++)
   {
      if ((paths[i][0]=='~') && (paths[i][1]=='/'))
      {
         sprintf(path,"%s%s",NXHomeDirectory(),paths[i]+1);
         paths[i]=NXUniqueString(path);
      }
      else if ((paths[i][0]=='#') && (paths[i][1]=='/'))
      {
	 if (![EnhanceBundle getPath:path forResource:&paths[i][2] ofType:NULL])
	 {
	    sprintf(path,"%s%s",[EnhanceBundle directory],paths[i]+1);
	 }
	 paths[i]=NXUniqueString(path);
      }
   }

   return YES;
}

// Try to determine whether to use KANJI quoting at runtime.
static BOOL use_kanji()
{
#if 0 // Old way; depended on user default language preference:
   const char *const *languages=[NXApp systemLanguages];

   if (languages && languages[0])
   {
      if (strcasecmp(languages[0],"Japanese")==0) return YES;
   }
   return NO;
#endif
   // Heuristically determines whether we are loaded into Japanese Mail.app:
   return [Defaults instancesRespondTo:@selector(isJapaneseSubjectEnabled)];
}

BOOL EnhanceUseKanji(void)
{
   // Cache result of use_kanji().
   static int _use_kanji = -1;

   if (_use_kanji < 0) _use_kanji = use_kanji();
   return (BOOL)_use_kanji;
}

void regerror(char *s)
{
   [NXApp logError:"regexp(3): %s",s];
}


// email address pattern matching.

static BOOL _match_addr(const char *str, const char **start, int *len, BOOL addr_only)
{
   static regexp *addrrx = 0;
   static const int addrsub[] = { 1, 4, 2, 3, 5 };
   int i;

   if (!(str && *str)) return NO;

   if (!addrrx)
      addrrx = regcomp("^[ \t\"']*([^<>]*[^ \t\"'<>]|)[ \t\"']*<([^<> \t]*)>[^<>]*$"  "|"
		       "^[ \t]*([^() \t]*)[ \t]*\\(([^()]*)\\)[ \t]*$"	   "|"
		       "^[ \t\"']*([^<>()]*[^ \t\"'<>()])[ \t\"']*$");

   if (!addrrx) return NO;
   if (!regexec(addrrx, str)) return NO;

   for (i = (addr_only) ? 2 : 0;  i < sizeof addrsub / sizeof addrsub[0];  i++)
   {
      int sub = addrsub[i];

      if (addrrx->endp[sub] > addrrx->startp[sub])
      {
	 (*start) = addrrx->startp[sub];
	 (*len) = addrrx->endp[sub] - addrrx->startp[sub];
	 return YES;
      }
   }
   return NO;
}

BOOL EnhanceGetAddress(const char *str, const char **start, int *len)
{
   return _match_addr(str, start, len, YES);
}

BOOL EnhanceGetUsername(const char *str, const char **start, int *len)
{
   return _match_addr(str, start, len, NO);
}


// Timed entry for periodic unread-mail check in MailBoxes panel.

static void _teNewMailCheckHandler(DPSTimedEntry te, double now, EnhanceTransferPanel *self)
{
   [self updateBrowser];
}

static void setNewMailCheckPeriod(id self, int minutes)
{
   static DPSTimedEntry te;
   static int pollTime = 0;

   if (pollTime == minutes) return;
   if (te) DPSRemoveTimedEntry(te);
   if (minutes > 0) te = DPSAddTimedEntry(minutes * 60, (DPSTimedEntryProc)_teNewMailCheckHandler,
					  (void *)self, NX_RUNMODALTHRESHOLD);
   pollTime = minutes;
}

static void setNewMailCheckTimer()
{
   Defaults *d = [Defaults new];

   setNewMailCheckPeriod([TransferPanel new], [d autoFetch] ? [d pollTime] : -1);
}


@implementation EnhanceMail

+ finishLoading:(struct mach_header *)header
{
   const char *beg=CVSName,*end=CVSName+strlen(CVSName);
   
   [self poseAs:[self superclass]];
   [NXApp changeClassTo:[self class]];

   while((beg<=end) && !isdigit(*beg)) beg++;
   while((beg<=end) && !isdigit(*end)) end--;
   
   if (beg<end)
   {
      char *c;

      end++;
      EnhanceVersion=strncpy(malloc(end-beg+1),beg,end-beg);
      EnhanceVersion[end-beg]='\0';
      for(c=EnhanceVersion;*c;c++) if (*c=='_') *c='.';
   }

   return self;
}

#if EMBEDDEDCELLFIX
- (void)_fixUrlifierGlitch
{
   // Attempt to fix Urlifier '<' copy/paste glitch.
   // (using dummy -{read,write}RichText:forView: inherited from Cell)
   Class urlClass = objc_lookUpClass("UrlGraphicCell");

   if (urlClass)
   {
      [Text registerDirective:"EnhanceUrlGraphicCell" forClass:urlClass];
   }
}
#endif

- (void)_maybeShowPanels
{
   Window *origMainWindow = [NXApp mainWindow];

   if (EnhanceLaunchMailboxes) [(MailDriver *)NXApp mailboxes:self];
   if (EnhanceLaunchAddresses) [(MailDriver *)NXApp address:self];
   [origMainWindow makeKeyAndOrderFront:self];
}

- initialCheck:sender
{
   ///if ([[Defaults new] autoLaunch])
   // {{Now always, since persistent summary cache should make it quick enough.}}
   {
      [[TransferPanel new] loadMailSummaries:YES];
   }
   if ([self isHidden])
   {
      windowsOnUnhide=YES;
   }
   else
   {
      [self perform:@selector(_maybeShowPanels) with:nil afterDelay:0 cancelPrevious:YES];
   }
   setNewMailCheckTimer();

#if EMBEDDEDCELLFIX
   // Hopefully, at this point all bundles are loaded.
   [self _fixUrlifierGlitch];
#endif
   return [super initialCheck:sender];
}

- appDidUnhide:sender
{
   if (windowsOnUnhide)
   {
      windowsOnUnhide=NO;
      [self perform:@selector(_maybeShowPanels) with:nil afterDelay:0 cancelPrevious:YES];
   }
   return [super appDidUnhide:sender];
}

#if 0
- (BOOL)updateCell:cell
{
   return [super updateCell:cell];
}
#endif

- checkNewMail:(const char *)fp16 beep:(BOOL)fp20
{
   id ret = [super checkNewMail:fp16 beep:fp20];
   [[TransferPanel new] updateBrowser];
   return ret;
}

- (void)defaultsChanged
{
   setNewMailCheckTimer();
   [super defaultsChanged];
}

- realQuit:sender
{
   [[TransferPanel new] flushSummaryCache];
   return [super realQuit:sender];
}

@end // EnhanceMail


@implementation Application(EnhanceMail)

- (const char *)appVersion
{
   return RC_RELEASE;
}

@end // Application(EnhanceMail)


@implementation Menu(EnhanceMail)

- findCellWithTitle:(const char *)title
{
   id cell=nil;
   int x, y, rows, cols;
   const char *c,*end;

   if (title==0) return nil;
   for(end=title;(*end) && (*end!='/');end++);
   
   [matrix getNumRows:&rows numCols:&cols];
   for(y=0;(y<rows) && (cell==nil);y++) for(x=0;(x<cols) && (cell==nil);x++)
   {
      if ((cell=[matrix cellAt:y:x])==nil) continue;
      
      if (((c=[cell title])==0)||(strncmp(title,c,end-title)!=0)||(c[end-title]!='\0'))
         cell=nil;
   }

   if (cell==nil)
      return nil;
   else if ((*end!='\0') && [cell hasSubmenu])
      return [[cell target] findCellWithTitle:end+1];
   else if (*end=='\0')
      return cell;
   else
      return nil;
}

- addSlashItem:(const char *)title action:(SEL)sel keyEquivalent:(unsigned short)code
{
   id cell=nil;
   int x, y, rows, cols;
   const char *c,*end;

   if (title==0) return nil;
   for(end=title;(*end) && (*end!='/');end++);

   [matrix getNumRows:&rows numCols:&cols];
   for(y=0;(y<rows) && (cell==nil);y++) for(x=0;(x<cols) && (cell==nil);x++)
   {
      if ((cell=[matrix cellAt:y:x])==nil) continue;
      
      if (((c=[cell title])==0)||(strncmp(title,c,end-title)!=0)||(c[end-title]!='\0'))
         cell=nil;
   }

   if ((cell==nil) && (*end=='\0'))
   {
      cell=[self addItem:title action:sel keyEquivalent:code];
      [self display];
      return cell;
   }
   else if (cell==nil)
   {
      char *d=alloca(end-title+1);
      strncpy(d,title,end-title);
      d[end-title]='\0';
      cell=[self addItem:d action:0 keyEquivalent:0];
      [self setSubmenu:[[Menu alloc] initTitle:d] forItem:cell];
      [self display];
      return [[cell target] addSlashItem:end+1 action:sel keyEquivalent:code];
   }
   else if ((*end!='\0') && [cell hasSubmenu])
      return [[cell target] addSlashItem:end+1 action:sel keyEquivalent:code];
   else if (*end=='\0')
      return cell;
   else
      return nil;
}

@end // Menu(EnhanceMail)


@implementation Object(EnhanceMail)

- changeClassTo:class
{
   /* Don't try this at home, kids */
   isa=class;
   return self;
}

@end // Object(EnhanceMail)


@implementation ButtonCell(EnhanceMail)

- (int)state
{
   return [self intValue];
}

- setState:(int)anInt
{
   return [self setIntValue:anInt];
}

@end // ButtonCell(EnhanceMail)


#if EMBEDDEDCELLFIX
/* Experiment.  My hope is this will avoid the `<' left around when
   text containing embedded Cell is copied. Unfortunately, this
   only helps for Forwarding messages -- conversion to plain-text
   (hence also quoting) still leave the droppings.  Actually, we're
   also leaving droppings except they're invisible. */

@interface EnhanceNullCell:Cell
@end

@implementation EnhanceNullCell
- calcCellSize:(NXSize *)s { s->width = s->height = 0; return self; }
- drawSelf:(const NXRect *)rect inView:view { return self; }
- highlight:(const NXRect*)rect inView:view lit:(BOOL)lit { return self; }
//- (BOOL)trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)rect ofView:view { return YES; }
@end

@implementation Cell (EnhanceTextEmbedFix)

+ finishLoading:(struct mach_header *)header
{
   [Text registerDirective:"EnhanceCell" forClass:[Cell class]];
   [Text registerDirective:"EnhanceNullCell" forClass:[EnhanceNullCell class]];
   return self;
}

- writeRichText:(NXStream *)stream forView:view
{
   return self;
}

- readRichText:(NXStream *)stream forView:view
{
   [self changeClassTo:[EnhanceNullCell class]];  // Gaaaaaaaaaaahhh!!!
   return nil;
}

@end // Cell (EnhanceTextEmbedFix)

#endif


// Adjust pre-/postSelSmartTables for more convenient email address pasting.
// (Original idea from Marc Guenther <yoda@cis.uni-muenchen.de>)
@interface EnhanceText : Text
@end

@implementation	EnhanceText

+ finishLoading:(struct mach_header *)header
{
   [self poseAs:[self superclass]];
   return self;
}

static const unsigned char *cacheAppendTo(const unsigned char *table, const unsigned char *extra)
{
   static HashTable *hash;
   unsigned char *result;

   if (!(extra && *extra)) return table;

   if (hash == nil) hash = [[HashTable alloc] initKeyDesc:"!" valueDesc:"*"];

   result = [hash valueForKey:table];
   if (result == NULL)
   {
      result = malloc(strlen(table) + strlen(extra) + 1);
      if (result == NULL) return table;

      strcat(strcpy(result, table), extra);
      [hash insertKey:table value:result];
   }
   return result;
}

- initFrame:(const NXRect *)frameRect
   text:(const char *)theText
   alignment:(int)mode
{
   if ((self = [super initFrame:frameRect text:theText alignment:mode]))
   {
      if (EnhancePreSelExtra && *EnhancePreSelExtra)
      {
	 [self setPreSelSmartTable:cacheAppendTo([self preSelSmartTable], EnhancePreSelExtra)];
      }
      if (EnhancePostSelExtra && *EnhancePostSelExtra)
      {
	 [self setPostSelSmartTable:cacheAppendTo([self postSelSmartTable], EnhancePostSelExtra)];
      }
   }
   return self;
}

@end // EnhanceText
