/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019,2020,2024 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/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include "config.h"

#include <ctype.h>

#include <librnd/config.h>
#include <librnd/core/actions.h>
#include <librnd/hid/hid.h>
#include <librnd/core/compat_misc.h>

#include "build_run.h"
#include "data.h"
#include "event.h"

static const char camv_acts_Quit[] = "Quit()";
static const char camv_acth_Quit[] = "Quits the application.";
static fgw_error_t camv_act_Quit(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	camv_rnd_quit_app();
	return FGW_SUCCESS;
}

extern fgw_error_t rnd_gui_act_zoom(fgw_arg_t *res, int argc, fgw_arg_t *argv);

TODO("Figure how to avoid copying this from librnd rnd_gui_acts_zoom")
const char camv_acts_Zoom[] = \
	"Zoom()\n" \
	"Zoom([+|-|=]factor)\n" \
	"Zoom(x1, y1, x2, y2)\n" \
	"Zoom(?)\n" \
	"Zoom(get)\n" \
	"Zoom(auto_first)\n";
const char camv_acth_Zoom[] = "GUI zoom";
fgw_error_t camv_act_Zoom(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	if ((argc > 1) && (argv[1].type & FGW_STR) == FGW_STR) {
		if (strcmp(argv[1].val.str, "auto_first") == 0) { /* zoom to extent when the first file is loaded (called from the menu file) */
			if (camv.layers.used == 1)
				return rnd_actionv_bin(RND_ACT_DESIGN, "rnd_zoom", res, 0, argv);
			return 0;
		}
	}
	return rnd_actionv_bin(RND_ACT_DESIGN, "rnd_zoom", res, argc, argv);
}

#define get_layer(ly, idx, actname) \
	if ((argv[1].type & FGW_STR) == FGW_STR) { \
		const char *targets = NULL; \
		RND_ACT_CONVARG(1, FGW_STR, actname, targets = argv[1].val.str); \
		if (strcmp(targets, "last") == 0) { \
			long n; \
			idx = -1; \
			for(n = 0; n < camv->layers.used; n++) { \
				camv_layer_t *l = camv->layers.array[n]; \
				if ((l != NULL) && !l->sub) \
					idx = n; \
			} \
		} \
		else if ((targets[0] == '@') && (targets[1] == '\0')) { \
			idx = camv->lysel; \
			if ((idx < 0) || (idx >= camv->layers.used)) { \
				rnd_message(RND_MSG_ERROR, "No layer selected\n"); \
				RND_ACT_IRES(-1); \
				return FGW_SUCCESS; \
			} \
		} \
		else \
			RND_ACT_CONVARG(1, FGW_INT, actname, idx = argv[1].val.nat_int); \
	} \
	else \
		RND_ACT_CONVARG(1, FGW_INT, actname, idx = argv[1].val.nat_int); \
	\
	if ((idx < 0) || (idx >= camv->layers.used)) { \
		rnd_message(RND_MSG_ERROR, "No such layer\n"); \
		RND_ACT_IRES(-1); \
		return FGW_SUCCESS; \
	} \
	ly = camv->layers.array[idx];



static const char camv_acts_RotateLayer[] = "RotateLayer(@|last|idx, [deg])";
static const char camv_acth_RotateLayer[] = "Rotates the layer addressed (@ for current) by deg degrees CCW. (The transformation is added to the current transformation matrix of the layer.)";
static fgw_error_t camv_act_RotateLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	camv_design_t *camv = (camv_design_t *)hl;
	double deg;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, RotateLayer);

	RND_ACT_CONVARG(2, FGW_DOUBLE, RotateLayer, deg = argv[2].val.nat_double);

	if (deg == 0) {
		char *rds = rnd_hid_prompt_for(hl, "Please rotation in degrees", "15", "rotate layer");
		deg = strtod(rds, NULL);
		free(rds);
		if (deg == 0)
			return -1;
	}

	rnd_xform_mx_rotate(ly->mx, -deg);
	ly->enable_mx = 1;

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static const char camv_acts_TranslateLayer[] = "TranslateLayer(@|last|idx, dx, dy)";
static const char camv_acth_TranslateLayer[] = "Translates (moves) the layer addressed (@ for current) by dx and dy. (The transformation is added to the current transformation matrix of the layer.)";
static fgw_error_t camv_act_TranslateLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	camv_design_t *camv = (camv_design_t *)hl;
	rnd_coord_t dx, dy;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, TranslateLayer);

	RND_ACT_CONVARG(2, FGW_COORD_, TranslateLayer, dx = fgw_coord(&argv[2]));
	RND_ACT_CONVARG(3, FGW_COORD_, TranslateLayer, dy = fgw_coord(&argv[3]));

	rnd_xform_mx_translate(ly->mx, dx, dy);
	ly->enable_mx = 1;

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static const char camv_acts_ScaleLayer[] = "ScaleLayer(@|last|idx, [sx, [sy]])";
static const char camv_acth_ScaleLayer[] = "Scales the layer addressed (@ for current) by the factor of sx and sy. If only sx is specified, it is used as sy as well. (The transformation is added to the current transformation matrix of the layer.)";
static fgw_error_t camv_act_ScaleLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	camv_design_t *camv = (camv_design_t *)hl;
	double sx = 0, sy;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, ScaleLayer);

	RND_ACT_MAY_CONVARG(2, FGW_DOUBLE, ScaleLayer, sx = sy = argv[2].val.nat_double);
	RND_ACT_MAY_CONVARG(3, FGW_DOUBLE, ScaleLayer, sy = argv[3].val.nat_double);

	if (sx == 0) {
		char *scs = rnd_hid_prompt_for(hl, "Please specify scale factor", "1.5", "scale layer");
		sx = sy = strtod(scs, NULL);
		free(scs);
		if (sx == 0)
			return -1;
	}

	rnd_xform_mx_scale(ly->mx, sx, sy);
	ly->enable_mx = 1;

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}


static const char camv_acts_ResetLayer[] = "ResetLayer(@|last|idx, sx[, sy])";
static const char camv_acth_ResetLayer[] = "Reset the transformation matrix of the layer.";
static fgw_error_t camv_act_ResetLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	static rnd_xform_mx_t ident = RND_XFORM_MX_IDENT;
	rnd_design_t *hl = RND_ACT_DESIGN;
	camv_design_t *camv = (camv_design_t *)hl;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, ResetLayer);
	memcpy(ly->mx, ident, sizeof(ident));
	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static const char camv_acts_InvertLayer[] = "InvertLayer(@|last|idx)";
static const char camv_acth_InvertLayer[] = "Inverts the layer addressed (@ for current). Not affected by ResetLayer().";
static fgw_error_t camv_act_InvertLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	camv_design_t *camv = (camv_design_t *)hl;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, InvertLayer);

	/* main layer */
	ly->prefill = !ly->prefill;
	ly->clearing = !ly->clearing;

	/* sublayers */
	for(idx++;idx < camv->layers.used;idx++) {
		ly = camv->layers.array[idx];
		if ((ly == NULL) || (!ly->sub))
			break;
		ly->clearing = !ly->clearing;
	}

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static const char camv_acts_ColorLayer[] = "ColorLayer(@|last|idx, colorstr)";
static const char camv_acth_ColorLayer[] = "Change the color of a layer addressed (@ for current). Not affected by ResetLayer(). Colorstr shall be #rrggbb with hex components.";
static fgw_error_t camv_act_ColorLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	const char *clr, *s;
	camv_design_t *camv = (camv_design_t *)hl;
	camv_layer_t *ly;
	int idx;

	get_layer(ly, idx, ColorLayer);
	RND_ACT_MAY_CONVARG(2, FGW_STR, ColorLayer, clr = argv[2].val.str);

	if (*clr != '#') {
		rnd_message(RND_MSG_ERROR, "ColorLayer(): color string must start with a # (it's in form #rrggbb)\n");
		return FGW_ERR_ARG_CONV;
	}

	for(s = clr+1; *s != '\0'; s++) {
		if (isdigit(*s)) continue;
		if ((*s >= 'a') && (*s <= 'f')) continue;
		if ((*s >= 'A') && (*s <= 'F')) continue;
		rnd_message(RND_MSG_ERROR, "ColorLayer(): invalid character '%c' in color string; must be in form #rrggbb with hex components\n");
		return FGW_ERR_ARG_CONV;
	}

	if ((s - clr) != 7) {
		rnd_message(RND_MSG_ERROR, "ColorLayer(): invalid length of color string; must be in form #rrggbb with hex components\n");
		return FGW_ERR_ARG_CONV;
	}

	/* main layer */
	if (rnd_color_load_str(&ly->color, clr) != 0) {
		rnd_message(RND_MSG_ERROR, "ColorLayer(): failed to convert color string\n");
		return FGW_ERR_ARG_CONV;
	}

	rnd_event(&camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static const char camv_acts_VisLayer[] = "VisLayer(@|last|idx, [0|1|toggle])";
static const char camv_acth_VisLayer[] = "Change the visibility of a layer addressed (@ for current). Not affected by ResetLayer().";
static fgw_error_t camv_act_VisLayer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	const char *cmd;
	camv_design_t *camv = (camv_design_t *)hl;
	camv_layer_t *ly;
	int idx, vis;

	get_layer(ly, idx, VisLayer);
	RND_ACT_MAY_CONVARG(2, FGW_STR, VisLayer, cmd = argv[2].val.str);

	switch(*cmd) {
		case '0': vis = 0; break;
		case '1': vis = 1; break;
		case 't': vis = !ly->vis; break;
		default:
			rnd_message(RND_MSG_ERROR, "VisLayer(): wrong second argument\n");
			return FGW_ERR_ARG_CONV;
	}

	camv_layer_set_vis(camv, idx, vis, 1);

	RND_ACT_IRES(0);
	return FGW_SUCCESS;
}

static rnd_action_t gui_action_list[] = {
	{"Quit", camv_act_Quit, camv_acth_Quit, camv_acts_Quit},
	{"Zoom", camv_act_Zoom, camv_acth_Zoom, camv_acts_Zoom},
	{"RotateLayer", camv_act_RotateLayer, camv_acth_RotateLayer, camv_acts_RotateLayer},
	{"TranslateLayer", camv_act_TranslateLayer, camv_acth_TranslateLayer, camv_acts_TranslateLayer},
	{"ScaleLayer", camv_act_ScaleLayer, camv_acth_ScaleLayer, camv_acts_ScaleLayer},
	{"ResetLayer", camv_act_ResetLayer, camv_acth_ResetLayer, camv_acts_ResetLayer},
	{"InvertLayer", camv_act_InvertLayer, camv_acth_InvertLayer, camv_acts_InvertLayer},
	{"ColorLayer", camv_act_ColorLayer, camv_acth_ColorLayer, camv_acts_ColorLayer},
	{"VisLayer", camv_act_VisLayer, camv_acth_VisLayer, camv_acts_VisLayer}
};

void gui_act_init(void)
{
	RND_REGISTER_ACTIONS(gui_action_list, NULL)
}

