/*
 * 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 TEXT_OPTIONS_HH
#define TEXT_OPTIONS_HH

#include "options.hh"

struct text_options_t
{
  text_options_t ()
  : gs (g_string_new (nullptr))
  {}
  ~text_options_t ()
  {
    g_free (text);
    g_free (text_file);
    if (gs)
      g_string_free (gs, true);
    if (in_fp && in_fp != stdin)
      fclose (in_fp);
  }

  void add_options (option_parser_t *parser);

  void post_parse (GError **error G_GNUC_UNUSED)
  {
    if (!text && !text_file)
      text_file = g_strdup ("-");

    if (text && text_file)
    {
      g_set_error (error,
		   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		   "Only one of text and text-file can be set");
      return;
    }

    if (text_file)
    {
      if (0 != strcmp (text_file, "-"))
	in_fp = fopen (text_file, "r");
      else
	in_fp = stdin;

      if (!in_fp)
	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
		     "Failed opening text file `%s': %s",
		     text_file, strerror (errno));
    }
  }

  const char *get_line (unsigned int *len);

  int text_len = -1;
  char *text = nullptr;
  char *text_file = nullptr;

  private:
  FILE *in_fp = nullptr;
  GString *gs = nullptr;
  char *line = nullptr;
  unsigned line_len = UINT_MAX;
  hb_bool_t single_par = false;
};

struct shape_text_options_t : text_options_t
{
  ~shape_text_options_t ()
  {
    g_free (text_before);
    g_free (text_after);
  }

  void add_options (option_parser_t *parser);

  char *text_before = nullptr;
  char *text_after = nullptr;
};


static gboolean
parse_text (const char *name G_GNUC_UNUSED,
	    const char *arg,
	    gpointer    data,
	    GError    **error G_GNUC_UNUSED)
{
  text_options_t *text_opts = (text_options_t *) data;

  if (text_opts->text && !*arg)
  {
    // Empty --text provided but --unicodes called; ignore it. This is useful for scripting.
    return true;
  }
  if (text_opts->text && !text_opts->text_len)
  {
    // Empty --unicodes provided as well as text; ignore it. This is useful for scripting.
    g_free (text_opts->text);
    text_opts->text = nullptr;
  }

  if (text_opts->text)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text or --unicodes can be provided but not both");
    return false;
  }

  text_opts->text_len = -1;
  text_opts->text = g_strdup (arg);
  return true;
}

static bool
encode_unicodes (const char *unicodes,
		 GString    *gs,
		 GError    **error)
{
#define DELIMITERS "<+-|>{},;&#\\xXuUnNiI\n\t\v\f\r "

  char *s = (char *) unicodes;
  char *p;

  while (s && *s)
  {
    while (*s && strchr (DELIMITERS, *s))
      s++;
    if (!*s)
      break;

    errno = 0;
    hb_codepoint_t u = strtoul (s, &p, 16);
    if (errno || s == p)
    {
      g_string_free (gs, TRUE);
      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		   "Failed parsing Unicode value at: '%s'", s);
      return false;
    }

    g_string_append_unichar (gs, u);

    s = p;
  }

#undef DELIMITERS

  return true;
}

static gboolean
parse_unicodes (const char *name G_GNUC_UNUSED,
		const char *arg,
		gpointer    data,
		GError    **error G_GNUC_UNUSED)
{
  text_options_t *text_opts = (text_options_t *) data;

  if (text_opts->text)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text or --unicodes can be provided but not both");
    return false;
  }

  GString *gs = g_string_new (nullptr);
  if (0 == strcmp (arg, "*"))
    g_string_append_c (gs, '*');
  else
    if (!encode_unicodes (arg, gs, error))
      return false;

  text_opts->text_len = gs->len;
  text_opts->text = g_string_free (gs, FALSE);
  return true;
}

static gboolean
parse_text_before (const char *name G_GNUC_UNUSED,
		   const char *arg,
		   gpointer    data,
		   GError    **error)
{
  auto *opts = (shape_text_options_t *) data;

  if (opts->text_before)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text-before or --unicodes-before can be provided but not both");
    return false;
  }

  opts->text_before = g_strdup (arg);
  fprintf(stderr, "%s\n", opts->text_before);
  return true;
}

static gboolean
parse_unicodes_before (const char *name G_GNUC_UNUSED,
		       const char *arg,
		       gpointer    data,
		       GError    **error)
{
  auto *opts = (shape_text_options_t *) data;

  if (opts->text_before)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text-before or --unicodes-before can be provided but not both");
    return false;
  }

  GString *gs = g_string_new (nullptr);
  if (!encode_unicodes (arg, gs, error))
    return false;

  opts->text_before = g_string_free (gs, FALSE);
  return true;
}

static gboolean
parse_text_after (const char *name G_GNUC_UNUSED,
		  const char *arg,
		  gpointer    data,
		  GError    **error)
{
  auto *opts = (shape_text_options_t *) data;

  if (opts->text_after)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text-after or --unicodes-after can be provided but not both");
    return false;
  }

  opts->text_after = g_strdup (arg);
  return true;
}

static gboolean
parse_unicodes_after (const char *name G_GNUC_UNUSED,
		      const char *arg,
		      gpointer    data,
		      GError    **error)
{
  auto *opts = (shape_text_options_t *) data;

  if (opts->text_after)
  {
    g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
		 "Either --text-after or --unicodes-after can be provided but not both");
    return false;
  }

  GString *gs = g_string_new (nullptr);
  if (!encode_unicodes (arg, gs, error))
    return false;

  opts->text_after = g_string_free (gs, FALSE);
  return true;
}

const char *
text_options_t::get_line (unsigned int *len)
{
  if (text)
  {
    if (!line)
    {
      line = text;
      line_len = text_len;
    }
    if (line_len == UINT_MAX)
      line_len = strlen (line);

    if (!line_len)
    {
      *len = 0;
      return nullptr;
    }

    const char *ret = line;
    const char *p = single_par ? nullptr : (const char *) memchr (line, '\n', line_len);
    unsigned int ret_len;
    if (!p)
    {
      ret_len = line_len;
      line += ret_len;
      line_len = 0;
    }
    else
    {
      ret_len = p - ret;
      line += ret_len + 1;
      line_len -= ret_len + 1;
    }

    *len = ret_len;
    return ret;
  }

  g_string_set_size (gs, 0);
  char buf[BUFSIZ];
  while (fgets (buf, sizeof (buf), in_fp))
  {
    unsigned bytes = strlen (buf);
    if (!single_par && bytes && buf[bytes - 1] == '\n')
    {
      bytes--;
      g_string_append_len (gs, buf, bytes);
      break;
    }
    g_string_append_len (gs, buf, bytes);
  }
  if (ferror (in_fp))
    fail (false, "Failed reading text: %s", strerror (errno));
  *len = gs->len;
  return !*len && feof (in_fp) ? nullptr : gs->str;
}

void
text_options_t::add_options (option_parser_t *parser)
{
  GOptionEntry entries[] =
  {
    {"text",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_text,		"Set input text",			"string"},
    {"text-file",	0, 0, G_OPTION_ARG_STRING,	&this->text_file,		"Set input text file-name",		"filename"},
    {"unicodes",      'u', 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_unicodes,	"Set input Unicode codepoints",		"list of hex numbers"},
    {"single-par",	0, 0, G_OPTION_ARG_NONE,	&this->single_par,		"Treat text as single paragraph",	nullptr},
    {nullptr}
  };
  parser->add_group (entries,
		     "text",
		     "Text options:\n\nIf no text is provided, standard input is used for input.\n",
		     "Options for the input text",
		     this);
}

void
shape_text_options_t::add_options (option_parser_t *parser)
{
  text_options_t::add_options (parser);

  GOptionEntry entries[] =
  {
    {"text-before",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_text_before,		"Set text context before each line",	"string"},
    {"text-after",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_text_after,		"Set text context after each line",	"string"},
    {"unicodes-before",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_unicodes_before,	"Set Unicode codepoints context before each line",	"list of hex numbers"},
    {"unicodes-after",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_unicodes_after,	"Set Unicode codepoints context after each line",	"list of hex numbers"},
    {nullptr}
  };
  parser->add_group (entries,
		     "text-context",
		     "Textual context options:",
		     "Options for the input context text",
		     this);
}

#endif
