/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - query
 *  Copyright (C) 2016,2020 Tibor 'Igor2' Palinkas (in pcb-rnd)
 *  Copyright (C) 2022 Tibor 'Igor2' Palinkas
 *  (copied from pcb-rnd by the author)
 *
 *  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
 */

/* Query language - access to / extract core data */

#include <libcschem/config.h>

#include <librnd/core/math_helper.h>
#include <librnd/core/compat_misc.h>
#include <libcschem/concrete.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_bitmap.h>
#include <libcschem/cnc_arc.h>
#include <libcschem/cnc_pen.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_conn.h>
#include <libcschem/cnc_obj.h>
#include "query_access.h"
#include "query_exec.h"
#include "fields_sphash.h"

#define APPEND(_lst_, _obj_) vtp0_append((vtp0_t *)_lst_, _obj_)

static int list_grp(void *ctx, csch_cgrp_t *grp, int enter, csch_cmask_t mask)
{
	htip_entry_t *e;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_chdr_t *obj = e->value;
		if (csch_ctype_in_cmask(obj->type, mask))
			APPEND(ctx, obj);
		if (enter && (csch_obj_is_grp(obj)))
			list_grp(ctx, (csch_cgrp_t *)obj, enter, mask);
	}

	return 0;
}

void pcb_qry_list_all_sheet(pcb_qry_val_t *lst, csch_sheet_t *sheet, csch_cmask_t mask)
{
	assert(lst->type == PCBQ_VT_LST);
	list_grp(&lst->data.lst, &sheet->direct, 1, mask);
}

void pcb_qry_list_all_sheet_indirect(pcb_qry_val_t *lst, csch_sheet_t *sheet, csch_cmask_t mask)
{
	assert(lst->type == PCBQ_VT_LST);
	list_grp(&lst->data.lst, &sheet->indirect, 1, mask);
}

void pcb_qry_list_all_grp(pcb_qry_val_t *lst, csch_cgrp_t *grp, csch_cmask_t mask)
{
	list_grp(&lst->data.lst, grp, 1, mask);
}


void pcb_qry_list_free(pcb_qry_val_t *lst_)
{
	vtp0_uninit(&lst_->data.lst);
}

int pcb_qry_list_cmp(pcb_qry_val_t *lst1, pcb_qry_val_t *lst2)
{
	abort();
}

/***********************/

static int chdr_level(csch_chdr_t *obj)
{
	int l = 0;
	csch_cgrp_t *grp;

	for(grp = obj->parent; grp != NULL; grp = grp->hdr.parent) l++;

	return l;
}

/* set dst to point to the idxth field assuming field 0 is fld. Returns -1
   if there are not enough fields */
#define fld_nth_req(dst, fld, idx) \
do { \
	int __i__ = idx; \
	pcb_qry_node_t *__f__; \
	for(__f__ = (fld); __i__ > 0; __i__--, __f__ = __f__->next) { \
		if (__f__ == NULL) \
			return -1; \
	} \
	(dst) = __f__; \
} while(0)

#define fld_nth_opt(dst, fld, idx) \
do { \
	int __i__ = idx; \
	pcb_qry_node_t *__f__; \
	for(__f__ = (fld); (__i__ > 0) && (__f__ != NULL); __i__--, __f__ = __f__->next) ; \
	(dst) = __f__; \
} while(0)

/* sets const char *s to point to the string data of the idxth field. Returns
   -1 if tere are not enooung fields */
#define fld2str_req(s, fld, idx) \
do { \
	pcb_qry_node_t *_f_; \
	fld_nth_req(_f_, fld, idx); \
	if ((_f_ == NULL) || (_f_->type != PCBQ_FIELD)) \
		return -1; \
	(s) = _f_->data.str; \
} while(0)

/* Same, but doesn't return -1 on missing field but sets s to NULL */
#define fld2str_opt(s, fld, idx) \
do { \
	pcb_qry_node_t *_f_; \
	const char *__res__; \
	fld_nth_opt(_f_, fld, idx); \
	if (_f_ != NULL) { \
		if (_f_->type != PCBQ_FIELD) \
			return -1; \
		__res__ = _f_->data.str; \
	} \
	else \
		__res__ = NULL; \
	(s) = __res__; \
} while(0)

/* sets query_fields_keys_t h to point to the precomp field of the idxth field.
   Returns -1 if tere are not enooung fields */
#define fld2hash_req(h, fld, idx) \
do { \
	pcb_qry_node_t *_f_; \
	fld_nth_req(_f_, fld, idx); \
	if ((_f_ == NULL) || (_f_->type != PCBQ_FIELD)) \
		return -1; \
	(h) = _f_->precomp.fld; \
} while(0)

/* Same, but doesn't return -1 on missing field but sets s to query_fields_SPHASH_INVALID */
#define fld2hash_opt(h, fld, idx) \
do { \
	pcb_qry_node_t *_f_; \
	query_fields_keys_t *__res__; \
	fld_nth_opt(_f_, fld, idx); \
	if (_f_ != NULL) { \
		if (_f_->type != PCBQ_FIELD) \
			return -1; \
		__res__ = _f_->precomp.fld; \
	} \
	else \
		__res__ = query_fields_SPHASH_INVALID; \
	(s) = __res__; \
} while(0)

#define COMMON_FIELDS(ec, obj, fh1, fld, res) \
do { \
	if (fh1 == query_fields_ID) { \
		PCB_QRY_RET_INT(res, obj->oid); \
	} \
 \
	if (fh1 == query_fields_bbox) { \
		csch_rtree_box_t *bx = &(obj)->bbox; \
		query_fields_keys_t fh2; \
 \
		fld2hash_req(fh2, fld, 1); \
		switch(fh2) { \
			case query_fields_x1:     PCB_QRY_RET_COORD(res, bx->x1); \
			case query_fields_y1:     PCB_QRY_RET_COORD(res, bx->y1); \
			case query_fields_x2:     PCB_QRY_RET_COORD(res, bx->x2); \
			case query_fields_y2:     PCB_QRY_RET_COORD(res, bx->y2); \
			case query_fields_width:  PCB_QRY_RET_COORD(res, bx->x2 - bx->x1); \
			case query_fields_height: PCB_QRY_RET_COORD(res, bx->y2 - bx->y1); \
			default:; \
		} \
		PCB_QRY_RET_INV(res); \
	} \
 \
	if (fh1 == query_fields_type) \
		PCB_QRY_RET_INT(res, obj->type); \
 \
	if (fh1 == query_fields_level) \
		PCB_QRY_RET_INT(res, chdr_level(obj)); \
 \
	if (fh1 == query_fields_selected) \
		PCB_QRY_RET_INT(res, csch_chdr_is_selected(obj)); \
 \
	if (fh1 == query_fields_locked) \
		PCB_QRY_RET_INT(res, obj->lock); \
 \
	if (fh1 == query_fields_anylocked) \
		PCB_QRY_RET_INT(res, csch_cobj_is_locked(obj)); \
 \
	if (fh1 == query_fields_floater) \
		PCB_QRY_RET_INT(res, obj->floater); \
 \
	if (fh1 == query_fields_parent) \
	 return field_parent_obj(ec, obj, fld->next, res); \
 \
	if (fh1 == query_fields_displayer) \
		PCB_QRY_RET_INT(res, obj->dsply); \
	if (fh1 == query_fields_displayer_name) \
		PCB_QRY_RET_STR(res, csch_dsply_name(obj->dsply)); \
	\
	if (fh1 == query_fields_conn) \
		return field_obj_conn(ec, &obj->conn, res); \
} while(0)


RND_INLINE double obj_thickness(csch_chdr_t *o)
{
	if (o->stroke == NULL)
		return 1;
	return o->stroke->size;
}

RND_INLINE int field_obj_attr(pcb_qry_exec_t *ec, csch_cgrp_t *o, const char *key, pcb_qry_val_t *res)
{
	csch_attrib_t *a = csch_attrib_get(&o->attr, key);
	long n;

	if (a == NULL)
		PCB_QRY_RET_STR(res, NULL);
	if (a->val != NULL)
		PCB_QRY_RET_STR(res, a->val);

	res->type = PCBQ_VT_LST;
	vtp0_init(&res->data.lst);
	for(n = 0; n < a->arr.used; n++) {
		pcb_qry_val_t *v = malloc(sizeof(pcb_qry_val_t));

		v->type = PCBQ_VT_STRING;
		v->data.str = a->arr.array[n];
		v->source = NULL;
		vtp0_append(&ec->autofree, v);
		vtp0_append(&res->data.lst, v);
	}
	return 0;
}

RND_INLINE int field_obj_conn(pcb_qry_exec_t *ec, vtp0_t *conn_fld, pcb_qry_val_t *res)
{
	long n;

	res->type = PCBQ_VT_LST;
	vtp0_init(&res->data.lst);
	for(n = 0; n < conn_fld->used; n++) {
		pcb_qry_val_t *v = malloc(sizeof(pcb_qry_val_t));

		v->type = PCBQ_VT_OBJ;
		v->data.obj = conn_fld->array[n];
		v->source = NULL;
		vtp0_append(&ec->autofree, v);
		vtp0_append(&res->data.lst, v);
	}

	return 0;
}

RND_INLINE int field_obj_pen(csch_chdr_t *o, csch_comm_str_t penname, csch_cpen_t *pen, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	query_fields_keys_t fh1;

	if (fld == NULL)
		PCB_QRY_RET_STR(res, penname.str);

	if (pen == NULL)
		PCB_QRY_RET_INV(res);

	fld2hash_req(fh1, fld, 0);
	switch(fh1) {
		case query_fields_shape:
			switch(pen->shape) {
				case CSCH_PSHP_ROUND: PCB_QRY_RET_STR(res, "round");
				case CSCH_PSHP_SQUARE: PCB_QRY_RET_STR(res, "square");
			}
			PCB_QRY_RET_INV(res);
		case query_fields_is_round:    PCB_QRY_RET_INT(res, pen->shape == CSCH_PSHP_ROUND);
		case query_fields_size:        PCB_QRY_RET_COORD(res, pen->size);
		case query_fields_color:       PCB_QRY_RET_STR(res, pen->color.str);
		case query_fields_dash:        PCB_QRY_RET_INT(res, pen->dash);
		case query_fields_dash_period: PCB_QRY_RET_COORD(res, pen->dash_period);
		case query_fields_font_height: PCB_QRY_RET_COORD(res, pen->font_height);
		case query_fields_font_family: PCB_QRY_RET_STR(res, pen->font_family);
		case query_fields_font_style:  PCB_QRY_RET_STR(res, pen->font_style);
		default: break;
	}

	PCB_QRY_RET_INV(res);
}


static double pcb_line_len2(csch_line_t *l)
{
	double x = l->inst.c.p1.x - l->inst.c.p2.x;
	double y = l->inst.c.p1.y - l->inst.c.p2.y;
	return x*x + y*y;
}

static int field_line(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_line_t *l = (csch_line_t *)obj;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);

	switch(fh1) {
		case query_fields_stroke:     return field_obj_pen(obj, obj->stroke_name, obj->stroke, fld->next, res);
		default: break;
	}

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_x1:         PCB_QRY_RET_COORD(res, l->inst.c.p1.x);
		case query_fields_y1:         PCB_QRY_RET_COORD(res, l->inst.c.p1.y);
		case query_fields_x2:         PCB_QRY_RET_COORD(res, l->inst.c.p2.x);
		case query_fields_y2:         PCB_QRY_RET_COORD(res, l->inst.c.p2.y);
		case query_fields_length:
			PCB_QRY_RET_DBL(res, ((rnd_coord_t)rnd_round(sqrt(pcb_line_len2(l)))));
			break;
		case query_fields_length2:
			PCB_QRY_RET_DBL(res, pcb_line_len2(l));
			break;
		case query_fields_area:
			{
				double th = obj_thickness(&l->hdr);
				double len = rnd_round(sqrt(pcb_line_len2(l)));
				PCB_QRY_RET_DBL(res, len * th + th*th/4*M_PI);
				break;
			}
		default:;
	}

	PCB_QRY_RET_INV(res);
}

static double pcb_arc_len(csch_arc_t *a)
{
	double r = (a->inst.c.r + a->inst.c.r)/2;
	return r * a->inst.c.delta;
}

static int field_arc(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_arc_t *a = (csch_arc_t *)obj;
	query_fields_keys_t fh1, fh2;

	fld2hash_req(fh1, fld, 0);

	if (fh1 == query_fields_angle) {
		fld2hash_req(fh2, fld, 1);
		switch(fh2) {
			case query_fields_start: PCB_QRY_RET_DBL(res, a->inst.c.start * RND_RAD_TO_DEG);
			case query_fields_delta: PCB_QRY_RET_DBL(res, a->inst.c.delta * RND_RAD_TO_DEG);
			default:;
		}
		PCB_QRY_RET_INV(res);
	}

	switch(fh1) {
		case query_fields_stroke:     return field_obj_pen(obj, obj->stroke_name, obj->stroke, fld->next, res);
		default: break;
	}

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_cx:
		case query_fields_x:         PCB_QRY_RET_COORD(res, a->inst.c.c.x);
		case query_fields_cy:
		case query_fields_y:         PCB_QRY_RET_COORD(res, a->inst.c.c.y);
		case query_fields_r:         PCB_QRY_RET_COORD(res, a->inst.c.r);
		case query_fields_length:
			PCB_QRY_RET_COORD(res, ((rnd_coord_t)rnd_round(pcb_arc_len(a))));
			break;
		case query_fields_length2:
			{
				double l = pcb_arc_len(a);
				PCB_QRY_RET_DBL(res, l*l);
			}
			break;
		case query_fields_area:
			{
				double th = obj_thickness(&a->hdr);
				double len = pcb_arc_len(a);
				PCB_QRY_RET_DBL(res, len * th + th*th/4*M_PI); /* approx */
			}
			break;
		default:;
	}

	PCB_QRY_RET_INV(res);
}

static int field_bitmap(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
/*	csch_cbitmap_t *b = (csch_cbitmap_t *)obj;*/
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

TODO("bitmap: field access");

	switch(fh1) {
		default:;
	}
	PCB_QRY_RET_INV(res);
}

static int field_text(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_text_t *t = (csch_text_t *)obj;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_x1:
		case query_fields_x:        PCB_QRY_RET_COORD(res, t->inst1.x);
		case query_fields_y1:
		case query_fields_y:        PCB_QRY_RET_COORD(res, t->inst1.y);
		case query_fields_x2:       PCB_QRY_RET_COORD(res, t->inst2.x);
		case query_fields_y2:       PCB_QRY_RET_COORD(res, t->inst2.y);
		case query_fields_rotation: PCB_QRY_RET_DBL(res, t->inst_rot);
		case query_fields_mirx:     PCB_QRY_RET_INT(res, t->inst_mirx);
		case query_fields_miry:     PCB_QRY_RET_INT(res, t->inst_miry);
		case query_fields_string:   PCB_QRY_RET_STR(res, t->text);
		case query_fields_text:     PCB_QRY_RET_STR(res, t->text);
		case query_fields_render:   PCB_QRY_RET_STR(res, csch_text_get_rtext(t));
		case query_fields_dyntext:  PCB_QRY_RET_INT(res, t->dyntext);
		case query_fields_has_bbox: PCB_QRY_RET_INT(res, t->has_bbox);
		case query_fields_halign:   PCB_QRY_RET_INT(res, t->halign);

		case query_fields_area:
			TODO("this is wrong for 45 degree rotated text, use isnt1 and isnt2 instead");
			PCB_QRY_RET_DBL(res, (double)(obj->bbox.y2 - obj->bbox.y1) * (double)(obj->bbox.x2 - obj->bbox.x1));
		default:;
	}

	PCB_QRY_RET_INV(res);
}

static int field_pen(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_cpen_t *p = (csch_cpen_t *)obj;

	return field_obj_pen(obj, p->name, p, fld, res);
}

static int field_conn(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_conn_t *c = (csch_conn_t *)obj;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_conn: return field_obj_conn(ec, &c->conn, res);
		default: break;
	}

	PCB_QRY_RET_INV(res);
}

static int field_polygon(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_cpoly_t *p = (csch_cpoly_t *)obj;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);

	switch(fh1) {
		case query_fields_stroke:     return field_obj_pen(obj, obj->stroke_name, obj->stroke, fld->next, res);
		case query_fields_fill:       return field_obj_pen(obj, obj->fill_name, obj->fill, fld->next, res);
		default: break;
	}

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_points: PCB_QRY_RET_INT(res, p->outline.used);
		case query_fields_area:
			TODO("calculate area");
			PCB_QRY_RET_DBL(res, 0);
		default:;
	}

	PCB_QRY_RET_INV(res);
}

static int field_grp(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_cgrp_t *g = (csch_cgrp_t *)obj;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);
	if (fh1 == query_fields_a) {
		const char *s2;
		fld2str_req(s2, fld, 1);
		return field_obj_attr(ec, g, s2, res);
	}

	if (fld->next != NULL)
		PCB_QRY_RET_INV(res);

	switch(fh1) {
		case query_fields_x:            PCB_QRY_RET_COORD(res, g->xform.x);
		case query_fields_y:            PCB_QRY_RET_COORD(res, g->xform.y);
		case query_fields_rotation:     PCB_QRY_RET_DBL(res, g->xform.rot);
		case query_fields_mirx:         PCB_QRY_RET_INT(res, g->xform.mirx);
		case query_fields_miry:         PCB_QRY_RET_INT(res, g->xform.miry);
		case query_fields_area:         PCB_QRY_RET_DBL(res, (double)(obj->bbox.y2 - obj->bbox.y1) * (double)(obj->bbox.x2 - obj->bbox.x1));
		case query_fields_uuid:         PCB_QRY_RET_UID(res, g->uuid);
		case query_fields_src_uuid:
			if ((g->hdr.type == CSCH_CTYPE_GRP) && (!minuid_is_nil(g->data.grp.src_uuid)))
				PCB_QRY_RET_UID(res, g->data.grp.src_uuid);
			break;
		default:;
	}
	PCB_QRY_RET_INV(res);
}

static int field_parent_obj(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res);

static int field_grp_from_ptr(pcb_qry_exec_t *ec, csch_cgrp_t *grp, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_chdr_t *obj = (csch_chdr_t *)grp;
	query_fields_keys_t fh1;

	fld2hash_req(fh1, fld, 0);
	COMMON_FIELDS(ec, obj, fh1, fld, res);

	return field_grp(ec, obj, fld, res);
}

static int field_parent_obj(pcb_qry_exec_t *ec, csch_chdr_t *obj, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	const char *s1;
	csch_cgrp_t *parent = obj->parent;

	/* if parent is not a group (or not available) evaluate to invalid */
	if (parent == NULL)
		PCB_QRY_RET_INV(res);

	/* check subfield, if there's none, return the subcircuit object */
	fld2str_opt(s1, fld, 0);
	if (s1 == NULL) {
		res->source = NULL;
		res->type = PCBQ_VT_OBJ;
		res->data.obj = (csch_chdr_t *)parent;
		return 0;
	}

	/* return subfields of the parent group */
	return field_grp_from_ptr(ec, parent, fld, res);
}

/***/

int pcb_qry_obj_field(pcb_qry_exec_t *ec, pcb_qry_val_t *objval, pcb_qry_node_t *fld, pcb_qry_val_t *res)
{
	csch_chdr_t *obj;
	query_fields_keys_t fh1;

	if (objval->type != PCBQ_VT_OBJ)
		return -1;
	obj = objval->data.obj;

	res->source = NULL;

	fld2hash_req(fh1, fld, 0);

	COMMON_FIELDS(ec, obj, fh1, fld, res);

	switch(obj->type) {
		case CSCH_CTYPE_LINE:     return field_line(ec, obj, fld, res);
		case CSCH_CTYPE_TEXT:     return field_text(ec, obj, fld, res);
		case CSCH_CTYPE_POLY:     return field_polygon(ec, obj, fld, res);
		case CSCH_CTYPE_ARC:      return field_arc(ec, obj, fld, res);
		case CSCH_CTYPE_BITMAP:   return field_bitmap(ec, obj, fld, res);
		case CSCH_CTYPE_GRP:      return field_grp(ec, obj, fld, res);
		case CSCH_CTYPE_GRP_REF:  return field_grp(ec, obj, fld, res);
		case CSCH_CTYPE_PEN:      return field_pen(ec, obj, fld, res);
		case CSCH_CTYPE_CONN:     return field_conn(ec, obj, fld, res);
		default:;
	}

	return -1;
}

