/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - GUI
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *  (loosely based on camv-rnd's implementation)
 *
 *  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/operation.h>
#include <libcschem/oidpath.h>
#include <libcschem/actions_csch.h>

#include <sch-rnd/funchash_core.h>
#include <sch-rnd/crosshair.h>
#include <sch-rnd/conf_core.h>
#include <sch-rnd/buffer.h>
#include <sch-rnd/select.h>

RND_INLINE
 int cmd_auto_obj_or_buff(int cmd)
{
	rnd_toolid_t buff;

	if (cmd != F_Auto) return cmd;

	buff = rnd_tool_lookup("buffer");
	if (rnd_conf.editor.mode == buff)
		return F_Buffer;
	return F_Object;
}

#define CMD_IDPATH(idx, aname, dst) \
	do { \
		csch_oidpath_t *oidp; \
		fgw_arg_t atmp; \
		memcpy(&atmp, &argv[idx], sizeof(fgw_arg_t)); \
		atmp.type &= ~FGW_DYN; \
		if (fgw_arg_conv(&rnd_fgw, &atmp, FGW_IDPATH) != 0) { \
			rnd_message(RND_MSG_ERROR, #aname ": invalid idpath\n"); \
			RND_ACT_FAIL(aname); \
			return FGW_ERR_ARG_CONV; \
		} \
		oidp = fgw_idpath(&atmp); \
		if (oidp != NULL) { \
			csch_sheet_t *sheet = (csch_sheet_t *)RND_ACT_DESIGN; \
			dst = csch_oidpath_resolve(sheet, oidp); \
			if ((FGW_BASE_TYPE(argv[idx].type) == FGW_STR) && ((argv[idx].val.str[0] != '0') || (argv[idx].val.str[1] != 'x'))) { \
				csch_oidpath_free(oidp); \
				free(oidp); \
				fgw_ptr_unreg(&rnd_fgw, &atmp, RND_PTR_DOMAIN_IDPATH); \
			} \
		} \
		else \
			dst = NULL; \
	} while(0) \


static const char csch_acts_Rotate90[] = "Rotate90([object|buffer|auto], [steps])\nRotate90(idpath, steps, idp, x, y)";
static const char csch_acth_Rotate90[] = "Rotate object or buffer CCW in 90 degree steps. Default target is auto. Default steps is 1.";
static fgw_error_t csch_act_Rotate90(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	int rv = -1, cmd = F_Auto, steps = 1;

	RND_ACT_MAY_CONVARG(1, FGW_KEYWORD, Rotate90, cmd = fgw_keyword(&argv[1]));
	RND_ACT_MAY_CONVARG(2, FGW_INT,     Rotate90, steps = argv[2].val.nat_int);

	cmd = cmd_auto_obj_or_buff(cmd);

	switch(cmd) {
		case F_Idpath:
			{
				csch_coord_t x, y;
				csch_chdr_t *obj;

				CMD_IDPATH(3, Rotate90, obj);
				RND_ACT_CONVARG(4, FGW_COORD, Rotate90, x = fgw_coord(&(argv[4])));
				RND_ACT_CONVARG(5, FGW_COORD, Rotate90, y = fgw_coord(&(argv[5])));
				if (obj != NULL) {
					csch_rotate90(sheet, obj, x, y, steps, 1);
					rv = 0;
				}
				break;
			}
		case F_Object:
			{
				csch_coord_t x, y;
				csch_chdr_t *obj;

				if (sch_rnd_get_coords("Click on object to rotate", &x, &y, 0) != 0)
					break;

				obj = sch_rnd_search_obj_at(sheet, x, y, sch_rnd_slop);
				if (obj != NULL)
					csch_rotate90(sheet, obj, x, y, steps, 1);
				rv = 0;
			}
			break;
		case F_Buffer:
			{
				csch_sheet_t *buff = SCH_RND_PASTEBUFFER;
				csch_cgrp_t *grp = &buff->direct;
				htip_entry_t *e;
				for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e))
					csch_rotate90(buff, e->value, 0, 0, steps, 0);
				rv = 0;
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "Invalid first arg for Rotate90()\n");
			break;
	}
	RND_ACT_IRES(rv);
	return 0;
}

static const char csch_acts_Rotate[] = "Rotate([object|buffer|auto], [deg|ask])\n";
static const char csch_acth_Rotate[] = "Rotate object or buffer CCW in def degrees. Default target is auto. Default deg is ask (which prompts for a value).";
static fgw_error_t csch_act_Rotate(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	int rv = -1, cmd = F_Auto, ask = 0;
	double deg;

	RND_ACT_MAY_CONVARG(1, FGW_KEYWORD, Rotate, cmd = fgw_keyword(&argv[1]));

	if (argc < 3)
		ask = 1;
	else if (((argv[2].type & FGW_STR) == FGW_STR) && ((argv[2].val.str[0] == 'a') || (argv[2].val.str[0] == 'A')))
		ask = 1;

	if (ask) {
		char *end, *degs = rnd_hid_prompt_for(hidlib, "Degrees to rotate:", "15", "Rotation angle");
		if (degs == NULL) /* cancel - not yet implemented */
			return 0;
		deg = strtod(degs, &end);
		if (*end != '\0') {
			rnd_message(RND_MSG_ERROR, "Invalid numeric value (at '%s')\n", end);
			free(degs);
			return 0;
		}
		free(degs);
	}
	else
		RND_ACT_MAY_CONVARG(2, FGW_DOUBLE,  Rotate, deg = argv[2].val.nat_double);

	cmd = cmd_auto_obj_or_buff(cmd);

	switch(cmd) {
		case F_Object:
			{
				csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
				csch_coord_t x, y;
				csch_chdr_t *obj;

				if (sch_rnd_get_coords("Click on object to rotate", &x, &y, 0) != 0)
					break;

				obj = sch_rnd_search_obj_at(sheet, x, y, sch_rnd_slop);
				if (obj != NULL)
					csch_rotate(sheet, obj, x, y, deg, 1);
				rv = 0;
			}
			break;
		case F_Buffer:
			{
				csch_sheet_t *buff = SCH_RND_PASTEBUFFER;
				csch_cgrp_t *grp = &buff->direct;
				htip_entry_t *e;
				for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e))
					csch_rotate(buff, e->value, 0, 0, deg, 0);
				rv = 0;
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "Invalid first arg for Rotate()\n");
			break;
	}
	RND_ACT_IRES(rv);
	return 0;
}

static const char csch_acts_Mirror[] = "Mirror([object|buffer|auto], [horizontal|vertical])\n";
static const char csch_acth_Mirror[] = "Mirror object or buffer. Default target is auto. Default direction is horizontal.";
static fgw_error_t csch_act_Mirror(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	int rv = -1, cmd = F_Auto, dir = F_Horizontal, mirx = 0, miry = 0;

	RND_ACT_MAY_CONVARG(1, FGW_KEYWORD, Mirror, cmd = fgw_keyword(&argv[1]));
	RND_ACT_MAY_CONVARG(2, FGW_KEYWORD, Mirror, dir = fgw_keyword(&argv[2]));

	switch(dir) {
		case F_Horizontal: mirx = 1; break;
		case F_Vertical: miry = 1; break;
		default:
			rnd_message(RND_MSG_ERROR, "Invalid second arg for Mirror()\n");
			RND_ACT_IRES(-1);
			return 0;
	}

	cmd = cmd_auto_obj_or_buff(cmd);

	switch(cmd) {
		case F_Object:
			{
				csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
				csch_chdr_t *obj;
				csch_coord_t x, y;

				if (sch_rnd_get_coords("Click on object to mirror", &x, &y, 0) != 0)
					break;

				obj = sch_rnd_search_obj_at(sheet, x, y, sch_rnd_slop);
				if (obj != NULL)
					csch_mirror(sheet, obj, x, y, mirx, miry, 1);
				rv = 0;
			}
			break;
		case F_Buffer:
			{
				csch_sheet_t *buff = SCH_RND_PASTEBUFFER;
				csch_cgrp_t *grp = &buff->direct;
				htip_entry_t *e;
				for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e))
					csch_mirror(buff, e->value, 0, 0, mirx, miry, 0);
				rv = 0;
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "Invalid first arg for Mirror()\n");
			break;
	}
	RND_ACT_IRES(rv);
	return 0;
}

#define EDIT_ACTS \
	{"Rotate90", csch_act_Rotate90, csch_acth_Rotate90, csch_acts_Rotate90}, \
	{"Rotate", csch_act_Rotate, csch_acth_Rotate, csch_acts_Rotate}, \
	{"Mirror", csch_act_Mirror, csch_acth_Mirror, csch_acts_Mirror}, \

