/*
 * Copyright © 2011  Google, Inc.
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Google Author(s): Behdad Esfahbod
 */

#ifndef VIEW_OPTIONS_HH
#define VIEW_OPTIONS_HH

#include "options.hh"

#define DEFAULT_MARGIN 16
#define DEFAULT_FORE "#000000"
#define DEFAULT_BACK "#FFFFFF"
#define DEFAULT_STROKE "#888"
#define DEFAULT_STROKE_WIDTH 1

struct view_options_t
{
  ~view_options_t ()
  {
    g_free (fore);
    g_free (stroke);
    g_free (back);
    g_free (custom_palette);
    if (foreground_palette)
      g_array_unref (foreground_palette);
    if (custom_palette_entries)
      g_array_unref (custom_palette_entries);
  }

  void add_options (option_parser_t *parser);
  bool include_logical_extents () const { return logical || !ink; }
  bool include_ink_extents () const { return ink || !logical; }
  void post_parse (GError **error G_GNUC_UNUSED)
  {
    stroke_enabled = false;
    stroke_width = DEFAULT_STROKE_WIDTH;
    if (!parse_color (DEFAULT_STROKE, stroke_color.r, stroke_color.g, stroke_color.b, stroke_color.a))
      fail (false, "Failed parsing default stroke color `%s`", DEFAULT_STROKE);

    const bool stroke_specified = stroke != nullptr;
    if (stroke_specified)
    {
      stroke_enabled = true;
      char *spec = g_strdup (stroke);
      char *s = g_strstrip (spec);
      if (*s)
      {
        char *plus = strchr (s, '+');
        if (plus && strchr (plus + 1, '+'))
          fail (false, "Failed parsing stroke spec `%s`", stroke);

        char *color_spec = s;
        char *width_spec = nullptr;
        if (plus)
        {
          *plus = '\0';
          width_spec = g_strstrip (plus + 1);
        }
        color_spec = g_strstrip (color_spec);

        if (*color_spec)
        {
          if (!parse_color (color_spec, stroke_color.r, stroke_color.g, stroke_color.b, stroke_color.a))
            fail (false, "Failed parsing stroke color `%s`", color_spec);
        }

        if (width_spec && *width_spec)
        {
          char *end = nullptr;
          errno = 0;
          double w = g_ascii_strtod (width_spec, &end);
          if (!end || errno || *g_strstrip (end) || w <= 0)
            fail (false, "Failed parsing stroke width `%s`", width_spec);
          stroke_width = w;
        }
      }
      g_free (spec);
    }

    if (!parse_custom_palette_entries (error))
      return;

    if (!parse_color (DEFAULT_BACK,
		      background_color.r, background_color.g,
		      background_color.b, background_color.a))
      fail (false, "Failed parsing default background color `%s`", DEFAULT_BACK);
    has_background = false;
    if (back && *back)
    {
      if (!parse_color (back,
			background_color.r, background_color.g,
			background_color.b, background_color.a))
      {
	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		     "Invalid background color `%s`", back);
	return;
      }
      has_background = true;
    }

    foreground_use_palette = false;
    if (foreground_palette)
    {
      g_array_unref (foreground_palette);
      foreground_palette = nullptr;
    }

    const char *foreground = fore ? fore : DEFAULT_FORE;
    if (!foreground)
      return;

    if (!parse_color (DEFAULT_FORE,
		      foreground_color.r, foreground_color.g,
		      foreground_color.b, foreground_color.a))
      fail (false, "Failed parsing default foreground color `%s`", DEFAULT_FORE);

    const bool use_rainbow =
      foreground_use_rainbow ||
      0 == g_ascii_strcasecmp (foreground, "rainbow") ||
      0 == g_ascii_strcasecmp (foreground, "lgbtq");

    if (use_rainbow)
    {
      static const char *rainbow[] =
      {
        "#DA0000BB",
        "#FF6200BB",
        "#D4B900BB",
        "#005100BB",
        "#000CFFBB",
        "#42005BBB",
        nullptr
      };

      foreground_palette = g_array_new (false, false, sizeof (rgba_color_t));
      for (const char **entry = rainbow; *entry; entry++)
      {
        rgba_color_t color = {0, 0, 0, 255};
        if (!parse_color (*entry, color.r, color.g, color.b, color.a))
          fail (false, "Failed parsing foreground color `%s`", *entry);
        g_array_append_val (foreground_palette, color);
      }
      if (foreground_palette->len)
	foreground_color =
	  g_array_index (foreground_palette, rgba_color_t, 0);
      foreground_use_palette = true;

      if (!stroke_specified)
        stroke_enabled = true;

      return;
    }

    if (!strchr (foreground, ','))
    {
      rgba_color_t color = {0, 0, 0, 255};
      if (!parse_color (foreground, color.r, color.g, color.b, color.a))
        fail (false, "Failed parsing foreground color `%s`", foreground);
      foreground_color = color;
      return;
    }

    foreground_palette = g_array_new (false, false, sizeof (rgba_color_t));
    char **entries = g_strsplit (foreground, ",", -1);
    for (unsigned i = 0; entries[i]; i++)
    {
      char *entry = g_strstrip (entries[i]);
      if (!*entry)
        continue;

      rgba_color_t color = {0, 0, 0, 255};
      if (!parse_color (entry, color.r, color.g, color.b, color.a))
        fail (false, "Failed parsing foreground color `%s`", entry);
      g_array_append_val (foreground_palette, color);
    }
    g_strfreev (entries);

    if (!foreground_palette->len)
    {
      g_array_unref (foreground_palette);
      foreground_palette = nullptr;
      return;
    }

    foreground_color = g_array_index (foreground_palette, rgba_color_t, 0);
    foreground_use_palette = true;
  }

  struct rgba_color_t {
    unsigned r, g, b, a;
  };
  struct custom_palette_entry_t {
    unsigned index;
    rgba_color_t color;
  };

  char *fore = nullptr;
  char *stroke = nullptr;
  char *back = nullptr;
  unsigned int palette = 0;
  char *custom_palette = nullptr;
  GArray *custom_palette_entries = nullptr;
  GArray *foreground_palette = nullptr;
  hb_bool_t foreground_use_palette = false;
  hb_bool_t foreground_use_rainbow = false;
  hb_bool_t stroke_enabled = false;
  rgba_color_t stroke_color = {0, 0, 0, 255};
  double stroke_width = DEFAULT_STROKE_WIDTH;
  rgba_color_t foreground_color = {0, 0, 0, 255};
  rgba_color_t background_color = {255, 255, 255, 255};
  hb_bool_t has_background = false;
  double line_space = 0;
  bool have_font_extents = false;
  struct font_extents_t {
    double ascent, descent, line_gap;
  } font_extents = {0., 0., 0.};
  struct margin_t {
    double t, r, b, l;
  } margin = {DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN};
  hb_bool_t logical = false;
  hb_bool_t ink = false;
  hb_bool_t show_extents = false;

  bool parse_custom_palette_entries (GError **error)
  {
    if (custom_palette_entries)
    {
      g_array_unref (custom_palette_entries);
      custom_palette_entries = nullptr;
    }
    if (!custom_palette || !*custom_palette)
      return true;

    custom_palette_entries = g_array_new (false, false, sizeof (custom_palette_entry_t));
    char **entries = g_strsplit (custom_palette, ",", -1);
    unsigned next_index = 0;
    for (unsigned i = 0; entries && entries[i]; i++)
    {
      char *entry = g_strstrip (entries[i]);
      if (!*entry)
        continue;

      unsigned idx = next_index;
      char *color_spec = entry;
      char *eq = strchr (entry, '=');
      if (eq)
      {
        *eq = '\0';
        char *idx_spec = g_strstrip (entry);
        color_spec = g_strstrip (eq + 1);
        if (!*idx_spec || !*color_spec)
        {
          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                       "Invalid --custom-palette entry");
          g_strfreev (entries);
          return false;
        }
        char *end = nullptr;
        errno = 0;
        unsigned long parsed = strtoul (idx_spec, &end, 10);
        if (errno || !end || *g_strstrip (end))
        {
          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                       "Invalid --custom-palette index `%s`", idx_spec);
          g_strfreev (entries);
          return false;
        }
        idx = (unsigned) parsed;
        next_index = idx + 1;
      }

      rgba_color_t color = {0, 0, 0, 255};
      if (!parse_color (color_spec, color.r, color.g, color.b, color.a))
      {
        g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                     "Invalid --custom-palette color `%s`", color_spec);
        g_strfreev (entries);
        return false;
      }

      custom_palette_entry_t parsed = {idx, color};
      g_array_append_val (custom_palette_entries, parsed);
      if (!eq)
        next_index++;
    }
    g_strfreev (entries);

    if (!custom_palette_entries->len)
    {
      g_array_unref (custom_palette_entries);
      custom_palette_entries = nullptr;
    }
    return true;
  }
};


static gboolean
parse_font_extents (const char *name G_GNUC_UNUSED,
		    const char *arg,
		    gpointer    data,
		    GError    **error G_GNUC_UNUSED)
{
  view_options_t *view_opts = (view_options_t *) data;
  view_options_t::font_extents_t &e = view_opts->font_extents;
  switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf", &e.ascent, &e.descent, &e.line_gap)) {
    case 1: HB_FALLTHROUGH;
    case 2: HB_FALLTHROUGH;
    case 3:
      view_opts->have_font_extents = true;
      return true;
    default:
      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		   "%s argument should be one to three space-separated numbers",
		   name);
      return false;
  }
}

static gboolean
parse_margin (const char *name G_GNUC_UNUSED,
	      const char *arg,
	      gpointer    data,
	      GError    **error G_GNUC_UNUSED)
{
  view_options_t *view_opts = (view_options_t *) data;
  view_options_t::margin_t &m = view_opts->margin;
  if (parse_1to4_doubles (arg, &m.t, &m.r, &m.b, &m.l))
    return true;
  g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
	       "%s argument should be one to four space-separated numbers",
	       name);
  return false;
}

void
view_options_t::add_options (option_parser_t *parser)
{
  GOptionEntry entries[] =
  {
    {"annotate",	0, G_OPTION_FLAG_HIDDEN,
			      G_OPTION_ARG_NONE,	&this->show_extents,		"Annotate output rendering",				nullptr},
    {"background",	0, 0, G_OPTION_ARG_STRING,	&this->back,			"Set background color (default: " DEFAULT_BACK ")",	"rrggbb/rrggbbaa"},
    {"foreground",	0, 0, G_OPTION_ARG_STRING,	&this->fore,			"Set foreground color (default: " DEFAULT_FORE ")",	"rrggbb/rrggbbaa[,...]"},
    {"rainbow",		0, 0, G_OPTION_ARG_NONE,	&this->foreground_use_rainbow,	"Rotate glyph foreground colors through a default palette",	nullptr},
    {"lgbtq",		0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,	&this->foreground_use_rainbow,	"Hidden alias for --rainbow",	nullptr},
    {"stroke",		0, 0, G_OPTION_ARG_STRING,	&this->stroke,			"Stroke glyph outlines; `--stroke=` enables defaults",	"rrggbb/rrggbbaa[+width]"},
    {"font-palette",    0, 0, G_OPTION_ARG_INT,         &this->palette,                 "Set font palette (default: 0)",                "index"},
    {"custom-palette",  0, 0, G_OPTION_ARG_STRING,      &this->custom_palette,          "Custom palette",                               "comma-separated colors"},
    {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&this->line_space,		"Set space between lines (default: 0)",			"units"},
    {"font-extents",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_font_extents,	"Set font ascent/descent/line-gap (default: auto)","one to three numbers"},
    {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
    {"logical",		0, 0, G_OPTION_ARG_NONE,	&this->logical,		"Render to logical box instead of union of logical and ink boxes",	nullptr},
    {"ink",		0, 0, G_OPTION_ARG_NONE,	&this->ink,			"Render to ink box instead of union of logical and ink boxes",	nullptr},
    {"show-extents",	0, 0, G_OPTION_ARG_NONE,	&this->show_extents,		"Draw glyph extents",							nullptr},
    {nullptr}
  };
  parser->add_group (entries,
		     "view",
		     "View options:",
		     "Options for output rendering",
			     this);
}

#endif
