/******************************************************************************
 *
 * 9998sketch/opts.c
 *
 * (c) Eugene K. Ressler, Jr. 2004, 2005
 *
 * A program related to the book
 *
 * Fundamentals of Virtual World Simulation
 * by Eugene K. Ressler, Jr.
 *
 * No warranty of correctness or capability of any kind is expressed or implied.
 *
 * The author grants free use of this code for any purpose as long any text,
 * executable program, library file, or source code distributed to others 
 * and employing any portion of this code clearly cites the book named above.
 *
 ******************************************************************************/

#include "opts.h"
#include "geometry.h"

DECLARE_DYNAMIC_ARRAY_FUNCS(OPT_LIST, OPT, opt_list, elt, n_elts, NO_OTHER_INIT)

// ---- useful string stuff ----------------------------------------------------

// slice a string using Perl/Python position indexing conventions
// (position == dst_size is always at the end of the string)
char *str_slice(char *dst, int dst_size, char *src, int beg, int end)
{
  int len;
  
  if (dst_size > 0) {
    len = strlen(src);
    if (beg < 0)
      beg = len + beg;
    else if (beg > len)
      beg = len;
    if (end < 0)
      end = len + end;
    else if (end > len)
      end = len;
    len = end - beg;
    if (len <= 0) {
      dst[0] = '\0';
    }
    else {
      if (len >= dst_size)
        len = dst_size - 1;
      memcpy(dst, &src[beg], len);
      dst[len] = '\0';
    }
  }
  return dst;
}

// a modified version of C library strtok
// uses state variable p, which should be
// initially set to zero.
char *istrtok(int *p, char *s, char sep)
{
  int i, r;

  // advance r to next non-space character
  for (r = *p; s[r] == ' ' || s[r] == '\t'; r++)
    /* skip */;

  // if we're at terminating null, return null
  if (s[r] == '\0') {
    *p = r;
    return NULL;
  }

  // look for a separator character
  for (i = r; s[i] != '\0'; i++) {
    if (s[i] == sep) {
      // found one; set to null char,
      // advance state variable, and
      // return pointer to first char
      s[i] = '\0';
      *p = i+1;
      return &s[r];
    }
  }
  // did not find a terminator, so this
  // is the last token; return it
  *p = i;
  return &s[r];
}

int str_last_occurance(char *src, char *set)
{
  int i;
  
  for (i = 0; src[i]; i++)
    /* skip */ ;
  for (--i ; i >= 0 && !strchr(set, src[i]); --i)
    /* skip */ ;
  return i;
}

// ---- options ----------------------------------------------------------------

void init_opts(OPTS *opts)
{
  init_opt_list(opts->list);
}

static OPTS *raw_opts(void)
{
  OPTS *r = safe_malloc(sizeof *r);
  init_opts(r);
  return r;
}

void setup_opts(OPTS *opts, char *opts_str, SRC_LINE line)
{
  int p_pair, p_side;
  char *pair, *key, *val, *buf;
  OPT *opt;

  clear_opts(opts);
  buf = safe_strdup(opts_str);
  p_pair = 0;
  while ((pair = istrtok(&p_pair, buf, ',')) != NULL) {
    p_side = 0;
    key = istrtok(&p_side, pair, '=');
    if (key == NULL) {
      err(line, "null keyword in option");
      key = "";
    }
    val = istrtok(&p_side, pair, ',');
    if (val == NULL) {
      err(line, "null value in option");
      val = "";
    }
    opt = pushed_opt_list_elt(opts->list);
    opt->key = safe_strdup(key);
    opt->val = safe_strdup(val);
  }
  safe_free(buf);
}

OPTS *new_opts(char *opts_str, SRC_LINE line)
{
  OPTS *r = raw_opts();
  setup_opts(r, opts_str, line);
  return r;
}

void clear_opts(OPTS *opts)
{
  int i;

  for (i = 0; i < opts->list->n_elts; ++i) {
    safe_free(opts->list->elt[i].key);
    safe_free(opts->list->elt[i].val);
  }
  clear_opt_list(opts->list);
}

char *opt_val(OPTS *opts, char *opt)
{
  int i;

  if (!opts)
    return 0;

  for (i = 0; i < opts->list->n_elts; i++)
    if (strcmp(opts->list->elt[i].key, opt) == 0)
      return opts->list->elt[i].val;
  return NULL;
}

int bool_opt_p(OPTS *opts, char *opt, int default_p)
{
  char *r = opt_val(opts, opt);
  if (!r)
    return default_p;
  return strcmp(r, "false") != 0;  // all not false is true
}

typedef struct opt_desc_t {
  char *opt;
  int type;
} OPT_DESC;

typedef struct opt_desc_tbl_t {
	OPT_DESC *key_desc;
	int n_key_desc;
	OPT_DESC *val_desc;
	int n_val_desc;
} OPT_DESC_TBL;

static OPT_DESC key_tbl_pst[] = {
  { "arrows",     OPT_LINE },
  { "cull",       OPT_INTERNAL },
  { "dash",       OPT_LINE },
  { "dotsep",     OPT_LINE },
  { "fillcolor",  OPT_POLYGON | OPT_FILL_COLOR },
  { "fillstyle",  OPT_POLYGON | OPT_FILL_STYLE },
  { "lay",        OPT_INTERNAL },
  { "linecolor",  OPT_LINE },
  { "linestyle",  OPT_LINE | OPT_LINE_STYLE },
  { "linewidth",  OPT_LINE },
  { "showpoints", OPT_LINE | OPT_POLYGON },
  { "split",      OPT_INTERNAL }
};

OPT_DESC_TBL opt_desc_tbl_pst[1] = {{	
	key_tbl_pst, ARRAY_SIZE(key_tbl_pst),
  NULL, 0
}};

static OPT_DESC opt_key_tbl_tikz[] = {  
	{ "arrows",										OPT_LINE },
	{ "cap",											OPT_LINE },
	{ "color",										OPT_LINE | OPT_POLYGON | OPT_FILL_COLOR },
  { "cull",											OPT_INTERNAL },
	{ "dash pattern",							OPT_LINE },
	{ "dash phase",								OPT_LINE },
	{ "double distance",					OPT_LINE },
	{ "draw",											OPT_LINE | OPT_LINE_STYLE },
	{ "draw opacity",							OPT_LINE },
	{ "fill",											OPT_POLYGON | OPT_FILL_COLOR },
	{ "fill opacity",							OPT_POLYGON },
	{ "fill style",								OPT_POLYGON | OPT_FILL_COLOR | OPT_EMIT_VAL },
	{ "join",											OPT_LINE },
  { "lay",											OPT_INTERNAL },
	{ "line style",								OPT_LINE | OPT_EMIT_VAL },
  { "line width",								OPT_LINE },
	{ "miter limit",							OPT_LINE },
	{ "pattern",									OPT_POLYGON | OPT_FILL_COLOR },
	{ "pattern color",						OPT_POLYGON },
  { "split",										OPT_INTERNAL },
	{ "style",										OPT_TYPE_IN_VAL | OPT_EMIT_VAL },
};

static OPT_DESC opt_val_tbl_tikz[] = {
	{ "dashed",										OPT_LINE },
	{ "densely dashed",						OPT_LINE },
	{ "densely dotted",						OPT_LINE },
	{ "dotted",										OPT_LINE },
	{ "double",										OPT_LINE },
	{ "loosely dashed",						OPT_LINE },
	{ "loosely dotted",						OPT_LINE },
	{ "nearly opaque",						OPT_POLYGON },
	{ "nearly transparent",				OPT_POLYGON },
	{ "semithick",								OPT_LINE },
	{ "semitransparent",					OPT_POLYGON },
	{ "solid",										OPT_LINE },
	{ "thick",										OPT_LINE },
	{ "thin",											OPT_LINE },
	{ "transparent",							OPT_POLYGON },
	{ "ultra nearly transparent", OPT_POLYGON },
	{ "ultra thick",							OPT_LINE },
	{ "ultra thin",								OPT_LINE },
	{ "very nearly transparent",	OPT_POLYGON },
	{ "very thick",								OPT_LINE },
	{ "very thin",								OPT_LINE },
};

OPT_DESC_TBL opt_desc_tbl_tikz[1] = {{
	opt_key_tbl_tikz, ARRAY_SIZE(opt_key_tbl_tikz),
	opt_val_tbl_tikz, ARRAY_SIZE(opt_val_tbl_tikz), 
}};

int opt_index(char *opt, OPT_DESC *desc, int n_desc)
{
  int hi, lo, mid, cmp_val;

	hi = n_desc - 1;
  lo = 0;
  while (hi >= lo) {
    mid = (hi + lo) / 2;
		cmp_val = strcmp(opt, desc[mid].opt);
    if (cmp_val < 0)
      hi = mid - 1;
    else if (cmp_val > 0)
      lo = mid + 1;
    else
			return mid;
  }
  return -1;
}

static OPT_DESC_TBL *lang_to_opt_desc_tbl[] = {
  opt_desc_tbl_pst,
	opt_desc_tbl_tikz,
};

int simple_opt_type(OPT *opt, int default_type, int lang)
{
	OPT_DESC_TBL *desc;
	int i;

	if (lang < 0)
		return default_type;
	desc = lang_to_opt_desc_tbl[lang];
	i = opt_index(opt->key, desc->key_desc, desc->n_key_desc);
	return (i < 0) ? default_type : desc->key_desc[i].type;
}

int opt_type(OPT *opt, int default_type, int lang)
{
	OPT_DESC_TBL *desc;
	int i, type;
	
	type = simple_opt_type(opt, default_type, lang);
	if (type & OPT_TYPE_IN_VAL) {
		desc = lang_to_opt_desc_tbl[lang];
		i = opt_index(opt->val, desc->val_desc, desc->n_val_desc);
		if (i < 0)
			return default_type;
		type = desc->val_desc[i].type;
	}
	return type;
}

typedef struct opts_desc_t {
	OPT *opts;
	int n_opts;
} OPTS_DESC;

OPT no_edges_opts_pst[] = {
	{ "linestyle", "none" }
};

OPT no_edges_opts_tikz[] = {
	{ "draw", "none" }
};

OPTS_DESC no_edges_opts_desc_tbl[] = {
	{ no_edges_opts_pst,  ARRAY_SIZE(no_edges_opts_pst) },
	{ no_edges_opts_tikz, ARRAY_SIZE(no_edges_opts_tikz) },
};

static int any_opt_p(OPTS *opts, int type, int lang)
{
	int i;

	if (!opts)
		return 0;
  for (i = 0; i < opts->list->n_elts; i++)
    if (type & opt_type(&opts->list->elt[i], OPT_NONE, lang)) 
			return 1;
	return 0;
}

static void add_default_opt(OPTS **opts_ptr, OPT *default_opt, int lang)
{
	OPT *opt;
  OPTS *opts;
	int default_type;
	
	opts = *opts_ptr;
	default_type = opt_type(default_opt, OPT_NONE, lang) & OPT_DEFAULTS;
	if (any_opt_p(opts, default_type, lang))
		return;
  if (!opts)
      opts = raw_opts();
  opt = pushed_opt_list_elt(opts->list);
  opt->key = safe_strdup(default_opt->key);
  opt->val = safe_strdup(default_opt->val);
  *opts_ptr = opts;
}

static void add_default_opts(OPTS **opts_ptr, OPTS_DESC *opts_desc, int lang)
{
	int i;
	for (i = 0; i < opts_desc->n_opts; i++)
		add_default_opt(opts_ptr, &opts_desc->opts[i], lang);
}

void add_no_edges_default_opt(OPTS **opts_ptr, int lang)
{
	add_default_opts(opts_ptr, &no_edges_opts_desc_tbl[lang], lang);
}

OPT solid_white_opts_pst[] = {
	{ "fillstyle", "solid" },
	{ "fillcolor", "white" }
};

OPT solid_white_opts_tikz[] = {
	{ "fill", "white" }
};

OPTS_DESC solid_white_opts_desc_tbl[] = {
	{ solid_white_opts_pst,  ARRAY_SIZE(solid_white_opts_pst) },
	{ solid_white_opts_tikz, ARRAY_SIZE(solid_white_opts_tikz) },
};

void add_solid_white_default_opt(OPTS **opts_ptr, int lang)
{
	add_default_opts(opts_ptr, &solid_white_opts_desc_tbl[lang], lang);
}

void check_opts(OPTS *opts, 
								int allowed, char *allowed_msg,
								int lang, SRC_LINE line)
{
	int i, type;

	if (!opts)
		return;

	for (i = 0; i < opts->list->n_elts; i++) {
    type = opt_type(&opts->list->elt[i], OPT_NONE, lang);
		if ((type & allowed) == 0)
			warn(line, allowed_msg, opts->list->elt[i].key, opts->list->elt[i].val);
	}
}

// selective copy for splitting option lists by type
OPTS *copy_opts(OPTS *opts, int type_mask, int lang)
{
  int i;
  OPTS *r;
  OPT *opt;

  if (!opts)
    return NULL;

  r = raw_opts();
  for (i = 0; i < opts->list->n_elts; i++)
    if (type_mask & opt_type(&opts->list->elt[i], OPT_NONE, lang)) {
      opt = pushed_opt_list_elt(r->list);
			opt->key = safe_strdup(opts->list->elt[i].key);
			opt->val = safe_strdup(opts->list->elt[i].val);
    }
  return r;
}

// selective copy for splitting out line options and modifying arrows
OPTS *copy_line_opts(OPTS *opts, int first_p, int last_p, int lang)
{
  int i;
  OPTS *r;
  char buf[100];

  if (!opts)
    return NULL;

  // no modifications necessary if line contains first and last points
  if (first_p && last_p)
    return opts;

  // make a clean copy and modify the arrows
  r = copy_opts(opts, OPT_LINE, lang);

  for (i = 0; i < r->list->n_elts; i++) {
    if (strcmp("arrows", r->list->elt[i].key) == 0) {
      char *val = r->list->elt[i].val;
      char *dash = strchr(val, '-');
      if (!dash) { 
        warn(no_line, "could not find '-' while splitting arrows option");
        continue;
      }
      if (first_p) {
        str_slice(buf, sizeof buf, val, 0, dash - val + 1);
      }
      else if (last_p) {
        str_slice(buf, sizeof buf, val, dash - val, SLICE_TO_END);
      }
      else {
        // could just delete option entirely, but this is good for debugging
        str_slice(buf, sizeof buf, val, dash - val, dash - val + 1);
      }
      r->list->elt[i].val = safe_strdup(buf);
    }
  }
  return r;
}

static void emit_opts_internal(FILE *f, OPTS *opts, int brackets_p, int lang)
{
  int i, n;

  if (!opts || !opts->list || opts->list->n_elts == 0)
    return;

  if (brackets_p)
    fputc('[', f);

  for (n = i = 0; i < opts->list->n_elts; i++) {
    int type = simple_opt_type(&opts->list->elt[i], OPT_NONE, lang);
    if ((type & OPT_INTERNAL) == 0) {
			if (n > 0)
				fprintf(f, ",");
			if (type & OPT_EMIT_VAL)
				fprintf(f, "%s", opts->list->elt[i].val); 
			else
				fprintf(f, "%s=%s", opts->list->elt[i].key, opts->list->elt[i].val);
      ++n;
    }
  }
  if (brackets_p)
    fputc(']', f);
}

void emit_opts_raw(FILE *f, OPTS *opts, int lang)
{
  emit_opts_internal(f, opts, 0, lang);
}

void emit_opts(FILE *f, OPTS *opts, int lang)
{
  emit_opts_internal(f, opts, 1, lang);
}
