 /*
  *                            COPYRIGHT
  *
  *  pcb-rnd, interactive printed circuit board design
  *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  *  Contact:
  *    Project page: http://repo.hu/projects/pcb-rnd
  *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
  *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
  */

#include <libcschem/config.h>

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

#include <librnd/core/error.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/compat_misc.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/compat_misc.h>
#include <librnd/plugins/lib_exp_pixmap/draw_pixmap.h>

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

static rnd_hid_t png_hid;

const char *png_cookie = "png HID";

static rnd_drwpx_t pctx_, *pctx = &pctx_;

static FILE *png_f;

static const rnd_export_opt_t png_attribute_list[] = {
	{"outfile", "Graphics output file",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_pngfile 0

	{"dpi", "Scale factor (pixels/inch). 0 to scale to specified size",
	 RND_HATT_INTEGER, 0, 10000, {100, 0, 0}, 0},
#define HA_dpi 1

	{"x-max", "Maximum width (pixels). 0 to not constrain",
	 RND_HATT_INTEGER, 0, 10000, {0, 0, 0}, 0},
#define HA_xmax 2

	{"y-max", "Maximum height (pixels). 0 to not constrain",
	 RND_HATT_INTEGER, 0, 10000, {0, 0, 0}, 0},
#define HA_ymax 3

	{"xy-max", "Maximum width and height (pixels). 0 to not constrain",
	 RND_HATT_INTEGER, 0, 10000, {0, 0, 0}, 0},
#define HA_xymax 4

	{"monochrome", "Convert to monochrome",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_mono 5

	{"fill-gray-threshold", "In monochrome, fill polygons with gray 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 6

	{"use-alpha", "Make the background transparent",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_use_alpha 7

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

	{"format", "Export file format",
	 RND_HATT_ENUM, 0, 0, {0, 0, 0}, rnd_drwpx_filetypes},
#define HA_filetype 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(png_attribute_list)/sizeof(png_attribute_list[0]))

static rnd_hid_attr_val_t png_values[NUM_OPTIONS];

static const rnd_export_opt_t *png_get_export_options(rnd_hid_t *hid, int *n, rnd_design_t *dsg, void *appspec)
{
	const char *val = png_values[HA_pngfile].str;

	if ((dsg != NULL) && ((val == NULL) || (*val == '\0')))
		csch_derive_default_filename(dsg, sch_rnd_export_appspec_prj(appspec), &png_values[HA_pngfile], ".png");

	if (n)
		*n = NUM_OPTIONS;
	return png_attribute_list;
}

static const char *filename;
static rnd_box_t *bounds;
static double png_fill_gray_thrs;


static rnd_hid_attr_val_t *png_options;

static void png_head(void)
{
	pctx->ymirror = 1;
	rnd_drwpx_start(pctx);
}

static void png_finish(FILE *f)
{
	rnd_drwpx_finish(pctx, f, png_options[HA_filetype].lng);
}

static void png_hid_export_to_file(rnd_design_t *hl, FILE *the_file, rnd_hid_attr_val_t *options, rnd_xform_t *xform)
{
	rnd_box_t region;
	rnd_hid_expose_ctx_t ctx;
	double dtmp;

	png_f = the_file;

	region.X1 = hl->dwg.X1;
	region.Y1 = hl->dwg.Y1;
	region.X2 = hl->dwg.X2;
	region.Y2 = hl->dwg.Y2;

	png_options = options;
	bounds = &region;

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

	xform->no_render_select = xform->no_render_hilight = !options[HA_screen_color].lng;
	pctx->in_mono = options[HA_mono].lng;
	png_head();

	ctx.design = hl;
	ctx.view = *bounds;
	rnd_app.expose_main(&png_hid, &ctx, xform);
}

static int png_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};
	FILE *f;

	rnd_drwpx_init(pctx, dsg);

	filename = cschem_export_filename(dsg, options[HA_pngfile].str, NULL, appspec->fn_page_suffix, ".png");

	if (rnd_drwpx_set_size(pctx, NULL, options[HA_dpi].lng, options[HA_xmax].lng, options[HA_ymax].lng, options[HA_xymax].lng) != 0) {
		rnd_drwpx_uninit(pctx);
		return -1;
	}

	if (rnd_drwpx_create(pctx, options[HA_use_alpha].lng) != 0) {
		rnd_message(RND_MSG_ERROR, "png_do_export():  Failed to create bitmap of %d * %d returned NULL. Aborting export.\n", pctx->w, pctx->h);
		rnd_drwpx_uninit(pctx);
		return -1;
	}

	f = rnd_fopen_askovr(dsg, filename, "wb", ovr);
	if (f == NULL) {
		perror(filename);
		rnd_drwpx_uninit(pctx);
		return -1;
	}

	sch_rnd_set_export_layers(&xform, options[HA_layers].str);
	png_hid_export_to_file(dsg, f, options, &xform);

	png_finish(f);
	if (f != NULL)
		fclose(f);

	rnd_drwpx_uninit(pctx);
	return 0;
}

static void png_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_;

	if (!options) {
		png_get_export_options(hid, 0, design, appspec);
		options = png_values;
	}

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

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

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

static int png_parse_arguments(rnd_hid_t *hid, int *argc, char ***argv)
{
	rnd_export_register_opts2(hid, png_attribute_list, sizeof(png_attribute_list) / sizeof(png_attribute_list[0]), png_cookie, 0);
	return rnd_hid_parse_command_line(argc, argv);
}

static int png_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)
{
	return 1;
}

static void png_set_drawing_mode(rnd_hid_t *hid, rnd_composite_op_t op, rnd_bool direct, const rnd_box_t *screen)
{
	rnd_drwpx_set_drawing_mode(pctx, hid, op, direct, screen);
}

static rnd_color_t png_last_color;
static void png_set_color(rnd_hid_gc_t gc, const rnd_color_t *color)
{
	png_last_color = *color;
	rnd_drwpx_set_color(pctx, gc, color);
}

static void png_fill_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	rnd_drwpx_fill_rect(pctx, gc, x1, y1, x2, y2);
}

static void png_draw_line(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	rnd_drwpx_draw_line(pctx, gc, x1, y1, x2, y2);
}

static void png_draw_rect(rnd_hid_gc_t gc, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2)
{
	png_draw_line(gc, x1, y1, x2, y1);
	png_draw_line(gc, x2, y1, x2, y2);
	png_draw_line(gc, x2, y2, x1, y2);
	png_draw_line(gc, x1, y2, x1, y1);
}

static void png_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_drwpx_draw_arc(pctx, gc, cx, cy, width, height, start_angle, delta_angle);
}

static void png_fill_circle(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t radius)
{
	rnd_drwpx_fill_circle(pctx, gc, cx, cy, radius);
}

static void png_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 (pctx->in_mono) {
		double intens2 = png_last_color.fr * png_last_color.fr + png_last_color.fg * png_last_color.fg + png_last_color.fb * png_last_color.fb;
		if (intens2 >= png_fill_gray_thrs) {
			rnd_color_t clr = png_last_color;
			int avg = rnd_round((double)(clr.r + clr.g + clr.b) / 3.0);
			pctx->in_mono = 0;
			clr.r = clr.g = clr.b = avg;
			rnd_drwpx_set_color(pctx, gc, &clr);
			pctx->in_mono = 1;
		}
	}

	rnd_drwpx_fill_polygon_offs(pctx, gc, n_coords, x, y, dx, dy);
}


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

#if 0
static void png_draw_pixmap(rnd_hid_t *hid, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t sx, rnd_coord_t sy, rnd_pixmap_t *pixmap)
{
	rnd_drwpx_draw_pixmap(pctx, hid, cx, cy, sx, sy, pixmap);
}
#endif

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

int pplg_check_ver_export_png(int ver_needed) { return 0; }

void pplg_uninit_export_png(void)
{
	rnd_export_remove_opts_by_cookie(png_cookie);

	if (rnd_drwpx_has_any_format())
		rnd_hid_remove_hid(&png_hid);
}

int pplg_init_export_png(void)
{
	RND_API_CHK_VER;

	memset(&png_hid, 0, sizeof(rnd_hid_t));

	rnd_hid_nogui_init(&png_hid);

	png_hid.struct_size = sizeof(rnd_hid_t);
	png_hid.name = "png";
	png_hid.description = "GIF/JPEG/PNG export";
	png_hid.exporter = 1;

	png_hid.get_export_options = png_get_export_options;
	png_hid.do_export = png_do_export;
	png_hid.parse_arguments = png_parse_arguments;
	png_hid.set_layer_group = png_set_layer_group;
	png_hid.make_gc = rnd_drwpx_make_gc;
	png_hid.destroy_gc = rnd_drwpx_destroy_gc;
	png_hid.set_drawing_mode = png_set_drawing_mode;
	png_hid.set_color = png_set_color;
	png_hid.set_line_cap = rnd_drwpx_set_line_cap;
	png_hid.set_line_width = rnd_drwpx_set_line_width;
	png_hid.set_draw_xor = rnd_drwpx_set_draw_xor;
	png_hid.draw_line = png_draw_line;
	png_hid.draw_arc = png_draw_arc;
	png_hid.draw_rect = png_draw_rect;
	png_hid.fill_circle = png_fill_circle;
	png_hid.fill_polygon = png_fill_polygon;
	png_hid.fill_polygon_offs = png_fill_polygon_offs;
	png_hid.fill_rect = png_fill_rect;
/*	png_hid.draw_pixmap = png_draw_pixmap;*/
	png_hid.argument_array = png_values;

	png_hid.usage = png_usage;

	if (rnd_drwpx_has_any_format()) {
		rnd_hid_register_hid(&png_hid);
		rnd_hid_load_defaults(&png_hid, png_attribute_list, NUM_OPTIONS);
	}
	return 0;
}
