/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2019 Tibor 'Igor2' Palinkas
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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 "config.h"

#include <librnd/core/misc_util.h>
#include <librnd/core/rnd_printf.h>

#include "event.h"
#include "undo.h"

#include "cnc_obj.h"
#include "cnc_poly.h"

RND_INLINE csch_displayer_t role2ly(csch_role_t role, csch_chdr_t *obj)
{
	switch(role) {
		case CSCH_ROLE_BUS_NET:       return CSCH_DSPLY_BUS;
		case CSCH_ROLE_BUS_TERMINAL:  return CSCH_DSPLY_HUBTERM;
		case CSCH_ROLE_HUB_POINT:     return CSCH_DSPLY_HUBTERM;
		case CSCH_ROLE_TERMINAL:      return CSCH_DSPLY_HUBTERM;
		case CSCH_ROLE_SYMBOL:        return CSCH_DSPLY_SYMBOL;
		case CSCH_ROLE_EXTOBJ_GFX:    return CSCH_DSPLY_SYMBOL;
		case CSCH_ROLE_WIRE_NET:      return CSCH_DSPLY_WIRE;
		case CSCH_ROLE_JUNCTION:      return CSCH_DSPLY_WIRE;
		case CSCH_ROLE_empty:         return obj->type == CSCH_CTYPE_POLY ? CSCH_DSPLY_BACKGROUND : CSCH_DSPLY_DECORATION; /* top level user grouping */
		case CSCH_ROLE_invalid:       break;
	}
	return CSCH_DSPLY_invalid;
}

csch_displayer_t csch_cobj_dsply(csch_chdr_t *obj)
{
	csch_cgrp_t *parent = obj->parent;
	csch_displayer_t ly;

	/* a connection object is always on the connection layer */
	if (obj->type == CSCH_CTYPE_CONN)
		return CSCH_DSPLY_CONN;

	/* a group is on a layer by its own right usually */
	if (csch_obj_is_grp(obj)) {
		csch_cgrp_t *grp = (csch_cgrp_t *)obj;
		csch_sheet_t *sheet = grp->hdr.sheet;

		if (grp == &sheet->direct)
			return CSCH_DSPLY_invalid;

		ly = role2ly(grp->role, obj);
		if (ly != CSCH_DSPLY_invalid)
			return ly;
	}

	if (parent == NULL)
		return CSCH_DSPLY_invalid;

	/* if parent is a top level group, object must be decoration as
	   anything non-decoration is in a group with role */
	if (parent->hdr.parent == NULL)
		return obj->type == CSCH_CTYPE_POLY ? CSCH_DSPLY_BACKGROUND : CSCH_DSPLY_DECORATION;

	/* direct descendant of known-role groups */
	ly = role2ly(parent->role, obj);
	if (ly != CSCH_DSPLY_invalid)
		return ly;

	/* grandchildren of known-role groups; parent group already has the layer set */
	switch(parent->hdr.dsply) {
		case CSCH_DSPLY_BACKGROUND: /* first level of objects without any role */
			return CSCH_DSPLY_DECORATION;
		default:
			return parent->hdr.dsply;
	}
	return CSCH_DSPLY_invalid;
}

int csch_cobj_update_dsply(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	csch_displayer_t newly = csch_cobj_dsply(obj);

	if (obj->dsply == newly)
		return 0;

	csch_cobj_rtree_del(sheet, obj);
	obj->dsply = newly;
	csch_cobj_rtree_add(sheet, obj);
	return 1;
}


int csch_cobj_insert(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_chdr_t *obj, csch_oid_t oid)
{
	if ((obj->parent != NULL) || (htip_get(&parent->id2obj, oid) != NULL))
		return -1;

	/* moving between sheets! */
	if ((sheet != obj->sheet) && (obj->sheet != NULL)) {
		gdl_remove(&obj->sheet->active, obj, link);
		obj->sheet = NULL;
	}

	csch_cobj_insert_(obj, sheet, parent, oid);
	return 0;
}

void csch_cobj_bbox_changed(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	csch_cgrp_t *prn = obj->parent;
	csch_chdr_t *phd;
	csch_rtree_box_t *bxfl;
	int changed = 0;

	if (prn == NULL) {
		if (!sheet->non_graphical && (obj == &sheet->direct.hdr)) {
			sheet->bbox = sheet->direct.bbox_flt;
			sheet->bbox_changed = 1;
		}
		return; /* happens in top groups, like direct or indirect */
	}

	phd = &prn->hdr;

		/* bbox-with-floaters-included: only if it did grow */
	if (csch_obj_is_grp(obj)) { /* use group's floater bbox instead of naked bbox */
		csch_cgrp_t *g = (csch_cgrp_t *)obj;
		bxfl = &g->bbox_flt;
	}
	else
		bxfl = &obj->bbox;
	if ((bxfl->x1 < prn->bbox_flt.x1) || (bxfl->y1 < prn->bbox_flt.y1) || (bxfl->x2 > prn->bbox_flt.x2) || (bxfl->y2 > prn->bbox_flt.y2)) {
		if (bxfl->x1 < prn->bbox_flt.x1) prn->bbox_flt.x1 = bxfl->x1;
		if (bxfl->y1 < prn->bbox_flt.y1) prn->bbox_flt.y1 = bxfl->y1;
		if (bxfl->x2 > prn->bbox_flt.x2) prn->bbox_flt.x2 = bxfl->x2;
		if (bxfl->y2 > prn->bbox_flt.y2) prn->bbox_flt.y2 = bxfl->y2;
		changed = 1;
	}

	if (!obj->floater) {
		/* only if it did grow */
		if ((obj->bbox.x1 < phd->bbox.x1) || (obj->bbox.y1 < phd->bbox.y1) || (obj->bbox.x2 > phd->bbox.x2) || (obj->bbox.y2 > phd->bbox.y2)) {
			csch_cobj_rtree_del(sheet, phd);
			if (obj->bbox.x1 < phd->bbox.x1) phd->bbox.x1 = obj->bbox.x1;
			if (obj->bbox.y1 < phd->bbox.y1) phd->bbox.y1 = obj->bbox.y1;
			if (obj->bbox.x2 > phd->bbox.x2) phd->bbox.x2 = obj->bbox.x2;
			if (obj->bbox.y2 > phd->bbox.y2) phd->bbox.y2 = obj->bbox.y2;
			csch_cobj_rtree_add(sheet, phd);
			changed = 1;
		}
	}

	if (changed)
		csch_cobj_bbox_changed(sheet, phd);
}

static const char core_commprp_cookie[] = "libcschem/core/cnc_obj.c";

typedef struct {
	csch_chdr_t *obj; /* it is safe to save the object pointer because it is persistent (through the removed object list) */

	unsigned lock:1;
	unsigned floater:1;
} undo_commprp_modify_t;


static int undo_commprp_modify_swap(void *udata)
{
	undo_commprp_modify_t *u = udata;

	rnd_swap(int, u->lock, u->obj->lock);
	rnd_swap(int, u->floater, u->obj->floater);

	csch_sheet_set_changed(u->obj->sheet, 1);
	return 0;
}

static void undo_commprp_modify_print(void *udata, char *dst, size_t dst_len)
{
	undo_commprp_modify_t *u = udata;
	rnd_snprintf(dst, dst_len, "common properties modified (lock: %d<->%d floater: %d<->%d)", u->obj->lock, u->lock, u->obj->floater, u->floater);
}


static const uundo_oper_t undo_commprp_modify = {
	core_commprp_cookie,
	NULL,
	undo_commprp_modify_swap,
	undo_commprp_modify_swap,
	undo_commprp_modify_print
};


void csch_commprp_modify(csch_sheet_t *sheet, csch_chdr_t *obj, int *lock, int *floater, int undoable)
{
	undo_commprp_modify_t utmp, *u = &utmp;

	if (undoable) u = uundo_append(&sheet->undo, &undo_commprp_modify, sizeof(undo_commprp_modify_t));

	u->obj = obj;
	u->lock = CSCH_UNDO_MODIFY(obj, lock);
	u->floater = CSCH_UNDO_MODIFY(obj, floater);

	undo_commprp_modify_swap(u);
	if (undoable) csch_undo_inc_serial(sheet);
}


unsigned csch_chdr_hash(const csch_chdr_t *hdr)
{
	unsigned res = hdr->type;

	if (hdr->stroke_name.str != NULL)
		res ^= strhash(hdr->stroke_name.str);

	if (hdr->fill_name.str != NULL)
		res ^= strhash(hdr->fill_name.str);

	res ^= ((unsigned)hdr->floater) << 9;

	return res;
}


unsigned csch_chdr_eq(const csch_chdr_t *hdr1, const csch_chdr_t *hdr2)
{
	if (hdr1->type != hdr2->type) return 0;

	if (hdr1->sheet == hdr2->sheet) {
		if (hdr1->stroke_name.str != hdr2->stroke_name.str) return 0;
		if (hdr1->fill_name.str != hdr2->fill_name.str) return 0;
	}
	else {
		if ((hdr1->stroke_name.str == NULL) && (hdr2->stroke_name.str != NULL)) return 0;
		if ((hdr1->stroke_name.str != NULL) && (hdr2->stroke_name.str == NULL)) return 0;
		if ((hdr1->fill_name.str == NULL) && (hdr2->fill_name.str != NULL)) return 0;
		if ((hdr1->fill_name.str != NULL) && (hdr2->fill_name.str == NULL)) return 0;
		if ((hdr1->stroke_name.str != NULL) && (strcmp(hdr1->stroke_name.str, hdr2->stroke_name.str) != 0)) return 0;
		if ((hdr1->fill_name.str != NULL) && (strcmp(hdr1->fill_name.str, hdr2->fill_name.str) != 0)) return 0;
	}

	if (hdr1->floater != hdr2->floater) return 0;

	return 1;
}

int csch_cobj_has_fill_(csch_chdr_t *obj)
{
	return csch_cpoly_has_fill((csch_cpoly_t *)obj);
}

