/* -*-C-*-
*******************************************************************************
*
* File:         Send.m
* RCS:          $Header: /usr/local/lib/cvs/EnhanceMail/Send.m,v 1.1.1.26 1996/07/01 01:10:28 cedman Exp $
* Description:  
* Author:       Carl Edman
* Created:      Fri Oct 13 11:48:05 1995
* Modified:     Sat Jun 29 15:01:43 1996 (Carl Edman) cedman@capitalist.princeton.edu
* Language:     C
* Package:      N/A
* Status:       Experimental (Do Not Distribute)
*
* (C) Copyright 1995, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import <ctype.h>
#import "EnhanceMail.h"
#import "Send.h"
#import "SimpleString.h"
#import "Preferences.h"
#import "XFace.h"
#import "regexp.h"

static HashTable *QuotationHash=nil;
static HashTable *ReplyBoxHash=nil;
static HashTable *InReplyToHash=nil;

/* RFC822 Date:
   ^( *(Mon|Tue|Wed|Thu|Fri|Sat|Sun) *, )? *([0-9][0-9]?) +(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +([0-9][0-9][0-9]?[0-9]?) +([0-9]?[0-9]:[0-9][0-9](:[0-9][0-9])?) +(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|1[A-Z]|\+[0-9][0-9][0-9][0-9]|-[0-9][0-9][0-9][0-9])
   */

static id interpret_escapes(const char *c, id mes, regexp *rx)
   {
   id s=[[SimpleString alloc] init];
   const char *end=c+strlen(c),*d;
   char *m;
   int n;
   static regexp *daterx=0, *addrrx=0;

   if (!daterx)
      daterx=regcomp("^(([ \t]*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[ \t]*,[ \t])?[ \t]*([0-9][0-9]?)[ \t]+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ \t]+([0-9][0-9][0-9]?[0-9]?))[ \t]+([0-9]?[0-9]:[0-9][0-9](:[0-9][0-9])?)");
   if (!addrrx)
      addrrx=regcomp("^[ \t]*(([^ \t].*)?[^ \t])[ \t]+<.*>[ \t]*$");
   
   while(c<end) if ((*c=='%')) switch((++c<end) ? *c++ : '%')
      {
    case '{':
      for(d=c;(c<end) && (*c!='}');c++);
      m=alloca(c-d+1);
      strncpy(m,d,c-d);
      m[c-d]='\0';
      c++;
      if (!(d=[mes headerValueForKey:m])) break;
      [s appendString:d];
      break;
      
    case 'd':
      if ((d=[mes headerValueForKey:"Date"])==0) break;
      if (daterx && regexec(daterx,d))
         [s appendString:daterx->startp[1] length:daterx->endp[1]-daterx->startp[1]];
      break;
      
    case 't':
      if ((d=[mes headerValueForKey:"Date"])==0) break;
      if (daterx && regexec(daterx,d))
         [s appendString:daterx->startp[7] length:daterx->endp[7]-daterx->startp[7]];
      break;
      
    case 'f':
      if (!(d=[mes headerValueForKey:"From"])) break;
      if (addrrx && regexec(addrrx,d)) 
         [s appendString:addrrx->startp[1] length:addrrx->endp[1]-addrrx->startp[1]];
      else
         [s appendString:d];
      break;

    case 'n':
      [s appendChar:'\n'];
      break;
      
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      for(n=*(c-1)-'0';isdigit(*c) && (c<end);c++) n=n*10+(*c-'0');
      if (n<NSUBEXP && rx && rx->startp[n] && rx->endp[n])
         [s appendString:rx->startp[n] length:rx->endp[n]-rx->startp[n]];
      break;

    default:
      [s appendChar:*(c-1)];  
      }
   else
      [s appendChar:*(c++)];
   
   return s;
   }

#ifndef KANJI
static id quote_text(const char *text,int length,id mes)
   {
   id s=[[SimpleString alloc] init],prefix=nil;
   int linebeg=0,i;
   int maxlen=[MailMessage lineLength];
   const char *line,*end,*word,*tmp,*prebeg,*preend;
   regexp *rx=regcomp(EnhanceQuoteRegex);

   if (!rx) return s;
   
   prebeg=preend=line=text;
   end=line+length;
   
   while(line<end)
      {
      if (!regexec(rx,line)) for(i=0;i<NSUBEXP;i++) rx->startp[i]=rx->endp[i]=line;
      word=rx->endp[0];
      
      if ((prefix==nil) ||
          ((word-line)!=(preend-prebeg)) ||
          (strncmp(line,prebeg,preend-prebeg)!=0))
         {
         prebeg=line;
         preend=word;
         if (prefix!=nil) prefix=[prefix free];
         prefix=interpret_escapes(EnhanceQuotePrefix, mes, rx);
         if (linebeg!=[s length]) linebeg=[s appendChar:'\n'];
         }
      
      while((word<end) && ((*word==' ')||(*word=='\t'))) word++;

      for(line=word;(line<end)&&(*line!='\n')&&(*line!='\r');line++);

      if ((*word=='\n')||(*word=='\r'))
         {
         if (linebeg!=[s length]) [s appendChar:'\n'];
         [s appendSimpleString:prefix];
         linebeg=[s appendChar:'\n'];
         }
      else if ([s length]-linebeg+(line-word)+
               (([s length]==linebeg) ? [prefix length] : 1)<maxlen)
         {
         if ([s length]==linebeg)
            [s appendSimpleString:prefix];
         else
            [s appendChar:' '];
         [s appendString:word length:(line-word)];
         linebeg=[s appendChar:'\n'];
         }
      else while(word<line)
         {
         for(tmp=word;(tmp<line)&&!((*tmp==' ')||(*tmp=='\t'));tmp++);
         if (!isascii(*tmp)) tmp++;
	 
         if ([s length]-linebeg+(tmp-word)+1>=maxlen)
            linebeg=[s appendChar:'\n'];
         
         if ([s length]==linebeg)
            [s appendSimpleString:prefix];
         else
            [s appendChar:' '];
         for(;word<tmp;word++) [s appendChar:*word];
         for(;(word<line)&&((*word==' ')||(*word=='\t'));word++);
         }
      
      if (line<end) line++;
      if ((line<end)&&((*line=='\n')||(*line=='\r'))&&(*line!=*(line-1))) line++;
      }
   
   if (linebeg!=[s length]) [s appendChar:'\n'];
   if (rx) free(rx);
   if (prefix!=nil) [prefix free];
   return s;
   }

#else /* KANJI */

static int iseow(const char *d)
   {
   static char *oldd;
   static int gotFirstByte=0;

   if (d==0)
      {
      gotFirstByte=0;
      return 0;
      }
   
   if (isascii(*d)) 
      {
      if ((*d==' ')||(*d=='\t')) return 1;
      else  return 0;
      }
   else 
      {
      if (gotFirstByte)
         {
         if (oldd != d) gotFirstByte=0;
         oldd = (char *)d; return 1;
         }
      else
         {
          if (oldd != d) gotFirstByte=1;
          oldd = (char *)d; 
	  if (isascii(*(d-1))&&(*(d-1)!='\n')&&(*(d-1)!='\r')&&(*(d-1)!=' ')&&(*(d-1)!='\t'))
	     return 1;
	  else 
	     return 0;     
         }
      }
   }

static id quote_text(const char *text,int length,id mes)
   {
   id s=[[SimpleString alloc] init],prefix=nil;
   int linebeg=0,i;
   int maxlen=[MailMessage lineLength];
   const char *line,*end,*word,*tmp,*prebeg,*preend;
   regexp *rx=regcomp(EnhanceQuoteRegex);
   char *lasteow;

   if (!rx) return s;
   
   prebeg=preend=line=text;
   end=line+length;
   iseow(0); tmp=0;
   
   while(line<end)
      {
      if (!regexec(rx,line)) for(i=0;i<NSUBEXP;i++) rx->startp[i]=rx->endp[i]=line;
      word=rx->endp[0];
      
      if ((prefix==nil) ||
          ((word-line)!=(preend-prebeg)) ||
          (strncmp(line,prebeg,preend-prebeg)!=0))
         {
         prebeg=line;
         preend=word;
         if (prefix!=nil) prefix=[prefix free];
         prefix=interpret_escapes(EnhanceQuotePrefix, mes, rx);
         if (linebeg!=[s length]) linebeg=[s appendChar:'\n'];
         }
      
      while((word<end) && iseow(word)) word++;

      for(line=word;(line<end)&&(*line!='\n')&&(*line!='\r');line++);

      if ((*word=='\n')||(*word=='\r'))
         {
         if (linebeg!=[s length]) [s appendChar:'\n'];
         [s appendSimpleString:prefix];
         linebeg=[s appendChar:'\n'];
         iseow(0);
         }
      else if ([s length]-linebeg+(line-word)+
               (([s length]==linebeg) ? [prefix length] : 1)<maxlen)
         {
         if ([s length]==linebeg)
            [s appendSimpleString:prefix];
         else
            if (isascii(*(word-2))) [s appendChar:' '];
         [s appendString:word length:(line-word)];
         linebeg=[s appendChar:'\n'];
         iseow(0);
         }
      else {
         while(word<line)
         {
         lasteow=(char *)tmp;
         for(tmp=word;(tmp<line)&&!iseow(tmp);tmp++);
         if (!isascii(*tmp)) tmp++;
	 
         if ([s length]-linebeg+(tmp-word)+1>=maxlen)
            {
            if (!isascii(*lasteow)) [s appendChar:*lasteow];
	    linebeg=[s appendChar:'\n'];
            }
         
         if ([s length]==linebeg)
            [s appendSimpleString:prefix];
         else
            {
	    if (!isascii(*lasteow)&&(*(lasteow+1)!='\n')&&(*(lasteow+1)!='\r')) 
	       [s appendString:lasteow length:word-lasteow];
	    else if (isascii(*lasteow)) [s appendChar:' '];
            }
         for(;word<tmp;word++) [s appendChar:*word];
         for(;(word<line)&&iseow(word);word++);
         }
         if (!isascii(*tmp)&&(*(tmp+1)=='\n')) [s appendChar:*tmp];
         iseow(0);
         }
      
      if (line<end) line++;
      if ((line<end)&&((*line=='\n')||(*line=='\r'))&&(*line!=*(line-1))) line++;
      }
   
   if (linebeg!=[s length]) [s appendChar:'\n'];
   if (rx) free(rx);
   if (prefix!=nil) [prefix free];
   return s;
   }
#endif /* KANJI */

@implementation EnhanceSend
+ finishLoading:(struct mach_header *)header
   {
   [self poseAs:[self superclass]];
   QuotationHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"i"];
   ReplyBoxHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"%"];
   InReplyToHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"%"];
   return self;
   }

+ startUnloading
   {
   QuotationHash=[QuotationHash free];
   ReplyBoxHash=[ReplyBoxHash free];
   InReplyToHash=[InReplyToHash free];
   return self;
   }

- initSendWindow:fp16
   {
   id ret=[super initSendWindow:fp16];
   id oreader=[self getMailbox];
   id obox=oreader ? [oreader mailbox] : nil;
   char path[FILENAME_MAX];
   const char *home=NXHomeDirectory();
   const char *box=obox ? [obox dirname] : 0;
   BOOL success=NO;
   
   [QuotationHash insertKey:self value:(void *)(NO)];
   [ReplyBoxHash insertKey:self value:0];
   [InReplyToHash insertKey:self value:0];

   if (!(EnhanceInsertSignature^EnhanceControlP())
       ||!EnhanceSignatureFilename) return ret;

   if (box && !success)
      {
      sprintf(path,"%s/%s.rtfd",box,EnhanceSignatureFilename);
      if (([text openRTFDFrom:path]==NX_RTFDErrorNone))
         success=YES;
      
      sprintf(path,"%s/%s.rtf",box,EnhanceSignatureFilename);
      if (!success && sendFormat!=0 && ([text openRichFrom:path]!=nil))
         success=YES;
      
      sprintf(path,"%s/%s",box,EnhanceSignatureFilename);
      if (!success && ([text openFrom:path]!=nil))
         success=YES;
      }
   
   if (home && !success)
      {
      sprintf(path,"%s/%s.rtfd",home,EnhanceSignatureFilename);
      if (([text openRTFDFrom:path]==NX_RTFDErrorNone))
         success=YES;
      
      sprintf(path,"%s/%s.rtf",home,EnhanceSignatureFilename);
      if (!success && sendFormat!=0 && ([text openRichFrom:path]!=nil))
         success=YES;
      
      sprintf(path,"%s/%s",home,EnhanceSignatureFilename);
      if (!success && ([text openFrom:path]!=nil))
         success=YES;
      }
   
   if (success)
      {
      [text setSel:0:0];
      [text replaceSel:EnhanceSignatureSeparator];
      }
   
   if (sendFormat==0) [self makeAscii:self];
   [self updateSpecialDelivery];
   [self updateSize:self];
   [form selectTextAt:0];
   
   return ret;
   }

- free
   {
   [QuotationHash removeKey:self];
   [ReplyBoxHash removeKey:self];
   [InReplyToHash removeKey:self];
   
   return [super free];
   }

- deliver:button
   {
   id ret, def;
   NXAtom replybox, origbox;
   
   if (EnhanceAutoSpellCheck ^ EnhanceAlternateP())
      {
      id spell=[NXSpellChecker sharedInstance];
      id panel=(spell!=nil) ? [spell spellingPanel] : nil;

      if ((spell!=nil)
          &&[spell checkSpelling:NX_CheckSpellingFromStart of:self])
         {
         if (panel!=nil) [panel orderFront:self];
         return nil;
         }
      else
         {
         if (panel!=nil) [panel orderOut:self];
         }
      }
   
   def=[Defaults new];
   origbox=NXUniqueString([def outgoingMailbox]);
   replybox=[ReplyBoxHash valueForKey:self];
      
   if (EnhanceRepliesWithOriginals && replybox)
      [def setOutgoingMailbox:replybox];
   
   ret=[super deliver:button];

   if (EnhanceRepliesWithOriginals && replybox)
      [def setOutgoingMailbox:origbox];
   
   return ret;
   }

- mailMessage
   {
   id mes=[super mailMessage];
   id ximage;
   char buf[1024],*tmp;

   sprintf(buf,"Mail 4.0 (Enhance %s)",EnhanceVersion);
   [mes setHeaderKey:"X-Nextstep-Mailer" value:buf];

   if (tmp=[InReplyToHash valueForKey:self])
      [mes setHeaderKey:"In-Reply-To" value:tmp];
   
   if (EnhanceInsertXFace && EnhanceXFace
       && (ximage=[[NXImage alloc] initFromFile:EnhanceXFace]))
      {
      tmp=[ximage xFace];
      ximage=[ximage free];
      if (tmp)
         {
         [mes setHeaderKey:"X-Face" value:tmp];
         free(tmp);
         }
      }

   if (EnhanceInsertXImageURL && EnhanceXImageURL)
      [mes setHeaderKey:"X-Image-URL" value:strcpy(alloca(strlen(EnhanceXImageURL)+1),EnhanceXImageURL)];
   
   return mes;
   }

- restoreDraftFromMessage:message
   {
#if 0
   if (EnhanceRestoreDeletesDraft)
      {
      [toc setState:'d' forMsg:number flush:YES];
      }
#endif
   [text setText:""];
   [self updateSpecialDelivery];
   return [super restoreDraftFromMessage:message];
   }

- replyHelper:(BOOL)flag
   {
   id oreader=[self getMailbox];
   id obox=oreader ? [oreader mailbox] : nil;
   id orig=oreader ? [oreader mailMessage] : nil;
   id otext=oreader ? [oreader text] : nil;
   id s;
   int start,len;
   NXSelPt beg,end;
   const char *new;
   char *buf,*c;

   [super replyHelper:flag];
   
   if ((orig!=nil)&&EnhanceFlagReplies)
      [oreader setFlagged:YES];
   
   if ((obox!=nil) && ((new=[obox dirname])!=0))
      [ReplyBoxHash insertKey:self value:(void *)NXUniqueString(new)];
   else
      [ReplyBoxHash removeKey:self];

   if ((orig!=nil) && ((new=[orig headerValueForKey:"Message-Id"])!=0))
      [InReplyToHash insertKey:self value:(void *)NXUniqueString(new)];
   else
      [InReplyToHash removeKey:self];

   if ([QuotationHash valueForKey:self]
       || (orig==nil) || (otext==nil)
       || !(EnhanceQuoteReplies^EnhanceControlP()))
      return self;

   s=[[SimpleString alloc] init];

   if (EnhanceQuoteIntro)
      {
      [s includeSimpleString:interpret_escapes(EnhanceQuoteIntro,orig,0)];
      [s appendChar:'\n'];
      }

   [otext getSel:&beg:&end];
   if (beg.cp!=end.cp)
      {
      start=beg.cp;
      len=end.cp-beg.cp;
      buf=alloca(len);
      [otext getSubstring:buf start:start length:len];
      }
   else
      {
      start=0;
      len=[otext byteLength];
      buf=alloca(len);
      [otext getSubstring:buf start:start length:len];
      for(c=buf;c<buf+len-1;c++) if ((c[0]=='\n') && (c[1]=='\n')) break;
      while ((c<buf+len) && (*c=='\n')) c++;
      len-=(c-buf);
      buf=c;
      }
   
   start=[s length];
   [s includeSimpleString:quote_text(buf,len,orig)];
   len=[s length]-start;
         
   [text setSel:0:0];
   [text replaceSel:[s string] length:[s length]];

   if ((sendFormat!=0) && EnhanceQuoteColoring)
      {
      [text setSel:start:start+len];
      [text setSelColor:EnhanceQuoteColor];
      }
   
   if (sendFormat==0) [self makeAscii:self];
   [self updateSpecialDelivery];
   
   [text setSel:start:start];
   
   s=[s free];
   [QuotationHash insertKey:self value:(void *)YES];
   return self;
   }

- openTextStream
   {
   [text openTextStream];
   return self;
   }

- (BOOL)seekToCharacterAt:(int)offset relativeTo:(int)seekMode
   {
   return [text seekToCharacterAt:offset relativeTo:seekMode];
   }

- (int)readCharacters:(char *)buffer count:(int)count
   {
   return [text readCharacters:buffer count:count];
   }

- (int)currentCharacterOffset
   {
   return [text currentCharacterOffset];
   }

- (BOOL)isAtEOTS
   {
   return [text isAtEOTS];
   }

- closeTextStream
   {
   [text closeTextStream];
   return self;
   }

- (void)selectCharactersFrom:(int)start to:(int)end
   {
   [text selectCharactersFrom:start to:end];
   }

- (int)selectionCharacterCount
   {
   return [text selectionCharacterCount];
   }

- (int)readCharactersFromSelection:(char *)buffer count:(int)count
   {
   return [text readCharactersFromSelection:buffer count:count];
   }

- (void)makeSelectionVisible
   {
   [text makeSelectionVisible];
   }

- changeSpelling:sender
   {
   return ([text changeSpelling:sender]==nil) ? nil : self;
   }

- (int)spellDocumentTag
   {
   return [text spellDocumentTag];
   }
@end

@implementation MediaText(EnhanceSend)
- openFrom:(const char *)file
   {
   BOOL ok=YES;
   NXStream *s=0;
   
   if (ok && !(s=NXMapFile(file,NX_READONLY)))
      ok=NO;
   
   if (ok && ([self readText:s]==nil))
      ok=NO;
   
   if (s)
      NXClose(s);
   
   return ok ? self : nil;
   }

- openRichFrom:(const char *)file
   {
   BOOL ok=YES;
   NXStream *s=0;
   
   if (ok && !(s=NXMapFile(file,NX_READONLY)))
      ok=NO;
   
   if (ok && ([self readRichText:s]==nil))
      ok=NO;
   
   if (s)
      NXClose(s);
   
   return ok ? self : nil;
   }
@end
