/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - alien file format helpers
 *  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., 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 <libcschem/config.h>
#include <libcschem/libcschem.h>
#include <libcschem/util_wirenet.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_arc.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/cnc_conn.h>
#include <libcschem/operation.h>
#include <libcschem/project.h>

#include <librnd/core/compat_misc.h>
#include <librnd/core/math_helper.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/error.h>

#include <sch-rnd/style.h>
#include <sch-rnd/sheet.h>
#include <sch-rnd/util_sheet.h>

#include <plugins/query/query.h>
#include <plugins/query/query_access.h>
#include <plugins/query/query_exec.h>

#include "read_helper.h"

/* Warn once per context if coords are too large for the GUI */
RND_INLINE double csch_alien_chk_range(csch_alien_read_ctx_t *ctx, double crd)
{
#define RMAX (RND_COORD_MAX/4.0/1024.0-1)
#define RMIN (-RMAX)

	if ((crd > RMAX) || (crd < RMIN)) {
		if (!ctx->warned_coord_range) {
			rnd_message(RND_MSG_ERROR, "Alien drawing has coordinates too large. Consider recompiling librnd for 64 bit coords.\n");
			ctx->warned_coord_range = 1;
		}
	}

	return crd;
#undef RMAX
#undef RMIN
}

#define CRD(crd) \
	(((ctx->coord_factor != 0) && (ctx->coord_factor != 1)) ? rnd_round(csch_alien_chk_range(ctx, (crd) * ctx->coord_factor)) : csch_alien_chk_range(ctx, ((crd))))

#define CRDX(crd)  CRD((ctx->flip_x ? -(crd) : (crd)) + ctx->ox)
#define CRDY(crd)  CRD((ctx->flip_y ? -(crd) : (crd)) + ctx->oy)

csch_coord_t csch_alien_coord_x(csch_alien_read_ctx_t *ctx, double crd)
{
	return CRDX(crd);
}

csch_coord_t csch_alien_coord_y(csch_alien_read_ctx_t *ctx, double crd)
{
	return CRDY(crd);
}

csch_coord_t csch_alien_coord(csch_alien_read_ctx_t *ctx, double crd)
{
	return CRD(crd);
}


csch_chdr_t *csch_alien_mknet(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double x1, double y1, double x2, double y2)
{
	if (parent != &ctx->sheet->direct) {
		rnd_message(RND_MSG_ERROR, "csch_alien_mknet(): can not create wire within a group at the moment\n");
		return NULL;
	}

	return csch_wirenet_draw(ctx->sheet, csch_comm_str(ctx->sheet, "wire", 1), CRDX(x1), CRDY(y1), CRDX(x2), CRDY(y2));
}

csch_chdr_t *csch_alien_mkline(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double x1, double y1, double x2, double y2, const char *penname)
{
	csch_line_t *line;

	line = csch_line_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	line->spec.p1.x = CRDX(x1);
	line->spec.p1.y = CRDY(y1);
	line->spec.p2.x = CRDX(x2);
	line->spec.p2.y = CRDY(y2);
	line->hdr.stroke_name = csch_comm_str(ctx->sheet, penname, 1);

	return &line->hdr;
}

RND_INLINE void poly_line(csch_cpoly_t *poly, csch_coord_t x1, csch_coord_t y1, csch_coord_t x2, csch_coord_t y2)
{
	csch_coutline_t *dst = csch_vtcoutline_alloc_append(&poly->outline, 1);
	csch_line_t *line = &dst->line;

	line->hdr.type = CSCH_CTYPE_LINE;
	line->spec.p1.x = x1; line->spec.p1.y = y1;
	line->spec.p2.x = x2; line->spec.p2.y = y2;
}

csch_chdr_t *csch_alien_mkrect(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double x1, double y1, double x2, double y2, const char *stroke_penname, const char *fill_penname)
{
	csch_cpoly_t *poly;

	x1 = CRDX(x1); y1 = CRDY(y1);
	x2 = CRDX(x2); y2 = CRDY(y2);

	poly = csch_cpoly_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	poly_line(poly, x1, y1, x1, y2);
	poly_line(poly, x1, y2, x2, y2);
	poly_line(poly, x2, y2, x2, y1);
	poly_line(poly, x2, y1, x1, y1);

	if (stroke_penname != NULL) {
		poly->hdr.stroke_name = csch_comm_str(ctx->sheet, stroke_penname, 1);
		poly->has_stroke = 1;
	}
	else
		poly->has_stroke = 0;

	if (fill_penname != NULL) {
		poly->hdr.fill_name = csch_comm_str(ctx->sheet, fill_penname, 1);
		poly->has_fill = 1;
	}
	else
		poly->has_fill = 0;

	return &poly->hdr;
}

csch_chdr_t *csch_alien_mkpin_line(csch_alien_read_ctx_t *ctx, csch_source_arg_t *src, csch_cgrp_t *parent, double x1, double y1, double x2, double y2)
{
	csch_cgrp_t *pin;
	csch_line_t *line;

	pin = csch_cgrp_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	csch_cobj_attrib_set(ctx->sheet, pin, CSCH_ATP_HARDWIRED, "role", "terminal", src);

	line = csch_line_alloc(ctx->sheet, pin, csch_oid_new(ctx->sheet, pin));
	line->spec.p1.x = CRDX(x1);
	line->spec.p1.y = CRDY(y1);
	line->spec.p2.x = CRDX(x2);
	line->spec.p2.y = CRDY(y2);
	line->hdr.stroke_name = csch_comm_str(ctx->sheet, "term-primary", 1);

	return &pin->hdr;
}

static void csch_alien_mkbezier_(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent_grp, csch_cpoly_t *parent_poly, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, const char *penname)
{
	double len, step, t;
	double lx, ly, x, y;
	csch_line_t *line;
	csch_comm_str_t pen = csch_comm_str(ctx->sheet, penname, 1);
	csch_coutline_t *dst;

	len = rnd_distance(x1, y1, x2, y2) + rnd_distance(x2, y2, x3, y3) + rnd_distance(x3, y3, x4, y4);
	step = (1000.0 / (len + 3000.0)) / 5.0;

	x1 = CRDX(x1); y1 = CRDY(y1);
	x2 = CRDX(x2); y2 = CRDY(y2);
	x3 = CRDX(x3); y3 = CRDY(y3);
	x4 = CRDX(x4); y4 = CRDY(y4);

	lx = x1; ly = y1;

	for(t = step; t < 1; t += step) {
		double mt = 1-t;
		double a = mt*mt*mt, b = 3*mt*mt*t, c = 3*mt*t*t, d = t*t*t;
		/* B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3   @   0 <= t <= 1 */

		x = a*x1 + b*x2 + c*x3 + d*x4;
		y = a*y1 + b*y2 + c*y3 + d*y4;

		if ((lx != x) || (ly != y)) {
			if (parent_grp == NULL) {
				dst = csch_vtcoutline_alloc_append(&parent_poly->outline, 1);
				dst->hdr = parent_poly->hdr;
				dst->hdr.type = CSCH_CTYPE_LINE;
				line = &dst->line;
			}
			else {
				line = csch_line_alloc(ctx->sheet, parent_grp, csch_oid_new(ctx->sheet, parent_grp));
				line->hdr.stroke_name = pen;
			}

			line->spec.p1.x = rnd_round(lx);
			line->spec.p1.y = rnd_round(ly);
			line->spec.p2.x = rnd_round(x);
			line->spec.p2.y = rnd_round(y);

			lx = x;
			ly = y;
		}
	}

	if ((lx != x4) || (ly != y4)) {
		if (parent_grp == NULL) {
			dst = csch_vtcoutline_alloc_append(&parent_poly->outline, 1);
			dst->hdr = parent_poly->hdr;
			dst->hdr.type = CSCH_CTYPE_LINE;
			line = &dst->line;
		}
		else {
			line = csch_line_alloc(ctx->sheet, parent_grp, csch_oid_new(ctx->sheet, parent_grp));
			line->hdr.stroke_name = pen;
		}
		line->spec.p1.x = rnd_round(lx);
		line->spec.p1.y = rnd_round(ly);
		line->spec.p2.x = rnd_round(x4);
		line->spec.p2.y = rnd_round(y4);
	}
}

void csch_alien_mkbezier(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, const char *penname)
{
	csch_alien_mkbezier_(ctx, parent, NULL, x1, y1, x2, y2, x3, y3, x4, y4, penname);
}

void csch_alien_append_poly_bezier(csch_alien_read_ctx_t *ctx, csch_chdr_t *poly_, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
{
	csch_alien_mkbezier_(ctx, NULL, (csch_cpoly_t *)poly_, x1, y1, x2, y2, x3, y3, x4, y4, NULL);
}


csch_chdr_t *csch_alien_mkarc(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double cx, double cy, double r, double sa, double da, const char *penname)
{
	csch_arc_t *arc;

	cx = CRDX(cx); cy = CRDY(cy);
	r = CRD(r);

	arc = csch_arc_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));
	arc->spec.c.x = rnd_round(cx);
	arc->spec.c.y = rnd_round(cy);
	arc->spec.r = rnd_round(r);
	arc->spec.start = sa / RND_RAD_TO_DEG;
	arc->spec.delta = da / RND_RAD_TO_DEG;
	arc->hdr.stroke_name = csch_comm_str(ctx->sheet, penname, 1);

	return &arc->hdr;
}


csch_chdr_t *csch_alien_mkearc(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double cx, double cy, double rx, double ry, double sa, double da, const char *stroke, const char *fill)
{
	csch_chdr_t *poly = csch_alien_mkpoly(ctx, parent, stroke, fill);
	double a, ea = sa+da, d, tmp, lx, ly, x, y;
	int first = 1;

	d = 1.0/((rx+ry)/2.0);
	if (d > 0.2)
		d = 0.2;

	if ((da > 2*M_PI) || (da < -2*M_PI)) {
		ea = 2*M_PI;
		sa = 0;
	}

	/* swap if necessary, so iteration is always positive */
	if (da < 0) {
		tmp = sa;
		sa = ea;
		ea = tmp;
		da = -da;
	}

	/* degenerate case: need at least two points so we don't end up with an empty poly */
	if (d > da/2.0)
		d = da/2.0;

	for(a = sa; a < ea; a += d) {
		x = cx + cos(a) * rx;
		y = cy + sin(a) * ry;
		if (!first)
			csch_alien_append_poly_line(ctx, poly, lx, ly, x, y);
		lx = x;
		ly = y;
		first = 0;
	}

	/* close */
	if (!first) {
		x = cx + cos(ea) * rx;
		y = cy + sin(ea) * ry;
		if ((x != lx) || (y != ly))
			csch_alien_append_poly_line(ctx, poly, lx, ly, x, y);
	}

	return poly;
}


void csch_alien_append_poly_arc(csch_alien_read_ctx_t *ctx, csch_chdr_t *poly_, double cx, double cy, double r, double sa, double da)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)poly_;
	csch_coutline_t *dst = csch_vtcoutline_alloc_append(&poly->outline, 1);
	dst->hdr = poly->hdr;
	dst->hdr.type = CSCH_CTYPE_ARC;
	dst->arc.spec.c.x = rnd_round(CRDX(cx));
	dst->arc.spec.c.y = rnd_round(CRDY(cy));
	dst->arc.spec.r = rnd_round(CRD(r));
	dst->arc.spec.start = sa / RND_RAD_TO_DEG;
	dst->arc.spec.delta = da / RND_RAD_TO_DEG;
}

csch_chdr_t *csch_alien_mktext(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double x, double y, const char *penname)
{
	csch_text_t *text = csch_text_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));

	text->spec1.x = CRDX(x);
	text->spec1.y = CRDY(y);
	text->hdr.stroke_name = csch_comm_str(ctx->sheet, penname, 1);

	return &text->hdr;
}


csch_chdr_t *csch_alien_mkpoly(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, const char *stroke_name, const char *fill_name)
{
	csch_cpoly_t *poly = csch_cpoly_alloc(ctx->sheet, parent, csch_oid_new(ctx->sheet, parent));

	if (stroke_name != NULL) {
		poly->hdr.stroke_name = csch_comm_str(ctx->sheet, stroke_name, 1);
		poly->has_stroke = 1;
	}
	if (fill_name != NULL) {
		poly->hdr.fill_name = csch_comm_str(ctx->sheet, fill_name, 1);
		poly->has_fill = 1;
	}

	return &poly->hdr;
}

void csch_alien_append_poly_line(csch_alien_read_ctx_t *ctx, csch_chdr_t *poly_, double x1, double y1, double x2, double y2)
{
	csch_cpoly_t *poly = (csch_cpoly_t *)poly_;
	csch_coutline_t *dst = csch_vtcoutline_alloc_append(&poly->outline, 1);
	dst->hdr = poly->hdr;
	dst->hdr.type = CSCH_CTYPE_LINE;
	dst->line.spec.p1.x = CRDX(x1);
	dst->line.spec.p1.y = CRDY(y1);
	dst->line.spec.p2.x = CRDX(x2);
	dst->line.spec.p2.y = CRDY(y2);
}

csch_chdr_t *csch_alien_mkcircle(csch_alien_read_ctx_t *ctx, csch_cgrp_t *parent, double cx, double cy, double r, const char *stroke_name, const char *fill_name)
{
	csch_chdr_t *res = csch_alien_mkpoly(ctx, parent, stroke_name, fill_name);

	/* use 3 arcs so that the poly is no special case in any sense */
	csch_alien_append_poly_arc(ctx, res, cx, cy, r, 0, 120);
	csch_alien_append_poly_arc(ctx, res, cx, cy, r, 120, 120);
	csch_alien_append_poly_arc(ctx, res, cx, cy, r, 240, 120);

	return res;
}


void csch_alien_sheet_setup(csch_alien_read_ctx_t *ctx, int pen)
{
	sch_rnd_sheet_setup(ctx->sheet, (pen ? SCH_RND_SSC_PENS : 0) | SCH_RND_SSC_UUID, NULL, NULL);
}

csch_cgrp_t *csch_alien_convert_to_grp(csch_alien_read_ctx_t *ctx, csch_chdr_t **obj)
{
	csch_cgrp_t *grp;
	csch_chdr_t *newo;

	if (csch_obj_is_grp(*obj))
		return (csch_cgrp_t *)(*obj);

	grp = csch_cgrp_alloc(ctx->sheet, (*obj)->parent, csch_oid_new(ctx->sheet, (*obj)->parent));

	csch_cnc_remove(ctx->sheet, (*obj));
	newo = csch_cobj_dup(ctx->sheet, grp, (*obj), 0, 0);
	csch_cobj_update(ctx->sheet, newo, 0);
/*	csch_op_inserted(sheet, dst, newo);*/
	*obj = newo;
	return grp;
}

csch_cgrp_t *csch_alien_attr_grp(csch_alien_read_ctx_t *ctx, csch_chdr_t **obj)
{
	csch_cgrp_t *parent = (*obj)->parent;

	if (csch_obj_is_grp(*obj))
		return (csch_cgrp_t *)(*obj);

	if ((parent->role == CSCH_ROLE_WIRE_NET) || (parent->role == CSCH_ROLE_SYMBOL))
		return parent;

	return csch_alien_convert_to_grp(ctx, obj);
}

static void alien_collect_conns(csch_alien_read_ctx_t *ctx, vtp0_t *dst, csch_cgrp_t *grp)
{
	htip_entry_t *e;

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_cgrp_t *g = e->value;
		if (csch_obj_is_grp(&g->hdr)) {
			if (g->role == CSCH_ROLE_TERMINAL)
				vtp0_append(dst, g);
			else
				alien_collect_conns(ctx, dst, g);
		}
	}
}

void csch_alien_update_conns(csch_alien_read_ctx_t *ctx)
{
	vtp0_t grps = {0};
	long n;

	/* collect all terminal groups recursively */
	alien_collect_conns(ctx, &grps, &ctx->sheet->direct);

	for(n = 0; n < grps.used; n++)
		csch_conn_auto_recalc(ctx->sheet, grps.array[n]);

	vtp0_uninit(&grps);
}

static int centerline_bbox(csch_alien_read_ctx_t *ctx, csch_chdr_t *src, csch_rtree_box_t *dst)
{
	switch(src->type) {
		case CSCH_CTYPE_LINE: csch_line_center_bbox(ctx->sheet, (csch_line_t *)src, dst); return 1;
		case CSCH_CTYPE_ARC:  csch_arc_center_bbox(ctx->sheet, (csch_arc_t *)src, dst); return 1;
		case CSCH_CTYPE_POLY: csch_cpoly_center_bbox(ctx->sheet, (csch_cpoly_t *)src, dst); return 1;
		case CSCH_CTYPE_GRP:
		case CSCH_CTYPE_GRP_REF:
			{
				htip_entry_t *e;
				csch_rtree_box_t tmp;
				csch_cgrp_t *grp = (csch_cgrp_t *)src;

				for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
					csch_bbox_reset(&tmp);
					if (centerline_bbox(ctx, e->value, &tmp)) {
						csch_bbox_bump(dst, x, tmp.x1);
						csch_bbox_bump(dst, y, tmp.y1);
						csch_bbox_bump(dst, x, tmp.x2);
						csch_bbox_bump(dst, y, tmp.y2);
					}
				}
			}
			return 1;
		default:
			break;
	}
	return 0;
}

void csch_alien_centerline_bbox(csch_alien_read_ctx_t *ctx, csch_chdr_t *src, csch_rtree_box_t *dst)
{
	csch_bbox_reset(dst);
	centerline_bbox(ctx, src, dst);
}


#include "read_postproc.c"
