/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999-2025 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/>. */

#include "mail.h"
#include <mu_umaxtostr.h>

#define ALIGN_UNDEF -1
#define ALIGN_RIGHT 0
#define ALIGN_LEFT  1

struct header_call_args
{
  msgset_t *mspec;
  mu_message_t msg;
  size_t cols_rest;
  char *buf;
  size_t size;
};
  
struct header_segm
{
  struct header_segm *next;
  int align;
  size_t width;
  void *data;
  char *(*get) (struct header_call_args *args, void *data);
};

void
header_ensure_space (struct header_call_args *args, size_t size)
{
  if (size > args->size)
    {
      args->buf = mu_realloc (args->buf, size);
      args->size = size;
    }
}

static char *
header_buf_string_len (struct header_call_args *args, const char *str,
		       size_t len)
{
  header_ensure_space (args, len + 1);
  memcpy (args->buf, str, len);
  args->buf[len] = 0;
  return args->buf;
}

static char *
header_buf_string (struct header_call_args *args, const char *str)
{
  if (!str)
    return header_buf_string_len (args, "", 0);
  return header_buf_string_len (args, str, strlen (str));
}

static void
format_pad (size_t n)
{
  for (; n; n--)
    mu_stream_write (mu_strout, " ", 1, NULL);
}

static void
format_headline (struct header_segm *seg, msgset_t *mspec, mu_message_t msg)
{
  int screen_cols = util_screen_columns () - 2;
  int out_cols = 0;
  struct header_call_args args;

  args.mspec = mspec;
  args.msg = msg;
  args.buf = NULL;
  args.size = 0;
  
  for (; seg; seg = seg->next)
    {
      size_t width, len;
      size_t cols_rest = screen_cols - out_cols;
      char *p;

      args.cols_rest = cols_rest;
      p = seg->get (&args, seg->data);

      if (!p)
	p = "";
      len = strlen (p);
      
      if (seg->width)
	width = seg->width;
      else
	width = len;
      if (width > cols_rest)
	width = cols_rest;

      if (len > width)
	len = width;
      
      if (seg->align == ALIGN_RIGHT)
	{
	  format_pad (width - len);
	  mu_printf ("%*.*s", (int) len, (int) len, p);
	}
      else
	{
	  mu_printf ("%*.*s", (int) len, (int) len, p);
	  format_pad (width - len);
	}
      out_cols += width;
    }

  mu_printf ("\n");
  free (args.buf);
}    

static void
free_headline (struct header_segm *seg)
{
  while (seg)
    {
      struct header_segm *next = seg->next;
      if (seg->data)
	free (seg->data);
      free (seg);
      seg = next;
    }
}


static char *
hdr_text (struct header_call_args *args, void *data)
{
  return data;
}

static char *
hdr_cur (struct header_call_args *args, void *data)
{
  if (is_current_message (msgset_msgno (args->mspec)))
    return (char*) data;
  return " ";
}

/* %a */
static char *
hdr_attr (struct header_call_args *args, void *data)
{
  mu_attribute_t attr;
  char cflag;
  
  mu_message_get_attribute (args->msg, &attr);
  
  if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_MBOXED))
    cflag = 'M';
  else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_PRESERVED))
    cflag = 'P';
  else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SAVED))
    cflag = '*';
  else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_TAGGED))
    cflag = 'T';
  else if (mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SHOWN))
    cflag = 'R';
  else if (mu_attribute_is_recent (attr))
    cflag = 'N';
  else if (!mu_attribute_is_read (attr))
    cflag = 'U';
  else
    cflag = ' ';
  return header_buf_string_len (args, &cflag, 1);
}

/* %d and %D*/
static char *
hdr_date (struct header_call_args *args, void *data)
{
  char date[80];
  mu_header_t hdr;
  char const *fmt = data ? data : "%a %b %e %H:%M";
  
  mu_message_get_header (args->msg, &hdr);
  
  date[0] = 0;
  if (mailvar_is_true (mailvar_name_datefield)
      && mu_header_get_value (hdr, MU_HEADER_DATE,
			      date, sizeof (date), NULL) == 0)
    {
      time_t t;
      if (mu_parse_date (date, &t, NULL) == 0)
	strftime (date, sizeof(date), fmt, localtime (&t));
      else
	date[0] = 0;
    }

  if (date[0] == 0)
    {
      const char *p;
      struct tm tm;
      struct mu_timezone tz;
      mu_envelope_t env;
      
      mu_message_get_envelope (args->msg, &env);
      if (mu_envelope_sget_date (env, &p) == 0
          && mu_scan_datetime (p, MU_DATETIME_FROM, &tm, &tz, NULL) == 0)
	strftime (date, sizeof(date), fmt, &tm);
    }
  return header_buf_string (args, date);
}

char *
sender_string (mu_message_t msg)
{
  char *from = NULL;
  
  if (mailvar_is_true (mailvar_name_fromfield))
    {  
      mu_header_t hdr;
      
      if (mu_message_get_header (msg, &hdr) == 0
	  && mu_header_aget_value_unfold (hdr, MU_HEADER_FROM, &from) == 0)
	{
	  mu_address_t address = NULL;
	  if (mu_address_create (&address, from) == 0)
	    {
	      char *name;
	      const char *email;
	  
	      if (mu_address_sget_email (address, 1, &email) == 0 && email)
		{
		  if (mailvar_is_true (mailvar_name_showto)
		      && mail_is_my_name (email))
		    {
		      char *tmp;

		      if (mu_header_aget_value_unfold (hdr, MU_HEADER_TO, 
						       &tmp) == 0)
			{
			  mu_address_t addr_to;
			  if (mu_address_create (&addr_to, tmp) == 0)
			    {
			      mu_address_destroy (&address);
			      address = addr_to;
			    }
			  free (tmp);
			}
		    }
		}
	      
	      if ((mu_address_aget_personal (address, 1, &name) == 0
		   && name)
		  || (mu_address_aget_email (address, 1, &name) == 0
		      && name))
		{
		  free (from);
		  from = name;
		}
	      mu_address_destroy (&address);
	    }
	}
      util_rfc2047_decode (&from);
    }
  else
    {
      mu_envelope_t env = NULL;
      const char *sender = "";
      
      if (mu_message_get_envelope (msg, &env) == 0)
	mu_envelope_sget_sender (env, &sender);
      from = mu_strdup (sender);
    }
  return from;
}

/* %f */
static char *
hdr_from (struct header_call_args *args, void *data)
{
  char *from = sender_string (args->msg);
  header_buf_string (args, from);
  free (from);
  return args->buf;
}

/* %l */
static char *
hdr_lines (struct header_call_args *args, void *data)
{
  size_t m_lines;

  mu_message_lines (args->msg, &m_lines);  
  return header_buf_string (args, mu_umaxtostr (m_lines));
}

/* %L */
static char *
hdr_quick_lines (struct header_call_args *args, void *data)
{
  size_t m_lines;
  int rc;
  const char *p;
  
  rc = mu_message_quick_lines (args->msg, &m_lines);
  if (rc == 0)
    p = mu_umaxtostr (m_lines);
  else
    p = "NA";
  return header_buf_string (args, p);
}

/* %m */
static char *
hdr_number (struct header_call_args *args, void *data)
{
  return header_buf_string (args, mu_umaxtostr (msgset_msgno (args->mspec)));
}

/* %o */
static char *
hdr_size (struct header_call_args *args, void *data)
{
  size_t m_size;
  mu_message_size (args->msg, &m_size);  
  return header_buf_string (args, mu_umaxtostr (m_size));
}

/* %s */
static char *
hdr_subject (struct header_call_args *args, void *data)
{
  mu_header_t hdr;
  char *subj = NULL;
  
  mu_message_get_header (args->msg, &hdr);
  mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj);
  util_rfc2047_decode (&subj);
  
  header_buf_string (args, subj);
  free (subj);
  return args->buf;
}

/* %S */
static char *
hdr_q_subject (struct header_call_args *args, void *data)
{
  mu_header_t hdr;
  char *subj = NULL;
  size_t len;

  if (args->cols_rest <= 2)
    return "\"\"";
  
  mu_message_get_header (args->msg, &hdr);
  mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT, &subj);
  if (!subj)
    return "";
  util_rfc2047_decode (&subj);

  len = strlen (subj);
  if (len + 2 > args->cols_rest)
    len = args->cols_rest - 2;
  header_ensure_space (args, len + 3);
  args->buf[0] = '"';
  memcpy (args->buf + 1, subj, len);
  args->buf[len+1] = '"';
  args->buf[len+2] = 0;
  free (subj);
  return args->buf;
}


static struct header_segm *
new_header_segment (int align, size_t width,
		    void *data,
		    char *(*get) (struct header_call_args *, void *))
{
  struct header_segm *seg = mu_alloc (sizeof (*seg));
  seg->next = NULL;
  seg->align = align;
  seg->width = width;
  seg->data = data;
  seg->get = get;
  return seg;
}

struct header_segm *
compile_headline (const char *str)
{
  struct header_segm *head = NULL, *tail = NULL;
  char *text;
  int align;
  size_t width;
  
#define ALIGN_STRING (align == ALIGN_UNDEF ? ALIGN_LEFT : align)
#define ALIGN_NUMBER (align == ALIGN_UNDEF ? ALIGN_RIGHT : align)
#define ATTACH(p)				\
  do						\
    {						\
      if (!head)				\
	head = p;				\
      else					\
	tail->next = p;				\
      tail = p;					\
    }						\
  while (0)
      
  while (*str)
    {
      struct header_segm *seg;
      size_t len;
      char *p = strchr (str, '%');
      if (!p)
	len = strlen (str);
      else
	len = p - str;
      if (len)
	{
	  text = mu_alloc (len + 1);
	  memcpy (text, str, len);
	  text[len] = 0;
	  seg = new_header_segment (ALIGN_LEFT, 0, text, hdr_text);
	  ATTACH (seg);
	}
      if (!p)
	break;

      str = ++p;

      if (*str == '-')
	{
	  str++;
	  align = ALIGN_LEFT;
	}
      else if (*str == '+')
	{
	  str++;
	  align = ALIGN_RIGHT;
	}
      else
	align = ALIGN_UNDEF;
      
      if (mu_isdigit (*str))
	width = strtoul (str, (char**)&str, 10);
      else
	width = 0;

      switch (*str++)
	{
	case '%':
	  seg = new_header_segment (ALIGN_LEFT, 0, mu_strdup ("%"), hdr_text);
	  break;
	  
	case 'a': /* Message attributes. */
	  seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_attr);
	  break;

	  /* FIXME: %c    The score of the message. */
	  
	case 'd': /* Message date; See also 'D', below. */
	  seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_date);
	  break;

	  /* FIXME: %e    The indenting level in threaded mode. */
	  
	case 'f': /* Message sender */
	  seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_from);
	  break;

	  /* FIXME: %i    The message thread structure. */
	  
	case 'l': /* The number of lines of the message */
	  seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_lines);
	  break;

	case 'L': /* Same, but in quick mode */
	  seg = new_header_segment (ALIGN_NUMBER, width, NULL,
				    hdr_quick_lines);
	  break;
	  
	case 'm': /* Message number */
	  seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_number);
	  break;
	  
	case 'o': /* The number of octets (bytes) in the message */
	  seg = new_header_segment (ALIGN_NUMBER, width, NULL, hdr_size);
	  break;
	  
	case 's': /* Message subject (if any) */
	  seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_subject);
	  break;

	case 'S': /* Message subject (if any) in double quotes */
	  seg = new_header_segment (ALIGN_STRING, width, NULL, hdr_q_subject);
	  break;
	  
	  /* FIXME: %t    The position in threaded/sorted order. */
	  
	case '>': /* A `>' for the current message, otherwise ` ' */
	  seg = new_header_segment (ALIGN_STRING, width, mu_strdup (">"), 
	                            hdr_cur);
	  break;
	  
	case '<': /* A `<' for the current message, otherwise ` ' */
	  seg = new_header_segment (ALIGN_STRING, width, mu_strdup ("<"),
	                            hdr_cur);
	  break;

	case 'D':
	  {
	    int i;
	    /* strftime conversion specifiers */
	    static char timespec[] =
	      "aAbBcCdDeFGghHIjklmMnpPrRsStTuUVwWxXyYzZ+%";
	    /* Specifiers that can follow the E modifier */
	    static char espec[] =
	      "cCxXyY";
	    /* Specifiers that can follow the O modifier */
	    static char ospec[] =
	      "deHImMSuUVwWy";
	    
	    if (*str == '{')
	      {
		for (i = 1; str[i] && str[i] != '}'; i++)
		  if (str[i] == '\\')
		    i++;
		if (str[i])
		  {
		    text = mu_alloc (i);
		    memcpy (text, str + 1, i - 1);
		    text[i - 1] = 0;
		    mu_c_str_unescape_inplace (text, "\\{}", NULL);
		    seg = new_header_segment (ALIGN_STRING, width, text,
					      hdr_date);
		    str += i + 1;
		    break;
		  }
	      }
	    else if (str[1] &&
		     ((*str == 'E' && strchr (espec, str[1]))
		       || (*str == 'O' && strchr (ospec, str[1]))))
	      {
		text = mu_alloc (4);
		text[0] = '%';
		text[1] = *str++;
		text[2] = *str++;
		text[3] = 0;
		seg = new_header_segment (ALIGN_STRING, width, text,
					  hdr_date);
		break;
	      }
	    else if (strchr (timespec, *str))
	      {
		text = mu_alloc (3);
		text[0] = '%';
		text[1] = *str++;
		text[2] = 0;
		seg = new_header_segment (ALIGN_STRING, width, text,
					  hdr_date);
		break;
	      }
	  }
	  
	default:
	  mu_error (_("unknown format specifier: %%%c"), str[-1]);
	  len = str - p;
	  text = mu_alloc (len);
	  memcpy (text, p, len-1);
	  text[len-1] = 0;
	  seg = new_header_segment (ALIGN_STRING, width, text, hdr_text);
	}
      ATTACH (seg);
    }
  return head;
#undef ALIGN_STRING
#undef ALIGN_NUMBER
#undef ATTACH
}

/* FIXME: Should it be part of struct mailvar_variable for "headline"? */
static struct header_segm *mail_header_line;

void
mail_compile_headline (char const *str)
{
  free_headline (mail_header_line);
  mail_header_line = compile_headline (str);
}


/*
 * f[rom] [msglist]
 */

int
mail_from0 (msgset_t *mspec, mu_message_t msg, void *data)
{
  format_headline (mail_header_line, mspec, msg);
  return 0;
}

int
mail_from (int argc, char **argv)
{
  return util_foreach_msg (argc, argv, MSG_NODELETED|MSG_SILENT,
			   mail_from0, NULL);
}

