/*  libgrbs - geometric rubber band sketch model
    Copyright (C) 2021  Tibor 'Igor2' Palinkas
    (Supported by NLnet NGI0 PET Fund in 2021)

    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/libgrbs
      lead developer: http://repo.hu/projects/pcb-rnd/contact.html
*/

#include <math.h>
#include "addr_hash.h"

static short int hash_ang(double radian)
{
	return floor(radian * 1000.0);
}

static void grbs_addr_new_to_key_(grbs_addr_key1_t *dst, const grbs_addr_t *addr)
{
	grbs_arc_t *arc;

	dst->valid = 1;
	dst->is_vconcave = 0;
	switch(addr->type & 0x0f) {
		case ADDR_ARC_CONVEX:   break;
		case ADDR_ARC_VCONCAVE: dst->is_vconcave = 1; break;
		case ADDR_POINT:
			dst->pt_uid = addr->obj.pt->uid;
			return;
		default:
			assert(!"grbs_addr_new_to_key: invalid address type");
			abort();
	}

	/* convex arc */
	arc = addr->obj.arc;
	dst->is_ccw = arc->new_adir < 0;
	dst->ang = hash_ang((addr->type & ADDR_ARC_END) ? arc->new_sa + arc->new_da : arc->new_sa);
	dst->pt_uid = arc->parent_pt->uid;

	for(dst->orbit = 0; arc != NULL; arc = arc->link_point.prev)
		if (arc->in_use)
			dst->orbit++;
}

grbs_addr_key_t grbs_addr_new_to_key(const grbs_addr_t *addr)
{
	grbs_addr_key_t res = {0};

	grbs_addr_new_to_key_(&res.curr, addr);
	if ((addr->type & 0x0f) == ADDR_ARC_VCONCAVE) {
		grbs_addr_new_to_key_(&res.last, addr->last_real);
		if (addr->last_real != NULL)
			grbs_addr_new_to_key_(&res.last2, addr->last_real->last_real);
	}

	return res;
}


static void grbs_det_addr_to_key_(grbs_addr_key1_t *dst, const grbs_detached_addr_t *det)
{
	grbs_arc_t *arc;

	dst->valid = 1;
	dst->pt_uid = det->pt->uid;

	if ((det->type & 0x0f) == ADDR_POINT) return;

	/* convex arc */
	dst->ang = hash_ang((det->type & ADDR_ARC_END) ? det->new_sa + det->new_da : det->new_sa);

	dst->is_ccw = det->new_adir < 0;

	if ((det->arc != NULL) && !det->arc->vconcave) { /* convex */
		dst->is_vconcave = 0;
		for(dst->orbit = 0, arc = det->arc; arc != NULL; arc = arc->link_point.prev)
			if (arc->in_use)
				dst->orbit++;
	}
	else {
		dst->is_vconcave = ((det->type & 0x0f) == ADDR_ARC_VCONCAVE);
		dst->orbit = 0; /* (det->arc == NULL) or vconcave means first orbit over a new sentinel */
	}
}

grbs_addr_key_t grbs_det_addr_to_key(const grbs_detached_addr_t det[2])
{
	grbs_addr_key_t res = {0};

	grbs_det_addr_to_key_(&res.curr, det);
	if ((det->type & 0x0f) == ADDR_ARC_VCONCAVE) {
		grbs_det_addr_to_key_(&res.last, det+1);
		if ((det[2].type & 0x0f) != 0)
			grbs_det_addr_to_key_(&res.last2, det+2);
	}

	return res;
}


grbs_addr_key_t grbs_point_to_key(const grbs_point_t *pt)
{
	grbs_addr_key_t res = {0};
	res.curr.pt_uid = pt->uid;
	res.curr.valid = 1;
	return res;
}


static int grbs_addr_hash_keyeq_(grbs_addr_key1_t a, grbs_addr_key1_t b)
{
	int diff;

	/* cheap tests first */
	if ((a.orbit != b.orbit) || (a.is_vconcave != b.is_vconcave)  || (a.pt_uid != b.pt_uid) || (a.is_ccw != b.is_ccw))
		return 0;

	/* for angle, allow +-1 for rounding errors */
	diff = a.ang - b.ang;
	return (diff >= -1) && (diff <= 1);
}

int grbs_addr_hash_keyeq(grbs_addr_key_t a, grbs_addr_key_t b)
{
	assert(a.curr.valid);
	assert(b.curr.valid);

	if (!grbs_addr_hash_keyeq_(a.curr, b.curr)) return 0;

	if (a.curr.is_vconcave) {
		assert(a.last.valid);
		assert(b.last.valid);
		return grbs_addr_hash_keyeq_(a.last, b.last);
	}

	return 1;
}

