/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999-2020 Free Software Foundation, Inc.

   GNU Mailutils 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 3, or (at your option)
   any later version.

   GNU Mailutils 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */

/* Functions for handling escape variables */

#include "mail.h"
#include <sys/stat.h>

static void
dump_headers (mu_stream_t out, compose_env_t *env)
{
  mu_stream_t stream = NULL;
  int rc;
  
  rc = mu_header_get_streamref (env->header, &stream);
  if (rc)
    {
      mu_error ("mu_header_get_streamref: %s",
		  mu_stream_strerror (stream, rc));
      return;
    }
  mu_stream_copy (out, stream, 0, NULL);
  mu_stream_destroy (&stream);
}

static int
check_headers (mu_stream_t input, compose_env_t *env)
{
  char *p;
  
  mu_stream_seek (input, 0, MU_SEEK_SET, NULL);
  switch (parse_headers (input, env))
    {
    case parse_headers_ok:
      return 0;
    case parse_headers_fatal:
      return -1;
    case parse_headers_error:
      break;
    }
  
  p = ml_readline (_("Edit again?"));
  return mu_true_answer_p (p);
}  

static void
escape_continue (void)
{
  mu_printf (_("(continue)\n"));
}

int 
escape_check_args (int argc, char **argv, int minargs, int maxargs)
{
  char *escape = "~";
  if (argc < minargs)
    {
      minargs--;
      mailvar_get (&escape, mailvar_name_escape, mailvar_type_string, 0);
      mu_error (ngettext ("%c%s requires at least %d argument",
			  "%c%s requires at least %d arguments",
			  minargs), escape[0], argv[0], minargs);
      return 1;
    }
  if (maxargs > 1 && argc > maxargs)
    {
      maxargs--;
      mailvar_get (&escape, mailvar_name_escape, mailvar_type_string, 0);
      mu_error (ngettext ("%c%s accepts at most %d argument",
			  "%c%s accepts at most %d arguments",
			  maxargs), escape[0], argv[0], maxargs);
      return 1;
    }
    
  return 0;
}

/* ~![shell-command] */
int
escape_shell (int argc, char **argv, compose_env_t *env)
{
  return mail_execute (1, argv[1], argc - 1, argv + 1);
}

/* ~:[mail-command] */
/* ~-[mail-command] */
int
escape_command (int argc, char **argv, compose_env_t *env)
{
  const struct mail_command_entry *entry;
  int status;
  
  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  if (argv[1][0] == '#')
    return 0;
  entry = mail_find_command (argv[1]);
  if (!entry)
    {
      mu_error (_("Unknown command: %s"), argv[1]);
      return 1;
    }
  if (entry->flags & (EF_FLOW | EF_SEND))
    {
      mu_error (_("Command not allowed in an escape sequence\n"));
      return 1;
    }

  status = (*entry->func) (argc - 1, argv + 1);
  return status;
}

/* ~? */
int
escape_help (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
{
  int status;
  if (argc < 2)
    status = mail_escape_help (NULL);
  else
    while (--argc)
      status |= mail_escape_help (*++argv);
  escape_continue ();
  return status;
}

/* ~A */
/* ~a */
int
escape_sign (int argc MU_ARG_UNUSED, char **argv, compose_env_t *env)
{
  char *p;

  if (mailvar_get (&p,
		   mu_isupper (argv[0][0])
		      ? mailvar_name_Sign : mailvar_name_sign,
		   mailvar_type_string, 1) == 0)
    {
      mu_stream_printf (env->compstr, "-- \n");
      if (mu_isupper (argv[0][0]))
	{
	  char *name = util_fullpath (p);
	  int rc;
	  mu_stream_t signstr;

	  rc = mu_file_stream_create (&signstr, name, MU_STREAM_READ);
	  if (rc)
	    mu_error (_("Cannot open %s: %s"), name, mu_strerror (rc));
	  else
	    {
	      mu_printf (_("Reading %s\n"), name);
	      mu_stream_copy (env->compstr, signstr, 0, NULL);
	      mu_stream_destroy (&signstr);
	    }
	}
      else
	mu_stream_printf (env->compstr, "%s\n", p);
      escape_continue ();
    }
  return 0;
}

/* ~b[bcc-list] */
int
escape_bcc (int argc, char **argv, compose_env_t *env)
{
  while (--argc)
    compose_header_set (env, MU_HEADER_BCC, *++argv, COMPOSE_SINGLE_LINE);
  return 0;
}

/* ~c[cc-list] */
int
escape_cc (int argc, char **argv, compose_env_t *env)
{
  while (--argc)
    compose_header_set (env, MU_HEADER_CC, *++argv, COMPOSE_SINGLE_LINE);
  return 0;
}

static int
verbose_copy (mu_stream_t dest, mu_stream_t src, const char *filename,
	      mu_off_t *psize)
{
  int rc;
  char *buf = NULL;
  size_t size = 0, n;
  size_t lines;
  mu_off_t total;
  
  total = lines = 0;

  while ((rc = mu_stream_getline (src, &buf, &size, &n)) == 0 && n > 0)
    {
      lines++;
      rc = mu_stream_write (dest, buf, n, NULL);
      if (rc)
	break;
      total += n;
    }
  if (rc)
    mu_error (_("error copying data: %s"), mu_strerror (rc));
  mu_printf ("\"%s\" %lu/%lu\n", filename,
		    (unsigned long) lines, (unsigned long) total);
  if (psize)
    *psize = total;
  return rc;
}

/* ~d */
int
escape_deadletter (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED,
		   compose_env_t *env)
{
  const char *name = getenv ("DEAD");
  mu_stream_t str;
  int rc = mu_file_stream_create (&str, name, MU_STREAM_READ);
  if (rc)
    {
      mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
      return 1;
    }
  verbose_copy (env->compstr, str, name, NULL);
  mu_stream_destroy (&str);
  return 0;
}

static int
run_editor (char *ed, char *arg)
{
  char *argv[3];

  argv[0] = ed;
  argv[1] = arg;
  argv[2] = NULL;
  return mail_execute (1, ed, 2, argv);
}

static int
escape_run_editor (char *ed, int argc, char **argv, compose_env_t *env)
{
  char *filename;
  int fd;
  mu_stream_t tempstream;
  int rc;
  
  rc = mu_tempfile (NULL, 0, &fd, &filename);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_tempfile", NULL, rc);
      return rc;
    }
  rc = mu_fd_stream_create (&tempstream, filename, fd, MU_STREAM_RDWR);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_fd_stream_create", filename, rc);
      unlink (filename);
      free (filename);
      close (fd);
      return rc;
    }

  mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
  if (mailvar_is_true (mailvar_name_editheaders))
    {
      dump_headers (tempstream, env);

      mu_stream_copy (tempstream, env->compstr, 0, NULL);
      
      do
	{
	  mu_stream_destroy (&tempstream);
	  run_editor (ed, filename);
	  rc = mu_file_stream_create (&tempstream, filename, MU_STREAM_RDWR);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERROR, "mu_file_stream_create",
			       filename, rc);
	      unlink (filename);
	      free (filename);
	      return rc;
	    }
	}
      while (check_headers (tempstream, env));
    }
  else
    {
      mu_stream_copy (tempstream, env->compstr, 0, NULL);
      mu_stream_destroy (&tempstream);
      run_editor (ed, filename);
      rc = mu_file_stream_create (&tempstream, filename, MU_STREAM_RDWR);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_file_stream_create",
			   filename, rc);
	  unlink (filename);
	  free (filename);
	  return rc;
	}
    }

  if (rc == 0)
    {
      mu_off_t size;
      
      mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
      mu_stream_copy (env->compstr, tempstream, 0, &size);
      mu_stream_truncate (env->compstr, size);
    }
  mu_stream_destroy (&tempstream);
  unlink (filename);
  free (filename);

  mu_stream_seek (env->compstr, 0, MU_SEEK_END, NULL);
  
  escape_continue ();
  return 0;
}

/* ~e */
int
escape_editor (int argc, char **argv, compose_env_t *env)
{
  return escape_run_editor (getenv ("EDITOR"), argc, argv, env);
}

/* ~l -- escape_list_attachments (send.c) */

/* ~v */
int
escape_visual (int argc, char **argv, compose_env_t *env)
{
  return escape_run_editor (getenv ("VISUAL"), argc, argv, env);
}

/* ~f[mesg-list] */
/* ~F[mesg-list] */
int
escape_print (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
{
  return mail_print (argc, argv);
}

void
reread_header (compose_env_t *env, char *hdr, char *prompt)
{
  char *p;
  p = mu_strdup (compose_header_get (env, hdr, ""));
  ml_reread (prompt, &p);
  compose_header_set (env, hdr, p, COMPOSE_REPLACE);
  free (p);
}

/* ~h */
int
escape_headers (int argc, char **argv, compose_env_t *env)
{
  reread_header (env, MU_HEADER_TO, "To: ");
  reread_header (env, MU_HEADER_CC, "Cc: ");
  reread_header (env, MU_HEADER_BCC, "Bcc: ");
  reread_header (env, MU_HEADER_SUBJECT, "Subject: ");
  escape_continue ();
  return 0;
}

/* ~i[var-name] */
int
escape_insert (int argc, char **argv, compose_env_t *env)
{
  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  mailvar_variable_format (env->compstr, mailvar_find_variable (argv[1], 0),
			   NULL);
  return 0;
}

/* ~m[mesg-list] */
/* ~M[mesg-list] */

struct quote_closure
{
  mu_stream_t str;
  int islower;
};

int
quote0 (msgset_t *mspec, mu_message_t mesg, void *data)
{
  struct quote_closure *clos = data;
  int rc;
  mu_stream_t stream;
  char *prefix = "\t";
  mu_stream_t flt;
  char *argv[3];

  mu_printf (_("Interpolating: %lu\n"), (unsigned long) msgset_msgno (mspec));

  mailvar_get (&prefix, mailvar_name_indentprefix, mailvar_type_string, 0);

  argv[0] = "INLINE-COMMENT";
  argv[1] = prefix;
  argv[2] = NULL;
  rc = mu_filter_create_args (&flt, clos->str, "INLINE-COMMENT",
			      2, (const char**) argv,
			      MU_FILTER_ENCODE,
			      MU_STREAM_WRITE);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_filter_create_args", NULL, rc);
      return rc;
    }
  
  if (clos->islower)
    {
      mu_header_t hdr;
      mu_body_t body;
      mu_iterator_t itr;
	
      mu_message_get_header (mesg, &hdr);
      mu_header_get_iterator (hdr, &itr);
      for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
	   mu_iterator_next (itr))
	{
	  const char *name, *value;

	  if (mu_iterator_current_kv (itr, (const void **)&name,
				      (void**)&value) == 0 &&
	      mail_header_is_visible (name))
	    mu_stream_printf (flt, "%s: %s\n", name, value);
	}
      mu_iterator_destroy (&itr);
      mu_stream_write (flt, "\n", 1, NULL);
      mu_message_get_body (mesg, &body);
      rc = mu_body_get_streamref (body, &stream);
    }
  else
    rc = mu_message_get_streamref (mesg, &stream);

  if (rc)
    {
      mu_error (_("get_streamref error: %s"), mu_strerror (rc));
      return rc;
    }

  mu_stream_copy (flt, stream, 0, NULL);

  mu_stream_destroy (&stream);
  mu_stream_destroy (&flt);
  
  return 0;
}

int
escape_quote (int argc, char **argv, compose_env_t *env)
{
  struct quote_closure clos;
  
  clos.str = env->compstr;
  clos.islower = mu_islower (argv[0][0]);
  util_foreach_msg (argc, argv, MSG_NODELETED|MSG_SILENT, quote0, &clos);
  escape_continue ();
  return 0;
}

/* ~p */
int
escape_type_input (int argc, char **argv, compose_env_t *env)
{
  /* FIXME: Enable paging */
  mu_printf (_("Message contains:\n"));
  dump_headers (mu_strout, env);
  mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
  mu_stream_copy (mu_strout, env->compstr, 0, NULL);
  escape_continue ();
  return 0;
}

/* ~r[filename] */
int
escape_read (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
{
  char *filename;
  mu_stream_t instr;
  int rc;
  
  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  filename = util_fullpath (argv[1]);

  rc = mu_file_stream_create (&instr, filename, MU_STREAM_READ);
  if (rc)
    {
      mu_error (_("Cannot open %s: %s"), filename, mu_strerror (rc));
      free (filename);
      return 1;
    }

  verbose_copy (env->compstr, instr, filename, NULL);
  mu_stream_destroy (&instr);
  free (filename);
  return 0;
}

/* ~s[string] */
int
escape_subj (int argc, char **argv, compose_env_t *env)
{
  char *buf;
  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  mu_argcv_string (argc - 1, argv + 1, &buf);
  compose_header_set (env, MU_HEADER_SUBJECT, buf, COMPOSE_REPLACE);
  free (buf);
  return 0;
}

/* ~t[name-list] */
int
escape_to (int argc, char **argv, compose_env_t *env)
{
  while (--argc)
    compose_header_set (env, MU_HEADER_TO, *++argv, COMPOSE_SINGLE_LINE);
  return 0;
}

/* ~w[filename] */
int
escape_write (int argc, char **argv, compose_env_t *env)
{
  char *filename;
  mu_stream_t wstr;
  int rc;
  mu_off_t size;
  
  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  filename = util_fullpath (argv[1]);
  /* FIXME: check for existence first */
  rc = mu_file_stream_create (&wstr, filename,
			      MU_STREAM_WRITE|MU_STREAM_CREAT);
  if (rc)
    {
      mu_error (_("Cannot open %s for writing: %s"),
		  filename, mu_strerror (rc));
      free (filename);
      return 1;
    }

  mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
  verbose_copy (wstr, env->compstr, filename, &size);
  mu_stream_truncate (wstr, size);
  mu_stream_destroy (&wstr);
  free (filename);
  return 0;
}

/* ~|[shell-command] */
int
escape_pipe (int argc, char **argv, compose_env_t *env)
{
  int rc, status;
  int p[2];
  pid_t pid;
  int fd;
  mu_off_t isize, osize = 0;
  mu_stream_t tstr;
  
  if (pipe (p))
    {
      mu_error ("pipe: %s", mu_strerror (errno));
      return 1;
    }

  if (mu_tempfile (NULL, 0, &fd, NULL))
    return 1;

  if ((pid = fork ()) < 0)
    {
      close (p[0]);
      close (p[1]);
      close (fd);
      mu_error ("fork: %s", mu_strerror (errno));
      return 1;
    }
  else if (pid == 0)
    {
      /* Child */

      /* Attach the pipes */
      close (0);
      dup (p[0]);
      close (p[0]);
      close (p[1]);

      close (1);
      dup (fd);
      close (fd);

      execvp (argv[1], argv + 1);
      mu_error (_("Cannot execute `%s': %s"), argv[1], mu_strerror (errno));
      _exit (127);
    }

  close (p[0]);

  rc = mu_stdio_stream_create (&tstr, p[1], MU_STREAM_WRITE);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", NULL, rc);
      kill (pid, SIGKILL);
      close (fd);
      return rc;
    }

  mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
  mu_stream_copy (tstr, env->compstr, 0, &isize);
  mu_stream_destroy (&tstr);

  waitpid (pid, &status, 0);
  if (!WIFEXITED (status))
    mu_error (_("Child terminated abnormally: %d"),
		WEXITSTATUS (status));
  else
    {
      struct stat st;
      if (fstat (fd, &st))
	mu_error (_("Cannot stat output file: %s"), mu_strerror (errno));
      else
	osize = st.st_size;
    }

  mu_stream_printf (mu_strout, "\"|%s\" in: %lu ", argv[1],
		    (unsigned long) isize);
  if (osize == 0)
    mu_stream_printf (mu_strout, _("no lines out\n"));
  else
    {
      mu_stream_t str;
      
      mu_stream_printf (mu_strout, "out: %lu\n", (unsigned long) osize);
      rc = mu_fd_stream_create (&str, NULL, fd,
				MU_STREAM_RDWR | MU_STREAM_SEEK);
      if (rc)
	{
	  mu_error (_("Cannot open composition stream: %s"),
		      mu_strerror (rc));
	  close (fd);
	}
      else
	{
	  mu_stream_destroy (&env->compstr);
	  env->compstr = str;
	}
    }
  return 0;
}
