/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - convert to polygon
 *  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., 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
 */

/* Convert floating objects to polygon (low level) */

#include <librnd/core/error.h>
#include <libcschem/concrete.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/operation.h>
#include <genht/hash.h>

typedef struct {
	csch_chdr_t *objs[2];  /* remember at most 2 objects for a point; 3rd is a junction -> error */
	char side[2];          /* 0 means start, 1 means end */
	char visited;          /* loop mapping: endpoint already visited */
} end_t;

static int keyeq(g2d_vect_t a, g2d_vect_t b)
{
	return (a.x == b.x) && (a.y == b.y);
}

static unsigned int keyhash(g2d_vect_t key)
{
	return jenhash32(key.x) ^ jenhash32(key.y >> 1);
}

static end_t end_invalid = {{0, 0}, {64, 64}};

/* Hash: g2d_vect_t -> end_t */
typedef g2d_vect_t htend_key_t;
typedef end_t htend_value_t;
#define HT(x) htend_ ## x
#define HT_INVALID_VALUE end_invalid
#include <genht/ht.h>
#include <genht/ht.c>
#undef HT

#define INSERT(x_, y_, sd) \
do { \
	g2d_vect_t v; \
	htend_entry_t *e; \
	v.x = x_; v.y = y_; \
	e = htend_getentry(&ends, v); \
	if (e == NULL) { \
		end_t end = {0}; \
		end.objs[0] = obj; \
		end.side[0] = sd; \
		htend_set(&ends, v, end); \
	} \
	else { \
		if (e->value.objs[1] == NULL) { \
			e->value.objs[1] = obj; \
			e->value.side[1] = sd; \
		} \
		else { \
			if (!silent) { \
				rnd_message(RND_MSG_ERROR, "Convert to polygon failed: more than 2 object endpoints in the same point at %d;%d\n", x_, y_); \
				goto quit; \
			} \
		} \
	} \
} while(0)


/* Return next (neighbor) endpoint jumping using from's ->objs[idx] */
RND_INLINE htend_entry_t *cursor_next_idx(htend_t *ends, htend_entry_t *from, int idx)
{
	csch_coord_t x0, y0, x1, y1;
	htend_key_t nk;
	int res;
	htend_entry_t *e;

	res = csch_cobj_get_endxy(from->value.objs[idx], 0, &x0, &y0);
	assert(res == 0);
	res = csch_cobj_get_endxy(from->value.objs[idx], 1, &x1, &y1);
	assert(res == 0);

	if ((from->key.x == x0) && (from->key.y == y0)) {
		nk.x = x1;
		nk.y = y1;
	}
	else {
		assert((from->key.x == x1) && (from->key.y == y1));
		nk.x = x0;
		nk.y = y0;
	}

	e = htend_getentry(ends, nk);
	assert(e != NULL);
	return e;
}

/* Starting from the top left corner, figure start direction */
RND_INLINE htend_entry_t *cursor_first(htend_t *ends, htend_entry_t *topleft, csch_chdr_t **bridge)
{
	htend_entry_t *next0, *next1, *next;

	/* top left means: y is minimum; the next node in CCW must have a smaller y;
	   if both neigbours have smaller y, topleft is a ^ triangle upper point,
	   choose the next with smaller x (left side)  */
	next0 = cursor_next_idx(ends, topleft, 0);
	next1 = cursor_next_idx(ends, topleft, 1);

	if ((next0->key.y < topleft->key.y) && (next1->key.y < topleft->key.y)) {
		/* both smaller case */
		if (next0->key.x < next1->key.x) {
			*bridge = topleft->value.objs[0];
			next = next0;
		}
		else {
			*bridge = topleft->value.objs[1];
			next = next1;
		}
	}
	else {
		/* only next0 or next1 is below topleft, the other has the same y */
		if (next0->key.y < topleft->key.y) {
			*bridge = topleft->value.objs[0];
			next = next0;
		}
		else {
			*bridge = topleft->value.objs[1];
			next = next1;
		}
	}

	return next;
}

RND_INLINE htend_entry_t *cursor_next(htend_t *ends, htend_entry_t *from, csch_chdr_t **bridge)
{
	htend_entry_t *n0 = cursor_next_idx(ends, from, 0);
	htend_entry_t *n1 = cursor_next_idx(ends, from, 1);

	/* we are coming from an already visited endpoint */
	assert(n0->value.visited || n1->value.visited);

	if (n0->value.visited) {
		*bridge = from->value.objs[1];
		return n1;
	}
	if (n1->value.visited) {
		*bridge = from->value.objs[0];
		return n0;
	}

	/* can't get here because either neighbor is already visited */
	abort();
	return NULL;
}

RND_INLINE void copy_ol_obj(csch_coutline_t *dst, const csch_chdr_t *src, int swap)
{
	/* assumes dst bytes are set to 0 */

	dst->hdr.type = src->type;
	switch(src->type) {
		case CSCH_CTYPE_LINE:
			{
				const csch_line_t *l = (const csch_line_t *)src;
				dst->line.spec.p1 = swap ? l->spec.p2 : l->spec.p1;
				dst->line.spec.p2 = swap ? l->spec.p1 : l->spec.p2;
			}
			break;
		case CSCH_CTYPE_ARC:
			{
				const csch_arc_t *a = (const csch_arc_t *)src;
				dst->arc.spec = a->spec;
				if (swap) {
					dst->arc.spec.start = a->spec.start + a->spec.delta;
					dst->arc.spec.delta = -a->spec.delta;
				}
			}
			break;

		default:
			/* can't happen */
			break;
	}
}

static csch_cpoly_t *sch_rnd_conv_poly(csch_sheet_t *sheet, csch_cgrp_t *parent, vtp0_t *atoms, int silent)
{
	long n;
	htend_t ends;
	csch_cpoly_t *poly = NULL;
	csch_chdr_t *bridge;
	htend_entry_t *e, *topleft, *cursor;

	/* sanity check for object type */
	if (atoms->used < 3) {
		if (!silent)
			rnd_message(RND_MSG_ERROR, "Can't convert objects to polygon: need at least 3 objects\n");
		return NULL;
	}

	for(n = 0; n < atoms->used; n++) {
		csch_chdr_t *obj = atoms->array[n];
		if ((obj->type != CSCH_CTYPE_LINE) && (obj->type != CSCH_CTYPE_ARC)) {
			if (!silent)
				rnd_message(RND_MSG_ERROR, "Can't convert objects to polygon: all objects need to be line or arc\n");
			return NULL;
		}
	}

	htend_init(&ends, keyhash, keyeq);

	/* build endpoint hash */
	for(n = 0; n < atoms->used; n++) {
		csch_chdr_t *obj = atoms->array[n];
		csch_coord_t x[2], y[2];
		int er;

		er =  csch_cobj_get_endxy(obj, 0, &x[0], &y[0]);
		er |= csch_cobj_get_endxy(obj, 1, &x[1], &y[1]);
		if (er != 0) {
			if (!silent)
				rnd_message(RND_MSG_ERROR, "Can't convert objects to polygon: failed to query object end coords\n");
			return NULL;
		}
		if ((x[0] == x[1]) && (y[0] == y[1])) {
			if (!silent)
				rnd_message(RND_MSG_ERROR, "Polygon convert: ignoring zero-length object\n");
			continue;
		}

		INSERT(x[0], y[0], 0);
		INSERT(x[1], y[1], 1);
	}

	/* find top y coord that has the smallest x coord, also make sure we have the chance for a closed loop */
	for(topleft = e = htend_first(&ends); e != NULL; e = htend_next(&ends, e)) {
		if (e->value.objs[1] == NULL) {
			if (!silent) {
				rnd_message(RND_MSG_ERROR, "Convert to polygon failed: loop not closed at %d;%d\n", e->key.x, e->key.y);
				goto quit;
			}
		}
		if (e->key.y > topleft->key.y)
			topleft = e;
		else if ((e->key.y == topleft->key.y) && (e->key.x < topleft->key.x))
			topleft = e;
	}

	/* find which objects need to be swapped for a loop; we are after all
	   sanity checks so this can't fail: we have a closed loop */
	poly = (csch_cpoly_t *)csch_op_create(sheet, parent, CSCH_CTYPE_POLY);
	topleft->value.visited = 1;
	bridge = NULL;
	for(n = 0, cursor = cursor_first(&ends, topleft, &bridge); !cursor->value.visited; cursor = cursor_next(&ends, cursor, &bridge), n++) {
		csch_coord_t xe, ye;
		csch_coutline_t *ol;
		int swap;

		cursor->value.visited = 1;
		csch_cobj_get_endxy(bridge, 1, &xe, &ye);

		/* if end of the bridge object is not where we landed, the bridge object is
		   in reverse and needs swapping before insertion */
		swap = (xe != cursor->key.x) || (ye != cursor->key.y);

		ol = csch_vtcoutline_alloc_append(&poly->outline, 1);
		copy_ol_obj(ol, bridge, swap);
		printf("mark: %ld %ld (swap=%d)\n", cursor->key.x, cursor->key.y, swap);

		/* defeat a special case: topleft had to be marked visited so the first
		   next() call knew where to proceed, but we didn't really add any
		   object from topleft; but there are surely at least 3 objects, so
		   after after adding two, unmark topleft so it can be visited last */
		if (n == 1)
			topleft->value.visited = 0;
	}

	poly->hdr.stroke_name = bridge->stroke_name;
	poly->has_stroke = 1;
	csch_poly_update(sheet, poly, 1);

	quit:;
	htend_uninit(&ends);

	return poly;
}

#undef INSERT
