/*
 *                            COPYRIGHT
 *
 *  libpolybool, 2D polygon bool operations
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2024)
 *
 *  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://www.repo.hu/projects/librnd
 *    lead developer: http://www.repo.hu/projects/librnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 *
 */

#ifdef RND_API_VER
#include <librnd/core/safe_fs.h>
#endif

#ifndef NDEBUG

/* compare numer of segments in ->all_segs and in ->seg_tree while drawing
   svg - helps spotting administrative error when a segment is added/removed
   in either but not both */
#define PB2_DEBUG_SEG_ADMIN

/* Auto determine thickness and annotation scale; if dimensions of the seg_tree
   is large enough, assume an export from pcb-rnd */
#define ANNOT_SCALE() \
do { \
	if (ctx->annot_scale <= 0) { \
		rnd_coord_t __size__ = (ctx->seg_tree.bbox.x2 - ctx->seg_tree.bbox.x1) + (ctx->seg_tree.bbox.y2 - ctx->seg_tree.bbox.y1); \
		if (__size__ > 3000000) ctx->annot_scale = 10000; \
		else if (__size__ > 300000) ctx->annot_scale = 1000; \
		else if (__size__ > 30000) ctx->annot_scale = 100; \
		else if (__size__ > 300) ctx->annot_scale = 10; \
		else ctx->annot_scale = 1; \
	} \
} while(0)

#define ANNOT(x)  ((double)x * ctx->annot_scale)

static void pb2_annot_seg_midpt(pb2_ctx_t *ctx, pb2_seg_t *seg, double *labx, double *laby)
{
	switch(seg->shape_type) {
		case RND_VNODE_LINE:
			*labx = (double)(seg->start[0] + seg->end[0])/2.0;
			*laby = (double)(seg->start[1] + seg->end[1])/2.0;
			break;

		case RND_VNODE_ARC:
			pb2_arc_get_midpoint_dbl(seg, labx, laby);
			break;
	}

	if (ctx->annot_scale > 0) {
		*labx += (rand() % ((long)ctx->annot_scale * 25))/25.0;
		*laby += (rand() % ((long)ctx->annot_scale * 25))/25.0;
	}
}

/* Build svg stroke dashing attribute scaled up to match annot_scale;
   base is the base dashing frequency at normal scale (small drawing).
   Result stored in dasha. */
static void mkdasha(pb2_ctx_t *ctx, char *dasha, int len, double base)
{
	double scale = ctx->annot_scale;
	if (scale < 1) scale = 1;
	rnd_snprintf(dasha, len, "stroke-dasharray=\"%.2f\"", base * scale);
}

static void pb2_draw_segs(pb2_ctx_t *ctx, FILE *F)
{
	rnd_rtree_it_t it;
	pb2_seg_t *seg;
	char dasha[256];
#ifdef PB2_DEBUG_SEG_ADMIN
	int f1 = 0, f2 = 0;
#endif

	ANNOT_SCALE();

	mkdasha(ctx, dasha, sizeof(dasha), 0.25);

	fprintf(F, "\n<!-- segments -->\n");
	for(seg = rnd_rtree_all_first(&it, &ctx->seg_tree); seg != NULL; seg = rnd_rtree_all_next(&it)) {
		double labx, laby;
		int large, sweep;
		const char *dash = seg->discarded ? dasha : "";

		switch(seg->shape_type) {
			case RND_VNODE_LINE:
				fprintf(F, " <line x1=\"%ld\" y1=\"%ld\" x2=\"%ld\" y2=\"%ld\" stroke-width=\"%.3f\" stroke=\"grey\" %s/>\n",
					(long)seg->start[0], (long)seg->start[1], (long)seg->end[0], (long)seg->end[1], ANNOT(0.02), dash);
				break;

			case RND_VNODE_ARC:
				large = fabs(seg->shape.arc.delta) > 180.0;
				sweep = seg->shape.arc.adir;
				fprintf(F, " <path d=\"M %ld %ld A %f,%f 0 %d %d %ld,%ld\" fill=\"none\" stroke-width=\"%.3f\" stroke=\"grey\" %s/>",
					(long)seg->start[0], (long)seg->start[1],
					seg->shape.arc.r, seg->shape.arc.r, large, sweep,
					(long)seg->end[0], (long)seg->end[1],
					ANNOT(0.02), dash);
				break;
			default: abort();
		}

		pb2_annot_seg_midpt(ctx, seg, &labx, &laby);
		fprintf(F, " <text x=\"%.2f\" y=\"%.2f\" stroke=\"none\" fill=\"grey\" font-size=\"%.2fpx\">S%ld:%d:%d</text>\n", labx, laby, ANNOT(0.5), PB2_UID_GET(seg), seg->cntA, seg->cntB);
#ifdef PB2_DEBUG_SEG_ADMIN
		if (!seg->discarded)
			f1++;
#endif
	}

#ifdef PB2_DEBUG_SEG_ADMIN
	/* number of non-discarded segments must match the number of segments in
	   the rtree as all existing segments must be in rtree. Discarded segments
	   are on the list only (so they are free'd later */
	for(seg = ctx->all_segs; seg != NULL; seg = seg->next_all)
		if (!seg->discarded)
			f2++;

	assert(f1==f2);
#endif
}


RND_INLINE void draw_curve_line(pb2_ctx_t *ctx, pb2_seg_t *seg, FILE *F, pb2_seg_t *fs, pb2_seg_t *ls, const char *dash)
{
		double x1 = seg->start[0], y1 = seg->start[1], x2 = seg->end[0], y2 = seg->end[1];
		double vx, vy, len;

		if ((seg == fs) || (seg == ls)) {
			vx = x2 - x1;
			vy = y2 - y1;
			len = sqrt(vx*vx + vy*vy);
			if (len != 0) {
				vx /= len;
				vy /= len;
			}
			vx *= 0.7;
			vy *= 0.7;
		}

		if (seg == fs) {
			x1 += vx;
			y1 += vy;
		}
		if (seg == ls) {
			x2 -= vx;
			y2 -= vy;
		}

		fprintf(F, " <line x1=\"%.2f\" y1=\"%.2f\" x2=\"%.2f\" y2=\"%.2f\" stroke-width=\"%.3f\" stroke=\"purple\" %s/>\n",
			x1, y1, x2, y2, ANNOT(0.05), dash);
}

RND_INLINE void draw_curve_arc(pb2_ctx_t *ctx, pb2_seg_t *seg, FILE *F, pb2_seg_t *fs, pb2_seg_t *ls, const char *dash)
{
	double x1 = seg->start[0], y1 = seg->start[1], x2 = seg->end[0], y2 = seg->end[1];
	int large, sweep;

	if (seg == fs)
		pb2_arc_shift_end_dbl(seg, 1, 0.7, &x1, &y1);
	if (seg == ls)
		pb2_arc_shift_end_dbl(seg, 2, 0.7, &x2, &y2);

	large = fabs(seg->shape.arc.delta) > 180.0;
	sweep = seg->shape.arc.adir;
	fprintf(F, " <path d=\"M %.2f %.2f A %f,%f 0 %d %d %.2f,%.2f\" fill=\"none\" stroke-width=\"%.3f\" stroke=\"purple\" %s/>",
		x1, y1, seg->shape.arc.r, seg->shape.arc.r, large, sweep, x2, y2,
		ANNOT(0.05), dash);
}

RND_INLINE void draw_curve(pb2_ctx_t *ctx, pb2_curve_t *curve, FILE *F, const char *dasha)
{
	pb2_seg_t *seg, *fs, *ls;
	double labx, laby;
	const char *dash = (curve->pruned ? dasha : "");


	fs = gdl_first(&curve->segs);
	ls = gdl_last(&curve->segs);

	for(seg = fs; seg != NULL; seg = gdl_next(&curve->segs, seg)) {
		switch(seg->shape_type) {
			case RND_VNODE_LINE: draw_curve_line(ctx, seg, F, fs, ls, dash); break;
			case RND_VNODE_ARC: draw_curve_arc(ctx, seg, F, fs, ls, dash); break;
		}
	}

	pb2_annot_seg_midpt(ctx, fs, &labx, &laby);
	laby -= 0.4 * ctx->annot_scale;
	fprintf(F, " <text x=\"%.2f\" y=\"%.2f\" stroke=\"none\" fill=\"purple\" font-size=\"%.2fpx\">C%ld</text>\n",
		labx, laby, ANNOT(0.5), curve->uid);

	if (fs != ls) {
		pb2_annot_seg_midpt(ctx, ls, &labx, &laby);
		laby -= 0.4 * ctx->annot_scale;
		fprintf(F, " <text x=\"%.2f\" y=\"%.2f\" stroke=\"none\" fill=\"purple\" font-size=\"%fpx\">C%ld</text>\n",
			labx, laby, ANNOT(0.5), curve->uid);
	}
}

static void pb2_draw_curves(pb2_ctx_t *ctx, FILE *F)
{
	pb2_curve_t *c;
	char dasha[256];

	mkdasha(ctx, dasha, sizeof(dasha), 0.35);

	fprintf(F, "\n<!-- curves -->\n");
	for(c = gdl_first(&ctx->curves); c != NULL; c = gdl_next(&ctx->curves, c))
		draw_curve(ctx, c, F, dasha);
}

RND_INLINE void draw_cgnode(pb2_ctx_t *ctx, pb2_cgnode_t *cgn, FILE *F)
{
	int n;

	fprintf(F, " <circle cx=\"%ld\" cy=\"%ld\" r=\"0.2\" stroke=\"none\" fill=\"purple\" />\n",
		(long)cgn->bbox.x1, (long)cgn->bbox.y1);

	fprintf(F, " <text x=\"%ld\" y=\"%.2f\" stroke=\"none\" fill=\"purple\" font-size=\"%.3fpx\">N%ld</text>\n",
		(long)cgn->bbox.x1, (double)(cgn->bbox.y1)+0.4, ANNOT(0.25), cgn->uid);

	for(n = 0; n < cgn->num_edges; n++) {
		pb2_seg_t *labseg;
		double vx, vy, len, labx, laby;
	
		if (cgn->edges[n].reverse)
			labseg = gdl_last(&cgn->edges[n].curve->segs);
		else
			labseg = gdl_first(&cgn->edges[n].curve->segs);

		vx = (labseg->end[0] - labseg->start[0]);
		vy = (labseg->end[1] - labseg->start[1]);
		len = sqrt(vx*vx + vy*vy);
		if (len != 0) {
			vx /= len;
			vy /= len;
		}

		if (cgn->edges[n].reverse) {
			labx = labseg->end[0] - vx * 0.5 * ctx->annot_scale;
			laby = labseg->end[1] - vy * 0.5 * ctx->annot_scale;
		}
		else {
			labx = labseg->start[0] + vx * 0.5 * ctx->annot_scale;
			laby = labseg->start[1] + vy * 0.5 * ctx->annot_scale;
		}

		fprintf(F, " <text x=\"%.2f\" y=\"%.2f\" stroke=\"none\" fill=\"black\" font-size=\"%.3fpx\">O%ld%s%s</text>\n",
			labx, laby, ANNOT(0.25), cgn->edges[n].uid, cgn->edges[n].reverse ? "r" : "", cgn->edges[n].corner_mark ? "*" : "");
	}
}

static void pb2_draw_curve_graph(pb2_ctx_t *ctx, FILE *F)
{
	pb2_cgnode_t *n;

	fprintf(F, "\n<!-- cgnodes -->\n");
	for(n = gdl_first(&ctx->cgnodes); n != NULL; n = gdl_next(&ctx->cgnodes, n))
		draw_cgnode(ctx, n, F);
}

RND_INLINE void draw_face(pb2_ctx_t *ctx, pb2_face_t *f, FILE *F)
{
	double dx, dy, len;

	dx = f->polarity_dir[0];
	dy = f->polarity_dir[1];
	len = sqrt(dx*dx + dy*dy);
	if (len != 0) {
		dx /= len;
		dy /= len;
	}
	dx *= ANNOT(0.75);
	dy *= ANNOT(0.75);
	dx += f->polarity_pt[0];
	dy += f->polarity_pt[1];

	fprintf(F, " <line x1=\"%ld\" y1=\"%ld\" x2=\"%.2f\" y2=\"%.2f\" stroke-width=\"%.3f\" stroke=\"red\"/>\n",
		(long)f->polarity_pt[0], (long)f->polarity_pt[1], dx, dy, ANNOT(0.05));

		fprintf(F, " <text x=\"%.2f\" y=\"%.2f\" stroke=\"none\" fill=\"red\" font-size=\"%.3fpx\">F%ld%s%s%s</text>\n",
			dx, dy, ANNOT(0.35), f->uid, f->inA ? "A" : "", f->inB ? "B" : "", f->out ? "o" : "");
}

static void pb2_draw_faces(pb2_ctx_t *ctx, FILE *F)
{
	pb2_face_t *f;

	fprintf(F, "\n<!-- faces -->\n");
	for(f = gdl_first(&ctx->faces); f != NULL; f = gdl_next(&ctx->faces, f))
		draw_face(ctx, f, F);
}

static void svg_print_poly_contour_node(FILE *F, const rnd_vnode_t *v)
{
	int large, sweep;

	switch(v->prev->flg.curve_type) {
		case RND_VNODE_LINE:
			fprintf(F, "    L %ld,%ld", (long)v->point[0], (long)v->point[1]);
			break;
		case RND_VNODE_ARC:
			{
				rnd_vnode_t *vp = v->prev;
				double r, dx, dy, sx, sy, ex, ey, cx, cy, cp;
				int large, sweep = vp->curve.arc.adir;

				dx = vp->curve.arc.center[0] - v->point[0];
				dy = vp->curve.arc.center[1] - v->point[1];
				r = dx*dx + dy*dy;
				if (r == 0)
					break;
				r = sqrt(r);

				/* decide if sweep is larger than 180 deg: e.g. if center is
				   right-of start-end and it's CCW it is large */
				sx = vp->point[0]; sy = vp->point[1];
				ex = v->point[0]; ey = v->point[1];
				cx = vp->curve.arc.center[0]; cy = vp->curve.arc.center[1];

				cp = (ex - sx)*(cy - sy) - (ey - sy)*(cx - sx);
				large = ((cp > 0) != sweep);

				fprintf(F, " A %.1f,%.1f 0 %d %d %ld,%ld", r, r, large, sweep, (long)v->point[0], (long)v->point[1]);
			}
			break;
	}
}

void pb2_svg_print_pline(FILE *F, const rnd_pline_t *pl)
{
	const rnd_vnode_t *v;

	v = pl->head;
	fprintf(F, "\n	M %ld,%ld", (long)v->point[0], (long)v->point[1]);
	v = v->next;
	do {
		svg_print_poly_contour_node(F, v);
	} while((v = v->next) != pl->head);

	/* draw last segment returning to first point, don't let 'z' do that, because
	   it may be an arc */
	svg_print_poly_contour_node(F, v);

	fprintf(F, "z");
}

void pb2_draw_polyarea(pb2_ctx_t *ctx, FILE *F, const rnd_polyarea_t *pa, const char *clr, double fill_opacity)
{
	const rnd_pline_t *pl;
	const rnd_polyarea_t *pn = pa;
	static const double poly_bloat = 0.02;

	if (pa == NULL)
		return;

	fprintf(F, "\n<g fill-rule=\"even-odd\" stroke-width=\"%.3f\" stroke=\"%s\" fill=\"%s\" fill-opacity=\"%.3f\" stroke-opacity=\"%.3f\">\n<path d=\"", poly_bloat, clr, clr, fill_opacity, fill_opacity*2);
	do {
		/* check if we have a contour for the given island */
		pl = pn->contours;
		if (pl != NULL) {
			/* iterate over the vectors of the contour */
			pb2_svg_print_pline(F, pl);

			/* iterate over all holes within this island */
			for(pl = pl->next; pl != NULL; pl = pl->next)
				pb2_svg_print_pline(F, pl);
		}
	} while ((pn = pn->f) != pa);
	fprintf(F, "\n\"/></g>\n");
}

RND_INLINE void pb2_draw_input_poly(pb2_ctx_t *ctx, FILE *F)
{
	fprintf(F, "\n<!-- input poly A -->\n");
	pb2_draw_polyarea(ctx, F, ctx->input_A, "#FF0000", 0.1);
	if (ctx->has_B) {
		fprintf(F, "\n<!-- input poly B -->\n");
		pb2_draw_polyarea(ctx, F, ctx->input_B, "#00FF00", 0.1);
	}
}

static void pb2_draw(pb2_ctx_t *ctx, const char *fn, pb2_dump_what_t what)
{
	FILE *F = rnd_fopen(NULL, fn, "w");

	if (F == NULL) {
		fprintf(stderr, "pb2_draw(): failed to open '%s' for write\n", fn);
		return;
	}


	fprintf(F, "<?xml version=\"1.0\"?>\n");
	fprintf(F, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.0\" width=\"1000\" height=\"1000\" viewBox=\"0 0 1000 1000\">\n");

	if (what & PB2_DRAW_INPUT_POLY) pb2_draw_input_poly(ctx, F);
	if (what & PB2_DUMP_CURVES) pb2_draw_curves(ctx, F);
	if (what & PB2_DUMP_CURVE_GRAPH) pb2_draw_curve_graph(ctx, F);
	if (what & PB2_DUMP_SEGS) pb2_draw_segs(ctx, F);
	if (what & PB2_DUMP_FACES) pb2_draw_faces(ctx, F);

#if 0
	if (what & PB2_DUMP_FACE_TREE) pb2_draw_face_tree(ctx, F);
#endif

	fprintf(F, "</svg>\n");

	fclose(F);
}
#else
static void pb2_draw(pb2_ctx_t *ctx, const char *fn, pb2_dump_what_t what) {}
#endif
