/*
   This file is part of pcb-rnd and was part of gEDA/PCB but lacked proper
   copyright banner at the fork. It probably has the same copyright as
   gEDA/PCB as a whole in 2011.
*/
#include <libcschem/config.h>

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

#include <libcschem/concrete.h>

#include <librnd/core/error.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/plugins.h>
#include <librnd/core/safe_fs.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/hid_nogui.h>
#include <librnd/hid/hid_init.h>
#include <librnd/hid/hid_attrib.h>
#include <librnd/core/actions.h>
#include <librnd/core/compat_misc.h>
#include <librnd/plugins/lib_exp_text/draw_ps.h>
#include <librnd/plugins/lib_exp_text/media.h>

#include <libcschem/util_export.h>
#include <sch-rnd/draw.h>
#include <sch-rnd/export.h>

#include "export_ps.h"

const char *ps_cookie = "ps HID";

static int ps_set_layer_group(rnd_hid_t *hid, rnd_design_t *design, rnd_layergrp_id_t group, const char *purpose, int purpi, rnd_layer_id_t layer, unsigned int flags, int is_empty, rnd_xform_t **xform);

static const rnd_export_opt_t ps_attribute_list[] = {
	/* other HIDs expect this to be first.  */

/* %start-doc options "91 Postscript Export"
@ftable @code
@item --psfile <string>
Name of the postscript output file. Can contain a path.
@end ftable
%end-doc
*/
	{"psfile", "Postscript output file",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_psfile 0

/* %start-doc options "91 Postscript Export"
@ftable @code
@item --fill-page
Scale output to make the sheet fit the page.
@end ftable
%end-doc
*/
	{"fill-page", "Scale drawing to fill page (default on)",
	 RND_HATT_BOOL, 0, 0, {1, 0, 0}, 0},
#define HA_fillpage 1

/* %start-doc options "91 Postscript Export"
@ftable @code
@item --ps-color
Postscript output in color.
@end ftable
%end-doc
*/
	{"ps-color", "Prints in color",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_color 2

	{"fill-gray-threshold", "If ps-color is not enabled, fill polygons with grey if color is lighter than this percentage (0 is black, 100 is white)",
	 RND_HATT_INTEGER, 0, 100, {80, 0, 0}, 0},
#define HA_fill_gray_threshold 3

/* %start-doc options "91 Postscript Export"
@ftable @code
@cindex ps-invert
@item --ps-invert
Draw objects as white-on-black.
@end ftable
%end-doc
*/
	{"ps-invert", "Draw objects as white-on-black",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_psinvert 4

/* %start-doc options "91 Postscript Export"
@ftable @code
@item --media <media-name>
Size of the media, the postscript is fitted to. The parameter
@code{<media-name>} can be any of the standard names for paper size: @samp{A0}
to @samp{A10}, @samp{B0} to @samp{B10}, @samp{Letter}, @samp{11x17},
@samp{Ledger}, @samp{Legal}, @samp{Executive}, @samp{A-Size}, @samp{B-size},
@samp{C-Size}, @samp{D-size}, @samp{E-size}, @samp{US-Business_Card},
@samp{Intl-Business_Card}.
@end ftable
%end-doc
*/
	{"media", "media type",
	 RND_HATT_ENUM, 0, 0, {22, 0, 0}, rnd_medias},
#define HA_media 5

/* %start-doc options "91 Postscript Export"
@ftable @code
@item --scale <num>
Scale value to compensate for printer sizing errors (1.0 = full scale).
@end ftable
%end-doc
*/
	{"scale", "Scale value to compensate for printer sizing errors (1.0 = full scale)",
	 RND_HATT_REAL, 0.01, 4, {0, 0, 1.00}, 0},
#define HA_scale 6

/* %start-doc options "91 Postscript Export"
@ftable @code
@cindex multi-file
@item --multi-file
Produce multiple files, one per page, instead of a single multi page file.
@end ftable
%end-doc
*/
	{"multi-file", "Produce multiple files, one per page, instead of a single file",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0.40}, 0},
#define HA_multifile 7

/* %start-doc options "91 Postscript Export"
@ftable @code
@cindex show-toc
@item --show-toc
Generate Table of Contents
@end ftable
%end-doc
*/
	{"show-toc", "Print Table of Content (automatic decision unless explicitly changed)",
	 RND_HATT_BOOL, 0, 0, {2, 0, 0}, 0},     /* 2 means "auto" */
#define HA_toc 8

	{"screen-colors", "Allow object highlight and selection color",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_screen_color 9

	{"layers", "List of layers to export or \"GUI\" for exporting what's visible on the GUI at the moment or empty for default export layer visibility",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_layers 10

	{"view", "If not empty, switch to view and compile before exporting",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_view 11

};

#define NUM_OPTIONS (sizeof(ps_attribute_list)/sizeof(ps_attribute_list[0]))

/* All file-scope data is in global struct */
static struct {
	rnd_ps_t ps;

	rnd_bool multi_file;
	double fill_gray_thrs;

	const char *filename;
	rnd_hid_expose_ctx_t exps;

	rnd_hid_attr_val_t ps_values[NUM_OPTIONS];

	int ovr_all;
	int had_page; /* 1 if we ever wrote a page */

	sch_rnd_export_appspec_t *appspec;
} global;

static const rnd_export_opt_t *ps_get_export_options(rnd_hid_t *hid, int *n, rnd_design_t *dsg, void *appspec)
{
	const char *val = global.ps_values[HA_psfile].str;

	if ((dsg != NULL) && ((val == NULL) || (*val == '\0')))
		csch_derive_default_filename(dsg, sch_rnd_export_appspec_prj(appspec), &global.ps_values[HA_psfile], ".ps");
	if (n)
		*n = NUM_OPTIONS;
	return ps_attribute_list;
}

static FILE *psopen(rnd_design_t *hl, const char *base, const char *which)
{
	FILE *ps_open_file;
	char *buf, *suff, *buf2;

	if (base == NULL) /* cam, file name template case */
		return NULL;

	if (!global.multi_file)
		return rnd_fopen_askovr(hl, base, "w", NULL);

	buf = (char *)malloc(strlen(base) + strlen(which) + 5);

	suff = (char *)strrchr(base, '.');
	if (suff) {
		strcpy(buf, base);
		buf2 = strrchr(buf, '.');
		sprintf(buf2, ".%s.%s", which, suff + 1);
	}
	else {
		sprintf(buf, "%s.%s.ps", base, which);
	}
	ps_open_file = rnd_fopen_askovr(hl, buf, "w", &global.ovr_all);
	free(buf);
	return ps_open_file;
}

/* This is used by other HIDs that use a postscript format, like lpr or eps.  */
void ps_hid_export_to_file(rnd_design_t *hl, FILE * the_file, rnd_hid_attr_val_t * options, rnd_xform_t *xform, void *appspec_)
{
	double dtmp;
	int toc;
	rnd_xform_t xform0 = {0};

	if (xform == NULL) xform = &xform0;

	rnd_ps_init(&global.ps, hl, the_file, options[HA_media].lng, options[HA_fillpage].lng, options[HA_scale].dbl);

	/* generic ps config: extra conf from export params */
	global.ps.incolor = options[HA_color].lng;
	global.ps.invert = options[HA_psinvert].lng;
	xform->no_render_select = xform->no_render_hilight = !options[HA_screen_color].lng;

	dtmp = (double)options[HA_fill_gray_threshold].lng / 100.0;
	global.fill_gray_thrs = dtmp * dtmp * 3;

	if (the_file)
		rnd_ps_start_file(&global.ps, "sch-rnd release: sch-rnd " CSCH_VERSION);

	/* reset static vars */
	rnd_ps_use_gc(&global.ps, NULL);

	global.exps.design = hl;
	global.exps.view.X1 = hl->dwg.X1;
	global.exps.view.Y1 = hl->dwg.Y1;
	global.exps.view.X2 = hl->dwg.X2;
	global.exps.view.Y2 = hl->dwg.Y2;

	/* print ToC */
	switch(options[HA_toc].lng) {
		case 0: toc = 0; break; /* explicit off */
		case 1: toc = 1; break; /* explicit on */
		default: toc = 0*global.appspec->exp_prj; /* auto: use toc for project export, don't use toc for single sheet export */
	}

	global.had_page = 0;
	if (!global.multi_file && toc) {
		rnd_ps_begin_toc(&global.ps);
		rnd_app.expose_main(&ps_hid, &global.exps, xform);
		rnd_ps_end_toc(&global.ps);
		global.had_page = 1;
	}

	/* print page(s) */
	rnd_ps_begin_pages(&global.ps);
	rnd_app.expose_main(&ps_hid, &global.exps, xform);
	rnd_ps_end_pages(&global.ps);
}


static FILE *fh;

static int ps_do_export_sheet(rnd_hid_t *hid, rnd_design_t *dsg, rnd_hid_attr_val_t *options, sch_rnd_export_appspec_t *appspec, int *ovr)
{
	rnd_xform_t xform = {0};

	global.appspec = appspec;
	sch_rnd_set_export_layers(&xform, options[HA_layers].str);
	ps_hid_export_to_file(dsg, fh, options, &xform, appspec);
	return 0;
}


static void ps_do_export(rnd_hid_t *hid, rnd_design_t *design, rnd_hid_attr_val_t *options, void *appspec_)
{
	rnd_design_t *hl = design;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	void *view_cookie;
	sch_rnd_export_appspec_t *appspec = (appspec_ == NULL) ? &sch_rnd_no_appspec : appspec_;

	global.ovr_all = 0;

	if (!options) {
		ps_get_export_options(hid, 0, design, appspec);
		options = global.ps_values;
	}

	if (cschem_export_compile_pre((csch_project_t *)sheet->hidlib.project, options[HA_view].str, &view_cookie) != 0)
		return;

	global.multi_file = options[HA_multifile].lng;
	global.filename = cschem_export_filename(hl, options[HA_psfile].str, NULL, NULL, ".ps");

	if (global.multi_file)
		fh = 0;
	else {
		const char *fn = global.filename;
		fh = psopen(hl, fn, "toc");
		if (!fh) {
			perror(fn);
			goto error;
		}
	}


	sch_rnd_export_project_or_sheet(hid, design, options, appspec, ps_do_export_sheet);

	global.multi_file = 0;
	if (fh) {
		rnd_ps_end_file(&global.ps);
		fclose(fh);
	}

	error:;
	cschem_export_compile_post((csch_project_t *)sheet->hidlib.project, &view_cookie);
}

static int ps_parse_arguments(rnd_hid_t *hid, int *argc, char ***argv)
{
	rnd_export_register_opts2(hid, ps_attribute_list, NUM_OPTIONS, ps_cookie, 0);
	return rnd_hid_parse_command_line(argc, argv);
}

static int ps_set_layer_group(rnd_hid_t *hid, rnd_design_t *design, rnd_layergrp_id_t group, const char *purpose, int purpi, rnd_layer_id_t layer, unsigned int flags, int is_empty, rnd_xform_t **xform)
{
	rnd_design_t *hl = design;
	gds_t tmp_ln;
	const char *name;
	int newpage;

	gds_init(&tmp_ln);
	TODO("project: needed for project multisheet export");
	name = "TODO:layer_name";

	if (rnd_ps_printed_toc(&global.ps, group, name)) {
		gds_uninit(&tmp_ln);
		return 0;
	}

	newpage = rnd_ps_is_new_page(&global.ps, group);
	if (newpage) {
		if ((global.ps.pagecount != 0) && global.had_page) {
			rnd_fprintf(global.ps.outf, "showpage\n");
		}
		if (global.multi_file) {
			int nr;
			const char *fn;
			gds_t tmp;

			gds_init(&tmp);
			fn = global.appspec->fn_page_suffix+1;
			nr = rnd_ps_new_file(&global.ps, psopen(hl, global.filename, fn), fn);
			gds_uninit(&tmp);
			if (nr != 0)
				return 0;

			rnd_ps_start_file(&global.ps, "sch-rnd release: sch-rnd " CSCH_VERSION);
		}
		else
			global.had_page = 1;


		{
			gds_t tmp = {0};
			const char *layer_fn = "TODO_ps_filename2";
			rnd_ps_page_frame(&global.ps, 1, layer_fn, 0);
			gds_uninit(&tmp);
		}

		rnd_ps_page_background(&global.ps, 0, 0, 1);
	}

	gds_uninit(&tmp_ln);
	return 1;
}

static void ps_set_drawing_mode(rnd_hid_t *hid, rnd_composite_op_t op, rnd_bool direct, const rnd_box_t *screen)
{
	rnd_ps_set_drawing_mode(&global.ps, hid, op, direct, screen);
}

static rnd_color_t ps_last_color;
static void ps_set_color(rnd_hid_gc_t gc, const rnd_color_t *color)
{
	ps_last_color = *color;
	rnd_ps_set_color(&global.ps, gc, color);
}

static void ps_draw_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	rnd_ps_draw_rect(&global.ps, gc, x1, y1, x2, y2);
}

static void ps_draw_line(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	rnd_ps_draw_line(&global.ps, gc, x1, y1, x2, y2);
}

static void ps_draw_arc(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t width, rnd_coord_t height, rnd_angle_t start_angle, rnd_angle_t delta_angle)
{
	rnd_ps_draw_arc(&global.ps, gc, cx, cy, width, height, start_angle, delta_angle);
}

static void ps_fill_circle(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t radius)
{
	rnd_ps_fill_circle(&global.ps, gc, cx, cy, radius);
}

static void ps_fill_polygon_offs(rnd_hid_gc_t gc, int n_coords, rnd_coord_t *x, rnd_coord_t *y, rnd_coord_t dx, rnd_coord_t dy)
{
	/* maybe tweak fill color so that light fills remain light grey */
	if (!global.ps.incolor) {
		double intens2 = ps_last_color.fr * ps_last_color.fr + ps_last_color.fg * ps_last_color.fg + ps_last_color.fb * ps_last_color.fb;

		if (intens2 >= global.fill_gray_thrs) {
			rnd_color_t clr = ps_last_color;
			int avg = rnd_round((double)(clr.r + clr.g + clr.b) / 3.0);
			clr.r = clr.g = clr.b = avg;

			global.ps.incolor = 1;
			rnd_ps_set_color(&global.ps, gc, &clr);
			global.ps.incolor = 0;
		}
	}

	rnd_ps_fill_polygon_offs(&global.ps, gc, n_coords, x, y, dx, dy);
}


static void ps_fill_polygon(rnd_hid_gc_t gc, int n_coords, rnd_coord_t *x, rnd_coord_t *y)
{
	ps_fill_polygon_offs(gc, n_coords, x, y, 0, 0);
}

static void ps_fill_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	rnd_ps_fill_rect(&global.ps, gc, x1, y1, x2, y2);
}


rnd_hid_t ps_hid;


static int ps_inited = 0;
void ps_ps_init(rnd_hid_t * hid)
{
	if (ps_inited)
		return;

	hid->get_export_options = ps_get_export_options;
	hid->do_export = ps_do_export;
	hid->parse_arguments = ps_parse_arguments;
	hid->set_layer_group = ps_set_layer_group;
	hid->make_gc = rnd_ps_make_gc;
	hid->destroy_gc = rnd_ps_destroy_gc;
	hid->set_drawing_mode = ps_set_drawing_mode;
	hid->set_color = ps_set_color;
	hid->set_line_cap = rnd_ps_set_line_cap;
	hid->set_line_width = rnd_ps_set_line_width;
	hid->set_draw_xor = rnd_ps_set_draw_xor;
	hid->draw_line = ps_draw_line;
	hid->draw_arc = ps_draw_arc;
	hid->draw_rect = ps_draw_rect;
	hid->fill_circle = ps_fill_circle;
	hid->fill_polygon_offs = ps_fill_polygon_offs;
	hid->fill_polygon = ps_fill_polygon;
	hid->fill_rect = ps_fill_rect;
	hid->set_crosshair = rnd_ps_set_crosshair;

	ps_inited = 1;
}

static int ps_usage(rnd_hid_t *hid, const char *topic)
{
	fprintf(stderr, "\nps exporter command line arguments:\n\n");
	rnd_hid_usage(ps_attribute_list, sizeof(ps_attribute_list) / sizeof(ps_attribute_list[0]));
	fprintf(stderr, "\nUsage: sch-rnd [generic_options] -x ps [ps options] foo.rs\n\n");
	return 0;
}

static void plugin_ps_uninit(void)
{
	rnd_remove_actions_by_cookie(ps_cookie);
	rnd_export_remove_opts_by_cookie(ps_cookie);
	ps_inited = 0;
}

void hid_ps_uninit()
{
	plugin_ps_uninit();
	rnd_hid_remove_hid(&ps_hid);
}

void hid_ps_init()
{
	memset(&ps_hid, 0, sizeof(rnd_hid_t));

	rnd_hid_nogui_init(&ps_hid);
	ps_ps_init(&ps_hid);

	ps_hid.struct_size = sizeof(rnd_hid_t);
	ps_hid.name = "ps";
	ps_hid.description = "Postscript export";
	ps_hid.exporter = 1;
	ps_hid.mask_invert = 1;
	ps_hid.argument_array = global.ps_values;

	ps_hid.usage = ps_usage;

	rnd_hid_register_hid(&ps_hid);
	rnd_hid_load_defaults(&ps_hid, ps_attribute_list, NUM_OPTIONS);
}
