/*
 * Copyright notice from original mutt:
 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
 *
 * This file is part of mutt-ng, see http://www.muttng.org/.
 * It's licensed under the GNU General Public License,
 * please see the file GPL in the top level source directory.
 */

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

#include "mutt.h"
#include "buffer.h"
#include "ascii.h"
#include "mutt_curses.h"
#include "mapping.h"

#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/str.h"
#include "lib/debug.h"

#include <string.h>
#include <stdlib.h>
#include <ctype.h>

/* globals */
int *ColorQuote;
int ColorQuoteUsed;
int ColorDefs[MT_COLOR_MAX];
COLOR_LINE *ColorHdrList = NULL;
COLOR_LINE *ColorBodyList = NULL;
COLOR_LINE *ColorIndexList = NULL;

/* local to this file */
static int ColorQuoteSize;

#ifdef HAVE_COLOR

#define COLOR_DEFAULT (-2)

typedef struct color_list {
  short fg;
  short bg;
  short index;
  short count;
  struct color_list *next;
} COLOR_LIST;

static COLOR_LIST *ColorList = NULL;
static int UserColors = 0;

static struct mapping_t Colors[] = {
  {"black", COLOR_BLACK},
  {"blue", COLOR_BLUE},
  {"cyan", COLOR_CYAN},
  {"green", COLOR_GREEN},
  {"magenta", COLOR_MAGENTA},
  {"red", COLOR_RED},
  {"white", COLOR_WHITE},
  {"yellow", COLOR_YELLOW},
#if defined (USE_SLANG_CURSES) || defined (HAVE_USE_DEFAULT_COLORS)
  {"default", COLOR_DEFAULT},
#endif
  {0, 0}
};

#endif /* HAVE_COLOR */

static struct mapping_t Fields[] = {
  {"hdrdefault", MT_COLOR_HDEFAULT},
  {"quoted", MT_COLOR_QUOTED},
  {"signature", MT_COLOR_SIGNATURE},
  {"indicator", MT_COLOR_INDICATOR},
  {"status", MT_COLOR_STATUS},
  {"tree", MT_COLOR_TREE},
  {"error", MT_COLOR_ERROR},
  {"normal", MT_COLOR_NORMAL},
  {"tilde", MT_COLOR_TILDE},
  {"markers", MT_COLOR_MARKERS},
  {"header", MT_COLOR_HEADER},
  {"body", MT_COLOR_BODY},
  {"message", MT_COLOR_MESSAGE},
  {"attachment", MT_COLOR_ATTACHMENT},
  {"search", MT_COLOR_SEARCH},
  {"bold", MT_COLOR_BOLD},
  {"underline", MT_COLOR_UNDERLINE},
  {"index", MT_COLOR_INDEX},
  {"sidebar_new", MT_COLOR_NEW},
  {"sidebar", MT_COLOR_SIDEBAR},
  {"sidebar_flagged", MT_COLOR_FLAGGED},
  {NULL, 0}
};

#define COLOR_QUOTE_INIT	8

static COLOR_LINE *mutt_new_color_line (void)
{
  COLOR_LINE *p = mem_calloc (1, sizeof (COLOR_LINE));

  p->fg = p->bg = -1;

  return (p);
}

static void mutt_free_color_line (COLOR_LINE ** l, int free_colors)
{
  COLOR_LINE *tmp;

  if (!l || !*l)
    return;

  tmp = *l;

#ifdef HAVE_COLOR
  if (free_colors && tmp->fg != -1 && tmp->bg != -1)
    mutt_free_color (tmp->fg, tmp->bg);
#endif

  /* we should really introduce a container
   * type for regular expressions.
   */

  regfree (&tmp->rx);
  mutt_pattern_free (&tmp->color_pattern);
  mem_free (&tmp->pattern);
  mem_free (l);
}

void ci_start_color (void)
{
  memset (ColorDefs, A_NORMAL, sizeof (int) * MT_COLOR_MAX);
  ColorQuote = (int *) mem_malloc (COLOR_QUOTE_INIT * sizeof (int));
  memset (ColorQuote, A_NORMAL, sizeof (int) * COLOR_QUOTE_INIT);
  ColorQuoteSize = COLOR_QUOTE_INIT;
  ColorQuoteUsed = 0;

  /* set some defaults */
  ColorDefs[MT_COLOR_STATUS] = A_REVERSE;
  ColorDefs[MT_COLOR_INDICATOR] = A_REVERSE;
  ColorDefs[MT_COLOR_SEARCH] = A_REVERSE;
  ColorDefs[MT_COLOR_MARKERS] = A_REVERSE;
  /* special meaning: toggle the relevant attribute */
  ColorDefs[MT_COLOR_BOLD] = 0;
  ColorDefs[MT_COLOR_UNDERLINE] = 0;

#ifdef HAVE_COLOR
  start_color ();
#endif
}

#ifdef HAVE_COLOR

#ifdef USE_SLANG_CURSES
static char *get_color_name (char *dest, size_t destlen, int val)
{
  static char *missing[3] = { "brown", "lightgray", "default" };
  int i;

  switch (val) {
  case COLOR_YELLOW:
    strfcpy (dest, missing[0], destlen);
    return dest;

  case COLOR_WHITE:
    strfcpy (dest, missing[1], destlen);
    return dest;

  case COLOR_DEFAULT:
    strfcpy (dest, missing[2], destlen);
    return dest;
  }

  for (i = 0; Colors[i].name; i++) {
    if (Colors[i].value == val) {
      strfcpy (dest, Colors[i].name, destlen);
      return dest;
    }
  }

  /* Sigh. If we got this far, the color is of the form 'colorN'
   * Slang can handle this itself, so just return 'colorN'
   */

  snprintf (dest, destlen, "color%d", val);
  return dest;
}
#endif

int mutt_alloc_color (int fg, int bg)
{
  COLOR_LIST *p = ColorList;
  int i;

#if defined (USE_SLANG_CURSES)
  char fgc[SHORT_STRING], bgc[SHORT_STRING];
#endif

  /* check to see if this color is already allocated to save space */
  while (p) {
    if (p->fg == fg && p->bg == bg) {
      (p->count)++;
      return (COLOR_PAIR (p->index));
    }
    p = p->next;
  }

  /* check to see if there are colors left */
  if (++UserColors > COLOR_PAIRS)
    return (A_NORMAL);

  /* find the smallest available index (object) */
  i = 1;
  FOREVER {
    p = ColorList;
    while (p) {
      if (p->index == i)
        break;
      p = p->next;
    }
    if (p == NULL)
      break;
    i++;
  }

  p = (COLOR_LIST *) mem_malloc (sizeof (COLOR_LIST));
  p->next = ColorList;
  ColorList = p;

  p->index = i;
  p->count = 1;
  p->bg = bg;
  p->fg = fg;

#if defined (USE_SLANG_CURSES)
  if (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
    SLtt_set_color (i, NULL, get_color_name (fgc, sizeof (fgc), fg),
                    get_color_name (bgc, sizeof (bgc), bg));
  else
#elif defined (HAVE_USE_DEFAULT_COLORS)
  if (fg == COLOR_DEFAULT)
    fg = -1;
  if (bg == COLOR_DEFAULT)
    bg = -1;
#endif

  init_pair (i, fg, bg);

  debug_print (1, ("Color pairs used so far: %d\n", UserColors));

  return (COLOR_PAIR (p->index));
}

void mutt_free_color (int fg, int bg)
{
  COLOR_LIST *p, *q;

  p = ColorList;
  while (p) {
    if (p->fg == fg && p->bg == bg) {
      (p->count)--;
      if (p->count > 0)
        return;

      UserColors--;
      debug_print (1, ("Color pairs used so far: %d\n", UserColors));

      if (p == ColorList) {
        ColorList = ColorList->next;
        mem_free (&p);
        return;
      }
      q = ColorList;
      while (q) {
        if (q->next == p) {
          q->next = p->next;
          mem_free (&p);
          return;
        }
        q = q->next;
      }
      /* can't get here */
    }
    p = p->next;
  }
}

#endif /* HAVE_COLOR */


#ifdef HAVE_COLOR

static int
parse_color_name (const char *s, int *col, int *attr, int brite, BUFFER * err)
{
  char *eptr;

  if (str_ncasecmp (s, "bright", 6) == 0) {
    *attr |= brite;
    s += 6;
  }

  /* allow aliases for xterm color resources */
  if (str_ncasecmp (s, "color", 5) == 0) {
    s += 5;
    *col = strtol (s, &eptr, 10);
    if (!*s || *eptr || *col < 0 ||
        (*col >= COLORS && !option (OPTNOCURSES) && has_colors ())) {
      snprintf (err->data, err->dsize, _("%s: color not supported by term"),
                s);
      return (-1);
    }
  }
  else if ((*col = mutt_getvaluebyname (s, Colors)) == -1) {
    snprintf (err->data, err->dsize, _("%s: no such color"), s);
    return (-1);
  }

  return 0;
}

#endif


/* usage: uncolor index pattern [pattern...]
 * 	  unmono  index pattern [pattern...]
 */

static int
_mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
                     BUFFER * err, short parse_uncolor);


#ifdef HAVE_COLOR

int mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
                        BUFFER * err)
{
  return _mutt_parse_uncolor (buf, s, data, err, 1);
}

#endif

int mutt_parse_unmono (BUFFER * buf, BUFFER * s, unsigned long data,
                       BUFFER * err)
{
  return _mutt_parse_uncolor (buf, s, data, err, 0);
}

static int
_mutt_parse_uncolor (BUFFER * buf, BUFFER * s, unsigned long data,
                     BUFFER * err, short parse_uncolor)
{
  int object = 0, do_cache = 0;
  COLOR_LINE *tmp, *last = NULL;

  mutt_extract_token (buf, s, 0);

  if ((object = mutt_getvaluebyname (buf->data, Fields)) == -1) {
    snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
    return (-1);
  }

  if (str_ncmp (buf->data, "index", 5) != 0) {
    snprintf (err->data, err->dsize,
              _("%s: command valid only for index object"),
              parse_uncolor ? "uncolor" : "unmono");
    return (-1);
  }

  if (!MoreArgs (s)) {
    snprintf (err->data, err->dsize,
              _("%s: too few arguments"),
              parse_uncolor ? "uncolor" : "unmono");
    return (-1);
  }

  if (
#ifdef HAVE_COLOR
       /* we're running without curses */
       option (OPTNOCURSES)
       ||                       /* we're parsing an uncolor command, and have no colors */
       (parse_uncolor && !has_colors ())
       /* we're parsing an unmono command, and have colors */
       || (!parse_uncolor && has_colors ())
#else
       /* We don't even have colors compiled in */
       parse_uncolor
#endif
    ) {
    /* just eat the command, but don't do anything real about it */
    do
      mutt_extract_token (buf, s, 0);
    while (MoreArgs (s));

    return 0;
  }


  do {
    mutt_extract_token (buf, s, 0);
    if (!str_cmp ("*", buf->data)) {
      for (tmp = ColorIndexList; tmp;) {
        if (!do_cache)
          do_cache = 1;
        last = tmp;
        tmp = tmp->next;
        mutt_free_color_line (&last, parse_uncolor);
      }
      ColorIndexList = NULL;
    }
    else {
      for (last = NULL, tmp = ColorIndexList; tmp;
           last = tmp, tmp = tmp->next) {
        if (!str_cmp (buf->data, tmp->pattern)) {
          if (!do_cache)
            do_cache = 1;
          debug_print (1, ("Freeing pattern \"%s\" from ColorIndexList\n", tmp->pattern));
          if (last)
            last->next = tmp->next;
          else
            ColorIndexList = tmp->next;
          mutt_free_color_line (&tmp, parse_uncolor);
          break;
        }
      }
    }
  }
  while (MoreArgs (s));


  if (do_cache && !option (OPTNOCURSES)) {
    int i;

    set_option (OPTFORCEREDRAWINDEX);
    /* force re-caching of index colors */
    for (i = 0; Context && i < Context->msgcount; i++)
      Context->hdrs[i]->pair = 0;
  }
  return (0);
}


static int
add_pattern (COLOR_LINE ** top, const char *s, int sensitive,
             int fg, int bg, int attr, BUFFER * err, int is_index)
{

  /* is_index used to store compiled pattern
   * only for `index' color object 
   * when called from mutt_parse_color() */

  COLOR_LINE *tmp = *top;

  while (tmp) {
    if (sensitive) {
      if (str_cmp (s, tmp->pattern) == 0)
        break;
    }
    else {
      if (str_casecmp (s, tmp->pattern) == 0)
        break;
    }
    tmp = tmp->next;
  }

  if (tmp) {
#ifdef HAVE_COLOR
    if (fg != -1 && bg != -1) {
      if (tmp->fg != fg || tmp->bg != bg) {
        mutt_free_color (tmp->fg, tmp->bg);
        tmp->fg = fg;
        tmp->bg = bg;
        attr |= mutt_alloc_color (fg, bg);
      }
      else
        attr |= (tmp->pair & ~A_BOLD);
    }
#endif /* HAVE_COLOR */
    tmp->pair = attr;
  }
  else {
    int r;
    char buf[STRING];

    tmp = mutt_new_color_line ();
    if (is_index) {
      int i;

      strfcpy (buf, NONULL (s), sizeof (buf));
      mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
      if ((tmp->color_pattern =
           mutt_pattern_comp (buf, M_FULL_MSG, err)) == NULL) {
        mutt_free_color_line (&tmp, 1);
        return -1;
      }
      /* force re-caching of index colors */
      for (i = 0; Context && i < Context->msgcount; i++)
        Context->hdrs[i]->pair = 0;
    }
    else
      if ((r =
           REGCOMP (&tmp->rx, s,
                    (sensitive ? mutt_which_case (s) : REG_ICASE))) != 0) {
      regerror (r, &tmp->rx, err->data, err->dsize);
      mutt_free_color_line (&tmp, 1);
      return (-1);
    }
    tmp->next = *top;
    tmp->pattern = str_dup (s);
#ifdef HAVE_COLOR
    if (fg != -1 && bg != -1) {
      tmp->fg = fg;
      tmp->bg = bg;
      attr |= mutt_alloc_color (fg, bg);
    }
#endif
    tmp->pair = attr;
    *top = tmp;
  }

  return 0;
}

static int
parse_object (BUFFER * buf, BUFFER * s, int *o, int *ql, BUFFER * err)
{
  int q_level = 0;
  char *eptr;

  if (!MoreArgs (s)) {
    strfcpy (err->data, _("Missing arguments."), err->dsize);
    return -1;
  }

  mutt_extract_token (buf, s, 0);
  if (!str_ncmp (buf->data, "quoted", 6)) {
    if (buf->data[6]) {
      *ql = strtol (buf->data + 6, &eptr, 10);
      if (*eptr || q_level < 0) {
        snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
        return -1;
      }
    }
    else
      *ql = 0;

    *o = MT_COLOR_QUOTED;
  }
  else if ((*o = mutt_getvaluebyname (buf->data, Fields)) == -1) {
    snprintf (err->data, err->dsize, _("%s: no such object"), buf->data);
    return (-1);
  }

  return 0;
}

typedef int (*parser_callback_t) (BUFFER *, BUFFER *, int *, int *, int *,
                                  BUFFER *);

#ifdef HAVE_COLOR

static int
parse_color_pair (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
                  BUFFER * err)
{
  if (!MoreArgs (s)) {
    strfcpy (err->data, _("color: too few arguments"), err->dsize);
    return (-1);
  }

  mutt_extract_token (buf, s, 0);

  if (parse_color_name (buf->data, fg, attr, A_BOLD, err) != 0)
    return (-1);

  if (!MoreArgs (s)) {
    strfcpy (err->data, _("color: too few arguments"), err->dsize);
    return (-1);
  }

  mutt_extract_token (buf, s, 0);

  if (parse_color_name (buf->data, bg, attr, A_BLINK, err) != 0)
    return (-1);

  return 0;
}

#endif

static int
parse_attr_spec (BUFFER * buf, BUFFER * s, int *fg, int *bg, int *attr,
                 BUFFER * err)
{

  if (fg)
    *fg = -1;
  if (bg)
    *bg = -1;

  if (!MoreArgs (s)) {
    strfcpy (err->data, _("mono: too few arguments"), err->dsize);
    return (-1);
  }

  mutt_extract_token (buf, s, 0);

  if (ascii_strcasecmp ("bold", buf->data) == 0)
    *attr |= A_BOLD;
  else if (ascii_strcasecmp ("underline", buf->data) == 0)
    *attr |= A_UNDERLINE;
  else if (ascii_strcasecmp ("none", buf->data) == 0)
    *attr = A_NORMAL;
  else if (ascii_strcasecmp ("reverse", buf->data) == 0)
    *attr |= A_REVERSE;
  else if (ascii_strcasecmp ("standout", buf->data) == 0)
    *attr |= A_STANDOUT;
  else if (ascii_strcasecmp ("normal", buf->data) == 0)
    *attr = A_NORMAL;           /* needs use = instead of |= to clear other bits */
  else {
    snprintf (err->data, err->dsize, _("%s: no such attribute"), buf->data);
    return (-1);
  }

  return 0;
}

static int fgbgattr_to_color (int fg, int bg, int attr)
{
#ifdef HAVE_COLOR
  if (fg != -1 && bg != -1)
    return attr | mutt_alloc_color (fg, bg);
  else
#endif
    return attr;
}

/* usage: color <object> <fg> <bg> [ <regexp> ] 
 * 	  mono  <object> <attr> [ <regexp> ]
 */

static int
_mutt_parse_color (BUFFER * buf, BUFFER * s, BUFFER * err,
                   parser_callback_t callback, short dry_run)
{
  int object = 0, attr = 0, fg = 0, bg = 0, q_level = 0;
  int r = 0;

  if (parse_object (buf, s, &object, &q_level, err) == -1)
    return -1;

  if (callback (buf, s, &fg, &bg, &attr, err) == -1)
    return -1;

  /* extract a regular expression if needed */

  if (object == MT_COLOR_HEADER || object == MT_COLOR_BODY
      || object == MT_COLOR_INDEX) {
    if (!MoreArgs (s)) {
      strfcpy (err->data, _("too few arguments"), err->dsize);
      return (-1);
    }

    mutt_extract_token (buf, s, 0);
  }

  if (MoreArgs (s)) {
    strfcpy (err->data, _("too many arguments"), err->dsize);
    return (-1);
  }

  /* dry run? */

  if (dry_run)
    return 0;


#ifdef HAVE_COLOR
# ifdef HAVE_USE_DEFAULT_COLORS
  if (!option (OPTNOCURSES) && has_colors ()
      /* delay use_default_colors() until needed, since it initializes things */
      && (fg == COLOR_DEFAULT || bg == COLOR_DEFAULT)
      && use_default_colors () != OK) {
    strfcpy (err->data, _("default colors not supported"), err->dsize);
    return (-1);
  }
# endif /* HAVE_USE_DEFAULT_COLORS */
#endif

  if (object == MT_COLOR_HEADER)
    r = add_pattern (&ColorHdrList, buf->data, 0, fg, bg, attr, err, 0);
  else if (object == MT_COLOR_BODY)
    r = add_pattern (&ColorBodyList, buf->data, 1, fg, bg, attr, err, 0);
  else if (object == MT_COLOR_INDEX) {
    r = add_pattern (&ColorIndexList, buf->data, 1, fg, bg, attr, err, 1);
    set_option (OPTFORCEREDRAWINDEX);
  }
  else if (object == MT_COLOR_QUOTED) {
    if (q_level >= ColorQuoteSize) {
      mem_realloc (&ColorQuote, (ColorQuoteSize += 2) * sizeof (int));
      ColorQuote[ColorQuoteSize - 2] = ColorDefs[MT_COLOR_QUOTED];
      ColorQuote[ColorQuoteSize - 1] = ColorDefs[MT_COLOR_QUOTED];
    }
    if (q_level >= ColorQuoteUsed)
      ColorQuoteUsed = q_level + 1;
    if (q_level == 0) {
      ColorDefs[MT_COLOR_QUOTED] = fgbgattr_to_color (fg, bg, attr);

      ColorQuote[0] = ColorDefs[MT_COLOR_QUOTED];
      for (q_level = 1; q_level < ColorQuoteUsed; q_level++) {
        if (ColorQuote[q_level] == A_NORMAL)
          ColorQuote[q_level] = ColorDefs[MT_COLOR_QUOTED];
      }
    }
    else
      ColorQuote[q_level] = fgbgattr_to_color (fg, bg, attr);
  }
  else
    ColorDefs[object] = fgbgattr_to_color (fg, bg, attr);

#ifdef HAVE_COLOR
# ifdef HAVE_BKGDSET
  if (object == MT_COLOR_NORMAL && !option (OPTNOCURSES) && has_colors ())
    BKGDSET (MT_COLOR_NORMAL);
# endif
#endif

  return (r);
}

#ifdef HAVE_COLOR

int mutt_parse_color (BUFFER * buff, BUFFER * s, unsigned long data,
                      BUFFER * err)
{
  int dry_run = 0;

  if (option (OPTNOCURSES) || !has_colors ())
    dry_run = 1;

  return _mutt_parse_color (buff, s, err, parse_color_pair, dry_run);
}

#endif

int mutt_parse_mono (BUFFER * buff, BUFFER * s, unsigned long data,
                     BUFFER * err)
{
  int dry_run = 0;

#ifdef HAVE_COLOR
  if (option (OPTNOCURSES) || has_colors ())
    dry_run = 1;
#else
  if (option (OPTNOCURSES))
    dry_run = 1;
#endif

  return _mutt_parse_color (buff, s, err, parse_attr_spec, dry_run);
}


syntax highlighted by Code2HTML, v. 0.9.1