/*
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
 *
 *     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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/* Close approximation of the mailx(1) builtin editor for sending mail. */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "mutt_curses.h"
#include "mutt_idna.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

/*
 * SLcurses_waddnstr() can't take a "const char *", so this is only
 * declared "static" (sigh)
 */
static char* EditorHelp1 = N_("\
~~		insert a line beginning with a single ~\n\
~b addresses	add addresses to the Bcc: field\n\
~c addresses	add addresses to the Cc: field\n\
~f messages	include messages\n\
~F messages	same as ~f, except also include headers\n\
~h		edit the message header\n\
~m messages	include and quote messages\n\
~M messages	same as ~m, except include headers\n\
~p		print the message\n");

static char* EditorHelp2 = N_("\
~q		write file and quit editor\n\
~r file		read a file into the editor\n\
~t users	add users to the To: field\n\
~u		recall the previous line\n\
~v		edit message with the $visual editor\n\
~w file		write message to file\n\
~x		abort changes and quit editor\n\
~?		this message\n\
.		on a line by itself ends input\n");

static char **
be_snarf_data (FILE *f, char **buf, int *bufmax, int *buflen, LOFF_T offset,
	       LOFF_T bytes, int prefix)
{
  char tmp[HUGE_STRING];
  char *p = tmp;
  size_t tmplen = sizeof (tmp);

  tmp[sizeof (tmp) - 1] = 0;
  if (prefix)
  {
    strfcpy (tmp, NONULL(Prefix), sizeof (tmp));
    tmplen = mutt_strlen (tmp);
    p = tmp + tmplen;
    tmplen = sizeof (tmp) - tmplen;
  }

  fseeko (f, offset, SEEK_SET);
  while (bytes > 0)
  {
    if (fgets (p, tmplen - 1, f) == NULL) break;
    bytes -= mutt_strlen (p);
    if (*bufmax == *buflen)
      safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
    buf[(*buflen)++] = safe_strdup (tmp);
  }
  if (buf && *bufmax == *buflen)    /* Do not smash memory past buf */
  {
    safe_realloc (&buf, sizeof (char *) * (++*bufmax));
  }
  if (buf) buf[*buflen] = NULL;
  return (buf);
}

static char **
be_snarf_file (const char *path, char **buf, int *max, int *len, int verbose)
{
  FILE *f;
  char tmp[LONG_STRING];
  struct stat sb;

  if ((f = fopen (path, "r")))
  {
    fstat (fileno (f), &sb);
    buf = be_snarf_data (f, buf, max, len, 0, sb.st_size, 0);
    if (verbose)
    {
      snprintf(tmp, sizeof(tmp), "\"%s\" %lu bytes\n", path, (unsigned long) sb.st_size);
      addstr(tmp);
    }
    safe_fclose (&f);
  }
  else
  {
    snprintf(tmp, sizeof(tmp), "%s: %s\n", path, strerror(errno));
    addstr(tmp);
  }
  return (buf);
}

static int be_barf_file (const char *path, char **buf, int buflen)
{
  FILE *f;
  int i;

  if ((f = fopen (path, "w")) == NULL)		/* __FOPEN_CHECKED__ */
  {
    addstr (strerror (errno));
    addch ('\n');
    return (-1);
  }
  for (i = 0; i < buflen; i++) fputs (buf[i], f);
  if (fclose (f) == 0) return 0;
  printw ("fclose: %s\n", strerror (errno));
  return (-1);
}

static void be_free_memory (char **buf, int buflen)
{
  while (buflen-- > 0)
    FREE (&buf[buflen]);
  if (buf)
    FREE (&buf);
}

static char **
be_include_messages (char *msg, char **buf, int *bufmax, int *buflen,
		     int pfx, int inc_hdrs)
{
  LOFF_T offset, bytes;
  int n;
  char tmp[LONG_STRING];

  while ((msg = strtok (msg, " ,")) != NULL)
  {
    if (mutt_atoi (msg, &n, 0) == 0 && n > 0 && n <= Context->msgcount)
    {
      n--;

      /* add the attribution */
      if (Attribution)
      {
        setlocale (LC_TIME, NONULL (AttributionLocale));
	mutt_make_string (tmp, sizeof (tmp) - 1, Attribution, Context, Context->hdrs[n]);
        setlocale (LC_TIME, "");
	strcat (tmp, "\n");	/* __STRCAT_CHECKED__ */
      }

      if (*bufmax == *buflen)
	safe_realloc ( &buf, sizeof (char *) * (*bufmax += 25));
      buf[(*buflen)++] = safe_strdup (tmp);

      bytes = Context->hdrs[n]->content->length;
      if (inc_hdrs)
      {
	offset = Context->hdrs[n]->offset;
	bytes += Context->hdrs[n]->content->offset - offset;
      }
      else
	offset = Context->hdrs[n]->content->offset;
      buf = be_snarf_data (Context->fp, buf, bufmax, buflen, offset, bytes,
			   pfx);

      if (*bufmax == *buflen)
	safe_realloc (&buf, sizeof (char *) * (*bufmax += 25));
      buf[(*buflen)++] = safe_strdup ("\n");
    }
    else
      printw (_("%d: invalid message number.\n"), n);
    msg = NULL;
  }
  return (buf);
}

static void be_print_header (ENVELOPE *env)
{
  char tmp[HUGE_STRING];

  if (env->to)
  {
    addstr ("To: ");
    tmp[0] = 0;
    rfc822_write_address (tmp, sizeof (tmp), env->to, 1);
    addstr (tmp);
    addch ('\n');
  }
  if (env->cc)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
    rfc822_write_address (tmp, sizeof (tmp), env->cc, 1);
    addstr (tmp);
    addch ('\n');
  }
  if (env->bcc)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
    rfc822_write_address (tmp, sizeof (tmp), env->bcc, 1);
    addstr (tmp);
    addch ('\n');
  }
  if (env->subject)
  {
    addstr ("Subject: ");
    addstr (env->subject);
    addch ('\n');
  }
  addch ('\n');
}

/* args:
 *	force	override the $ask* vars (used for the ~h command)
 */
static void be_edit_header (ENVELOPE *e, int force)
{
  char tmp[HUGE_STRING];

  mutt_window_move (MuttMessageWindow, 0, 0);

  addstr ("To: ");
  tmp[0] = 0;
  mutt_addrlist_to_local (e->to);
  rfc822_write_address (tmp, sizeof (tmp), e->to, 0);
  if (!e->to || force)
  {
    if (mutt_enter_string (tmp, sizeof (tmp), 4, 0) == 0)
    {
      rfc822_free_address (&e->to);
      e->to = mutt_parse_adrlist (e->to, tmp);
      e->to = mutt_expand_aliases (e->to);
      mutt_addrlist_to_intl (e->to, NULL);	/* XXX - IDNA error reporting? */
      tmp[0] = 0;
      rfc822_write_address (tmp, sizeof (tmp), e->to, 1);
      mutt_window_mvaddstr (MuttMessageWindow, 0, 4, tmp);
    }
  }
  else
  {
    mutt_addrlist_to_intl (e->to, NULL);	/* XXX - IDNA error reporting? */
    addstr (tmp);
  }
  addch ('\n');

  if (!e->subject || force)
  {
    addstr ("Subject: ");
    strfcpy (tmp, e->subject ? e->subject: "", sizeof (tmp));
    if (mutt_enter_string (tmp, sizeof (tmp), 9, 0) == 0)
      mutt_str_replace (&e->subject, tmp);
    addch ('\n');
  }

  if ((!e->cc && option (OPTASKCC)) || force)
  {
    addstr ("Cc: ");
    tmp[0] = 0;
    mutt_addrlist_to_local (e->cc);
    rfc822_write_address (tmp, sizeof (tmp), e->cc, 0);
    if (mutt_enter_string (tmp, sizeof (tmp), 4, 0) == 0)
    {
      rfc822_free_address (&e->cc);
      e->cc = mutt_parse_adrlist (e->cc, tmp);
      e->cc = mutt_expand_aliases (e->cc);
      tmp[0] = 0;
      mutt_addrlist_to_intl (e->cc, NULL);
      rfc822_write_address (tmp, sizeof (tmp), e->cc, 1);
      mutt_window_mvaddstr (MuttMessageWindow, 0, 4, tmp);
    }
    else
      mutt_addrlist_to_intl (e->cc, NULL);
    addch ('\n');
  }

  if (option (OPTASKBCC) || force)
  {
    addstr ("Bcc: ");
    tmp[0] = 0;
    mutt_addrlist_to_local (e->bcc);
    rfc822_write_address (tmp, sizeof (tmp), e->bcc, 0);
    if (mutt_enter_string (tmp, sizeof (tmp), 5, 0) == 0)
    {
      rfc822_free_address (&e->bcc);
      e->bcc = mutt_parse_adrlist (e->bcc, tmp);
      e->bcc = mutt_expand_aliases (e->bcc);
      mutt_addrlist_to_intl (e->bcc, NULL);
      tmp[0] = 0;
      rfc822_write_address (tmp, sizeof (tmp), e->bcc, 1);
      mutt_window_mvaddstr (MuttMessageWindow, 0, 5, tmp);
    }
    else
      mutt_addrlist_to_intl (e->bcc, NULL);
    addch ('\n');
  }
}

int mutt_builtin_editor (SEND_CONTEXT *sctx)
{
  HEADER *msg, *cur;
  const char *path;
  char **buf = NULL;
  int bufmax = 0, buflen = 0;
  char tmp[LONG_STRING];
  int abort = 0;
  int done = 0;
  int i;
  char *p;

  msg = sctx->msg;
  /* note: the built-in editor is not backgroundable.
   * it's not likely the user backgrounds, then switches to the builtin
   * editor and wants to include cur, so leaving the null-cur check
   * logic rather than trying to port over to using message-id.
   */
  cur = sctx->cur;
  path = sctx->msg->content->filename;

  scrollok (stdscr, TRUE);

  be_edit_header (msg->env, 0);

  addstr (_("(End message with a . on a line by itself)\n"));

  buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);

  tmp[0] = 0;
  while (!done)
  {
    if (mutt_enter_string (tmp, sizeof (tmp), 0, 0) == -1)
    {
      tmp[0] = 0;
      continue;
    }
    addch ('\n');

    if (EscChar && tmp[0] == EscChar[0] && tmp[1] != EscChar[0])
    {
      /* remove trailing whitespace from the line */
      p = tmp + mutt_strlen (tmp) - 1;
      while (p >= tmp && ISSPACE (*p))
	*p-- = 0;

      p = tmp + 2;
      SKIPWS (p);

      switch (tmp[1])
      {
	case '?':
	  addstr (_(EditorHelp1));
          addstr (_(EditorHelp2));
	  break;
	case 'b':
	  msg->env->bcc = mutt_parse_adrlist (msg->env->bcc, p);
	  msg->env->bcc = mutt_expand_aliases (msg->env->bcc);
	  break;
	case 'c':
	  msg->env->cc = mutt_parse_adrlist (msg->env->cc, p);
	  msg->env->cc = mutt_expand_aliases (msg->env->cc);
	  break;
	case 'h':
	  be_edit_header (msg->env, 1);
	  break;
	case 'F':
	case 'f':
	case 'm':
	case 'M':
	  if (Context)
	  {
	    if (!*p && cur)
 	    {
	      /* include the current message */
	      p = tmp + mutt_strlen (tmp) + 1;
	      snprintf (tmp + mutt_strlen (tmp), sizeof (tmp) - mutt_strlen (tmp), " %d",
                        cur->msgno + 1);
	    }
	    buf = be_include_messages (p, buf, &bufmax, &buflen,
				       (ascii_tolower (tmp[1]) == 'm'),
				       (ascii_isupper ((unsigned char) tmp[1])));
	  }
	  else
	    addstr (_("No mailbox.\n"));
	  break;
	case 'p':
	  addstr ("-----\n");
	  addstr (_("Message contains:\n"));
	  be_print_header (msg->env);
	  for (i = 0; i < buflen; i++)
	    addstr (buf[i]);
          /* L10N:
             This entry is shown AFTER the message content,
             not IN the middle of the content.
             So it doesn't mean "(message will continue)"
             but means "(press any key to continue using mutt)". */
	  addstr (_("(continue)\n"));
	  break;
	case 'q':
	  done = 1;
	  break;
	case 'r':
	  if (*p)
          {
            BUFFER *filename = mutt_buffer_pool_get ();
	    mutt_buffer_strcpy (filename, p);
	    mutt_buffer_expand_path (filename);
	    buf = be_snarf_file (mutt_b2s (filename), buf, &bufmax, &buflen, 1);
            mutt_buffer_pool_release (&filename);
          }
	  else
	    addstr (_("missing filename.\n"));
	  break;
	case 's':
	  mutt_str_replace (&msg->env->subject, p);
	  break;
	case 't':
	  msg->env->to = rfc822_parse_adrlist (msg->env->to, p);
	  msg->env->to = mutt_expand_aliases (msg->env->to);
	  break;
	case 'u':
	  if (buflen)
	  {
	    buflen--;
	    strfcpy (tmp, buf[buflen], sizeof (tmp));
	    tmp[mutt_strlen (tmp)-1] = 0;
	    FREE (&buf[buflen]);
	    buf[buflen] = NULL;
	    continue;
	  }
	  else
	    addstr (_("No lines in message.\n"));
	  break;

	case 'e':
	case 'v':
	  if (be_barf_file (path, buf, buflen) == 0)
	  {
	    char *tag, *err;
	    be_free_memory (buf, buflen);
	    buf = NULL;
	    bufmax = buflen = 0;

	    if (option (OPTEDITHDRS))
	    {
	      mutt_env_to_local (msg->env);
	      mutt_edit_headers (NONULL(Visual), sctx, 0);
	      if (mutt_env_to_intl (msg->env, &tag, &err))
		printw (_("Bad IDN in %s: '%s'\n"), tag, err);
	    }
	    else
	      mutt_edit_file (NONULL(Visual), path);

	    buf = be_snarf_file (path, buf, &bufmax, &buflen, 0);

	    addstr (_("(continue)\n"));
	  }
	  break;
	case 'w':
	  be_barf_file (*p ? p : path, buf, buflen);
	  break;
	case 'x':
	  abort = 1;
	  done = 1;
	  break;
	default:
	  printw (_("%s: unknown editor command (~? for help)\n"), tmp);
	  break;
      }
    }
    else if (mutt_strcmp (".", tmp) == 0)
      done = 1;
    else
    {
      safe_strcat (tmp, sizeof (tmp), "\n");
      if (buflen == bufmax)
	safe_realloc (&buf, sizeof (char *) * (bufmax += 25));
      buf[buflen++] = safe_strdup (tmp[1] == '~' ? tmp + 1 : tmp);
    }

    tmp[0] = 0;
  }

  if (!abort) be_barf_file (path, buf, buflen);
  be_free_memory (buf, buflen);

  return (abort ? -1 : 0);
}
