
/*  Composer.c: the message maker
 *
 *  This file is part of Aileron, a WINGs email client. 
 *
 *  Copyright (c) 1999 - 2001  Nwanua Elumeze
 *
 *  This program 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 program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "Aileron.h"

typedef struct {
  WMWindow  *window;
  
  WMPopUpButton *fieldb;
  WMText *fieldt;
  WMTextField *replytot;
  WMTextField *bcct;  /* this fella is never mapped */
  
  WMButton *archiveb;

} SendOptionsPanel;
  

typedef struct {
  WMWindow  *window;
  
  WMTextField *tot, *subjectt, *cct;
  WMButton  *replyb;
  
  WMText *messageView;
  SendOptionsPanel *options;
  
  char    *path;   

  /* to be able to update the "Answered" flag */
  void *mbox;
  unsigned long msgno; 
  MAILSTREAM  *stream; 
  
  unsigned short  saved:1;
  unsigned short  cType:2;
  unsigned short  sending:1;
  unsigned short  status:1;
  unsigned short  RESERVED:10;
} Composer;


char*
wstrdupDataBytes(char *str, size_t size)
{
  char *ptr;
  assert(str && size > 0);
  
  ptr = strncpy(wmalloc(size+1), str, size);
  ptr[size] = 0;
  return ptr;
}

static void *
getFile(char *fname, int *size)
{
  int fd, itemsRead = 0;
  size_t i;
  char buf[513], *data = NULL;
  
  fd = open(fname, O_RDONLY);
  if(fd == -1) {
    *size = 0;
    return NULL;
  }   
  
  do {
    i = 0;
    i = read(fd, buf, 512);
    if(!(i>0)) 
       break;
    data = wrealloc(data, sizeof(void *)*(itemsRead+i));
    memcpy(data + itemsRead, (const char *)buf, i);
    itemsRead += i;
  } while(i != 0); 
  
  *size = itemsRead;
  close(fd);
  return data;
}   

/* archive ruins inline...  newlines inserted, fix */
static int 
archiveComposition(char *where, Composer *c)
{
  FILE *archive;
  char *path;
  char *bytes = NULL;
  WMArray *messageArray;
  time_t t;

  if(!WMGetUDStringForKey(Aileron.defaultsDB, "MailDir"))
    return 0;

  path = wstrdup(WMGetUDStringForKey(Aileron.defaultsDB, "MailDir"));
  path = wstrappend(path, "/");
  path = wstrappend(path, where);
  archive = fopen(path, "a");
  free(path);

  if(!archive) {
    RunAlertPanel("Archive",
      "Was not able to archive the composition.",
      "OK", NULL, NULL);
    return -1;
  }
    
  time(&t);  
  { char *from = wstrdup(myusername());
    from = wstrappend(from, "@");
    from = wstrappend(from, mylocalhost());

    fprintf(archive, "\nFrom %s %s", from, ctime(&t));
  }
  fprintf(archive, "Subject: %s\n", 
    WMGetTextFieldText(c->subjectt));
  fprintf(archive, "From: %s\n", WMGetTextFieldText(c->tot));
  fprintf(archive, "To: %s\n", WMGetTextFieldText(c->tot));
  fprintf(archive, "Cc: %s\n", WMGetTextFieldText(c->cct));


  if ((messageArray = WMGetTextObjects(c->messageView))) {
    char *section = NULL, *contents;
    WMData *data = NULL;
    unsigned char *text;
    size_t size;
    unsigned long l;
    short i, count = WMGetArrayItemCount(messageArray);

    fprintf(archive, "Mime-Version: 1.0\n");
    fprintf(archive, "Content-Type: multipart/mixed;");
    fprintf(archive, " boundary=\"Aileron Composition\"\n");

    for(i=0; i<count; i++) {

      if(section) {
        free(section);
        section = NULL;
      }
        
      data = (WMData *) WMGetFromArray(messageArray, i);
      if(!(bytes = (char *)WMDataBytes(data)))
        continue;

      size = WMGetDataLength(data);
      section = wstrdupDataBytes((char *)bytes, size);

      fprintf(archive, "\n\n--Aileron Composition\n");

      if(WMGetDataFormat(data) == TYPEIMAGE) {
        fprintf(archive, 
          "Content-Type: image/jpeg; name=\"%s\"\n", 
          strrchr(section, '/'));
        fprintf(archive, "Content-Transfer-Encoding: base64\n");
        fprintf(archive, "Content-Description: %s\n",
          strrchr(section, '/'));
        fprintf(archive, 
          "Content-disposition: inline; filename=\"%s\"\n\n",
          strrchr(section, '/'));
        
        text = (unsigned char *) getFile(section, &size);
        contents = (char *)rfc822_binary (text, size, &l);
        fwrite(contents, l, 1, archive);
      } else {
        fprintf(archive, 
          "Content-Type: text/plain; charset=us-ascii\n");
        fprintf(archive, "Content-Transfer-Encoding: 7bit\n\n");
        fwrite(section, size, 1, archive);
        
      }

      fprintf(archive, "\n");
    }
    fprintf(archive, "--Aileron Composition--\n\n");
  } else {
    fprintf(archive, "Blank message\n\n");
  }

  fclose(archive);

  return 1;
}

static void
setTitle(Composer *c)
{
  char title[128];
  char *text = WMGetTextFieldText(c->subjectt);
  

  if(strlen(text) == 0) 
    text = wstrappend(text, "no subject");


  snprintf(title, 127, "Aileron: Compose Message - %s ", text);
  WMSetWindowTitle(c->window, title);

  snprintf(title, 127, "Aileron: Send Options (%s)", text);
  WMSetWindowTitle(c->options->window, title);

  WMSetWindowMiniwindowTitle(c->window, text);

  wfree(text);
}


static void
setStatus(Composer *c, Bool status)
{
  c->saved = status;
  WMSetWindowDocumentEdited(c->window, !status);
} 


static int 
setCompositionType(Composer *c, int type)
{
  char *to=NULL, subject[128], *cc=NULL, *str;
  ENVELOPE *env;
  MAILSTREAM *stream;
  unsigned long msgno;

  if(!Aileron.mbox) {
    PostComposeError(type);
    return 0;
  }

  stream = GetMailBoxStream(Aileron.mbox);
  msgno = GetMailBoxSelectedMessage(Aileron.mbox);

  if(!stream || msgno < 1) {
    PostComposeError(type);
    return 0;
  }

  env = mail_fetchenvelope(stream, msgno);
  if(!env) {
    PostComposeError(type);
    return 0;
  }   


  WMFreezeText(c->messageView);
  str = GetCharFromAddress(env->from);
  if(str) { 
    to = wstrdup(str);
    free(str);
    str = NULL;
  } 


  if(type == CMReplyAll)  {
    str = GetCharFromAddress(env->cc);
    if(str) {
      cc = wstrdup(str);
      free(str);
      str = NULL;
      str = GetCharFromAddress(env->to);
      if(str) {
        cc = wstrappend(cc, ", ");
        cc = wstrappend(cc, str);
        free(str);
        str = NULL;
      }   
    } else {
      str = GetCharFromAddress(env->to);
      if(str) { 
        cc = wstrdup(str);
        free(str);
        str = NULL;
  }   }   }


  if(env->subject)
    str = wstrdup(env->subject);
  
  c->mbox = Aileron.mbox;
  c->stream = stream;
  c->msgno = msgno;
  
  WMAppendTextStream(c->messageView, NULL);

  if(type == CMForward)  {
    BODY *body;

    mail_fetchstructure(stream, msgno, &body);
    WMAppendTextStream(c->messageView, "<p><B>Begin forwarded message:</B><p>");

    FetchMessagePart(c->messageView, c->stream, 
      msgno, body, NIL, 0, bestAlternative, 0);

    if(str) {
      snprintf(subject, 127, "Fwd: %s", str);
      free(str);
      str = NULL;
    } else sprintf(subject, "Fwd: message");

   } else if(type == CMReply || type == CMReplyAll)  {

    if(WMGetUDBoolForKey(Aileron.defaultsDB, "CopyMessageIntoReply")) { 
      BODY *body;
      Bool quote = WMGetUDBoolForKey(Aileron.defaultsDB, "QuoteReplies");

      mail_fetchstructure(stream, msgno, &body);

      if(quote) { 
        char *option = 
          WMGetUDStringForKey(Aileron.defaultsDB, "ReplyIntroduction");
  
        if(option) {
          WMAppendTextStream(c->messageView, option);
          WMAppendTextStream(c->messageView, "\n\n");
        }

      }

      FetchMessagePart(c->messageView, c->stream, 
        msgno, body, NIL, 0, bestAlternative, quote);
    }

#if 0
      if((Prefs.CopyMessageIntoReply && !Aileron.shiftHeld) 
        || ((!Prefs.CopyMessageIntoReply && Aileron.shiftHeld))) {
        if(Prefs.QuoteReplies)  {
          replyPrefix(c->messageView, c->stream, msgno, body);
        } else {
          FetchMessagePart(c->messageView, c->stream, 
            msgno, body, NIL, 0, bestAlternative);
         }
       }
    }
#endif


    if(str) {
      snprintf(subject, 127, "Re: %s", str);
      free(str);
      str = NULL;
    } else sprintf(subject, "Re: your message");

  } 

  WMSetTextFieldText(c->cct, cc);
  if(type != CMForward)
    WMSetTextFieldText(c->tot,  to);
  WMSetTextFieldText(c->subjectt, subject);
  
  WMThawText(c->messageView);
  setStatus(c, False);
  return 1; 
  

}

static void 
addressesCB(WMWidget *w, void *client)
{   
  RunAddressesPanel();
}

static void 
changeCType(WMWidget *w, void *client)
{   
  Composer *c = (Composer *) client;

  if(c->cType == CMForward)
    return;

  if(c->cType == CMCompose) {
    c->cType = CMReply;
  }


  if(c->cType == CMReplyAll) {
    if(setCompositionType(c, CMReplyAll)) { 
      WMSetButtonText(c->replyb, "Reply");
      c->cType = CMReply;
    } else return;
  } else if(c->cType == CMCompose || c->cType == CMReply) {
    if(setCompositionType(c, CMReply)) { 
      WMSetButtonText(c->replyb, "Reply All");
      c->cType = CMReplyAll;
    } else return;
  }
  
  setTitle(c);
}


static void
changeForward(WMWidget *w, void *client)
{
  setCompositionType((Composer *) client, CMForward);
}

static void
handleWindowStatus(XEvent *event, void *client)
{ 
  Composer *c = (Composer *) client;

  if(c->saved) 
    setStatus(c, False);
}


static void
handleKeyPresses(XEvent *event, void *client)
{
  Composer *c = (Composer *) client;
  KeySym ksym;
  
  XLookupString(&event->xkey, NULL, 0, &ksym, NULL);
  if(ksym == XK_Shift_R || ksym == XK_Shift_L) {
    Aileron.shiftHeld = True;
  }

  if(event->xkey.state & XK_Alt_L &  8) {
  
  switch (ksym) {
    case XK_c :  EnterSelection(); break;
    case XK_f :  RunFindPanel(); break;
    case XK_g :  FindNext(); break;
    case XK_G:   FindPrevious(); break;

    case XK_n :  ComposeMessage(CMCompose); break;
    case XK_r :  ComposeMessage(CMReply); break;
    case XK_R :  ComposeMessage(CMReplyAll); break;
    case XK_W :  ComposeMessage(CMForward); break;

    case XK_q :  QuitAileron();
    case XK_s :  break;

    
    case XK_w :  { 
      void closeComposer(WMWidget *self, void *client);
      closeComposer(c->window, c); break;
    }
    
    case XK_H:  WMHideOthers(Aileron.appContext); break;
  }
  }


}

static void
handleKeyReleases(XEvent *event, void *client)
{
  KeySym ksym;
  
  XLookupString(&event->xkey, NULL, 0, &ksym, NULL);
  if(ksym == XK_Shift_R || ksym == XK_Shift_L) {
    Aileron.shiftHeld = False;
  }
}

static void
setSubjectAsTitle(XEvent *event, void *client)
{
  Composer *c = (Composer *)client;
    setTitle(c);
}

static void
handleCCKeyPress (XEvent *event, void *client)
{
  Composer *c = (Composer *)client;
  KeySym ksym;

  XLookupString(&event->xkey, NULL, 0, &ksym, NULL);
  if(ksym == XK_Tab) {
    WMSetFocusToWidget(c->messageView);
  //  WMForceTextFocus(c->messageView);
  }
}
    
static void
changeHeaderInText(WMWidget *w, void *client)
{
  Composer *c = (Composer *)client;
  int which;
  char *text = NULL;

  if(!c->options)
    return;

  which = WMGetPopUpButtonSelectedItem(c->options->fieldb);
  switch(which) {   
    case -1:
    case 0: text = WMGetTextFieldText(c->tot); break;

    case 1: text = WMGetTextFieldText(c->cct); break;
    case 2: text = WMGetTextFieldText(c->options->bcct); break;

#if 0
    case 3: 
      if(Prefs.AddHeadV[0]) 
        text = wstrdup(Prefs.AddHeadV[0]); 
    break;

    case 4: 
      if(Prefs.AddHeadV[1]) 
        text = wstrdup(Prefs.AddHeadV[1]); break;
#endif

    default: return;
  }
    
  if(text) {
    WMAppendTextStream(c->options->fieldt, NULL);
    WMAppendTextStream(c->options->fieldt, text);
    WMThawText(c->options->fieldt);
    free(text);
  }
}


static void
handleAddrFieldKeyPress(XEvent *event, void *client)
{
  Composer *c = (Composer *)client;
  KeySym ksym;

  if(!c->options)
    return;

  XLookupString(&event->xkey, NULL, 0, &ksym, NULL);

  if(ksym == XK_Return || ksym == XK_Tab) {
    int which = WMGetPopUpButtonSelectedItem(c->options->fieldb);
    char *text = WMGetTextStream(c->options->fieldt);

    if(!text)
      return;

    switch(which) {   
      case -1:
      case 0: WMSetTextFieldText(c->tot, text); break;
      case 1: WMSetTextFieldText(c->cct, text); break;
      case 2: WMSetTextFieldText(c->options->bcct, text); break;
#if 0
      case 3: snprintf(Prefs.AddHeadV[0], 23, text);
      case 4: snprintf(Prefs.AddHeadV[1], 23, text);
#endif
    }
    
    free(text);
  }
}


static void 
appendtoTextField(WMTextField *tPtr, char *new)
{
  char *address; 

  if(!new || !tPtr)
    return;
    
  address = WMGetTextFieldText(tPtr);

  if(!address ||strlen(address) < 1) {
    WMSetTextFieldText(tPtr, new);

  } else {
    int nlen = strlen(new);
    int alen = strlen(address);

    address = wrealloc(address, 3+nlen);
    snprintf(&address[alen], nlen+3, ", %s", new);
    WMSetTextFieldText(tPtr, address);
    free(address); 
  }
}

void AppendCC(void *vc, char *text)
{
  Composer *c = (Composer *)vc;
  if(!c)
    return;
  appendtoTextField(c->cct, text);
}

void AppendTo(void *vc, char *text)
{
  Composer *c = (Composer *)vc;
  if(!c)
    return;
  appendtoTextField(c->tot, text);
}
  
    

void ShowSendOptionsPanel(void *vc)
{
  Composer *c = (Composer *)vc;

  if(!c)
    return;

  WMMapWidget(c->options->window);
  WMRaiseWidget(c->options->window);
}


void hideSendOptionsPanel(WMWidget *self, void *client)
{
  Composer *c = (Composer *) client;

  if(!c)
    return;

  WMUnmapWidget(c->options->window);
}

static char *getReplyTo(void)
{
  char * option = WMGetUDStringForKey(Aileron.defaultsDB, "ReplyTo");

  if(option)
    return wstrdup(option);
  else {
    char *replyto = wstrdup(myusername());

    replyto = wstrappend(replyto, "@");
    replyto = wstrappend(replyto, mylocalhost());
    return replyto;
  }
}


static SendOptionsPanel *
createSendOptionsPanel(Composer *c)
{
  SendOptionsPanel *options;
  WMFrame *frame;
  char *replyto;

  options = wmalloc(sizeof(SendOptionsPanel));
  if(!options)
    return NULL;
  
  memset(options, 0, sizeof(SendOptionsPanel));
  
  options->window = WMCreateWindowWithStyle(Aileron.screen, "Aileron",
    WMTitledWindowMask|WMClosableWindowMask);
  WMSetWindowCloseAction(options->window, hideSendOptionsPanel, c);
  WMResizeWidget(options->window, 390, 225);
  
  frame = WMCreateFrame(options->window);
  WMResizeWidget(frame, 370, 165);
  WMMoveWidget(frame, 10, 10);
  WMSetFrameTitle(frame, "Address Fields");
  
  options->fieldb = WMCreatePopUpButton(frame);
  WMResizeWidget(options->fieldb, 120, 20);
  WMMoveWidget(options->fieldb, 125, 15);
  WMSetPopUpButtonText(options->fieldb, "Address Fields");
  WMAddPopUpButtonItem(options->fieldb, "To");
  WMAddPopUpButtonItem(options->fieldb, "Cc");
  WMAddPopUpButtonItem(options->fieldb, "Bcc");
#if 0
  WMSetBalloonTextForView("Press here to choose a type of address.",
    WMWidgetView(options->fieldb));
#endif
  
#if 0
  if(strlen(Prefs.AddHeadK[0]) > 2) 
    WMAddPopUpButtonItem(options->fieldb, Prefs.AddHeadK[0]);
  if(strlen(Prefs.AddHeadK[1]) > 2) 
    WMAddPopUpButtonItem(options->fieldb, Prefs.AddHeadK[1]);
#endif

  WMSetPopUpButtonAction(options->fieldb, changeHeaderInText, c);

  options->fieldt = WMCreateText(frame);
  WMSetTextHasVerticalScroller(options->fieldt, True);
  WMResizeWidget(options->fieldt, 350, 90);
  WMMoveWidget(options->fieldt, 10, 40);
  WMSetTextIgnoresNewline(options->fieldt, True);
  WMSetTextRelief(options->fieldt, WRSunken);
  WMSetTextUsesMonoFont(options->fieldt, True);

#if 0
  WMSetBalloonTextForView("Type a list of addresses here.\n"
    "Separate addresses with a comma or space.\n"
    "Press the Enter key when finished typing",
    WMWidgetView(options->fieldt));
#endif
  
  WMCreateEventHandler(WMWidgetView(options->fieldt), KeyPressMask,
    handleAddrFieldKeyPress, c);

  NewLabel(frame, "Reply-To:", 10, 135, 63, 20);
  options->replytot = NewTextField(frame, NULL, 75, 135, 285, 20);
  replyto = getReplyTo();
  WMSetTextFieldText(options->replytot, replyto);
  wfree(replyto);

#if 0
  WMSetBalloonTextForView("Type the address of any person or group\n"
    "who should receive replies to the message",
    WMWidgetView(options->replytot));
#endif
  
  options->bcct = NewTextField(frame, NULL, 75, 135, 20, 20);

  frame = WMCreateFrame(options->window);
  WMResizeWidget(frame, 370, 30);
  WMMoveWidget(frame, 10, 185);

  options->archiveb = WMCreateSwitchButton(frame);
  WMResizeWidget(options->archiveb, 200, 20);
  WMMoveWidget(options->archiveb, 90, 5);
  WMSetButtonText(options->archiveb, "Archive in Outgoing mailbox");
#if 0
  WMSetBalloonTextForView("Check this box to save a copy of the\n"
    "message in your Outgoing mailbox.",
    WMWidgetView(options->archiveb));
#endif
#if 0
  WMSetButtonSelected(options->archiveb, Prefs.ArchiveOutgoingMail);
#endif

  WMMapSubwidgets(frame);

  WMMapSubwidgets(options->window);
  WMUnmapWidget(options->bcct);
  WMRealizeWidget(options->window);
  WMAppAddWindow(Aileron.appContext, WMWidgetXID(options->window));

  return options;
}



void closeComposer(WMWidget *self, void *client)
{
  Composer *c = (Composer *) client;
  
  if(!c->saved) {
    int i = strlen(WMGetTextFieldText(c->subjectt));
    char string[25+i];
    int result;

    if(i>0) 
      snprintf(string, 24+i, "Save draft entitled "
        "\"%s\"?", WMGetTextFieldText(c->subjectt));
    else snprintf(string, 22, "Save  untitled draft?");

    result = RunAlertPanel("Close Composer",
      string, "Save", "Don't save", Aileron.aileronQuitting?NULL:"Cancel");

    if(result==-1) {
      return;  
    } else if(result==0) {
      SaveDraft(c);
    }
  }
    

  if(!Aileron.aileronQuitting)
    WMRemoveFromBag(Aileron.openComposers, (void *)c);

  XDeleteContext(Aileron.display,
    WMWidgetXID(c->window), Aileron.xContext);
  WMHashRemove(Aileron.windowTable, c->path);
  wfree(c->path);

  WMDestroyWidget(c->options->window);
  wfree(c->options);
  c->options = NULL;

  WMDestroyWidget(c->window);
  wfree(c);
  c = NULL;

  Aileron.composer = NULL; 
}

void CloseComposer(void *vc)
{
  Composer *c = (Composer *) vc;

  assert(c);

  closeComposer(c->window, c);
}


int getFileType(char *fname, char *subtype)
{
  char *command, output[512], *fprop=NULL, *mark=NULL;
  FILE *pipe;

  assert(fname && subtype);

  command = wstrdup("file ");
  command = wstrappend(command, fname);
  if(!(pipe = popen(command, "r"))) {
    wfree(command);
    snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEOTHER;
  }

  wfree(command);

  if(fgets(output, 510, pipe)) {
    output[511] = 0;
    pclose(pipe);

  } else {
    pclose(pipe);
    snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEOTHER;
  }

  fprop = strstr(output, ": ");
  if(!fprop) {
    snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEOTHER;
  }
 
  if(strlen(fprop) < 4) {
    snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEOTHER;
  }
    
  fprop += 2;
 
  if((mark = strchr(fprop, '(')) || (mark = strchr(fprop, ',')))
    if(mark > fprop) { 
      fprop[(int)(mark-fprop)] = 0; 
  } 
        
  if(fprop[strlen(fprop)-1] == '\n')
    fprop[strlen(fprop)-1] = 0;

 
/* TODO: add more formats */
  if(strstr(fprop, "image")) { 
    if(!strncmp(fprop, "TIFF", 4))
      snprintf(subtype, MAILTMPLEN-1, "TIFF");
    else if(!strncmp(fprop, "JPEG", 4))
      snprintf(subtype, MAILTMPLEN-1, "JPEG");
    else if(!strncmp(fprop, "X pixmap", 8))
      snprintf(subtype, MAILTMPLEN-1, "X-pixmap");
    else if(!strncmp(fprop, "GIF", 3))
      snprintf(subtype, MAILTMPLEN-1, "GIF");
    else
      snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEIMAGE;
    
  } else if(strstr(fprop, "ASCII text")) {
    snprintf(subtype, MAILTMPLEN-1, "plain");
    return TYPETEXT;
 
  } else if(strstr(fprop, "mail text")) {
    snprintf(subtype, MAILTMPLEN-1, "message");
    return TYPEMESSAGE;

  } else if(strstr(fprop, "executable,")) {
    snprintf(subtype, MAILTMPLEN-1, "executable");
    return TYPEAPPLICATION;

  } else if(strstr(fprop, "gzip")) {
    snprintf(subtype, MAILTMPLEN-1, "gzip");
    return TYPEAPPLICATION;
 
  } else if(strstr(fprop, "tar archive")) {
    snprintf(subtype, MAILTMPLEN-1, "tar-archive");
    return TYPEAPPLICATION;
 
  } else if(strstr(fprop, "audio")) {
    if(!strncmp(fprop, "RIFF", 4))
      snprintf(subtype, MAILTMPLEN-1, "RIFF");
    else
      snprintf(subtype, MAILTMPLEN-1, "unknown");
    return TYPEAUDIO;

  }

  snprintf(subtype, MAILTMPLEN-1, "unknown");
  return TYPEOTHER;
}
 
  


void AddAttachment(void *vc)
{
  Composer *c = (Composer *)vc;
  WMOpenPanel *panel;
  char *fname, *path, subtype[MAILTMPLEN];
  WMPixmap *pixmap;
  int type;
  
  if(!c) {
    RunAlertPanel("Add attachment",
      "You are not currently composing a message,\n"
      "so that can't be done!",
      "OK", NULL, NULL);
    return;
  }

  path = WMGetUDStringForKey(Aileron.defaultsDB, "LastAttachment");
  if(!path)
    path = wstrdup(wgethomedir());

  panel = WMGetOpenPanel(Aileron.screen);
  WMSetFilePanelAutoCompletion(panel, True);
  if(WMRunModalFilePanelForDirectory(panel, NULL, path, NULL, NULL)) {   

    fname = WMGetFilePanelFileName(panel);
    WMSetUDStringForKey(Aileron.defaultsDB, fname, "LastAttachment");
   
    type = getFileType(fname, subtype);

    pixmap = WMCreatePixmapFromFile(Aileron.screen, fname);
    if(pixmap) {  /* need to use file(1) or something to figure out */
      WMAppendTextBlock(c->messageView, 
        WMCreateTextBlockWithPixmap(c->messageView, pixmap, fname,
        WMDarkGrayColor(Aileron.screen), 0, TYPEIMAGE));
      WMReleasePixmap(pixmap);
    } else { 
      WMAppendTextBlock(c->messageView, 
        WMCreateTextBlockWithPixmap(c->messageView, 
        Aileron.ApplicationIcon, fname,
        WMDarkGrayColor(Aileron.screen), 0, TYPEAPPLICATION));
    }

    WMThawText(c->messageView);
  }
    
}



#include <signal.h>
#include <sys/wait.h>


static void 
masqueradeMe(char **curusr, char **curhst, char **personalname, char *replyto)
{
  #include <pwd.h>
  char *start, *mark = NULL;
  int len=0;

  start = WMGetUDStringForKey(Aileron.defaultsDB, "OtherAddressesForSelf");
  if(start)
    *personalname = wstrdup(start);
  else
    *personalname = wstrdup(myusername());


  start = replyto;
  if((mark = strchr(start, '@'))) { 
    len = (int)(mark-start);
    *curusr = wmalloc(len+1);
    memcpy(*curusr, start, len);
    (*curusr)[len] = 0;
    *curhst = wstrdup(mark+1);
  } else {
    *curusr = wstrdup(replyto);
    *curhst = wstrdup(mylocalhost());
  }

}


enum { OHSMTP=0, OHApp, OHServer};

static char * get_method(void) 
{ 
   char *method;
   char* agent=NULL, *user=NULL, *passwd=NULL;
   int which;
  
  which = WMGetUDIntegerForKey(Aileron.defaultsDB, "OutGoingMailAgent");
  
  switch (which) {
  
    case OHSMTP:
USE_OHSMTP:
       method = wstrdup("localhost");
    break;
    
    case OHServer:
      if(!(user = WMGetUDStringForKey(Aileron.defaultsDB, "OutgoingName")))
        goto USE_OHSMTP;

      if(!(passwd = WMGetUDStringForKey(Aileron.defaultsDB, "OutgoingPasswd"))) 
        goto USE_OHSMTP;

      
    case OHApp:
      if(!(agent = WMGetUDStringForKey(Aileron.defaultsDB, "OutgoingServer"))) 
        goto USE_OHSMTP;
      
    if(user && passwd && agent) { 
        method = wmalloc(5+strlen(agent)+strlen(user)+strlen(passwd));
        sprintf(method, "%s/%s/%s", agent, user, passwd);

    } else if(agent) { 
        method = wstrdup(agent);

    } else { 
        goto USE_OHSMTP;
    }

    break;

    default:
        goto USE_OHSMTP;
   }


    return method;

}
   



void
DeliverMessage(void *vc)
{
  Composer *c = (Composer *)vc; 
/* from prefs sender to here ... */
  SENDSTREAM *stream = NIL;
  ENVELOPE *msg; 
  BODY *body;
  char date[MAILTMPLEN];
  char *curhst = NIL, *curusr = NIL, *personalname = NIL;
  WMArray *messageArray;
  short count, i;
  int result = 45;
  PART *part = NULL;
  char *section = NULL, *bytes = NULL, *to, *cc, *bcc;
  size_t size;
  unsigned char *text, subtype[MAILTMPLEN];
  WMData *data = NULL;
  char *method;
  #include <pwd.h>


  if(!c)
    return;


  to = WMGetTextFieldText(c->tot);
  if(strlen(to)<1) {
    RunAlertPanel("Send", "No one to send to.", "OK", NULL, NULL);
    return;
  }

  masqueradeMe(&curusr, &curhst, &personalname,
    WMGetTextFieldText(c->options->replytot));


  msg = mail_newenvelope();

  msg->from = mail_newaddr();
  msg->from->personal = cpystr(personalname);
  msg->from->mailbox = cpystr(curusr);
  msg->from->host = cpystr(curhst);


  msg->return_path = mail_newaddr ();
  msg->return_path->mailbox = cpystr(curusr); /* parse replyto...@ */
  msg->return_path->host = cpystr (curhst);

  rfc822_parse_adrlist(&msg->to, ExpandAddress(to), curhst);
  free(to);

  cc = WMGetTextFieldText(c->cct);
  if(strlen(cc)>0) {
    rfc822_parse_adrlist(&msg->cc, ExpandAddress(cc), curhst);
    free(cc);
  }

  bcc = WMGetTextFieldText(c->options->bcct);
  if(strlen(bcc)>0)  {
    rfc822_parse_adrlist(&msg->bcc, ExpandAddress(bcc), curhst);
    free(bcc);
  }
  
  msg->subject = WMGetTextFieldText(c->subjectt);
  

  body = mail_newbody();

    //body->parameter = mail_newbody_parameter();
    //body->parameter->attribute = cpystr("CHARSET");
    //body->parameter->value = cpystr(charsets[Prefs.OutgoingCharset]);

    
  messageArray = WMGetTextObjects(c->messageView);
  if(messageArray) { 
    if((count = WMGetArrayItemCount(messageArray)) > 1)
      body->type = TYPEMULTIPART;

    for(i=0; i<count; i++) {

      if(count > 1) { 
        if(i==0) {
          part = mail_newbody_part();
          body->nested.part = part;
        } else {
          part->next = mail_newbody_part();
          part = part->next;
        }
      }
      
/*
don't uncomment unless ...contents.text.data = wstrdup(section);
      if(section) {
        wfree(section);
        section = NULL;
      }
*/

      data = (WMData *) WMGetFromArray(messageArray, i);
      if(!data || !(bytes = (char *)WMDataBytes(data)))
        continue;

      size = WMGetDataLength(data);
      section = wstrdupDataBytes((char *)bytes, size);

      if(WMGetDataFormat(data) == TYPETEXT) {
        if(count > 1) { 
          part->body.contents.text.data = (unsigned char *)section;
          part->body.contents.text.size = size;
          part->body.encoding  = ENC7BIT; /* depends on locale... */
          part->body.type    = TYPETEXT; 
          part->body.subtype  = wstrdup("plain");
        } else {
          body->contents.text.data = (unsigned char *)section;
          body->contents.text.size = size;
          body->encoding  = ENC7BIT; /* depends on locale... */
          body->type    = TYPETEXT; 
          body->subtype  = wstrdup("plain");
        }

      } else {
        text = (unsigned char *) getFile(section, &size);
        if(count > 1) { 
          part->body.contents.text.data =
            (char *)rfc822_binary (text, size, 
            &part->body.contents.text.size);
          part->body.encoding  = ENCBASE64;
          part->body.type = getFileType(section, subtype);
          part->body.subtype  = wstrdup(subtype);
          part->body.parameter   = mail_newbody_parameter();
          part->body.parameter->attribute = wstrdup("NAME");
          part->body.parameter->value = wstrdup(1+strrchr(section, '/'));
          part->body.description = wstrdup(part->body.parameter->value);
          part->body.disposition.type = wstrdup("inline");

        } else {
          body->contents.text.data =
            (char *)rfc822_binary (text, size, 
            &body->contents.text.size);
          body->encoding  = ENCBASE64;
          body->type = getFileType(section, subtype);
          body->subtype  = wstrdup(subtype);
          body->parameter   = mail_newbody_parameter();
          body->parameter->attribute = wstrdup("NAME");
          body->parameter->value = wstrdup(1+strrchr(section, '/'));
          body->description = wstrdup(body->parameter->value);
          body->disposition.type = wstrdup("inline");
        }
      }


    }
    WMFreeArray(messageArray);
    
  } else {
    body->type = TYPETEXT;
    body->contents.text.data = (unsigned char *)wstrdup("Blank message");
    body->contents.text.size = strlen("Blank message");
  }
    

  rfc822_date(date);
  msg->date = (char *) fs_get (1+strlen(date));
  strcpy (msg->date, date);

  wfree(personalname);
  wfree(curusr);
  wfree(curhst);

  method = get_method();
  {
     char *hostlist[] = {method, "mail", NIL};
     stream = smtp_open (hostlist, 1);
  }
  wfree(method);

  if(!stream) { 
    RunAlertPanel("Send", "Could not open a send stream.\n"
      "It would be wise to save this composition first,\n"
      "then check the host list to make sure it is valid.",
      "Save", NULL, NULL);
    result = 1;
    c->saved = True;
    goto _archive;
    return;
  }

trySendAgain:
  if(!smtp_mail(stream, "MAIL", msg, body)) {
    result = RunAlertPanel("Send", "Could not send the message.\n"
      "Check to make sure all the recipients are valid.\n"
      "Do you want to save as a draft or try sending again?", 
      "Try Again", "Save", "Don't Send");

_archive:
    if(result==1) {
      result = archiveComposition("Drafts", c);
      c->saved = True;
    } else if(result == 0) {
      result = -1;
      goto trySendAgain;
    }
      
  } else { 
    if(WMGetButtonSelected(c->options->archiveb) 
    || WMGetUDBoolForKey(Aileron.defaultsDB, "ArchiveOutgoingMail")) 
      archiveComposition("Outgoing", c);

    c->saved = True;

    if(c->stream && c->cType != CMCompose) {
      char buf[10];
      sprintf(buf, "%ld", c->msgno);
      mail_setflag(c->stream, buf, "\\ANSWERED");
printf("ANSWERED: what happens if mbox is closed before finish?\n");
      RefreshMailBoxSummary(c->mbox);
      c->mbox = NULL;
      c->stream = NULL;
    }
  }

  smtp_close(stream);
  mail_free_envelope(&msg);
  mail_free_body(&body);

  if(result != -1)
    closeComposer(c->window, c);

}


void SaveDraft(void *vc) 
{
  if(vc)
    archiveComposition("Drafts", (Composer *)vc);
}

WMWindow *GetComposerWindow(void *vc)
{
  if(!vc)
    return NULL;

  return ((Composer *)vc)->window;
}

WMText *GetComposerMessageView(void *vc)
{
  if(!vc)
    return NULL;

  return ((Composer *)vc)->messageView;
}

void  
SetComposerTextAlignment(void *vc, WMAlignment alignment)
{
  if(!vc)
    return;

  WMSetTextAlignment(((Composer *)vc)->messageView, alignment);
}
  

void HideShowComposerRuler(void *vc)
{
  Composer *c = (Composer *)vc;
  
  if(!c)
    return;

  WMShowTextRuler(c->messageView, !WMGetTextRulerShown(c->messageView));

}


void RestoreDraft(void)
{
  Composer *c;
  void *draftsMbox = NULL;
  char *path;
  unsigned long msgno;
  MAILSTREAM *stream;
  int ok=False, openedhere=False;

  if(!WMGetUDStringForKey(Aileron.defaultsDB, "MailDir"))
    return;

  path = wstrdup(WMGetUDStringForKey(Aileron.defaultsDB, "MailDir"));
  path = wstrappend(path, "/Drafts");
  draftsMbox = GetMailBoxForName(path);
  
  if(draftsMbox) { 
    stream = GetMailBoxStream(draftsMbox);
    msgno = GetMailBoxSelectedMessage(draftsMbox);
    if(msgno == 0) {
      if(GetMailBoxTotalMessages(draftsMbox)) {
        msgno = stream->nmsgs;
        ok = True;
      }
    } else { 
      ok = True;
    }
  } else {

    stream = mail_open(NIL, path, NIL);
    if(stream) {
      openedhere = True;

      if(stream->nmsgs  > 0) {
        msgno = stream->nmsgs;
        ok = True;
  } } }
      
      
  free(path);

  if(ok) {
    BODY *body;
    char s[256];
  
    ComposeMessage(CMCompose);
    c = (Composer *)Aileron.composer;
    if(!c)
      return;


    mail_fetchstructure(stream, msgno, &body);
    WMAppendTextStream(c->messageView, NULL);
    FetchMessagePart(c->messageView, stream, 
      msgno, body, NIL, 0, bestAlternative, 0);
    WMThawText(c->messageView);
  
    mail_fetchfrom(s, stream, msgno, 255);
    WMSetTextFieldText(c->tot, s);

    mail_fetchsubject(s, stream, msgno, 255);
    WMSetTextFieldText(c->subjectt, s);

  }
  
  if(openedhere)
    mail_close(stream);
  
}

static void 
buttonDeliverMessageCB(WMWidget *w, void *client)
{
  DeliverMessage(client);
}


static void 
buttonAttachCB(WMWidget *w, void *client)
{
  AddAttachment(client);
}


void sizeChanged(void *self, WMNotification *notif)
{
  Composer *c = (Composer *)self;
  void *object = WMGetNotificationObject(notif);
  
  if (WMGetNotificationName(notif) == WMViewSizeDidChangeNotification
    && object == WMWidgetView(c->window)) {
      
    unsigned short w = WMWidgetWidth(c->window);
    unsigned short h = WMWidgetHeight(c->window);

    WMResizeWidget(c->messageView, w-16, h-182);
    WMResizeWidget(c->tot, w-72, 20);
    WMResizeWidget(c->subjectt, w-72, 20);
    WMResizeWidget(c->cct, w-72, 20);
  }
}


void 
PostComposeError(int type)
{
  if(type == CMForward) 
    RunAlertPanel("Forward", "No message to foward.", "OK", 
      NULL, NULL);
  else if(type != CMCompose)
    RunAlertPanel( "Reply", "No message to reply to.", "OK", 
      NULL, NULL);
}

static WMTextDelegate textDelegate = {
    NULL,
    (void *)OpenFile/* didDoubleClickOnPixmap */
};  
   
void ComposeMessageTo(char *to)
{
  Composer *c;

  if(!to)
    return;

  ComposeMessage(CMCompose);
  c = (Composer *)Aileron.composer;
  if(!c)
    return;
  WMSetTextFieldText(c->tot,  to);
}

  
void ComposeMessage(int type)
{
  Composer *c;
  unsigned short w=472, h=460;
  WMFrame *frame;
  WMButton *b;

  if(type == CMReply || type == CMReplyAll || type == CMForward) {
    if(!GetMailBoxSelectedMessage(Aileron.mbox)) {
      PostComposeError(type);
      return;
    }
  }
    
  if (!(c = (Composer *) wmalloc(sizeof(Composer))))
    return;

  c->window = WMCreateWindow(Aileron.screen, "Aileron: Composer");
  if(Aileron.ComposerImage)
    WMSetWindowMiniwindowPixmap(c->window, Aileron.ComposerImage);
  WMCreateEventHandler(WMWidgetView(c->window),
    KeyPressMask, handleKeyPresses, c);
  WMCreateEventHandler(WMWidgetView(c->window),
    KeyReleaseMask, handleKeyReleases, c);

  WMSetWindowMinSize(c->window, w, h-100);
  WMResizeWidget(c->window, w, h);
  WMRealizeWidget(c->window);
  WMSetViewNotifySizeChanges(WMWidgetView(c->window), True);
  

  {
    XClassHint *class = XAllocClassHint();
    class->res_name = "Composer";
    class->res_class = "Aileron";
    XSetClassHint(Aileron.display, WMWidgetXID(c->window), class);
    XFree(class);
  }


  NewLabel(c->window, "To:", 38, 10, 30, 20);
  c->tot = NewTextField(c->window, NULL, 62, 10, w-72, 20);
  WMCreateEventHandler(WMWidgetView(c->tot),
    KeyPressMask, handleWindowStatus, c);
  WMCreateEventHandler(WMWidgetView(c->tot),
    KeyPressMask, handleKeyPresses, c);

  NewLabel(c->window, "Subject:", 10, 35, 60, 20);
  c->subjectt = NewTextField(c->window, NULL, 62, 35, w-72, 20);
  WMCreateEventHandler(WMWidgetView(c->subjectt),
    KeyPressMask, setSubjectAsTitle, c);
  WMCreateEventHandler(WMWidgetView(c->subjectt),
    KeyPressMask, handleWindowStatus, c);
  WMCreateEventHandler(WMWidgetView(c->subjectt),
    KeyPressMask, handleKeyPresses, c);

  NewLabel(c->window, "CC:", 34, 60, 30, 20);
  c->cct = NewTextField(c->window, NULL, 62, 60, w-72, 20);
  WMCreateEventHandler(WMWidgetView(c->cct),
    KeyPressMask, handleWindowStatus, c);
  WMCreateEventHandler(WMWidgetView(c->cct),
    KeyPressMask, handleKeyPresses, c);

  /* Box for buttons */
  frame = WMCreateFrame(c->window);
  WMSetFrameRelief(frame, WRFlat);
  WMSetFrameTitlePosition(frame, WTPNoTitle);
  WMMoveWidget(frame, 8, 94);
  WMResizeWidget(frame, WMWidgetWidth(c->window) - 16, 82);

  b = CreateBlendedButton(frame, WBTMomentaryPush,
    "Send", "deliver.tif");
  WMMoveWidget(b, 21, 0);
  WMSetButtonAction(b, buttonDeliverMessageCB, c);
#if 0
  WMSetBalloonTextForView("Click to send the message",
    WMWidgetView(b));
#endif

  b = CreateBlendedButton(frame, WBTMomentaryPush,
    "Attach", "clip.tif");
  WMMoveWidget(b, 93, 0);
  WMSetButtonAction(b, buttonAttachCB, c);
#if 0
  WMSetBalloonTextForView("Click to attach a file to the message",
    WMWidgetView(b));
#endif
  
  b = CreateBlendedButton(frame, WBTMomentaryPush,
    "Addresses", "addresses.tif");
  WMSetButtonAction(b, addressesCB, c);
  WMMoveWidget(b, 165, 0);
#if 0
  WMSetBalloonTextForView("Click to open up the addresses panel",
    WMWidgetView(b));
#endif
  
  b = CreateBlendedButton(frame, WBTMomentaryPush,
    "Reply", "reply.tif");
  c->replyb = b;
  WMSetButtonAction(b, changeCType, c);
  WMMoveWidget(b, 237, 0);
#if 0
  WMSetBalloonTextForView("Click while holding down shift to include a copy"
    " of the message you're responding to.\n", WMWidgetView(b));
#endif

  b = CreateBlendedButton(frame, WBTMomentaryPush,
    "Forward", "forward.tif");
  WMSetButtonAction(b, changeForward, c);
  WMMoveWidget(b, 309, 0);
#if 0
  WMSetBalloonTextForView("Click to forward the message you're"
  " responding to.",
    WMWidgetView(b));
#endif
  

  c->messageView = WMCreateTextForDocumentType(c->window,
    HTMLParser, NULL);//HTMLWriter);
  WMSetTextHasRuler(c->messageView, True);
  WMSetTextDelegate(c->messageView, &textDelegate);

  WMSetTextHasVerticalScroller(c->messageView, True);
  WMSetTextRelief(c->messageView, WRSunken);
  WMSetViewNotifySizeChanges(WMWidgetView(c->messageView), True);
  WMMoveWidget(c->messageView, 8, 172);
  WMResizeWidget(c->messageView, w-16, h-182);
  WMCreateEventHandler(WMWidgetView(c->messageView),
    KeyPressMask, handleWindowStatus, c);
  WMCreateEventHandler(WMWidgetView(c->messageView),
    KeyPressMask, handleKeyPresses, c);

  c->options = createSendOptionsPanel(c); 

#if 0
WMAppendTextBlock(c->messageView, 
WMCreateTextBlockWithPixmap(c->messageView, Aileron.ComposerImage, 
"fname", WMDarkGrayColor(Aileron.screen), 0, 0));
#endif

  WMRealizeWidget(c->window);
  WMMapSubwidgets(c->window);
  /* it grabs focus... but if user releases, we remain in shift mode...
     this is a hack... */
  Aileron.shiftHeld = False;
  WMMapSubwidgets(frame);
  WMMapWidget(c->window);
  WMSetFocusToWidget(c->tot);
  WMAppAddWindow(Aileron.appContext, WMWidgetXID(c->window));

  WMAddNotificationObserver(sizeChanged, c,
    WMViewSizeDidChangeNotification, WMWidgetView(c->window));
  
  
  /* We will use ("AISnd " + mkstemp ) to uniquely identify 
  * each Composer window. This will come in handy for Find (etc)
  * to perform operations on the appropriate window */
  c->path = wstrdup("CMPSR.XXXXXX");
  mkstemp(c->path); 
  WMHashInsert(Aileron.windowTable, (char *) c->path, c);

  /* save the unique id... we'll retrive it as needed later */
  XSaveContext(Aileron.display, WMWidgetXID(c->window), 
    Aileron.xContext, (XPointer) c->path);

  WMSetWindowCloseAction(c->window, closeComposer,
    (void *) c);

  WMCreateEventHandler(WMWidgetView(c->cct), KeyPressMask,
    handleCCKeyPress, c);

  WMSetTextFieldNextTextField(c->tot, c->subjectt);
  WMSetTextFieldNextTextField(c->subjectt, c->cct);

  setStatus(c, True);
  c->stream = NULL;

  if(type == CMReply || type == CMReplyAll) {
    c->cType = type;
    changeCType(NULL, c);

  } else if(type == CMForward){
    c->cType = CMForward;
    changeForward(NULL, c);
  } else c->cType = type;

  if(WMGetUDBoolForKey(Aileron.defaultsDB, "UseSignature")) { 
    char *option = WMGetUDStringForKey(Aileron.defaultsDB, "Signature");
    if(option) { 
      FILE *file = fopen(option, "r");
      if(file) {
        char buf[1024];

        WMFreezeText(c->messageView);
        WMAppendTextStream(c->messageView, "\n\n--\n");
        while(fgets(buf, 1023, file))
          WMAppendTextStream(c->messageView, buf);

        WMThawText(c->messageView);
        fclose(file);
      }
    }
  }
  

  setTitle(c);

  WMPutInBag(Aileron.openComposers, (void *)c);
  Aileron.composer = c;
  return;

}
