/*
 * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu>
 * 
 *     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 "mutt.h"
#include "mutt_curses.h"
#include "mutt_menu.h"
#include "rfc1524.h"
#include "mime.h"
#include "mailbox.h"
#include "state.h"
#include "attach.h"

#ifdef _PGPPATH
#include "pgp.h"
#endif

#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

static ATTACHPTR *gen_attach_list (BODY *m, int level, int compose)
{
  ATTACHPTR *top = NULL, *end = NULL, *new;

  while (m)
  {
    if (m->type == TYPEMULTIPART && m->parts)
    {
      if (top)
	end->next = gen_attach_list (m->parts, level, compose);
      else
	end = top = gen_attach_list (m->parts, level, compose);
      while (end->next)
	end = end->next;
    }
    else
    {
      new = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR));
      new->content = m;
      new->level = level;

      if (top)
      {
        end->next = new;
        end = new;
      }
      else
        top = end = new;
        
      /* We don't support multipart messages in the compose menu yet */
      if (!compose && m->type == TYPEMESSAGE &&
	    (!strcasecmp (m->subtype, "rfc822") ||
	    !strcasecmp (m->subtype, "news")) &&
	    is_multipart (m->parts))
      {
	end->next = gen_attach_list (m->parts, level + 1, compose);
	while (end->next)
	  end = end->next;
      }
    }

    m = m->next;
  }

  return (top);
}

void mutt_update_tree (ATTACHPTR **idx, short idxlen)
{
  char buf[STRING];
  char *s;
  int x;

  for (x = 0; x < idxlen; x++)
  {
    if (2 * (idx[x]->level + 2) < sizeof (buf))
    {
      if (idx[x]->level)
      {
	s = buf + 2 * (idx[x]->level - 1);
	*s++ = (idx[x]->content->next) ? '\003' : '\001';
	*s++ = '\004';
	*s++ = '\007';
      }
      else
	s = buf;
      *s = 0;
    }

    if (idx[x]->tree)
    {
      if (strcmp (idx[x]->tree, buf) != 0)
      {
	safe_free ((void **) &idx[x]->tree);
	idx[x]->tree = safe_strdup (buf);
      }
    }
    else
      idx[x]->tree = safe_strdup (buf);

    if (2 * (idx[x]->level + 2) < sizeof (buf) && idx[x]->level)
    {
      s = buf + 2 * (idx[x]->level - 1);
      *s++ = (idx[x]->content->next) ? '\005' : '\006';
      *s++ = '\006';
    }
  }
}

ATTACHPTR **mutt_gen_attach_list (BODY *cur, short *idxlen,
					      short *idxmax, int compose)
{
  ATTACHPTR *list, *plist;
  ATTACHPTR **idx;

  plist = list = gen_attach_list (cur, 0, compose);
  idx = (ATTACHPTR **) safe_malloc (sizeof (ATTACHPTR *) * (*idxmax = 5));

  for (*idxlen = 0; plist; (*idxlen)++, plist = plist->next)
  {
    if (*idxlen == *idxmax)
      safe_realloc ((void **) &idx, sizeof (ATTACHPTR *) * (*idxmax += 5));
    idx[*idxlen] = plist;
  }

  mutt_update_tree (idx, *idxlen);
  return (idx);
}

void attach_entry (char *b, size_t blen, MUTTMENU *menu, int num)
{
  char t[SHORT_STRING];
  char s[SHORT_STRING];
  char size[SHORT_STRING];
  ATTACHPTR **idx = (ATTACHPTR **) menu->data;
  BODY *m;

  m = idx[num]->content;
  s[0] = 0;
  if (m->type == TYPEMESSAGE && (!strcasecmp ("rfc822", m->subtype) ||
      !strcasecmp ("news", m->subtype)) && MsgFmt[0])
    _mutt_make_string (s, sizeof (s), MsgFmt, m->hdr, M_FORCESUBJ);

  mutt_pretty_size (size, sizeof (size), m->length);
  snprintf (t, sizeof (t), "[%.7s/%.10s, %.6s, %s]",
	    TYPE (m->type), m->subtype, ENCODING (m->encoding), size);
  snprintf (b, blen, " %c %2d %-34.34s %s%s",
	    m->tagged ? '*' : ' ',
	    num + 1,
	    t,
	    idx[num]->tree ? idx[num]->tree : "",
	    s[0] ? s : (m->description ? m->description :
			(m->filename ? m->filename : "<no description>")));
}

int mutt_tag_attach (MUTTMENU *menu, int n)
{
  return (((ATTACHPTR **) menu->data)[n]->content->tagged = !((ATTACHPTR **) menu->data)[n]->content->tagged);
}

static void mutt_query_save_attachment (FILE *fp, BODY *body)
{
  char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];

  if (fp && body->filename)
    strfcpy (buf, body->filename, sizeof (buf));
  else
    buf[0] = 0;
  if (mutt_get_field ("Save to file: ", buf, sizeof (buf), M_FILE | M_CLEAR) != 0 || !buf[0])
    return;
  mutt_expand_path (buf, sizeof (buf));
  if (mutt_check_overwrite (body->filename, buf, tfile, sizeof (tfile), 0))
    return;
  mutt_message ("Saving...");
  if (mutt_save_attachment (fp, body, tfile, 0) == 0)
    mutt_message ("Attachment saved.");
}

void mutt_save_attachment_list (FILE *fp, int tag, BODY *top)
{
  for (; top; top = top->next)
  {
    if (!tag || top->tagged)
      mutt_query_save_attachment (fp, top);
    else if (top->parts)
      mutt_save_attachment_list (fp, 1, top->parts);
    if (!tag)
      return;
  }
}

static void
mutt_query_pipe_attachment (char *command, FILE *fp, BODY *body, int filter)
{
  char tfile[_POSIX_PATH_MAX];
  char warning[STRING+_POSIX_PATH_MAX];

  if (filter)
  {
    snprintf (warning, sizeof (warning),
	      "WARNING!  You are about to overwrite %s, continue?",
	      body->filename);
    if (mutt_yesorno (warning, M_NO) != M_YES)
      return;
    mutt_mktemp (tfile);
  }
  else
    tfile[0] = 0;

  if (mutt_pipe_attachment (fp, body, command, tfile))
  {
    if (filter)
    {
      mutt_unlink (body->filename);
      mutt_rename_file (tfile, body->filename);
      mutt_update_encoding (body);
      mutt_message ("Attachment filtered.");
    }
  }
  else
  {
    if (filter && tfile[0])
      mutt_unlink (tfile);
  }
}

static void
pipe_attachment_list (char *command, FILE *fp, int tag, BODY *top, int filter)
{
  for (; top; top = top->next)
  {
    if (!tag || top->tagged)
      mutt_query_pipe_attachment (command, fp, top, filter);
    else if (top->parts)
      pipe_attachment_list (command, fp, tag, top->parts, filter);
    if (!tag)
      break;
  }
}

void mutt_pipe_attachment_list (FILE *fp, int tag, BODY *top, int filter)
{
  char buf[SHORT_STRING];

  if (fp)
    filter = 0; /* sanity check: we can't filter in the recv case yet */

  buf[0] = 0;
  if (mutt_get_field ((filter ? "Filter through: " : "Pipe to: "),
				  buf, sizeof (buf), 0) != 0 || !buf[0])
    return;
  mutt_expand_path (buf, sizeof (buf));
  pipe_attachment_list (buf, fp, tag, top, filter);
}

static void print_attachment_list (FILE *fp, int tag, BODY *top)
{
  for (; top; top = top->next)
  {
    if (!tag || top->tagged)
      mutt_print_attachment (fp, top);
    else if (top->parts)
      mutt_print_attachment_list (fp, tag, top->parts);
    if (!tag)
      return;
  }
}

void mutt_print_attachment_list (FILE *fp, int tag, BODY *top)
{
  if (query_quadoption (OPT_PRINT, tag ? "Print tagged attachment(s)?" : "Print attachment?") != M_YES)
    return;
  print_attachment_list (fp, tag, top);
}

int mutt_is_message_type (int type, char *subtype)
{
  if (type != TYPEMESSAGE)
    return 0;
  if (strcasecmp (subtype, "rfc822") == 0 || strcasecmp (subtype, "news") == 0)
    return 1;
  return 0;
}

static void
bounce_attachment_list (ADDRESS *adr, int tag, BODY *body, HEADER *hdr)
{
  for (; body; body = body->next)
  {
    if (!tag || body->tagged)
    {
      if (!mutt_is_message_type (body->type, body->subtype))
      {
	mutt_error ("You may only bounce message/rfc822 parts.");
	continue;
      }
      body->hdr->msgno = hdr->msgno;
      mutt_bounce_message (body->hdr, adr);
    }
    else if (body->parts)
      bounce_attachment_list (adr, tag, body->parts, hdr);
    if (!tag)
      break;
  }
}

static void query_bounce_attachment (int tag, BODY *top, HEADER *hdr)
{
  char prompt[SHORT_STRING];
  char buf[HUGE_STRING];
  ADDRESS *adr = NULL;
  int rc;

  buf[0] = 0;
  snprintf (prompt, sizeof (prompt), "Bounce %smessage%s to: ",
	    tag ? "tagged " : "", tag ? "s" : "");
  rc = mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS);

  if (rc || !buf[0])
    return;

  rfc822_parse_adrlist (&adr, buf, "@");
  adr = mutt_expand_aliases (adr);
  buf[0] = 0;
  rfc822_write_address (buf, sizeof (buf), adr);
  snprintf (prompt, sizeof (prompt), "Bounce message%s to %s...?", (tag ? "s" : ""), buf);
  if (mutt_yesorno (prompt, 1) != 1)
  {
    mutt_free_address (&adr);
    CLEARLINE (LINES-1);
    return;
  }
  bounce_attachment_list (adr, tag, top, hdr);
  mutt_free_address (&adr);
}

/* make a list of all the tagged attachments.  not that these are not real
 * copies, so mutt_free_body() should NOT be called on the members in the list
 */
static BODY *build_reply_attachment_list (int tag, BODY *body)
{
  BODY *top = NULL;
  BODY *last = NULL;
  BODY *m = NULL;

  for (; body; body = body->next)
  {
    if (!tag || body->tagged)
    {
      m = mutt_dup_body (body);
      /* make sure not to include any of the sibling attachments in the
	 list.  note that this only changes the pointer in the copy of the
	 list we are building, and not the orignal.  if this attachment is the
	 last in the list, we could accidentally include all the rest of
	 the attachemnts if we don't clear this pointer. */
      m->next = NULL;
    }
    else if (body->parts)
      m = build_reply_attachment_list (tag, body->parts);
    if (m)
    {
      if (last)
	last->next = m;
      else
	top = m;
      last = m;
    }
    if (!tag)
      break;
  }
  return top;
}

static void reply_attachment_list (int op, int tag, BODY *body, HEADER *hdr)
{
  BODY *m;
  HEADER *hn;

  if (!tag && body->hdr)
  {
    hn = body->hdr;
    hn->msgno = hdr->msgno;
  }
  else
  {
    /* build a fake message which consists of only the tagged attachments */
    m = build_reply_attachment_list (tag, body);
    hn = mutt_dup_header (hdr);
    hn->content = mutt_new_body ();
    hn->content->type = TYPEMULTIPART;
    hn->content->subtype = safe_strdup ("mixed");
    hn->content->parts = m;
  }

  ci_send_message (op, NULL, NULL, hn);

  if (hn->replied && !hdr->replied)
    mutt_set_flag (Context, hdr, M_REPLIED, 1);

  if (tag || !body->hdr)
  {
    /* free the duplicated attachments */
    while (hn->content->parts)
    {
      m = hn->content->parts;
      hn->content->parts = hn->content->parts->next;
      safe_free ((void **) &m);
    }
    mutt_free_body (&hn->content);
    safe_free ((void **) &hn);
  }

  return;
}

void mutt_view_attachments (HEADER *hdr)
{
#ifdef _PGPPATH
  char tempfile[_POSIX_PATH_MAX];
  int pgp = 0;
#endif
  char buf[STRING];
  char helpstr[SHORT_STRING];
  MUTTMENU *menu;
  BODY *cur;
  MESSAGE *msg;
  FILE *fp;
  ATTACHPTR **idx = NULL;
  short idxlen = 0;
  short idxmax = 0;
  int flags = 0;
  int op;
  
  /* make sure we have parsed this message */
  mutt_parse_mime_message (hdr);

  if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
    return;

#ifdef _PGPPATH
  if (hdr->pgp == PGPENCRYPT && hdr->content->type == TYPEMULTIPART)
  {
    STATE s;

    memset (&s, 0, sizeof (s));
    s.fpin = msg->fp;
    mutt_mktemp (tempfile);
    if ((fp = safe_fopen (tempfile, "w+")) == NULL)
    {
      mutt_perror (tempfile);
      mx_close_message (&msg);
      return;
    }
    cur = pgp_decrypt_part (hdr->content->parts->next, &s, fp);
    rewind (fp);

    pgp = 1;
  }
  else
#endif /* _PGPPATH */
  {
    fp = msg->fp;
    cur = hdr->content;
  }

  idx = mutt_gen_attach_list (cur, &idxlen, &idxmax, 0);

  menu = mutt_new_menu ();
  menu->max = idxlen;
  menu->make_entry = attach_entry;
  menu->tag = mutt_tag_attach;
  menu->menu = MENU_ATTACH;
  menu->title = "Attachments";
  menu->data = idx;

  helpstr[0] = 0;
  mutt_make_help (buf, sizeof (buf), "Exit  ", MENU_ATTACH, OP_EXIT);
  strcat (helpstr, buf);
  mutt_make_help (buf, sizeof (buf), "Save  ", MENU_ATTACH, OP_SAVE);
  strcat (helpstr, buf);
  mutt_make_help (buf, sizeof (buf), "Pipe  ", MENU_ATTACH, OP_PIPE);
  strcat (helpstr, buf);
  mutt_make_help (buf, sizeof (buf), "Print  ", MENU_ATTACH, OP_PRINT);
  strcat (helpstr, buf);
  mutt_make_help (buf, sizeof (buf), "Help", MENU_ATTACH, OP_HELP);
  strcat (helpstr, buf);
  menu->help = helpstr;

  FOREVER
  {
    switch (op = mutt_menuLoop (menu))
    {
      case OP_GENERIC_SELECT_ENTRY:
      case OP_DISPLAY_HEADERS:
	if (op == OP_DISPLAY_HEADERS)
	  unset_option (OPTWEED);
	while (mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR) == 2)
	{
	  if (option (OPTWEED))
	    unset_option (OPTWEED);
	  else
	    set_option (OPTWEED);
	}
	set_option (OPTWEED);		/* turn header weeding back on. */
	menu->redraw = REDRAW_FULL;
	break;

      case OP_ATTACH_VIEW_MAILCAP:
	mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP);
	menu->redraw = REDRAW_FULL;
	break;

      case OP_ATTACH_VIEW_TEXT:
	mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT);
	menu->redraw = REDRAW_FULL;
	break;

#ifdef _PGPPATH
      case OP_EXTRACT_KEYS:
        pgp_extract_keys_from_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content);
        menu->redraw = REDRAW_FULL;
        break;
#endif
      
      case OP_PRINT:
	mutt_print_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content);
	break;

      case OP_PIPE:
	mutt_pipe_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content, 0);
	break;

      case OP_SAVE:
	mutt_save_attachment_list (fp, menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content);
	break;

      case OP_BOUNCE_MESSAGE:
	query_bounce_attachment (menu->tagprefix, menu->tagprefix ? cur : idx[menu->current]->content, hdr);
	break;

      case OP_REPLY:
      case OP_GROUP_REPLY:
      case OP_LIST_REPLY:
      case OP_FORWARD_MESSAGE:
#ifdef _PGPPATH
	if (hdr->pgp == PGPENCRYPT && hdr->content->type == TYPEMULTIPART)
	{
	  mutt_error (
	    "This operation is not currently supported for PGP messages.");
	  break;
	}
#endif
	if (op == OP_FORWARD_MESSAGE)
	  flags = SENDFORWARD;
	else
	  flags = SENDREPLY | 
		  (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
		  (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
	reply_attachment_list (flags, menu->tagprefix,
			       menu->tagprefix ? cur : idx[menu->current]->content,
			       hdr);
	menu->redraw = REDRAW_FULL;
	break;

      case OP_EXIT:
	mx_close_message (&msg);
	while (idxlen-- > 0)
	{
	  safe_free ((void **) &idx[idxlen]->tree);
	  safe_free ((void **) &idx[idxlen]);
	}
	safe_free ((void **) &idx);
	idxmax = 0;

#ifdef _PGPPATH
	if (pgp)
	{
	  fclose (fp);
	  mutt_free_body (&cur);
	  unlink (tempfile);
	}
#endif /* _PGPPATH */

	mutt_menuDestroy  (&menu);
	return;
    }
  }

  /* not reached */
}
