/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - property editor
 *  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., 31 Milk Street, # 960789 Boston, MA 02196 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 <libcschem/search.h>
#include <libcschem/util_grp.h>
#include <libcschem/actions_csch.h>
#include <librnd/core/actions.h>
#include <librnd/core/plugins.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/misc_util.h>
#include <sch-rnd/funchash_core.h>
#include <sch-rnd/crosshair.h>

#include "props.h"
#include "propsel.h"
#include "propdlg.h"

const char csch_propedit_cookie[] = "propedit";

extern csch_chdr_t *csch_obj_clicked;

int prop_scope_add_obj(csch_propedit_t *pe, const csch_chdr_t *obj)
{
	csch_oidpath_t *tmp;

	tmp = calloc(sizeof(csch_oidpath_t), 1);
	csch_oidpath_from_obj(tmp, obj);
	csch_oidpath_list_append(&pe->objs, tmp);
	return 0;
}

int prop_scope_add(csch_propedit_t *pe, const char *cmd, int quiet)
{
	csch_chdr_t *obj = NULL;

	if (strncmp(cmd, "parent", 6) == 0) {
		if (csch_obj_clicked != NULL)
			obj = &csch_obj_clicked->parent->hdr;
		goto add_obj;
	}
	else if (strncmp(cmd, "object", 6) == 0) {
		csch_oidpath_t idp = {0}, *tmp;

		if (cmd[6] == ':') {
			if (csch_oidpath_parse(&idp, cmd+7) != 0) {
				if (!quiet)
					rnd_message(RND_MSG_ERROR, "Failed to convert object ID: '%s'\n", cmd+7);
				return FGW_ERR_ARG_CONV;
			}
			tmp = malloc(sizeof(csch_oidpath_t));
			*tmp = idp;
			csch_oidpath_list_append(&pe->objs, tmp);
		}
		else {
			obj = csch_obj_clicked;

			add_obj:;
			if (obj == NULL) {
				if (!quiet)
					rnd_message(RND_MSG_ERROR, "No object under the cursor\n");
				return FGW_ERR_ARG_CONV;
			}

			tmp = calloc(sizeof(csch_oidpath_t), 1);
			csch_oidpath_from_obj(tmp, obj);
			csch_oidpath_list_append(&pe->objs, tmp);
		}
	}
	else if (strcmp(cmd, "sheet") == 0)
		pe->use_sheet = 1;
	else if (strcmp(cmd, "selection") == 0)
		pe->use_selection = 1;
	else if (strcmp(cmd, "geo") == 0)
		pe->geo = 1;
	else {
		if (!quiet)
			rnd_message(RND_MSG_ERROR, "Invalid propedit scope: %s\n", cmd);
		return FGW_ERR_ARG_CONV;
	}

	return 0;
}

/* pair of prop_scope_add() - uninits the scope */
static void prop_scope_finish(csch_propedit_t *pe)
{
	csch_oidpath_t *idp;

	/* need to remove idpaths from the scope list, else it won't be possible
	   to add them again */
	while((idp = csch_oidpath_list_first(&pe->objs)) != NULL) {
		csch_oidpath_list_remove(idp);
		free(idp);
	}
}

csch_oidpath_t *prop_scope_single_obj(csch_propedit_t *pe)
{
	if (pe->use_selection || pe->use_sheet)
		return NULL;

	if (csch_oidpath_list_length(&pe->objs) != 1)
		return NULL;

	return csch_oidpath_list_first(&pe->objs);
}

static const char csch_acts_propset[] = "propset([scope], name, value)";
static const char csch_acth_propset[] = "Change the named property of scope or all selected objects to/by value. Scope is documented at PropEdit(). Existing attributes can be renamed to value by using a name rename/a/old_name, where old_name is the current name of the attribute.";
static fgw_error_t csch_act_propset(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	const char *first, *name, *val;
	csch_propedit_t ctx;

	csch_props_init(&ctx, sheet, &sheet->direct);


	RND_ACT_CONVARG(1, FGW_STR, propset, first = argv[1].val.str);
	if (strcmp(first, "objbin") == 0) {
		fgw_arg_conv(&rnd_fgw, &argv[2], (FGW_PTR | FGW_STRUCT));
		if (!fgw_ptr_in_domain(&rnd_fgw, &argv[2], CSCH_PTR_DOMAIN_COBJ)) {
			rnd_message(RND_MSG_ERROR, "propset: objbin arg is not a pointer to a concrete model object\n");
			return FGW_ERR_ARG_CONV;
		}
		prop_scope_add_obj(&ctx, argv[2].val.ptr_void);
		RND_ACT_CONVARG(3, FGW_STR, propset, name = argv[3].val.str);
		RND_ACT_CONVARG(4, FGW_STR, propset, val = argv[4].val.str);
	}
	else if (strcmp(first, "@") == 0) {
		if (sheet->currobj == NULL) {
			rnd_message(RND_MSG_ERROR, "propset: @ without sheet->currobj set\n");
			return FGW_ERR_ARG_CONV;
		}
		prop_scope_add_obj(&ctx, sheet->currobj);
		RND_ACT_CONVARG(2, FGW_STR, propset, name = argv[2].val.str);
		RND_ACT_CONVARG(3, FGW_STR, propset, val = argv[3].val.str);
	}
	else if (prop_scope_add(&ctx, first, 1) == 0) {
		RND_ACT_CONVARG(2, FGW_STR, propset, name = argv[2].val.str);
		RND_ACT_CONVARG(3, FGW_STR, propset, val = argv[3].val.str);
	}
	else {
		name = first;
		ctx.use_selection = 1;
		RND_ACT_CONVARG(2, FGW_STR, propset, val = argv[2].val.str);
	}

	RND_ACT_IRES(csch_propsel_set_str(&ctx, name, val));

	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);
	return 0;
}

static const char csch_acts_proptoggle[] = "proptoggle([scope], name, [create])";
static const char csch_acth_proptoggle[] = "Toggle the named property of scope or all selected objects, assuming the property is boolean. Scope is documented at PropEdit(). If create is true, non-existing attributes are created as true.";
fgw_error_t csch_act_proptoggle(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	const char *first, *name, *create = NULL;
	csch_propedit_t ctx;

	csch_props_init(&ctx, sheet, &sheet->direct);

	RND_ACT_CONVARG(1, FGW_STR, proptoggle, first = argv[1].val.str);
	if (prop_scope_add(&ctx, first, 1) == 0) {
		RND_ACT_CONVARG(2, FGW_STR, proptoggle, name = argv[2].val.str);
		RND_ACT_MAY_CONVARG(3, FGW_STR, proptoggle, create = argv[3].val.str);
	}
	else {
		name = first;
		RND_ACT_MAY_CONVARG(2, FGW_STR, proptoggle, create = argv[2].val.str);
		ctx.use_selection = 1;
	}

	RND_ACT_IRES(csch_propsel_toggle(&ctx, name, rnd_istrue(create)));

	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);
	return 0;
}

static const char csch_acts_propdel[] = "propdel([scope], key, [regex])";
static const char csch_acth_propdel[] = "Delete attribute(s) by key; if the third attribute has a value of true, delete any attribute that matches the regular expression in key (the regex still needs to be prefixed by a/ to indicate it is addressing attributes)";
fgw_error_t csch_act_propdel(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	const char *first, *name, *regex = NULL;
	csch_propedit_t ctx;

	csch_props_init(&ctx, sheet, &sheet->direct);

	RND_ACT_CONVARG(1, FGW_STR, propdel, first = argv[1].val.str);
	if (prop_scope_add(&ctx, first, 1) == 0) {
		RND_ACT_CONVARG(2, FGW_STR, propdel, name = argv[2].val.str);
		RND_ACT_MAY_CONVARG(3, FGW_STR, propdel, regex = argv[3].val.str);
	}
	else {
		name = first;
		RND_ACT_MAY_CONVARG(2, FGW_STR, propdel, regex = argv[2].val.str);
		ctx.use_selection = 1;
	}

	ctx.aregex = rnd_istrue(regex);
	csch_propsel_del(&ctx, name);
	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);

	RND_ACT_IRES(0);
	return 0;
}


static const char csch_acts_propget[] = "propget([scope], name, [stattype])";
static const char csch_acth_propget[] = "Return the named property of scope or all selected objects to/by value. Scope is documented at PropEdit().";
fgw_error_t csch_act_propget(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	const char *first, *name, *stty = "common";
	csch_props_t *p;
	csch_propval_t *out, most_common, min, max, avg;
	htprop_entry_t *e;
	int r;
	csch_propedit_t ctx;
	minuid_str_t utmp;

	csch_props_init(&ctx, sheet, &sheet->direct);

	RND_ACT_CONVARG(1, FGW_STR, propget, first = argv[1].val.str);
	if (strcmp(first, "objbin") == 0) {
		fgw_arg_conv(&rnd_fgw, &argv[2], (FGW_PTR | FGW_STRUCT));
		if (!fgw_ptr_in_domain(&rnd_fgw, &argv[2], CSCH_PTR_DOMAIN_COBJ)) {
			rnd_message(RND_MSG_ERROR, "propget: objbin arg is not a pointer to a concrete model object\n");
			return FGW_ERR_ARG_CONV;
		}
		prop_scope_add_obj(&ctx, argv[2].val.ptr_void);
		RND_ACT_CONVARG(3, FGW_STR, propset, name = argv[3].val.str);
	}
	else if (prop_scope_add(&ctx, first, 1) == 0) {
		RND_ACT_CONVARG(2, FGW_STR, propget, name = argv[2].val.str);
		RND_ACT_MAY_CONVARG(3, FGW_STR, propget, stty = argv[3].val.str);
	}
	else {
		name = first;
		ctx.use_selection = 1;
		RND_ACT_CONVARG(2, FGW_STR, propget, stty = argv[2].val.str);
	}

	ctx.geo = 1;
	csch_propsel_map_core(&ctx);

	p = htsp_get(&ctx.props, name);
	if (p == NULL)
		goto error;

	if (p->type == CSCH_PROPT_STRING)
		r = csch_props_stat(&ctx, p, &most_common, NULL, NULL, NULL);
	else
		r = csch_props_stat(&ctx, p, &most_common, &min, &max, &avg);

	if (r != 0)
		goto error;

	if (strcmp(stty, "common") == 0) out = &most_common;
	else if (strcmp(stty, "min") == 0) out = &min;
	else if (strcmp(stty, "max") == 0) out = &max;
	else if (strcmp(stty, "avg") == 0) out = &avg;
	else goto error;

	switch(p->type) {
		case CSCH_PROPT_STRING:
			if (out != &most_common)
				goto error;

			/* get the first one for now */
			e = htprop_first(&p->values);
			most_common = e->key;
			res->type = FGW_STR | FGW_DYN;
			res->val.str = rnd_strdup(out->string == NULL ? "" : out->string);
			break;
		case CSCH_PROPT_COMMS: res->type = FGW_STR | FGW_DYN; res->val.str = rnd_strdup(out->comms.str); break;
		case CSCH_PROPT_COORD: res->type = FGW_LONG; res->val.nat_long = out->coord; break;
		case CSCH_PROPT_ANGLE: res->type = FGW_DOUBLE; res->val.nat_double = out->angle; break;
		case CSCH_PROPT_DOUBLE: res->type = FGW_DOUBLE; res->val.nat_double = out->d; break;
		case CSCH_PROPT_BOOL:
		case CSCH_PROPT_ENUM:
		case CSCH_PROPT_INT:   res->type = FGW_INT; res->val.nat_int = out->i; break;
		case CSCH_PROPT_UUID:  minuid_bin2str(utmp, out->u); res->type = FGW_STR | FGW_DYN; res->val.str = rnd_strdup(utmp); break;

		case CSCH_PROPT_ATTRARR:
		case CSCH_PROPT_invalid:
		case CSCH_PROPT_max: goto error;
	}
	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);
	return 0;

	error:;
	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);
	return FGW_ERR_ARG_CONV;
}

static const char csch_acts_propprint[] = "PropPrint([scope])";
static const char csch_acth_propprint[] = "Print a property map of objects matching the scope. Scope is documented at PropEdit().";
fgw_error_t csch_act_propprint(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	const char *scope = NULL;
	htsp_entry_t *e, *sorted;
	csch_propedit_t ctx;

	csch_props_init(&ctx, sheet, &sheet->direct);

	RND_ACT_MAY_CONVARG(1, FGW_STR, propprint, scope = argv[1].val.str);
	if (scope != NULL) {
		if (prop_scope_add(&ctx, scope, 0) != 0)
			return FGW_ERR_ARG_CONV;
	}
	else
		ctx.use_selection = 1;

	csch_propsel_map_core(&ctx);
	sorted = csch_props_sort(&ctx);
	for(e = sorted; e->key != NULL; e++) {
		csch_props_t *p = e->value;
		csch_propval_t com, min, max, avg;
		minuid_str_t utmp;

		printf("%s\n", e->key);
		if (p->type == CSCH_PROPT_STRING)
			csch_props_stat(&ctx, p, &com, NULL, NULL, NULL);
		else
			csch_props_stat(&ctx, p, &com, &min, &max, &avg);
		switch(p->type) {
			case CSCH_PROPT_STRING: printf("	common='%s'\n", com.string); break;
			case CSCH_PROPT_COMMS: printf("	common='%s'\n", com.comms.str); break;
			case CSCH_PROPT_COORD:
				rnd_printf("	common='%$$rc'\n", com.coord);
				rnd_printf("	min/avg/max=%$$rc/%$$rc/%$$rc\n", min.coord, avg.coord, max.coord);
				break;
			case CSCH_PROPT_ANGLE:
				rnd_printf("	common='%f'\n", com.angle);
				rnd_printf("	min/avg/max=%f/%f/%f\n", min.angle, avg.angle, max.angle);
				break;
			case CSCH_PROPT_DOUBLE:
				rnd_printf("	common='%f'\n", com.d);
				rnd_printf("	min/avg/max=%f/%f/%f\n", min.d, avg.d, max.d);
				break;
			case CSCH_PROPT_INT:
			case CSCH_PROPT_BOOL:
			case CSCH_PROPT_ENUM:
				rnd_printf("	common='%d'\n", com.i);
				rnd_printf("	min/avg/max=%d/%d/%d\n", min.i, avg.i, max.i);
				break;
			case CSCH_PROPT_UUID:
				minuid_bin2str(utmp, com.u);
				rnd_printf("	common=%s\n", utmp);
				break;
			case CSCH_PROPT_ATTRARR:
			case CSCH_PROPT_invalid:
			case CSCH_PROPT_max:
				break;
		}
	}
	free(sorted);

	prop_scope_finish(&ctx);
	csch_props_uninit(&ctx);
	RND_ACT_IRES(0);
	return 0;
}


static rnd_action_t sch_propedit_action_list[] = {
	{"propedit", csch_act_propedit, csch_acth_propedit, csch_acts_propedit},
	{"propprint", csch_act_propprint, csch_acth_propprint, csch_acts_propprint},
	{"propset", csch_act_propset, csch_acth_propset, csch_acts_propset},
	{"proptoggle", csch_act_proptoggle, csch_acth_proptoggle, csch_acts_proptoggle},
	{"propdel", csch_act_propdel, csch_acth_propdel, csch_acts_propdel},
	{"propget", csch_act_propget, csch_acth_propget, csch_acts_propget}
};


int pplg_check_ver_propedit(int ver_needed) { return 0; }

void pplg_uninit_propedit(void)
{
	csch_propdlg_uninit();
	rnd_remove_actions_by_cookie(csch_propedit_cookie);
}

int pplg_init_propedit(void)
{
	RND_API_CHK_VER;

	RND_REGISTER_ACTIONS(sch_propedit_action_list, csch_propedit_cookie);
	csch_propdlg_init();
	return 0;
}

