/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design - web2 (html+svg+js) export
 *  (this file is based on PCB, interactive printed circuit board design)
 *  Copyright (C) 2006 Dan McMahill
 *  Copyright (C) 2016,2022,2024,2026 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., 31 Milk Street, # 960789 Boston, MA 02196 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")
 */

/* Based on the svg exporter */

#include "config.h"
#include "conf_core.h"

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <genvector/gds_char.h>

#include <librnd/core/math_helper.h>
#include <librnd/core/rnd_conf.h>
#include "board.h"
#include "data.h"
#include "draw.h"
#include <librnd/core/error.h>
#include "layer.h"
#include "layer_vis.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 "funchash_core.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 "hid_cam.h"

#include <librnd/plugins/lib_exp_text/draw_web2.h>

static rnd_hid_t web2_hid;

const char *web2_cookie = "web2 HID";

static pcb_cam_t web2_cam;

static rnd_web2_t pctx_, *pctx = &pctx_;

static const char *colorspace_names[] = {
	"color",
	"grayscale",
	"monochrome",
	NULL
};

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

/* %start-doc options "93 SVG Options"
@ftable @code
@item --outfile <string>
Name of the file to be exported to. Can contain a path.
@end ftable
%end-doc
*/
	{"outfile", "Graphics output file",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_web2file 0

/* %start-doc options "93 SVG Options"
@ftable @code
@cindex photo-mode
@item --photo-mode
Export a photo realistic image of the layout.
@end ftable
%end-doc
*/
	{"photo-mode", "Photo-realistic export mode",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_photo_mode 1

/* %start-doc options "93 SVG Options"
@ftable @code
@cindex opacity
@item --opacity
Layer opacity
@end ftable
%end-doc
*/
	{"opacity", "Layer opacity in percentage (integer, 0..100)",
	 RND_HATT_INTEGER, 0, 100, {100, 0, 0}, 0},
#define HA_opacity 2

/* %start-doc options "93 SVG Options"
@ftable @code
@cindex flip
@item --flip
Flip board, look at it from the bottom side
@end ftable
%end-doc
*/
	{"flip", "Flip board",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_flip 3

	{"as-shown", "Render similar to as shown on screen (display overlays)",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_as_shown 4

	{"true-size", "Attempt to preserve true size for printing",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_true_size 5

	{"photo-noise", "Add noise in photo mode to make the output more realistic",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_photo_noise 6

	{"colorspace", "Export in color or reduce colorspace",
	 RND_HATT_ENUM, 0, 0, {0, 0, 0}, colorspace_names},
#define HA_colorspace 7

/* %start-doc options "93 web2 Options"
@ftable @code
@item --template <string>
Name of the lihata template file to be used instead of the default web2 template. Can contain a path.
@end ftable
%end-doc
*/
	{"template", "web2 template (lihata file)",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_template 8

	{"cam", "CAM instruction",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0}
#define HA_cam 9
};

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

static rnd_hid_attr_val_t web2_values[NUM_OPTIONS];

static const rnd_export_opt_t *web2_get_export_options(rnd_hid_t *hid, int *n, rnd_design_t *dsg, void *appspec)
{
	const char *suffix = ".html";
	const char *val = web2_values[HA_web2file].str;

	if ((dsg != NULL) && ((val == NULL) || (*val == '\0')))
		pcb_derive_default_filename(dsg->loadname, &web2_values[HA_web2file], suffix);

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

static void web2_gen_search(rnd_design_t *dsg)
{
	pcb_subc_t *subc;
	gdl_iterator_t it;
	pcb_board_t *pcb = (pcb_board_t *)dsg;
	static const rnd_coord_t boarder = RND_MM_TO_COORD(1);

	subclist_foreach(&pcb->Data->subc, &it, subc) {
		if ((subc->refdes != NULL) && (*subc->refdes != '\0'))
			rnd_web2_search_append(pctx, subc->refdes, subc->BoundingBox.X1 - boarder, subc->BoundingBox.Y1 - boarder, subc->BoundingBox.X2 + boarder, subc->BoundingBox.Y2 + boarder);
	}
}

static void web2_hid_export_to_file(rnd_design_t *dsg, FILE *the_file, rnd_hid_attr_val_t * options, rnd_xform_t *xform)
{
	static int saved_layer_stack[PCB_MAX_LAYER];
	rnd_hid_expose_ctx_t ctx;

	ctx.design = dsg;
	ctx.view.X1 = dsg->dwg.X1;
	ctx.view.Y1 = dsg->dwg.Y1;
	ctx.view.X2 = dsg->dwg.X2;
	ctx.view.Y2 = dsg->dwg.Y2;

	pctx->outf = the_file;

	memcpy(saved_layer_stack, pcb_layer_stack, sizeof(pcb_layer_stack));

	{
		rnd_conf_force_set_bool(conf_core.editor.show_solder_side, 0);

		if (options[HA_photo_mode].lng) {
			pctx->svg.photo_mode = 1;
		}
		else
			pctx->svg.photo_mode = 0;

		if (options[HA_photo_noise].lng)
			pctx->svg.photo_noise = 1;
		else
			pctx->svg.photo_noise = 0;

		if (options[HA_flip].lng) {
			rnd_layer_id_t topcop[32];
			int n, v;

			rnd_conf_force_set_bool(conf_core.editor.show_solder_side, 1);

			/* make sure bottom side copper is top on visibility so it is rendered last */
			pcb_layervis_save_stack();
			v = pcb_layer_list(PCB, PCB_LYT_BOTTOM | PCB_LYT_COPPER, topcop, sizeof(topcop)/sizeof(topcop[0]));
			for(n = 0; n < v; n++)
				pcb_layervis_change_group_vis(&PCB->hidlib, topcop[n], 1, 1);
		}
	}

	rnd_web2_background(pctx);

	if (options[HA_as_shown].lng) {
		pcb_draw_setup_default_gui_xform(xform);

		/* disable (exporter default) hiding overlay in as_shown */
		xform->omit_overlay = 0;
		xform->add_gui_xform = 1;
		xform->enable_silk_invis_clr = 1;
	}

	rnd_web2_layer_group_map(pctx, 1);
	rnd_app.expose_main(&web2_hid, &ctx, xform);
	rnd_web2_layer_group_map(pctx, 0);


	/* pcb-rnd always emits a single page as boards are single-page by definition */
	rnd_web2_print_page_pre(pctx, rnd_hid_export_fn(pctx->hidlib->loadname));
	rnd_app.expose_main(&web2_hid, &ctx, xform);
	web2_gen_search(dsg);
	rnd_web2_print_page_post(pctx);

	if (pctx->svg.flip)
		pcb_layervis_restore_stack();
	rnd_conf_update(NULL, -1); /* restore forced sets */
}

static void web2_do_export(rnd_hid_t *hid, rnd_design_t *design, rnd_hid_attr_val_t *options, void *appspec)
{
	const char *filename;
	int save_ons[PCB_MAX_LAYER];
	rnd_xform_t xform;
	FILE *f = NULL;

	pctx->svg.comp_cnt = 0;

	if (!options) {
		web2_get_export_options(hid, 0, design, appspec);
		options = web2_values;
	}

	;
	pcb_cam_begin(PCB, &web2_cam, &xform, options[HA_cam].str, web2_attribute_list, NUM_OPTIONS, options);

	if (web2_cam.fn_template == NULL) {
		const char *fn;

		filename = options[HA_web2file].str;
		if (!filename)
			filename = "pcb.html";

		fn = web2_cam.active ? web2_cam.fn : filename;
		f = rnd_fopen_askovr(&PCB->hidlib, fn, "wb", NULL);
		if (f == NULL) {
			int ern = errno;
			rnd_message(RND_MSG_ERROR, "web2_do_export(): failed to open %s: %s\n", fn, strerror(ern));
			perror(filename);
			return;
		}
	}
	else
		f = NULL;

	rnd_web2_init(pctx, design, f, options[HA_template].str);
	pctx->svg.opacity = options[HA_opacity].lng;
	pctx->svg.flip = options[HA_flip].lng;
	pctx->svg.true_size = options[HA_true_size].lng;

	if (f != NULL)
		rnd_web2_print_header(pctx);

	pctx->svg.colorspace = options[HA_colorspace].lng;

	if (!web2_cam.active)
		pcb_hid_save_and_show_layer_ons(save_ons);

	web2_hid_export_to_file(design, pctx->outf, options, &xform);


	if (!web2_cam.active)
		pcb_hid_restore_layer_ons(save_ons);

	if (pctx->outf != NULL) {
		rnd_web2_print_footer(pctx);
		fclose(pctx->outf);
	}
	pctx->outf = NULL;

	if (!web2_cam.active) web2_cam.okempty_content = 1; /* never warn in direct export */

	if (pcb_cam_end(&web2_cam) == 0) {
		if (!web2_cam.okempty_group)
			rnd_message(RND_MSG_ERROR, "web2 cam export for '%s' failed to produce any content (layer group missing)\n", options[HA_cam].str);
	}
	else if (pctx->drawn_objs == 0) {
		if (!web2_cam.okempty_content)
			rnd_message(RND_MSG_ERROR, "web2 cam export for '%s' failed to produce any content (no objects)\n", options[HA_cam].str);
	}

	rnd_web2_uninit(pctx);
}

static int web2_parse_arguments(rnd_hid_t *hid, int *argc, char ***argv)
{
	rnd_export_register_opts2(hid, web2_attribute_list, sizeof(web2_attribute_list) / sizeof(web2_attribute_list[0]), web2_cookie, 0);
	return rnd_hid_parse_command_line(argc, argv);
}

static int web2_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)
{
	int is_our_mask = 0, is_our_silk = 0;

	if (is_empty)
		return 0;

	if (flags & PCB_LYT_UI)
		return 0;

	pcb_cam_set_layer_group(&web2_cam, group, purpose, purpi, flags, xform);

	if (!pctx->mapping) {
		if (web2_cam.fn_changed || (pctx->outf == NULL)) {
			FILE *f = rnd_fopen_askovr(&PCB->hidlib, web2_cam.fn, "wb", NULL);

			if (rnd_web2_new_file(pctx, f, web2_cam.fn) != 0)
				return 0;
			rnd_web2_print_header(pctx);
		}
	}

	if (!web2_cam.active) {
		if (flags & PCB_LYT_INVIS)
			return 0;

		if (flags & PCB_LYT_MASK) {
			if ((!pctx->svg.photo_mode) && (!PCB->LayerGroups.grp[group].vis))
				return 0; /* not in photo mode or not visible */
		}
	}

	switch(flags & PCB_LYT_ANYTHING) {
		case PCB_LYT_MASK: is_our_mask = PCB_LAYERFLG_ON_VISIBLE_SIDE(flags); break;
		case PCB_LYT_SILK: is_our_silk = PCB_LAYERFLG_ON_VISIBLE_SIDE(flags); break;
		default:;
	}

	if (!web2_cam.active) {
		if (!(flags & PCB_LYT_COPPER) && (!is_our_silk) && (!is_our_mask) && !(PCB_LAYER_IS_DRILL(flags, purpi)) && !(PCB_LAYER_IS_ROUTE(flags, purpi)))
			return 0;
	}

	if (pctx->svg.photo_mode && (group < 0)) {
		pctx->svg.drawing_hole = PCB_LAYER_IS_DRILL(flags, purpi);
		return 1; /* photo mode drill: do not create a separate group */
	}

	{
		gds_t tmp_ln;
		const char *name;

		gds_init(&tmp_ln);
		name = pcb_layer_to_file_name(&tmp_ln, layer, flags, purpose, purpi, PCB_FNS_fixed);
		rnd_web2_layer_group_begin(pctx, group, name, is_our_mask);
		gds_uninit(&tmp_ln);
	}

	if (pctx->svg.photo_mode) {
		if (is_our_silk)
			rnd_svg_photo_color = RND_SVG_PHOTO_SILK;
		else if (is_our_mask)
			rnd_svg_photo_color = RND_SVG_PHOTO_MASK;
		else if (group >= 0) {
			if (PCB_LAYERFLG_ON_VISIBLE_SIDE(flags))
				rnd_svg_photo_color = RND_SVG_PHOTO_COPPER;
			else
				rnd_svg_photo_color = RND_SVG_PHOTO_INNER;
		}
	}

	pctx->svg.drawing_hole = PCB_LAYER_IS_DRILL(flags, purpi);
	return 1;
}

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

static void web2_set_color(rnd_hid_gc_t gc, const rnd_color_t *color)
{
	rnd_web2_set_color(pctx, gc, color);
}

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

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

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

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

static void web2_fill_circle(rnd_hid_gc_t gc, rnd_coord_t cx, rnd_coord_t cy, rnd_coord_t radius)
{
	rnd_web2_fill_circle(pctx, gc, cx, cy, radius);
}

static void web2_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)
{
	rnd_web2_fill_polygon_offs(pctx, gc, n_coords, x, y, dx, dy);
}

static void web2_fill_polygon(rnd_hid_gc_t gc, int n_coords, rnd_coord_t *x, rnd_coord_t *y)
{
	rnd_web2_fill_polygon_offs(pctx, gc, n_coords, x, y, 0, 0);
}

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

int pplg_check_ver_export_web2(int ver_needed) { return 0; }

void pplg_uninit_export_web2(void)
{
	rnd_export_remove_opts_by_cookie(web2_cookie);
	rnd_hid_remove_hid(&web2_hid);
}

int pplg_init_export_web2(void)
{
	RND_API_CHK_VER;

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

	rnd_hid_nogui_init(&web2_hid);

	web2_hid.struct_size = sizeof(rnd_hid_t);
	web2_hid.name = "web2";
	web2_hid.description = "Scalable Vector Graphics export";
	web2_hid.exporter = 1;

	web2_hid.get_export_options = web2_get_export_options;
	web2_hid.do_export = web2_do_export;
	web2_hid.parse_arguments = web2_parse_arguments;
	web2_hid.set_layer_group = web2_set_layer_group;
	web2_hid.make_gc = rnd_web2_make_gc;
	web2_hid.destroy_gc = rnd_web2_destroy_gc;
	web2_hid.set_drawing_mode = web2_set_drawing_mode;
	web2_hid.set_color = web2_set_color;
	web2_hid.set_line_cap = rnd_web2_set_line_cap;
	web2_hid.set_line_width = rnd_web2_set_line_width;
	web2_hid.set_draw_xor = rnd_web2_set_draw_xor;
	web2_hid.draw_line = web2_draw_line;
	web2_hid.draw_arc = web2_draw_arc;
	web2_hid.draw_rect = web2_draw_rect;
	web2_hid.fill_circle = web2_fill_circle;
	web2_hid.fill_polygon = web2_fill_polygon;
	web2_hid.fill_polygon_offs = web2_fill_polygon_offs;
	web2_hid.fill_rect = web2_fill_rect;
	web2_hid.set_crosshair = rnd_web2_set_crosshair;
	web2_hid.argument_array = web2_values;

	web2_hid.usage = web2_usage;

	rnd_hid_register_hid(&web2_hid);
	rnd_hid_load_defaults(&web2_hid, web2_attribute_list, NUM_OPTIONS);

	return 0;
}
