#include <math.h>
#include "emit.h"
#include "scene.h"
#include "version.h"

// ---- emit output -----------------------------------------------------------

char standard_us_doc_template_file_name_flag[]   = "<standard us doc template file name flag>";
char standard_euro_doc_template_file_name_flag[] = "<standard euro doc template file name flag>";

// concise floating point output
// idea of %g but with fixed rather than relative precision
// removing excessive 0's often reduces output file size dramatically
// and also eases reading
char *flt_str_fmt(char *fmt, char *buf, double f)
{
  size_t i;

  sprintf(buf, fmt, f);

  // trim off useless zeros and decimals
  for (i = strlen(buf); i > 0 && buf[i-1] == '0'; i--)
    /* skip */ ;
  if (i > 0 && buf[i-1] == '.')
    i--;
  buf[i] = '\0';

  // remove leading zeros before decimal
  if (buf[0] == '0' && buf[1] == '.')
    for (i = 0; (buf[i] = buf[i+1]) != '\0'; i++)
      /* skip */;
  else if (buf[0] == '-' && buf[1] == '0' && buf[2] == '.')
    for (i = 1; (buf[i] = buf[i+1]) != '\0'; i++)
      /* skip */;

  // fix -0
  if (strcmp(buf, "-0") == 0)
    strcpy(buf, "0");

  return buf;
}

char *flt_str(char *buf, double f)
{
	return flt_str_fmt("%.3f", buf, f);
}

// scan and return all the legal forms of special arg: ints, int range, 
// *arg_len is set to number of chars consumed by scanning even if there are range errors
// return value is number of good arg indices scanned into *arg_index_1|2
static int scan_special_arg(SPECIAL_OBJECT *special, 
														int i,								// start index
                            int *arg_len,					// chars scanned
														int *arg_index_1,			// arg array indices (# - 1)
														int *arg_index_2, 
                            SRC_LINE line)				// line # for error messages
{
  int i1, i2, len, n_args, n_errs;

  // try two-arg cases and then one arg and then assume zero
  if (sscanf(&special->code[i], "%d-%d%n",   &i1, &i2, &len) >= 2 ||
      sscanf(&special->code[i], "{%d-%d}%n", &i1, &i2, &len) >= 2) {
    *arg_len = len;
    *arg_index_1 = i1 - 1;
    *arg_index_2 = i2 - 1;
    n_args = 2;
  }
  else if (sscanf(&special->code[i], "%d%n",   &i1, &len) >= 1 ||
           sscanf(&special->code[i], "{%d}%n", &i1, &len) >= 1) {
    *arg_len = len;
    *arg_index_1 = i1 - 1;
    n_args = 1;
  }
  else {
    *arg_len = 0;
    n_args = 0;
  }
  n_errs = 0;
  if (n_args >= 1 && (i1 < 1 || i1 > special->pts->n_pts)) {
    err(line, "special arg #%d: out of range #[1-%d]", i1, special->pts->n_pts);
    ++n_errs;
  }
  if (n_args >= 2 && (i2 < 1 || i2 > special->pts->n_pts)) {
    err(line, "special arg #n-%d: out of range #[1-%d]", i2, special->pts->n_pts);
    ++n_errs;
  }
  return n_errs > 0 ? 0 : n_args;
}

// TikZ only does integer angles
char *fmt_angle_tikz(char *buf, double theta, SRC_LINE line)
{
	int i_theta = (int)((theta >= 0) ? (theta + 0.5) : (theta - 0.5));
	double err = theta - i_theta;
	if (fabs(err) >= 0.1)
		warn(line, "TikZ angle rounding error is %.2 degrees", err);
	return flt_str_fmt("%1.f", buf, theta);
}

char *fmt_angle_pst(char *buf, double theta, SRC_LINE line)
{
	return flt_str(buf, theta);
}

typedef char * (*FMT_ANGLE_FUNC)(char *buf, double theta, SRC_LINE line);

FMT_ANGLE_FUNC fmt_angle_tbl[] = {
	fmt_angle_pst,
	fmt_angle_tikz,
};

// this parses, substitues, notes any errors, and (if f is set) prints special output
// so it's used both for syntax checking during input and to generate output
void process_special(FILE *f, SPECIAL_OBJECT *special, SRC_LINE line)
{
  char ch, buf1[16], buf2[16];
  int i_arg_prev, i_arg, arg_len, arg_index_1, arg_index_2;

  i_arg_prev = i_arg = 0; 
  while ((ch = special->code[i_arg]) != '\0') {
    if (ch == '#') {
      if (special->code[i_arg + 1] == '#') {
        if (f)
          fprintf(f, "%.*s#",
                  i_arg - i_arg_prev,
                  &special->code[i_arg_prev]);
        arg_len = 1;
      }
      else {
        switch (scan_special_arg(special, i_arg + 1, &arg_len, &arg_index_1, &arg_index_2, line)) {
        case 2:
          if (f)
            fprintf(f, "%.*s{%s}", 
              i_arg - i_arg_prev, // number of chars to write
              &special->code[i_arg_prev], // start of chars
							(*fmt_angle_tbl[global_env->output_language])
								(	buf1, 
									180 / PI *
									atan2(special->pts->v[arg_index_2][Y] - special->pts->v[arg_index_1][Y],
										    special->pts->v[arg_index_2][X] - special->pts->v[arg_index_1][X]),
									line));
          break;
        case 1:
          if (f)
            fprintf(f, "%.*s(%s,%s)", 
              i_arg - i_arg_prev, // number of chars to write
              &special->code[i_arg_prev], // start of chars
              flt_str(buf1, special->pts->v[arg_index_1][X]),
              flt_str(buf2, special->pts->v[arg_index_1][Y]));
          break;
        case 0:
          if (arg_len == 0) {  // couldn't scan an index at all
            if (f)
              fprintf(f, "%.*s#", i_arg - i_arg_prev, &special->code[i_arg_prev]);
            warn(line, "use of '#' not as special arg (try ##)", 
              arg_len, &special->code[i_arg]);
          }
          break;
        }
      }
      i_arg += (arg_len + 1);
      i_arg_prev = i_arg;
    }
    else  {
      ++i_arg;
    }
  }
  // print out the last stretch of code
  if (f)
    fprintf(f, "%s\n", &special->code[i_arg_prev]);
}

static void emit_points_pst(FILE *f, POINT_LIST_3D *pts)
{
  int i;
  char buf1[16], buf2[16];

  for (i = 0; i < pts->n_pts; i++) 
    fprintf(f, "(%s,%s)", 
      flt_str(buf1, pts->v[i][X]), 
      flt_str(buf2, pts->v[i][Y]));
}

static void emit_dots_pst(FILE *f, OBJECT *obj)
{
  DOTS_OBJECT *dots = (DOTS_OBJECT*)obj;
  fprintf(f, "\\psdots");
	emit_opts(f, dots->opts, global_env->output_language);
  emit_points_pst(f, dots->pts);
  fprintf(f, "\n");
}

static void emit_line_pst(FILE *f, OBJECT *obj)
{
  LINE_OBJECT *line = (LINE_OBJECT*)obj;
  fprintf(f, "\\psline");
  emit_opts(f, line->opts, global_env->output_language);
  emit_points_pst(f, line->pts);
  fprintf(f, "\n");
}

static void emit_curve_pst(FILE *f, OBJECT *obj)
{
  CURVE_OBJECT *curve = (CURVE_OBJECT*)obj;
  fprintf(f, "\\pscurve");
  emit_opts(f, curve->opts, global_env->output_language);
  emit_points_pst(f, curve->pts);
  fprintf(f, "\n");
}

static void emit_polygon_pst(FILE *f, OBJECT *obj)
{
  POLYGON_OBJECT *poly = (POLYGON_OBJECT*)obj;
  fprintf(f, "\\pspolygon");
  emit_opts(f, poly->opts, global_env->output_language);
  emit_points_pst(f, poly->pts);
  fprintf(f, "\n");
}

static void emit_special_pst(FILE *f, OBJECT *obj)
{
  process_special(f, (SPECIAL_OBJECT*)obj, no_line);
}

typedef void (*EMIT_FUNC)(FILE *f, OBJECT *);

static EMIT_FUNC emit_tbl_pst[] = 
{
  NULL, // O_BASE
  NULL, // O_TAG_DEF
  NULL, // O_OPTS_DEF
  NULL, // O_SCALAR_DEF
  NULL, // O_POINT_DEF
  NULL, // O_VECTOR_DEF
  NULL, // O_TRANSFORM_DEF
  emit_dots_pst,
  emit_line_pst,
  emit_curve_pst,
  emit_polygon_pst,
  emit_special_pst,
  NULL, // O_SWEEP (flattened)
  NULL, // O_REPEAT (flattened)
  NULL, // O_COMPOUND (flattened)
};

static void emit_points_tkz(FILE *f, POINT_LIST_3D *pts, 
														char *twixt, char *final)
{
  int i;
  char buf1[16], buf2[16];

	for (i = 0; i < pts->n_pts; i++)
    fprintf(f, "(%s,%s)%s",
      flt_str(buf1, pts->v[i][X]), 
      flt_str(buf2, pts->v[i][Y]),
			(i == pts->n_pts - 1) ? final : twixt);
}

static void emit_dots_tkz(FILE *f, OBJECT *obj)
{
  DOTS_OBJECT *dots = (DOTS_OBJECT*)obj;
  fprintf(f, "\\fill");
  emit_opts(f, dots->opts, global_env->output_language);
  emit_points_tkz(f, dots->pts, " circle (2pt)", " circle (2pt)");
  fprintf(f, ";\n");
}

static void emit_line_tkz(FILE *f, OBJECT *obj)
{
  LINE_OBJECT *line = (LINE_OBJECT*)obj;
  fprintf(f, "\\draw");
  emit_opts(f, line->opts, global_env->output_language);
  emit_points_tkz(f, line->pts, "--", "");
  fprintf(f, ";\n");
}

static void emit_curve_tkz(FILE *f, OBJECT *obj)
{
  CURVE_OBJECT *curve = (CURVE_OBJECT*)obj;
  fprintf(f, "\\curve");
  emit_opts(f, curve->opts, global_env->output_language);
  emit_points_tkz(f, curve->pts, "--", "");
  fprintf(f, ";\n");
}

static void emit_polygon_tkz(FILE *f, OBJECT *obj)
{
  POLYGON_OBJECT *poly = (POLYGON_OBJECT*)obj;
  fprintf(f, "\\filldraw");
  emit_opts(f, poly->opts, global_env->output_language);
  emit_points_tkz(f, poly->pts, "--", "--cycle");
  fprintf(f, ";\n");
}

static void emit_special_tkz(FILE *f, OBJECT *obj)
{
  process_special(f, (SPECIAL_OBJECT*)obj, no_line);
}

static EMIT_FUNC emit_tbl_tkz[] = 
{
  NULL, // O_BASE
  NULL, // O_TAG_DEF
  NULL, // O_OPTS_DEF
  NULL, // O_SCALAR_DEF
  NULL, // O_POINT_DEF
  NULL, // O_VECTOR_DEF
  NULL, // O_TRANSFORM_DEF
  emit_dots_tkz,
  emit_line_tkz,
  emit_curve_tkz,
  emit_polygon_tkz,
  emit_special_tkz,
  NULL, // O_SWEEP (flattened)
  NULL, // O_REPEAT (flattened)
  NULL, // O_COMPOUND (flattened)
};

static EMIT_FUNC *emit_tbl_tbl[] = {
	emit_tbl_pst,
	emit_tbl_tkz,
};

#define DOC_TEMPLATE_ESCAPE_STRING "%%SKETCH_OUTPUT%%"
#define DOC_TEMPLATE_ESCAPE_STRING_LEN (sizeof(DOC_TEMPLATE_ESCAPE_STRING) - 1)

char standard_us_doc_template_tikz[] = 
  "\\documentclass[letterpaper,12pt]{article}\n"
  "\\usepackage[x11names,rgb]{xcolor}\n"
  "\\usepackage{tikz}\n"
  "\\usetikzlibrary{snakes}\n"
  "\\usetikzlibrary{arrows}\n"
  "\\usetikzlibrary{shapes}\n"
  "\\usetikzlibrary{backgrounds}\n"
  "\\usepackage{amsmath}\n"
  "\\oddsidemargin 0in\n"
  "\\evensidemargin 0in\n"
  "\\topmargin 0in\n"
  "\\headheight 0in\n"
  "\\headsep 0in\n"
  "\\textheight 9in\n"
  "\\textwidth 6.5in\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n"
  "\\vspace*{\\fill}\n"
  "\\end{document}\n";

char standard_euro_doc_template_tikz[] = 
  "\\documentclass[a4paper,12pt]{article}\n"
  "\\usepackage[x11names,rgb]{xcolor}\n"
  "\\usepackage{tikz}\n"
  "\\usetikzlibrary{snakes}\n"
  "\\usetikzlibrary{arrows}\n"
  "\\usetikzlibrary{shapes}\n"
  "\\usetikzlibrary{backgrounds}\n"
  "\\usepackage{amsmath}\n"
  "\\oddsidemargin -10mm\n"
  "\\evensidemargin -10mm\n"
  "\\topmargin 5mm\n"
  "\\headheight 0cm\n"
  "\\headsep 0cm\n"
  "\\textheight 247mm\n"
  "\\textwidth 160mm\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n"
  "\\vspace*{\\fill}\n"
  "\\end{document}\n";

char standard_us_doc_template_pst[] = 
  "\\documentclass[letterpaper,12pt]{article}\n"
  "\\usepackage{amsmath}\n"
  "\\usepackage{pstricks}\n"
  "\\oddsidemargin 0in\n"
  "\\evensidemargin 0in\n"
  "\\topmargin 0in\n"
  "\\headheight 0in\n"
  "\\headsep 0in\n"
  "\\textheight 9in\n"
  "\\textwidth 6.5in\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n"
  "\\vspace*{\\fill}\n"
  "\\end{document}\n";

char standard_euro_doc_template_pst[] = 
  "\\documentclass[a4paper,12pt]{article}\n"
  "\\usepackage{amsmath}\n"
  "\\usepackage{pstricks}\n"
  "\\oddsidemargin -10mm\n"
  "\\evensidemargin -10mm\n"
  "\\topmargin 5mm\n"
  "\\headheight 0cm\n"
  "\\headsep 0cm\n"
  "\\textheight 247mm\n"
  "\\textwidth 160mm\n"
  "\\begin{document}\n"
  "\\pagestyle{empty}\n"
  "\\vspace*{\\fill}\n"
  "\\begin{center}\n"
  DOC_TEMPLATE_ESCAPE_STRING "\n"
  "\\end{center}\n"
  "\\vspace*{\\fill}\n"
  "\\end{document}\n";

char *standard_us_doc_template[] = {
	standard_us_doc_template_pst,
	standard_us_doc_template_tikz,
};

char *standard_euro_doc_template[] = {
	standard_euro_doc_template_pst,
	standard_euro_doc_template_tikz,
};

char *read_file_as_string(FILE *f)
{
  size_t len = 0;
  int buf_size = 1024;
  char *buf = safe_malloc(buf_size + 1);
  for (;;) {
    len += fread(buf + len, 1, buf_size - len, f);
    if (feof(f) || ferror(f)) {
      buf[len] = '\0';
      return buf;
    }
    buf_size *= 2;
    buf = safe_realloc(buf, buf_size + 1);
  }
}

char *doc_template_from_file(char *file_name, int output_language)
{
  FILE *f;
  char *r;

	if (file_name == NULL)
		return NULL;
  if (file_name == standard_us_doc_template_file_name_flag)
    return safe_strdup(standard_us_doc_template[output_language]);
  if (file_name == standard_euro_doc_template_file_name_flag)
    return safe_strdup(standard_euro_doc_template[output_language]);

  f = fopen(file_name, "r");
  if (!f) {
    err(no_line, "can't open document template '%s%' for input\n", 
      file_name);
    return safe_strdup(standard_us_doc_template_pst);
  }
  r = read_file_as_string(f);
  fclose(f);
  return r;
}

void emit_preamble_pst(FILE *f, BOX_3D *ext, GLOBAL_ENV *env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];

	if (global_env_is_set_p(env, GE_OPTS)) {
    fprintf(f, "\\psset{");
    emit_opts_raw(f, env->opts, global_env->output_language);
    fprintf(f, "}\n");
  }

  if (global_env_is_set_p(env, GE_FRAME)) {
    if (env->frame_opts)
      fprintf(f, "\\psframebox[%s]{", env->frame_opts);
    else
      fprintf(f, "\\psframebox[framesep=0pt]{");
  }

  fprintf(f, "\\begin{pspicture%s}", 
    global_env_is_set_p(env, GE_EXTENT) ? "*" : "");

  if (global_env_is_set_p(env, GE_BASELINE))
    fprintf(f, "[%s]", flt_str(buf1, env->baseline));

  fprintf(f, 
    "(%s,%s)(%s,%s)\n",
    flt_str(buf1, ext->min[X]), 
    flt_str(buf2, ext->min[Y]), 
    flt_str(buf3, ext->max[X]), 
    flt_str(buf4, ext->max[Y]));

	fprintf(f, "\\pstVerb{1 setlinejoin}\n");
}

void emit_preamble_tkz(FILE *f, BOX_3D *ext, GLOBAL_ENV *env)
{
  char buf1[16], buf2[16], buf3[16], buf4[16];
	int picture_opts_p = 0;

  if (global_env_is_set_p(env, GE_FRAME)) {
    if (env->frame_opts)
			warn(no_line, "frame options [%s] ignored (TikZ)", env->frame_opts);
		else {
			fprintf(f, "{\\fboxsep=0pt\\fbox{");
			warn(no_line, "remove frame around TikZ/PGF pictures for debugging");
		}
  }

  fprintf(f, "\\begin{tikzpicture}[join=round");
	if (global_env_is_set_p(env, GE_OPTS)) {
		fprintf(f, ",");
    emit_opts_raw(f, env->opts, global_env->output_language);
	}
	if (global_env_is_set_p(env, GE_BASELINE)) {
		fprintf(f, ",");
    fprintf(f, "baseline=%s", flt_str(buf1, env->baseline));
	}
	fprintf(f, "]\n");
	if (global_env_is_set_p(env, GE_EXTENT)) {
		flt_str(buf1, ext->min[X]); 
		flt_str(buf2, ext->min[Y]); 
		flt_str(buf3, ext->max[X]); 
		flt_str(buf4, ext->max[Y]);
		fprintf(f, 
			"\\useasboundingbox(%s,%s) rectangle (%s,%s);\n"
			"\\clip(%s,%s) rectangle (%s,%s);\n",
			buf1, buf2, buf3, buf4,
			buf1, buf2, buf3, buf4);
	}
}

typedef void (*EMIT_PREAMBLE_FUNC)(FILE *f, BOX_3D *ext, GLOBAL_ENV *env);

EMIT_PREAMBLE_FUNC emit_preamble_tbl[] = {
	emit_preamble_pst,
	emit_preamble_tkz,
};

void emit_postamble_pst(FILE *f, GLOBAL_ENV *env)
{
  fprintf(f, "\\end{pspicture%s}",  
    global_env_is_set_p(env, GE_EXTENT) ? "*" : "");
  if (global_env_is_set_p(env, GE_FRAME))
		fprintf(f, "}");
}

void emit_postamble_tkz(FILE *f, GLOBAL_ENV *env)
{
  fprintf(f, "\\end{tikzpicture}");
  if (global_env_is_set_p(env, GE_FRAME))
		fprintf(f, "}}");
}

typedef void (*EMIT_POSTAMBLE_FUNC)(FILE *f, GLOBAL_ENV *env);

EMIT_POSTAMBLE_FUNC emit_postamble_tbl[] = {
	emit_postamble_pst,
	emit_postamble_tkz,
};

void emit(FILE *f, OBJECT *obj, GLOBAL_ENV *env, char *doc_template_file_name)
{
  BOX_3D ext[1];
  int n_obj;
  OBJECT *p;
  char buf1[16], buf2[16], buf3[16], buf4[16];
  char *escape, *doc_template;

	doc_template = doc_template_from_file(doc_template_file_name, env->output_language);

  get_extent(obj, ext, &n_obj);
  if (n_obj == 0)
    err(no_line, "no objects to write");
  else {

    remark(no_line, "scene bb=(%s,%s)(%s,%s)",
      flt_str(buf1, ext->min[X]), 
      flt_str(buf2, ext->min[Y]), 
      flt_str(buf3, ext->max[X]), 
      flt_str(buf4, ext->max[Y]));

    if (get_transformed_global_env_extent(ext, env)) {
      remark(no_line, "actual bb=(%s,%s)(%s,%s)",
        flt_str(buf1, ext->min[X]), 
        flt_str(buf2, ext->min[Y]), 
        flt_str(buf3, ext->max[X]), 
        flt_str(buf4, ext->max[Y]));
    }

    remark(no_line, "writing %d objects", n_obj);

    fprintf(f, 
			"%% Sketch output, version " VER_STRING "\n"
			"%% Output language: %s\n", output_language_str[env->output_language]);
    escape = NULL;
    if (doc_template) {
      escape = strstr(doc_template, DOC_TEMPLATE_ESCAPE_STRING);
      if (escape)
        fprintf(f, "%.*s", escape - doc_template, doc_template);
      else
        warn(no_line, "document template with no escape '%s' has been ignored",
          DOC_TEMPLATE_ESCAPE_STRING);
    }

		(*emit_preamble_tbl[env->output_language])(f, ext, env);
 
    for (p = obj; p; p = p->sibling) {
			if (emit_tbl_tbl[global_env->output_language][p->tag] == NULL)
        die(no_line, "emit: bad tag %d", p->tag);
      if (xy_overlap_p(p, ext))
        (*emit_tbl_tbl[global_env->output_language][p->tag])(f, p);
    }

		(*emit_postamble_tbl[env->output_language])(f, env);

    if (escape) {
      escape += DOC_TEMPLATE_ESCAPE_STRING_LEN;
      fprintf(f, "%s", escape);
      if (strstr(escape, DOC_TEMPLATE_ESCAPE_STRING)) 
        warn(no_line, "more than one escape in document template; all but first ignored");
    }
    fprintf(f, "%% End sketch output\n");
  }
}
