/* -*-C-*-
*******************************************************************************
*
* File:         DisplayFilter.m
* RCS:          /usr/local/sources/CVS/EnhanceMail/DisplayFilter.m,v 1.8 1998/07/12 21:21:40 tom Exp
* Description:  
* Author:       Carl Edman
* Created:      Fri Oct 13 11:48:05 1995
* Modified:     13-Nov-97 (Tom Hageman)
* Language:     C
* Package:      N/A
* Status:       Experimental (Do Not Distribute)
*
* (C) Copyright 1995, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import "EnhanceMail.h"
#import "DisplayFilter.h"
#import "Preferences.h"
#import "PGP.h"
#import "XImageURL.h"
#import "regexp.h"


#ifndef EMDEDDEDCELLFIX
#  define EMBEDDEDCELLFIX  1
#endif

#if EMBEDDEDCELLFIX
/* this is an attempt to restore the textual representation of the smiley.
   XXX This only works with Forwarding, and copy/paste _within_ Mail, but
   not (yet) with Quoting (since that circumvents the copy/paste route). */

@interface EnhanceSmileyCell : Cell
{
   char *smileyText;
}
- initIconCell:(const char *)iconName text:(const char *)text;
@end


@implementation EnhanceSmileyCell

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

- initIconCell:(const char *)iconName text:(const char *)text
{
   if ((self = [super initIconCell:iconName]) != nil)
   {
      if (text) smileyText = NXCopyStringBufferFromZone(text, [self zone]);
   }
   return self;
}

- free
{
   free(smileyText);
   return [super free];
}

- calcCellSize:(NXSize *)s
{
   if ([self type] != NX_ICONCELL)
   {
      s->width = s->height = 0;
      return self;
   }
   return [super calcCellSize:s];
}

- writeRichText:(NXStream *)stream forView:view
{
   char c, *t = smileyText;

   if (t != NULL)
   {
      /* Cheat.  Write out two cells for the price of one, plus the original
         smiley text inbetween (properly rtf-escaped). */
      NXPrintf(stream, "}\n¬}");
      while ((c = *t++) != '\0')
      {
	 switch (c)
	 {
	 case '{': case '}': case '\\':
	    NXPutc(stream, '\\');
	 }
	 NXPutc(stream, c);
      }
      NXPrintf(stream, "\n{{\\EnhanceNullCell1 ");
   }	    
   return self;
}

// - readRichText inherited from Cell.

@end // SmileyCell
#endif // EMBEDDEDCELLFIX


@implementation EnhanceDisplayFilter

+ finishLoading:(struct mach_header *)header
{
   [NXApp addDisplayFilter:[[self alloc] init]];
   return self;
}

- (BOOL)findRegionToAvoid:(int *)startP :(int *)endP inText:(Text *)txt
{
   // XXX We should probably take these from user defaults.
   static const char * const rxtable[] =
   {
      "^begin[\t ][\t ]*[0-7][0-7]*[\t ][\t ]*[^\t ].*", "^end[\t ]*$",
///   "^-----BEGIN PGP .*------$", "^------END PGP .*------$", 
      NULL
   };
   int bestStart, bestEnd;
   const char * const *rxP = rxtable;
   BOOL found = NO;

   bestStart = [txt textLength];
   bestEnd = bestStart;

   while (*rxP != NULL)
   {
      const char *startrx = *rxP++;
      const char *endrx = *rxP++;
      int start1 = *startP;
      int end1 = *endP;

      if ([txt findRegExpr:startrx ignoreCase:NO loc1:&start1 loc2:&end1] == 0 &&
	  (*startP < end1))
      {
	 int start2 = end1;
	 int end2 = end1;

	 [txt setSel:start2:end2];
	 if ([txt findRegExpr:endrx ignoreCase:NO loc1:&start2 loc2:&end2] == 0 &&
	     (end1 < end2))
	 {
	    end1 = end2;
	 }
	 else
	 {
	    end1 = [txt textLength];
	 }
	 if (start1 < bestStart)
	 {
	    bestStart = start1;
	    bestEnd = end1;
	    found = YES;
	 }
	 [txt setSel:*startP:*endP];
      }
   }
   *startP = bestStart;
   *endP = bestEnd;

   return found;
}

- (void)doSmilies:(Text *)txt
{
   int start,end,ostart,seladjust;
   int avoidStart,avoidEnd;
   int textLength = [txt textLength];
   BOOL shouldCheckAvoid = YES, firstAvoidCheck = YES; // Optimization.
   const char *c,*d;
   char *smilyreg,*smilyname;
   Cell *smilycell;

   for(c=EnhanceSmilies;*c;)
   {
      for(d=c;(*d!='\0') && (*d!='\n');d++);
      smilyreg=strncpy(alloca(d-c+1),c,d-c);
      smilyreg[d-c]='\0';
      if (!*d) break;
      c=d+1;
      for(d=c;(*d!='\0') && (*d!='\n');d++);
      smilyname=strncpy(alloca(d-c+1),c,d-c);
      smilyname[d-c]='\0';
      if (*d) d++;
      c=d;

      start=end=0;
      smilycell=nil;
      [txt setSel:start:end];
      avoidStart = avoidEnd = 0;

      while (end < textLength)
      {
         ostart=start;
         if ([txt findRegExpr:smilyreg ignoreCase:NO loc1:&start loc2:&end]) break;
         if (ostart>=end) break;
	 if (end > avoidStart && shouldCheckAvoid)
	 {
	    // Find avoidance region: loop until either:
	    // - smiley inside avoidance region
	    //	  -> skip to end of region and continue
	    // or
	    // - avoidance region found after smiley position.
	    [txt setSel:ostart:ostart];
	    while (!(start < avoidEnd || end <= avoidStart))
	    {
	       if (![self findRegionToAvoid:&avoidStart :&avoidEnd inText:txt])
	       {
		  if (firstAvoidCheck)
		  {
		     firstAvoidCheck = NO;
		     shouldCheckAvoid = NO;
		  }
		  break;
	       }
	       firstAvoidCheck = NO;
	    }
	    if (start < avoidEnd && !(end <= avoidStart))
	    {
	       start = end = avoidStart = avoidEnd;
	       [txt setSel:start:end];
	       continue;
	    }
	 }
	 [txt setSel:start:end];
         if (smilycell==nil)
	 {
            NXImage *smilyimage=[[NXImage alloc] initNameInPath:smilyname];
            if (smilyimage)
	    {
#if EMBEDDEDCELLFIX
	       /* XXX regexp may match multiple textual representations of
	          the smiley.  Currently we're just remembering the first
		  one we've encountered. */
	       // allow room for (Japanese) multi-byte characters.
	       char *t = alloca(3 * (end - start) + 1);
	       t[[txt getSubstring:t start:start length:end-start]] = '\0';
	       [smilyimage setName:smilyname];
	       smilycell=[[EnhanceSmileyCell alloc] initIconCell:smilyname text:t];
#else
	       smilycell=[[Cell alloc] initIconCell:smilyname];
#endif
	    }
	 }
         if (smilycell==nil)
            break;
         [txt replaceSelWithCell:smilycell];
	 // This changes text length, so avoidance region will be inaccurate.
	 // compensate for that here.
	 // XXX although [txt selectionLength] returns 0 here, the actual
	 // length of the cell selection seems to be 1.
	 seladjust = (end - start) - 1;
	 avoidStart -= seladjust;
	 avoidEnd -= seladjust;
	 textLength -= seladjust;
      }
   }
}

void colorize_quotes(Text *txt, const char *buf, int len, regexp *rx)
{
   const char *end = buf + len;
   const char *s = buf;

   // Skip header.
   while (s < end-1)
   {
      if (*s++ == '\n' && *s == '\n') break;
   }
   while (s < end && *s == '\n') s++;

   while (s < end)
   {
      if (regexec(rx, s))
      {
	 // quote found; find quoted region.
	 int quoteStart = (s - buf);
	 int quoteEnd;

	 do
	 {
	    // Skip to start of next line.
	    while (s < end && *s++ != '\n') ;
	 }
	 while (s < end && regexec(rx, s));

	 quoteEnd = (s - buf);

	 [txt setSel:quoteStart :quoteEnd];
	 [txt setSelColor:EnhanceQuoteColor];
      }
      // Skip to start of next line.
      while (s < end && *s++ != '\n') ;
   }
   [txt setSel:0:0];
}

- (void)doColorizeQuotes:(Text *)txt
{
   regexp *quoterx = regcomp(EnhanceQuoteRegex);

   if (!quoterx)
   {
      [self logWarning:"-%s malformed QuoteRegex \"%s\"", sel_getName(_cmd), EnhanceQuoteRegex];
   }
   else if (regexec(quoterx, ""))
   {
      [self logWarning:"-%s QuoteRegex \"%s\" matches empty string", sel_getName(_cmd), EnhanceQuoteRegex];
   }
   else
   {
      int len = [txt textLength];
      char *buf = NXZoneMalloc(EnhanceScratchZone(), len+1);

      // Use NXReadOnlyTextStream to avoid problems with Japanese.
      [txt openTextStream];
      [txt seekToCharacterAt:0 relativeTo:NX_StreamStart];
      len = [txt readCharacters:buf count:len];
      [txt closeTextStream];
      buf[len] = '\0';
      colorize_quotes(txt, buf, len, quoterx);
      free(buf);
   }
   free(quoterx);
}

- (void)willDisplayText:(Text *)txt
{
   if (EnhanceExpandSmilies &&
       ([txt textLength] / 1024 < EnhanceMaxSmileyExpansionMessageSize))
   {
      [self doSmilies:txt];
   }
   if (EnhanceQuoteColoringInMailbox &&
       ([txt textLength] / 1024 < EnhanceMaxQuoteColorMessageSize))
   {
      [self doColorizeQuotes:txt];
   }
}

- (void)willDisplayMessage:(MailMessage *)mes
{
   if (EnhanceUsePGP) [[EnhancePGP new] decodePGP:mes];
}

@end // EnhanceDisplayFilter
