/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018 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., 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
 */

/* Generic object helpers */

#ifndef CSCH_CONCRETE_OBJ_H
#define CSCH_CONCRETE_OBJ_H

#include <genht/htsp.h>
#include <genht/hash.h>
#include <assert.h>

#include "event.h"
#include "attrib.h"
#include "cnc_pen.h"
#include "cnc_text.h"

#define csch_obj_bbox_reset(obj) csch_bbox_reset(&((obj)->hdr.bbox))

#define csch_cobj_redraw(obj) \
do { \
	csch_chdr_t *__obj__ = (csch_chdr_t *)obj; \
	if (__obj__->bbox.x1 < __obj__->bbox.x2) { \
		if (__obj__->sheet->redraw_inhibit == 0) \
			rnd_event(&__obj__->sheet->hidlib, CSCH_EVENT_BOX_NEEDS_REDRAW, "p", &__obj__->bbox); \
		else \
			csch_sheet_inval_lr(__obj__->sheet, &__obj__->bbox); \
	} \
} while(0)

/* Stop generating redraw events on object changes */
RND_INLINE void csch_cobj_redraw_freeze(csch_sheet_t *sheet);
RND_INLINE void csch_cobj_redraw_unfreeze(csch_sheet_t *sheet);

/* implement heuristics to figure on which display layer an object is supposed to go */
csch_displayer_t csch_cobj_dsply(csch_chdr_t *obj);

/* Recalculates the display layer of obj and do all the rtree administration
   if it has changed. Returns whether it has changed. */
int csch_cobj_update_dsply(csch_sheet_t *sheet, csch_chdr_t *obj);


/* Object rtree management; del returns 0 on success (object got removed
   from the tree) and non-zero when the object was not in the tree. */
RND_INLINE int  csch_cobj_rtree_del(csch_sheet_t *sheet, csch_chdr_t *obj);
RND_INLINE void csch_cobj_rtree_add(csch_sheet_t *sheet, csch_chdr_t *obj);

/* Insert an object in a group by setting the common header fields; do not use
   directly, use csch_cobj_insert() */
RND_INLINE void csch_cobj_insert_(csch_chdr_t *dst, csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid);

/* Initialize an object by setting the common header fields; the only purpose
   of this function is to get compiler warnings at every caller when the header
   is extended (because the number of args would be extended here) */
RND_INLINE void csch_cobj_init(csch_chdr_t *dst, csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid, csch_ctype_t type);

/* free common fields */
RND_INLINE void csch_cobj_uninit(csch_chdr_t *dst);

/* Recalculate stroke/fill cache in dst */
RND_INLINE void csch_cobj_update_pen(csch_chdr_t *dst);

/* Insert an object in parent group, using new oid, if the object is not
   already in a group (so obj->parent must be NULL). Sheet is (the new)
   parent's sheet. Returns 0 on success. */
int csch_cobj_insert(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_chdr_t *obj, csch_oid_t oid);

/* Returns whether the group is atomic (group parts are not directly editable) */
RND_INLINE int csch_grp_is_atomic(csch_sheet_t *sheet, csch_cgrp_t *grp);

/* Returns 1 if obj is locked for any reason (lock property, group-lock); this
   means obj should not be edited directly. */
RND_INLINE int csch_cobj_is_locked(csch_chdr_t *obj);

/* Returns 1 if obj is locked with the lock property directly or through
   parent, but ignores group locking */
RND_INLINE int csch_cobj_is_explicit_locked(csch_chdr_t *obj);

/* Similar to csch_cobj_is_locked() but returns the first parent that is
   not locked; doesn't return sheet->direct; returns NULL if no such
   parent found */
RND_INLINE csch_chdr_t *csch_cobj_first_unlocked(csch_chdr_t *obj);

/* Similar to csch_cobj_is_locked() but returns the first parent that is
   explicitly locked using the lock attr; doesn't return sheet->direct;
   returns NULL if no such parent found */
RND_INLINE csch_chdr_t *csch_cobj_first_explicit_locked(csch_chdr_t *obj);

/* Modify common properties (doc: {des3:81}) of any object */
void csch_commprp_modify(csch_sheet_t *sheet, csch_chdr_t *obj, int *lock, int *floater, int undoable);

/* Call this after obj's bbox changed; updates parent group's bbox recursively
   up to the root group, as needed */
void csch_cobj_bbox_changed(csch_sheet_t *sheet, csch_chdr_t *obj);

/*** Hash ***/

unsigned csch_chdr_hash(const csch_chdr_t *hdr);
RND_INLINE unsigned csch_coord_hash(csch_coord_t crd);
RND_INLINE unsigned csch_angle_hash(double angle);

unsigned csch_chdr_eq(const csch_chdr_t *hdr1, const csch_chdr_t *hdr2);
RND_INLINE unsigned csch_angle_eq(double angle1, double angle2);



/*** Implementation ***/

/* Internal call for csch_cobj_redraw(): add box to the redraw bbox
   (sheet->inbbox) without any immediate redraw */
RND_INLINE void csch_sheet_inval_lr(csch_sheet_t *sheet, csch_rtree_box_t *box)
{
	if (box->x1 < sheet->invbox.x1) sheet->invbox.x1 = box->x1;
	if (box->x2 > sheet->invbox.x2) sheet->invbox.x2 = box->x2;
	if (box->y1 < sheet->invbox.y1) sheet->invbox.y1 = box->y1;
	if (box->y2 > sheet->invbox.y2) sheet->invbox.y2 = box->y2;
	sheet->invbox_valid = 1;
}

RND_INLINE void csch_cobj_redraw_freeze(csch_sheet_t *sheet)
{
	sheet->redraw_inhibit++;
	if (sheet->redraw_inhibit == 1) {
		sheet->invbox.x1 = sheet->invbox.y1 = CSCH_COORD_MAX;
		sheet->invbox.x2 = sheet->invbox.y2 = -CSCH_COORD_MAX;
		sheet->invbox_valid = 0;
	}
}

RND_INLINE void csch_cobj_redraw_unfreeze(csch_sheet_t *sheet)
{
	assert(sheet->redraw_inhibit > 0);
	if (sheet->redraw_inhibit > 0) {
		sheet->redraw_inhibit--;
		if ((sheet->redraw_inhibit == 0) && sheet->invbox_valid) {
			rnd_event(&sheet->hidlib, CSCH_EVENT_BOX_NEEDS_REDRAW, "p", &sheet->invbox);
			sheet->invbox_valid = 0;
		}
	}
}

int csch_cobj_has_fill_(csch_chdr_t *obj);
RND_INLINE int csch_cobj_has_fill(csch_chdr_t *obj)
{
	if (obj->type != CSCH_CTYPE_POLY) return 0;
	return csch_cobj_has_fill_(obj);
}


RND_INLINE int csch_cobj_rtree_del(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	if ((obj->dsply < 0) || (obj->dsply >= CSCH_DSPLY_max) || (csch_bbox_is_invalid(&obj->bbox)))
		return -1;

	if (csch_cobj_has_fill(obj))
		csch_rtree_delete(&sheet->dsply_fill[obj->dsply], obj, &obj->bbox);

	return csch_rtree_delete(&sheet->dsply[obj->dsply], obj, &obj->bbox);
}

RND_INLINE void csch_cobj_rtree_add(csch_sheet_t *sheet, csch_chdr_t *obj)
{
	if ((obj->dsply < 0) || (obj->dsply >= CSCH_DSPLY_max) || (csch_bbox_is_invalid(&obj->bbox)) || obj->indirect)
		return;
	if (csch_cobj_has_fill(obj))
		csch_rtree_insert(&sheet->dsply_fill[obj->dsply], obj, &obj->bbox);
	csch_rtree_insert(&sheet->dsply[obj->dsply], obj, &obj->bbox);
}


RND_INLINE void csch_cobj_insert_(csch_chdr_t *dst, csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_cgrp_t *p;

	dst->oid = oid;
	dst->parent = parent;
	dst->sheet = sheet;
	dst->dsply = csch_cobj_dsply(dst);
	gdl_append(&sheet->active, dst, link);
	if (dst->parent != NULL)
		htip_set(&parent->id2obj, oid, dst);

	dst->indirect = 0;
	for(p = parent; p != NULL; p = p->hdr.parent) {
		if (p == &sheet->indirect) {
			dst->indirect = 1;
			break;
		}
	}
}

RND_INLINE void csch_cobj_init(csch_chdr_t *dst, csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid, csch_ctype_t type)
{
	dst->type = type;
	csch_cobj_insert_(dst, sheet, parent, oid);
	csch_bbox_reset(&dst->bbox);
}

RND_INLINE void csch_cobj_uninit(csch_chdr_t *dst)
{
	csch_chdr_t *o;

	if (dst->link.parent != NULL)
		gdl_remove(dst->link.parent, dst, link);
	csch_cobj_rtree_del(dst->sheet, dst);

	if ((dst->sheet != NULL) && (dst->parent != NULL)) {
		o = htip_pop(&dst->parent->id2obj, dst->oid);
		if (o != NULL)
			assert(o == dst);
	}

	vtp0_uninit(&dst->conn);
}

RND_INLINE void csch_cobj_update_pen(csch_chdr_t *dst)
{
	csch_cobj_redraw(dst);
	csch_stroke_(dst);
	csch_fill_(dst);
	if (dst->type == CSCH_CTYPE_TEXT)
		csch_text_invalidate_font((csch_text_t *)dst);
	csch_cobj_redraw(dst);
}


RND_INLINE int csch_grp_is_atomic(csch_sheet_t *sheet, csch_cgrp_t *grp)
{
	if ((grp == NULL) || (grp == &sheet->direct) || (grp == &sheet->indirect))
		return 0;

	switch(grp->role) {
		case CSCH_ROLE_invalid:
		case CSCH_ROLE_WIRE_NET:
			return 0;
		case CSCH_ROLE_empty: /* the user groupped it for this specific reason */
		case CSCH_ROLE_JUNCTION:
		case CSCH_ROLE_BUS_NET:
		case CSCH_ROLE_BUS_TERMINAL:
		case CSCH_ROLE_HUB_POINT:
		case CSCH_ROLE_TERMINAL:
			return 1;
		case CSCH_ROLE_SYMBOL:
			return !sheet->loose_sym;
	}
	return 0;
}

RND_INLINE int csch_cobj_is_explicit_locked(csch_chdr_t *obj)
{
	csch_chdr_t *o;

	/* if the object or any parent is locked, object is locked */
	for(o = obj; o != NULL; o = &o->parent->hdr)
		if (o->lock)
			return 1;

	return 0;
}

RND_INLINE int csch_cobj_is_locked(csch_chdr_t *obj)
{
	/* group lock */
	if (csch_grp_is_atomic(obj->sheet, obj->parent) && !obj->floater)
		return 1;

	return csch_cobj_is_explicit_locked(obj);
}


RND_INLINE csch_chdr_t *csch_cobj_first_unlocked(csch_chdr_t *obj)
{
	csch_chdr_t *o;

	/* obj not group locked */
	if (!obj->lock && (!csch_grp_is_atomic(obj->sheet, obj->parent) || obj->floater))
		return obj;

	/* find first unlocked parent */
	for(o = &obj->parent->hdr; ((o != NULL) && (o != &obj->sheet->direct.hdr)); o = &o->parent->hdr)
		if (!o->lock && (!csch_grp_is_atomic(o->sheet, obj->parent) || o->floater))
			return o;

	return NULL;
}


RND_INLINE csch_chdr_t *csch_cobj_first_explicit_locked(csch_chdr_t *obj)
{
	csch_chdr_t *o;

	if (obj->lock)
		return obj;

	/* find first unlocked parent */
	for(o = &obj->parent->hdr; ((o != NULL) && (o != &obj->sheet->direct.hdr)); o = &o->parent->hdr)
		if (o->lock)
			return o;

	return NULL;
}

RND_INLINE unsigned csch_coord_hash(csch_coord_t crd)
{
	return crd;
}

RND_INLINE unsigned csch_angle_hash(double angle)
{
	return floor(angle * 100.0);
}

RND_INLINE unsigned csch_angle_eq(double angle1, double angle2)
{
	double diff = angle1 - angle2;
	return ((diff >= -0.01) && (diff <= +0.01));
}

#endif

