/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - drawing actions for scripting
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  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/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */


#include <libcschem/config.h>

#include <librnd/core/actions.h>
#include <librnd/hid/hid.h>
#include <librnd/core/plugins.h>
#include <librnd/core/conf.h>
#include <librnd/core/error.h>
#include <librnd/core/event.h>
#include <librnd/core/compat_misc.h>

#include <libcschem/concrete.h>
#include <libcschem/attrib.h>
#include <libcschem/cnc_arc.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/extobj.h>
#include <libcschem/util_wirenet.h>
#include <libcschem/operation.h>
#include <libcschem/actions_csch.h>

#define DRAWOPTARG \
	int retptr = 0, noundo = 0, ao = 0; \
	for(;;) { \
		if (((argv[ao+1].type & FGW_STR) == FGW_STR) && (strcmp(argv[ao+1].val.str, "noundo") == 0)) { \
			noundo = 1; \
			ao++; \
		} \
		else if (((argv[ao+1].type & FGW_STR) == FGW_STR) && (strcmp(argv[ao+1].val.str, "retptr") == 0)) { \
			retptr = 1; \
			ao++; \
		} \
		else \
			break; \
	}

#define RETPTR(ptr) \
	if (retptr) \
		fgw_ptr_reg(&rnd_fgw, res, CSCH_PTR_DOMAIN_COBJ, FGW_PTR | FGW_STRUCT, ptr); \
	else \
		RND_ACT_IRES(0);


#define DRAWSCOPE(actname, dst_grp) \
	do { \
		rnd_design_t *hl = rnd_multi_get_current(); \
		csch_sheet_t *sheet = (csch_sheet_t *)hl; \
		const char *scope; \
		ao++; \
		if (((argv[ao].type & FGW_PTR) == FGW_PTR) && ((argv[ao].type & FGW_STRUCT) == FGW_STRUCT)) { \
			it_is_ptr:; \
			if (fgw_ptr_in_domain(&rnd_fgw, &argv[ao], CSCH_PTR_DOMAIN_COBJ)) { \
				dst_grp = argv[ao].val.ptr_void; \
				if (csch_obj_is_grp(&dst_grp->hdr)) \
					break; \
				rnd_message(RND_MSG_ERROR, "Invalid scope argument in action " #actname ": concrete object is not a group\n"); \
				return FGW_ERR_ARG_CONV; \
			} \
			if (fgw_ptr_in_domain(&rnd_fgw, &argv[ao], CSCH_PTR_DOMAIN_SHEET)) { \
				sheet = argv[ao].val.ptr_void; \
				dst_grp = &sheet->direct; \
				break; \
			} \
			rnd_message(RND_MSG_ERROR, "Invalid scope argument in action " #actname ": wrong pointer domain\n"); \
			return FGW_ERR_ARG_CONV; \
		} \
		RND_ACT_CONVARG(ao, FGW_STR, actname, scope = argv[ao].val.str); \
		if ((scope[0] == '0') && (scope[1] == 'x')) { \
			RND_ACT_CONVARG(ao, FGW_PTR, DrawRelease, ;); \
			goto it_is_ptr; \
		} \
		if (strncmp(scope, "object:", 7) == 0) {\
			csch_oidpath_t idp = {0}; \
			if (csch_oidpath_parse(&idp, scope+7) != 0) { \
				rnd_message(RND_MSG_ERROR, "Failed to convert object ID: '%s' in " #actname "\n", scope+7); \
				return FGW_ERR_ARG_CONV; \
			} \
			dst_grp = (csch_cgrp_t *)csch_oidpath_resolve(sheet, &idp); \
			csch_oidpath_free(&idp); \
			if (!csch_obj_is_grp(&dst_grp->hdr)) { \
				rnd_message(RND_MSG_ERROR, "Object is not a group: '%s' in " #actname "\n", scope+7); \
				return FGW_ERR_ARG_CONV; \
			} \
		} \
		else if (strcmp(scope, "sheet") == 0) \
			dst_grp = &sheet->direct; \
		else { \
			rnd_message(RND_MSG_ERROR, "Invalid scope argument in action " #actname ": '%s'\n", scope); \
			return FGW_ERR_ARG_CONV; \
		} \
	} while(0)

static const char csch_acts_DrawRelease[] = "DrawRelease(objptr)\n";
static const char csch_acth_DrawRelease[] = "Release the pointer of an object. Call this after Draw*(retptr,...) when the object is no longer needed. Unreleased objects are leaked. Warning: there is NO reference counting; getting the same object twice will get only one registration, then releasing \"one of them\" will release both! NEVER keep pointers across invocations.";
static fgw_error_t csch_act_DrawRelease(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	RND_ACT_CONVARG(1, FGW_PTR, DrawRelease, ;);

	if (!fgw_ptr_in_domain(&rnd_fgw, &argv[1], CSCH_PTR_DOMAIN_COBJ)) {
		rnd_message(RND_MSG_ERROR, "Invalid argument in action DrawRelease: pointer is not a concrete object\n");
		return FGW_ERR_ARG_CONV;
	}

	fgw_ptr_unreg(&rnd_fgw, &argv[1], CSCH_PTR_DOMAIN_COBJ);

	RND_ACT_IRES(0);
	return 0;
}

static const char csch_acts_DrawLine[] = "DrawLine([noundo], [retptr], scope, x1, y1, x2, y2, [pen])\n";
static const char csch_acth_DrawLine[] = "Draw a decoration line.";
static fgw_error_t csch_act_DrawLine(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp;
	const char *penstr = "sheet-decor";
	csch_coord_t x1, y1, x2, y2;
	csch_line_t *line;

	DRAWOPTARG;
	DRAWSCOPE(DrawLine, dst_grp);

	RND_ACT_CONVARG(ao+1, FGW_LONG, DrawLine, x1 = argv[ao+1].val.nat_long);
	RND_ACT_CONVARG(ao+2, FGW_LONG, DrawLine, y1 = argv[ao+2].val.nat_long);
	RND_ACT_CONVARG(ao+3, FGW_LONG, DrawLine, x2 = argv[ao+3].val.nat_long);
	RND_ACT_CONVARG(ao+4, FGW_LONG, DrawLine, y2 = argv[ao+4].val.nat_long);
	RND_ACT_MAY_CONVARG(ao+5, FGW_STR,  DrawLine, penstr = argv[ao+5].val.str);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);

	line = (csch_line_t *)csch_op_create(dst_grp->hdr.sheet, dst_grp, CSCH_CTYPE_LINE);
	line->spec.p1.x = x1; line->spec.p1.y = y1;
	line->spec.p2.x = x2; line->spec.p2.y = y2;
	line->hdr.stroke_name = csch_comm_str(dst_grp->hdr.sheet, penstr, 1);
	csch_line_update(dst_grp->hdr.sheet, line, 1);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(line);
	return 0;
}

static const char csch_acts_DrawCircle[] = "DrawCircle([noundo], [retptr], scope, cx, cy, r, [pen])\n";
static const char csch_acth_DrawCircle[] = "Draw a 360 degree arc (full circle).";
static fgw_error_t csch_act_DrawCircle(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp;
	const char *penstr = "sheet-decor";
	csch_coord_t cx, cy, r;
	csch_arc_t *arc;

	DRAWOPTARG;
	DRAWSCOPE(DrawCircle, dst_grp);

	RND_ACT_CONVARG(ao+1, FGW_LONG, DrawCircle, cx = argv[ao+1].val.nat_long);
	RND_ACT_CONVARG(ao+2, FGW_LONG, DrawCircle, cy = argv[ao+2].val.nat_long);
	RND_ACT_CONVARG(ao+3, FGW_LONG, DrawCircle, r = argv[ao+3].val.nat_long);
	RND_ACT_MAY_CONVARG(ao+4, FGW_STR,  DrawCircle, penstr = argv[ao+4].val.str);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);

	arc = (csch_arc_t *)csch_op_create(dst_grp->hdr.sheet, dst_grp, CSCH_CTYPE_ARC);
	arc->spec.c.x = cx; arc->spec.c.y = cy;
	arc->spec.r = r;
	arc->spec.start = 0; arc->spec.delta = 2*G2D_PI;

	arc->hdr.stroke_name = csch_comm_str(dst_grp->hdr.sheet, penstr, 1);
	csch_arc_update(dst_grp->hdr.sheet, arc, 1);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(arc);
	return 0;
}

static const char csch_acts_DrawArc[] = "DrawArc([noundo], [retptr], scope, cx, cy, r, start_ang, delta_ang, [pen])\n";
static const char csch_acth_DrawArc[] = "Draw an arc. Angles are in degrees.";
static fgw_error_t csch_act_DrawArc(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp;
	const char *penstr = "sheet-decor";
	csch_coord_t cx, cy, r;
	double sa, da;
	csch_arc_t *arc;

	DRAWOPTARG;
	DRAWSCOPE(DrawArc, dst_grp);

	RND_ACT_CONVARG(ao+1, FGW_LONG, DrawArc, cx = argv[ao+1].val.nat_long);
	RND_ACT_CONVARG(ao+2, FGW_LONG, DrawArc, cy = argv[ao+2].val.nat_long);
	RND_ACT_CONVARG(ao+3, FGW_LONG, DrawArc, r = argv[ao+3].val.nat_long);
	RND_ACT_CONVARG(ao+4, FGW_DOUBLE, DrawArc, sa = argv[ao+4].val.nat_double);
	RND_ACT_CONVARG(ao+5, FGW_DOUBLE, DrawArc, da = argv[ao+5].val.nat_double);
	RND_ACT_MAY_CONVARG(ao+6, FGW_STR,  DrawArc, penstr = argv[ao+6].val.str);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);

	arc = (csch_arc_t *)csch_op_create(dst_grp->hdr.sheet, dst_grp, CSCH_CTYPE_ARC);
	arc->spec.c.x = cx; arc->spec.c.y = cy;
	arc->spec.r = r;
	arc->spec.start = sa / RND_RAD_TO_DEG; arc->spec.delta = da / RND_RAD_TO_DEG;

	arc->hdr.stroke_name = csch_comm_str(dst_grp->hdr.sheet, penstr, 1);
	csch_arc_update(dst_grp->hdr.sheet, arc, 1);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(arc);
	return 0;
}

static const char csch_acts_DrawWire[] = "DrawWire([noundo], [retptr], scope, x1, y1, x2, y2, [pen])\n";
static const char csch_acth_DrawWire[] = "Draw a wire on the sheet of scope. Draws junctions and makes connections and merges nets as needed.";
static fgw_error_t csch_act_DrawWire(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp;
	const char *penstr = "wire";
	csch_coord_t x1, y1, x2, y2;
	csch_chdr_t *wire;

	DRAWOPTARG;
	DRAWSCOPE(DrawWire, dst_grp);

	RND_ACT_CONVARG(ao+1, FGW_LONG, DrawWire, x1 = argv[ao+1].val.nat_long);
	RND_ACT_CONVARG(ao+2, FGW_LONG, DrawWire, y1 = argv[ao+2].val.nat_long);
	RND_ACT_CONVARG(ao+3, FGW_LONG, DrawWire, x2 = argv[ao+3].val.nat_long);
	RND_ACT_CONVARG(ao+4, FGW_LONG, DrawWire, y2 = argv[ao+4].val.nat_long);
	RND_ACT_MAY_CONVARG(ao+5, FGW_STR,  DrawWire, penstr = argv[ao+5].val.str);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);

	wire = csch_wirenet_draw(dst_grp->hdr.sheet, csch_comm_str(dst_grp->hdr.sheet, penstr, 1), x1, y1, x2, y2);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(wire);
	return 0;
}

static const char csch_acts_DrawText[] = "DrawText([noundo], [retptr], scope, x, y, text, [pen])\n";
static const char csch_acth_DrawText[] = "Create a non-bbox-specified text object.";
static fgw_error_t csch_act_DrawText(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp;
	char *textstr;
	const char *penstr = "sheet-decor";
	csch_coord_t x, y;
	csch_text_t *text;

	DRAWOPTARG;
	DRAWSCOPE(DrawText, dst_grp);

	RND_ACT_CONVARG(ao+1, FGW_LONG, DrawText, x = argv[ao+1].val.nat_long);
	RND_ACT_CONVARG(ao+2, FGW_LONG, DrawText, y = argv[ao+2].val.nat_long);
	RND_ACT_CONVARG(ao+3, FGW_STR,  DrawText, textstr = argv[ao+3].val.str);
	RND_ACT_MAY_CONVARG(ao+4, FGW_STR,  DrawText, penstr = argv[ao+4].val.str);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);


	text = (csch_text_t *)csch_op_create(dst_grp->hdr.sheet, dst_grp, CSCH_CTYPE_TEXT);

	text->spec1.x = x;
	text->spec1.y = y;
	text->text = rnd_strdup(textstr);
	text->hdr.stroke_name = csch_comm_str(dst_grp->hdr.sheet, penstr, 1);
	csch_text_update(dst_grp->hdr.sheet, text, 1);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(text);
	return 0;
}


static const char csch_acts_DrawGroup[] = "DrawGroup([noundo], [retptr], scope, [key, value], [key, value]...)\n";
static const char csch_acth_DrawGroup[] = "Create a new concrete group. Optionally create key=value string attributes in the new group.";
static fgw_error_t csch_act_DrawGroup(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	csch_cgrp_t *dst_grp, *new_grp;
	int n;

	DRAWOPTARG;
	DRAWSCOPE(DrawGroup, dst_grp);

	if (noundo)
		uundo_freeze_add(&dst_grp->hdr.sheet->undo);

	new_grp = (csch_cgrp_t *)csch_op_create(dst_grp->hdr.sheet, dst_grp, CSCH_CTYPE_GRP);
	for(n = ao+1; n < argc; n+=2) {
		const char *key, *val;
		csch_source_arg_t *src;

		RND_ACT_CONVARG(n+0, FGW_STR, DrawGroup, key = argv[n+0].val.str);
		RND_ACT_CONVARG(n+1, FGW_STR, DrawGroup, val = argv[n+1].val.str);

		src = csch_attrib_src_c(NULL, 0, 0, "DrawGroup");
		csch_attrib_set(&new_grp->attr, CSCH_ATP_USER_DEFAULT, key, val, src, NULL);
	}
	csch_cgrp_update(dst_grp->hdr.sheet, new_grp, 1);
	csch_extobj_update(new_grp);

	if (noundo)
		uundo_unfreeze_add(&dst_grp->hdr.sheet->undo);

	RETPTR(new_grp);
	return 0;
}


rnd_action_t draw_action_list[] = {
	{"DrawRelease", csch_act_DrawRelease, csch_acth_DrawRelease, csch_acts_DrawRelease},
	{"DrawLine", csch_act_DrawLine, csch_acth_DrawLine, csch_acts_DrawLine},
	{"DrawCircle", csch_act_DrawCircle, csch_acth_DrawCircle, csch_acts_DrawCircle},
	{"DrawArc", csch_act_DrawArc, csch_acth_DrawArc, csch_acts_DrawArc},
	{"DrawWire", csch_act_DrawWire, csch_acth_DrawWire, csch_acts_DrawWire},
	{"DrawText", csch_act_DrawText, csch_acth_DrawText, csch_acts_DrawText},
	{"DrawGroup", csch_act_DrawGroup, csch_acth_DrawGroup, csch_acts_DrawGroup}
};

static const char *draw_cookie = "draw plugin";

int pplg_check_ver_act_draw(int ver_needed) { return 0; }

void pplg_uninit_act_draw(void)
{
	rnd_remove_actions_by_cookie(draw_cookie);
}

int pplg_init_act_draw(void)
{
	RND_API_CHK_VER;

	RND_REGISTER_ACTIONS(draw_action_list, draw_cookie)
	return 0;
}
