/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,2022 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  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/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 "config.h"

#include <librnd/core/rnd_printf.h>
#include <librnd/core/misc_util.h>
#include <librnd/core/math_helper.h>

#include "event.h"
#include "concrete.h"
#include "cnc_line.h"
#include "cnc_obj.h"
#include "cnc_grp_child.h"
#include "cnc_grp.h"
#include "cnc_conn.h"
#include "op_common.h"
#include "operation.h"
#include "undo.h"
#include "rotate.h"
#include "util_wirenet.h"

#include "gengeo2d/sline.h"
#include "gengeo2d/xform.h"

csch_line_t *csch_line_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_line_t *line;

	line = htip_get(&parent->id2obj, oid);
	if (line != NULL)
		return NULL;
	line = calloc(sizeof(csch_line_t), 1);

	csch_cobj_init(&line->hdr, sheet, parent, oid, CSCH_CTYPE_LINE);
	return line;
}

csch_line_t *csch_line_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_line_t *src, int keep_id, int inst2spec)
{
	csch_line_t *dst = csch_line_alloc(sheet, parent, CSCH_KEEP_OID(parent, src->hdr.oid));

	csch_chdr_copy_meta4dup(&dst->hdr, &src->hdr);

	if (inst2spec) {
		g2d_xform_t mx;
		g2d_vect_t v1 = src->inst.c.p1, v2 = src->inst.c.p2;

		csch_cgrp_inverse_matrix(&mx, parent);
		v1 = g2d_xform_vect2vect(mx, v1);
		v2 = g2d_xform_vect2vect(mx, v2);
		dst->spec.p1 = v1;
		dst->spec.p2 = v2;
		csch_line_update(sheet, dst, 1);
	}
	else {
		dst->inst = src->inst;
		dst->spec = src->spec;
	}

	return dst;
}


void csch_line_free(csch_line_t *line)
{
	csch_cobj_uninit(&line->hdr);
	free(line);
}

csch_line_t *csch_line_get(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_oid_t oid)
{
	csch_line_t *line = htip_get(&grp->id2obj, oid);
	if ((line != NULL) && (line->hdr.type != CSCH_CTYPE_LINE))
		return NULL;
	return line;
}

void csch_line_update_xform(csch_sheet_t *sheet, csch_line_t *line)
{
	if (line->hdr.parent != NULL) {
		g2d_xform_t mx = csch_grp_ref_parent_mx(&line->hdr, NULL);
		line->inst.c.p1 = g2d_xform_vect2vect(mx, line->spec.p1);
		line->inst.c.p2 = g2d_xform_vect2vect(mx, line->spec.p2);
	}
	else
		line->inst.c = line->spec;
	if (line->hdr.stroke != NULL) {
		line->inst.s.width = line->hdr.stroke->size;
		line->inst.s.cap = (line->hdr.stroke->shape == CSCH_PSHP_ROUND) ? G2D_CAP_ROUND : G2D_CAP_SQUARE;
	}
}

void csch_line_update_bbox(csch_sheet_t *sheet, csch_line_t *line)
{
	g2d_box_t gb;

	csch_obj_bbox_reset(line);
	gb = g2d_sline_bbox(&line->inst);
	line->hdr.bbox.x1 = gb.p1.x; line->hdr.bbox.y1 = gb.p1.y;
	line->hdr.bbox.x2 = gb.p2.x; line->hdr.bbox.y2 = gb.p2.y;
}

void csch_line_center_bbox(csch_sheet_t *sheet, const csch_line_t *line, csch_rtree_box_t *dst)
{
	g2d_box_t gb = g2d_cline_bbox(&line->inst.c);

	dst->x1 = gb.p1.x; dst->y1 = gb.p1.y;
	dst->x2 = gb.p2.x; dst->y2 = gb.p2.y;
}

void csch_line_update(csch_sheet_t *sheet, csch_line_t *line, int do_xform)
{
	csch_cobj_update_pen(&line->hdr);

	if (do_xform)
		csch_line_update_xform(sheet, line);

	csch_cobj_rtree_del(sheet, &line->hdr);
	csch_line_update_bbox(sheet, line);
	csch_cobj_rtree_add(sheet, &line->hdr);

	csch_cobj_bbox_changed(sheet, &line->hdr);
}

int csch_line_get_endxy(const csch_line_t *line, int side, csch_coord_t *x, csch_coord_t *y)
{
	if (side == 0) {
		*x = line->inst.c.p1.x;
		*y = line->inst.c.p1.y;
	}
	else {
		*x = line->inst.c.p2.x;
		*y = line->inst.c.p2.y;
	}
	return 0;
}


static csch_chdr_t *line_create(csch_sheet_t *sheet, csch_cgrp_t *parent)
{
	csch_line_t *line = csch_line_alloc(sheet, parent, csch_oid_new(sheet, parent));
	if (line == NULL) return NULL;
	return &line->hdr;
}

static void line_remove_alloc(csch_undo_remove_t *slot)
{
}

static void line_remove_redo(csch_undo_remove_t *slot)
{
	csch_cgrp_t *parent_wn = NULL;
	
	if (slot->side_effects) {
		parent_wn = slot->obj->parent;
		csch_conn_auto_del_obj_all(slot->sheet, slot->obj); /* note: any object may be in conn, not only wires! */
		if ((parent_wn != NULL) && (parent_wn->role != CSCH_ROLE_WIRE_NET))
			parent_wn = NULL; /* do not recalc wirenet side effects for non-wirenets */
	}

	csch_cnc_common_remove_redo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT);

	if (parent_wn != NULL)
		csch_wirenet_recalc_junctions(slot->sheet, parent_wn);
}

static void line_remove_undo(csch_undo_remove_t *slot)
{
	csch_cnc_common_remove_undo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT);
}

static int line_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box)
{
	csch_line_t *line = (csch_line_t *)obj;
	g2d_cline_t l1, l2;
	g2d_box_t gbox;
	g2d_calc_t w2;

	gbox.p1.x = box->x1; gbox.p1.y = box->y1;
	gbox.p2.x = box->x2; gbox.p2.y = box->y2;

	/* line fully within the box and/or cheap case: endpoint of line within the box */
	if (g2d_point_in_box(&gbox, line->inst.c.p1) || g2d_point_in_box(&gbox, line->inst.c.p2)) return 1;

	/* box fully within the sline (any box point inside) */
	w2 = line->inst.s.width/2;
	if (g2d_dist2_cline_pt(&line->inst.c, gbox.p1) <= w2*w2) return 1;


	g2d_sline_sides(&line->inst, &l1, &l2);

	/* box side crosses sline sides */
	if (g2d_isc_cline_box(&l1, &gbox) || g2d_isc_cline_box(&l1, &gbox)) return 1;


TODO("check end cap circle");

	return 0;
}

static void line_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_line_t *line = (csch_line_t *)obj;
	csch_line_modify(sheet, line, &dx, &dy, &dx, &dy, undoable, 1, 0);

	if ((line->hdr.parent != NULL) && (line->hdr.parent->role == CSCH_ROLE_WIRE_NET))
		csch_wirenet_recalc_obj_conn(sheet, &line->hdr);

	/* no need to call csch_wirenet_recalc_line_chg() even in a wirenet, line_modify does that */
}

static void line_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_line_t *line = csch_line_dup(sheet, obj->parent, (csch_line_t *)obj, 0, 0);
	if (line != NULL) {
		line->spec.p1.x += dx; line->spec.p1.y += dy;
		line->spec.p2.x += dx; line->spec.p2.y += dy;
		csch_line_update(sheet, line, 1);
		if (obj->parent->role == CSCH_ROLE_WIRE_NET)
			csch_wirenet_recalc_line_chg(line->hdr.sheet, line);
		csch_op_inserted(sheet, obj->parent, &line->hdr);
	}
}

static void line_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable)
{
	csch_line_t *line = (csch_line_t *)obj;
	csch_coord_t ncx1 = line->spec.p1.x, ncy1 = line->spec.p1.y;
	csch_coord_t ncx2 = line->spec.p2.x, ncy2 = line->spec.p2.y;
	double rad = -da / RND_RAD_TO_DEG;
	double cs = cos(rad), sn = sin(rad);

	csch_rotate_pt(&ncx1, &ncy1, rcx, rcy, cs, sn);
	csch_rotate_pt(&ncx2, &ncy2, rcx, rcy, cs, sn);

	csch_line_modify(sheet, line, &ncx1, &ncy1, &ncx2, &ncy2, undoable, 0, 0);
}

static void line_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int n, int undoable)
{
	csch_line_t *line = (csch_line_t *)obj;
	csch_coord_t ncx1 = line->spec.p1.x, ncy1 = line->spec.p1.y;
	csch_coord_t ncx2 = line->spec.p2.x, ncy2 = line->spec.p2.y;

	csch_rotate90_pt(&ncx1, &ncy1, rcx, rcy, n);
	csch_rotate90_pt(&ncx2, &ncy2, rcx, rcy, n);

	csch_line_modify(sheet, line, &ncx1, &ncy1, &ncx2, &ncy2, undoable, 0, 0);
}

static void line_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable)
{
	csch_line_t *line = (csch_line_t *)obj;
	csch_coord_t ncx1 = line->spec.p1.x, ncy1 = line->spec.p1.y;
	csch_coord_t ncx2 = line->spec.p2.x, ncy2 = line->spec.p2.y;

	csch_mirror_pt(&ncx1, &ncy1, mcx, mcy, mirx, miry);
	csch_mirror_pt(&ncx2, &ncy2, mcx, mcy, mirx, miry);

	csch_line_modify(sheet, line, &ncx1, &ncy1, &ncx2, &ncy2, undoable, 0, 0);
}


static void line_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *in, int undoable)
{
	csch_line_t *line = (csch_line_t *)in;
	csch_coord_t x1 = line->inst.c.p1.x, y1 = line->inst.c.p1.y;
	csch_coord_t x2 = line->inst.c.p2.x, y2 = line->inst.c.p2.y;

	csch_line_modify(sheet, (csch_line_t *)obj, &x1, &y1, &x2, &y2, undoable, 0, 0);
}

const csch_ops_t csch_ops_line = {
	line_create, line_remove_alloc, line_remove_redo, line_remove_undo,
	line_isc_with_box,
	line_move, line_copy, line_rotate, line_rotate90, line_mirror,
	line_inst2spec
};

/*** hash ***/

unsigned csch_line_hash_(const csch_line_t *line, csch_hash_ignore_t ignore, int in_contour)
{
	unsigned res = 0;
	if (!in_contour) res ^= csch_chdr_hash(&line->hdr);
	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !line->hdr.floater) {
		res ^= csch_coord_hash(line->spec.p1.x) + csch_coord_hash(line->spec.p2.x);
		res ^= csch_coord_hash(line->spec.p1.y) + csch_coord_hash(line->spec.p2.y);
	}
	else
		res ^= (unsigned)rnd_distance2(line->spec.p1.x, line->spec.p1.y, line->spec.p2.x, line->spec.p2.y);
	return res;
}

int csch_line_keyeq_(const csch_line_t *l1, const csch_line_t *l2, csch_hash_ignore_t ignore, int in_contour)
{
	if (!in_contour && !csch_chdr_eq(&l1->hdr, &l2->hdr)) return 0;
	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !l1->hdr.floater || !l2->hdr.floater) {
		if (l1->spec.p1.x != l2->spec.p1.x) return 0;
		if (l1->spec.p1.y != l2->spec.p1.y) return 0;
		if (l1->spec.p2.x != l2->spec.p2.x) return 0;
		if (l1->spec.p2.y != l2->spec.p2.y) return 0;
	}
	else {
		double d1 = rnd_distance2(l1->spec.p1.x, l1->spec.p1.y, l1->spec.p2.x, l1->spec.p2.y);
		double d2 = rnd_distance2(l2->spec.p1.x, l2->spec.p1.y, l2->spec.p2.x, l2->spec.p2.y);
		if (d1 != d2) return 0;
	}
	return 1;
}

unsigned csch_line_hash(const csch_line_t *line, csch_hash_ignore_t ignore)
{
	return csch_line_hash_(line, ignore, 0);
}

int csch_line_keyeq(const csch_line_t *l1, const csch_line_t *l2, csch_hash_ignore_t ignore)
{
	return csch_line_keyeq_(l1, l2, ignore, 0);
}



/*** Modify ***/
typedef struct {
	csch_line_t *line; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	g2d_cline_t spec;
	unsigned disable_recalc:1;
	unsigned recalc_conn_geo:1;
} undo_line_modify_t;


static int undo_line_modify_swap(void *udata)
{
	undo_line_modify_t *u = udata;

	csch_cobj_redraw(u->line);

	rnd_swap(g2d_cline_t, u->spec, u->line->spec);
	csch_line_update(u->line->hdr.sheet, u->line, 1);
	if (!u->disable_recalc && (u->line->hdr.parent != NULL) && (u->line->hdr.parent->role == CSCH_ROLE_WIRE_NET))
		csch_wirenet_recalc_line_chg(u->line->hdr.sheet, u->line);
	if (u->recalc_conn_geo) {
		long n;
		for(n = 0; n < u->line->hdr.conn.used; n++)
			csch_conn_auto_recalc_geo(u->line->hdr.sheet, u->line->hdr.conn.array[n]);
	}

	csch_op_geo_edited(u->line->hdr.sheet, &u->line->hdr);

	return 0;
}

static void undo_line_modify_print(void *udata, char *dst, size_t dst_len)
{
	rnd_snprintf(dst, dst_len, "line geometry change");
}

static const char core_line_cookie[] = "libcschem/core/cnc_line.c";

static const uundo_oper_t undo_line_modify = {
	core_line_cookie,
	NULL,
	undo_line_modify_swap,
	undo_line_modify_swap,
	undo_line_modify_print
};

RND_INLINE void line_modify_endp(g2d_xform_t imx, csch_cgrp_t *parent, g2d_vect_t *dst_spec, g2d_vect_t *spec, g2d_vect_t *inst, csch_coord_t *x, csch_coord_t *y, int relative, int invxform)
{
	g2d_vect_t t;

	if ((x == NULL) && (y == NULL)) {
		/* cheap */
		dst_spec->x = spec->x;
		dst_spec->y = spec->y;
	}

	if (relative) {
		t.x = (x == NULL) ? 0 : *x;
		t.y = (y == NULL) ? 0 : *y;
		if (invxform) {
			t.x += parent->xform.x;
			t.y += parent->xform.y;
			t = g2d_xform_vect2vect(imx, t);
		}
		dst_spec->x = spec->x + t.x;
		dst_spec->y = spec->y + t.y;
	}
	else {
		t.x = (x == NULL) ? spec->x : *x;
		t.y = (y == NULL) ? spec->y : *y;
		if (invxform)
			t = g2d_xform_vect2vect(imx, t);
		dst_spec->x = t.x;
		dst_spec->y = t.y;
	}
}

void csch_line_modify(csch_sheet_t *sheet, csch_line_t *line, csch_coord_t *x1, csch_coord_t *y1, csch_coord_t *x2, csch_coord_t *y2, int undoable, int relative, int invxform)
{
	undo_line_modify_t utmp, *u = &utmp;
	g2d_xform_t imx;

	if (undoable) u = uundo_append(&sheet->undo, &undo_line_modify, sizeof(undo_line_modify_t));

	u->line = line;
	u->spec = line->spec; /* copy all fields */

	if (invxform)
		csch_cgrp_inverse_matrix(&imx, line->hdr.parent);

	line_modify_endp(imx, line->hdr.parent, &u->spec.p1, &line->spec.p1, &line->inst.c.p1, x1, y1, relative, invxform);
	line_modify_endp(imx, line->hdr.parent, &u->spec.p2, &line->spec.p2, &line->inst.c.p2, x2, y2, relative, invxform);

	u->disable_recalc = 0;
	u->recalc_conn_geo = 0;
	undo_line_modify_swap(u);
	u->recalc_conn_geo = 1; /* undo should recalc coord geo */
	u->disable_recalc = 1; /* undo shouldn't have side effects */

	if (undoable) csch_undo_inc_serial(sheet);
}
