/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2018,2022 Tibor 'Igor2' Palinkas
 *
 *  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_arc.h"
#include "cnc_obj.h"
#include "cnc_grp_child.h"
#include "op_common.h"
#include "operation.h"
#include "undo.h"
#include "rotate.h"

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

csch_arc_t *csch_arc_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_oid_t oid)
{
	csch_arc_t *arc;

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

	csch_cobj_init(&arc->hdr, sheet, parent, oid, CSCH_CTYPE_ARC);
	return arc;
}

csch_arc_t *csch_arc_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_arc_t *src, int keep_id)
{
	csch_arc_t *dst = csch_arc_alloc(sheet, parent, CSCH_KEEP_OID(parent, src->hdr.oid));
	dst->inst = src->inst;
	dst->spec = src->spec;
	csch_chdr_copy_meta4dup(&dst->hdr, &src->hdr);
	return dst;
}



void csch_arc_free(csch_arc_t *arc)
{
	csch_cobj_uninit(&arc->hdr);
	free(arc);
}

csch_arc_t *csch_arc_get(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_oid_t oid)
{
	csch_arc_t *arc = htip_get(&grp->id2obj, oid);
	if ((arc != NULL) && (arc->hdr.type != CSCH_CTYPE_ARC))
		return NULL;
	return arc;
}

void csch_arc_update_xform(csch_sheet_t *sheet, csch_arc_t *arc)
{
	arc->inst.c = arc->spec; /* copies radius and angles */
	if (arc->hdr.parent != NULL) {
		csch_coord_t centx, centy;
		double start, delta, rot;
		csch_child_xform_t *cx;
		g2d_xform_t mx = csch_grp_ref_parent_mx(&arc->hdr, &cx);

		arc->inst.c.c = g2d_xform_vect2vect(mx, arc->spec.c);
		csch_arc_mirror_(arc,
			arc->hdr.parent->xform.x + cx->movex, arc->hdr.parent->xform.y + cx->movey,
			arc->hdr.parent->xform.mirx ^ cx->mirx, arc->hdr.parent->xform.miry ^ cx->miry,
			&centx, &centy, &start, &delta);

		arc->inst.c.start = start;
		arc->inst.c.delta = delta;

		rot = (arc->hdr.parent->xform.rot + cx->rot) / RND_RAD_TO_DEG;
		arc->inst.c.start += fmod(rot, 2.0*G2D_PI);
	}
	else
		arc->inst.c = arc->spec;
	if (arc->hdr.stroke != NULL) {
		arc->inst.s.width = arc->hdr.stroke->size;
		arc->inst.s.cap = (arc->hdr.stroke->shape == CSCH_PSHP_ROUND) ? G2D_CAP_ROUND : G2D_CAP_SQUARE;
	}
}

void csch_arc_update_bbox(csch_sheet_t *sheet, csch_arc_t *arc)
{
	g2d_box_t gb;

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

void csch_arc_center_bbox(csch_sheet_t *sheet, const csch_arc_t *arc, csch_rtree_box_t *dst)
{
	g2d_box_t gb = g2d_carc_bbox(&arc->inst.c);
	dst->x1 = gb.p1.x; dst->y1 = gb.p1.y;
	dst->x2 = gb.p2.x; dst->y2 = gb.p2.y;
}

void csch_arc_update(csch_sheet_t *sheet, csch_arc_t *arc, int do_xform)
{

	csch_cobj_update_pen(&arc->hdr);

	if (do_xform)
		csch_arc_update_xform(sheet, arc);

	csch_cobj_rtree_del(sheet, &arc->hdr);
	csch_arc_update_bbox(sheet, arc);
	csch_cobj_rtree_add(sheet, &arc->hdr);

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


int csch_arc_get_endxy(const csch_arc_t *arc, int side, csch_coord_t *x, csch_coord_t *y)
{
	g2d_vect_t v = g2d_carc_offs(&arc->inst.c, ((side == 0) ? 0.0 : 1.0));
	*x = v.x;
	*y = v.y;
	return 0;
}

int csch_arc_get_midxy(const csch_arc_t *arc, csch_coord_t *x, csch_coord_t *y)
{
	g2d_vect_t v = g2d_carc_offs(&arc->inst.c, 0.5);
	*x = v.x;
	*y = v.y;
	return 0;
}

int csch_arc_get_spec_endxy(const csch_arc_t *arc, int side, csch_coord_t *x, csch_coord_t *y)
{
	g2d_vect_t v = g2d_carc_offs(&arc->spec, ((side == 0) ? 0.0 : 1.0));
	*x = v.x;
	*y = v.y;
	return 0;
}

int csch_arc_get_spec_midxy(const csch_arc_t *arc, csch_coord_t *x, csch_coord_t *y)
{
	g2d_vect_t v = g2d_carc_offs(&arc->spec, 0.5);
	*x = v.x;
	*y = v.y;
	return 0;
}


static csch_chdr_t *arc_create(csch_sheet_t *sheet, csch_cgrp_t *parent)
{
	csch_arc_t *arc = csch_arc_alloc(sheet, parent, csch_oid_new(sheet, parent));
	if (arc == NULL) return NULL;
	return &arc->hdr;
}

static void arc_remove_alloc(csch_undo_remove_t *slot)
{
}

static void arc_remove_redo(csch_undo_remove_t *slot)
{
	csch_cnc_common_remove_redo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT);
}

static void arc_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 arc_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box)
{
	csch_arc_t *arc = (csch_arc_t *)obj;
	g2d_box_t gbox, abox;
	g2d_carc_t outer, inner;
	g2d_vect_t tmp1[8];
	g2d_offs_t tmp2[8];

	gbox.p1.x = box->x1; gbox.p1.y = box->y1;
	gbox.p2.x = box->x2; gbox.p2.y = box->y2;
	abox.p1.x = arc->hdr.bbox.x1; abox.p1.y = arc->hdr.bbox.y1;
	abox.p2.x = arc->hdr.bbox.x2; abox.p2.y = arc->hdr.bbox.y2;

	/* arc fully within the box (cheap) */
	if (g2d_box_in_box(&gbox, &abox)) return 1;

	g2d_sarc_sides(&arc->inst, &outer, &inner);

	/* check if outer arc is hit by the box; this is the most common case:
	   if the box crosses the arc, it surely hits outer arc */
	if (g2d_iscp_carc_box(&outer, &gbox, tmp1, tmp2)) return 1;

	/* special cases, cheaper first */
TODO("check end cap circle");

	/* the corner of a small box within the sarc may hit the inner arc... */
	return g2d_iscp_carc_box(&inner, &gbox, tmp1, tmp2);
}


static void arc_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_arc_t *arc = (csch_arc_t *)obj;
	csch_arc_modify(sheet, arc, &dx, &dy, NULL, NULL, NULL, undoable, 1);
}

static void arc_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable)
{
	csch_arc_t *arc = csch_arc_dup(sheet, obj->parent, (csch_arc_t *)obj, 0);
	if (arc != NULL) {
		arc->spec.c.x += dx; arc->spec.c.y += dy;
		csch_arc_update(sheet, arc, 1);
		csch_op_inserted(sheet, obj->parent, &arc->hdr);
	}

}

void csch_arc_rotate_(csch_arc_t *arc, csch_coord_t rcx, csch_coord_t rcy, double da, double cs, double sn, csch_coord_t *cx_out, csch_coord_t *cy_out, double *start_out)
{
	csch_coord_t ncx = arc->spec.c.x, ncy = arc->spec.c.y;

	csch_rotate_pt(&ncx, &ncy, rcx, rcy, cs, sn);

	*cx_out = ncx;
	*cy_out = ncy;
	*start_out = fmod(arc->spec.start + da / RND_RAD_TO_DEG, 2.0*G2D_PI);
}

static void arc_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable)
{
	csch_arc_t *arc = (csch_arc_t *)obj;
	double rad = -da / RND_RAD_TO_DEG;
	csch_coord_t ncx, ncy;
	double nstart;

	csch_arc_rotate_(arc, rcx, rcy, da, cos(rad), sin(rad), &ncx, &ncy, &nstart);
	csch_arc_modify(sheet, arc, &ncx, &ncy, NULL, &nstart, NULL, undoable, 0);
}

void csch_arc_rotate90_(csch_arc_t *arc, csch_coord_t rcx, csch_coord_t rcy, int n, csch_coord_t *cx_out, csch_coord_t *cy_out, double *start_out)
{
	csch_coord_t ncx = arc->spec.c.x, ncy = arc->spec.c.y;

	csch_rotate90_pt(&ncx, &ncy, rcx, rcy, n);

	*cx_out = ncx;
	*cy_out = ncy;
	*start_out = fmod(arc->spec.start + (double)n * G2D_PI/2.0, 2.0*G2D_PI);
}

static void arc_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int n, int undoable)
{
	csch_arc_t *arc = (csch_arc_t *)obj;
	csch_coord_t ncx, ncy;
	double nstart;

	csch_arc_rotate90_(arc, rcx, rcy, n, &ncx, &ncy, &nstart);
	csch_arc_modify(sheet, arc, &ncx, &ncy, NULL, &nstart, NULL, undoable, 0);
}

void csch_arc_mirror_(csch_arc_t *arc, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, csch_coord_t *cx_out, csch_coord_t *cy_out, double *start_out, double *delta_out)
{
	csch_coord_t ncx = arc->spec.c.x, ncy = arc->spec.c.y;
	double nstart = arc->spec.start, ndelta = arc->spec.delta, nend = nstart+ndelta;
	double sdx = cos(nstart), sdy = sin(nstart), edx = cos(nend), edy = sin(nend);
	int delta_pos = (ndelta > 0);
	int delta_full = (ndelta >= G2D_PI * 2.0) || (ndelta <= -(G2D_PI * 2.0));

	csch_mirror_pt(&ncx, &ncy, mcx, mcy, mirx, miry);
	if (mirx) {
		sdx = -sdx;
		edx = -edx;
		delta_pos = !delta_pos;
	}
	if (miry) {
		sdy = -sdy;
		edy = -edy;
		delta_pos = !delta_pos;
	}


	nstart = atan2(sdy, sdx);
	if (!delta_full) {
		nend   = atan2(edy, edx);
		ndelta = nend - nstart;

		if (delta_pos && (ndelta < 0)) ndelta += G2D_PI * 2.0;
		if (!delta_pos && (ndelta > 0)) ndelta -= G2D_PI * 2.0;
	}
	else
		ndelta = G2D_PI * 2.0;

	*cx_out = ncx;
	*cy_out = ncy;
	*start_out = nstart;
	*delta_out = ndelta;
}

static void arc_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable)
{
	csch_arc_t *arc = (csch_arc_t *)obj;
	csch_coord_t ncx, ncy;
	double nstart, ndelta;

	csch_arc_mirror_(arc, mcx, mcy, mirx, miry, &ncx, &ncy, &nstart, &ndelta);
	csch_arc_modify(sheet, arc, &ncx, &ncy, NULL, &nstart, &ndelta, undoable, 0);
}

static void arc_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *in, int undoable)
{
	csch_arc_t *arc = (csch_arc_t *)in;
	csch_coord_t cx = arc->inst.c.c.x, cy = arc->inst.c.c.y, r = arc->inst.c.r;
	double start = arc->inst.c.start, delta = arc->inst.c.delta;

	csch_arc_modify(sheet, (csch_arc_t *)obj, &cx, &cy, &r, &start, &delta, undoable, 0);
}


const csch_ops_t csch_ops_arc = {
	arc_create, arc_remove_alloc, arc_remove_redo, arc_remove_undo,
	arc_isc_with_box,
	arc_move, arc_copy, arc_rotate, arc_rotate90, arc_mirror,
	arc_inst2spec
};

/*** Hash ***/
unsigned csch_arc_hash_(const csch_arc_t *arc, csch_hash_ignore_t ignore, int in_contour)
{
	unsigned res = 0;

	if (!in_contour) res ^= csch_chdr_hash(&arc->hdr);
	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !arc->hdr.floater) {
		res ^= csch_coord_hash(arc->spec.c.x);
		res ^= csch_coord_hash(arc->spec.c.y);
		/* angles change with rotation, thus they are part of the floater set */
		res ^= csch_angle_hash(arc->spec.start);
		res ^= csch_angle_hash(arc->spec.delta);
	}
	res ^= csch_coord_hash(arc->spec.r);
	return res;
}

unsigned csch_arc_hash(const csch_arc_t *arc, csch_hash_ignore_t ignore)
{
	return csch_arc_hash_(arc, ignore, 0);
}

int csch_arc_keyeq_(const csch_arc_t *a1, const csch_arc_t *a2, csch_hash_ignore_t ignore, int in_contour)
{
	if (!in_contour && !csch_chdr_eq(&a1->hdr, &a2->hdr)) return 0;

	if (!(ignore & CSCH_HIGN_FLOATER_GEO) || !a1->hdr.floater || !a2->hdr.floater) {
		if (a1->spec.c.x != a2->spec.c.x) return 0;
		if (a1->spec.c.y != a2->spec.c.y) return 0;
		/* angles change with rotation, thus they are part of the floater set */
		if (!csch_angle_eq(a1->spec.start, a2->spec.start)) return 0;
		if (!csch_angle_eq(a1->spec.delta, a2->spec.delta)) return 0;
	}
	if (a1->spec.r != a2->spec.r) return 0;
	return 1;
}

int csch_arc_keyeq(const csch_arc_t *a1, const csch_arc_t *a2, csch_hash_ignore_t ignore)
{
	return csch_arc_keyeq_(a1, a2, ignore, 0);
}


/*** Modify ***/
typedef struct {
	csch_arc_t *arc; /* it is safe to save the object pointer because it is persistent (through the removed object list) */
	g2d_carc_t spec;
} undo_arc_modify_t;


static int undo_arc_modify_swap(void *udata)
{
	undo_arc_modify_t *u = udata;


	csch_cobj_redraw(u->arc);

	rnd_swap(g2d_carc_t, u->spec, u->arc->spec);
	csch_arc_update(u->arc->hdr.sheet, u->arc, 1);

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

	return 0;
}

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

static const char core_arc_cookie[] = "libcschem/core/cnc_arc.c";

static const uundo_oper_t undo_arc_modify = {
	core_arc_cookie,
	NULL,
	undo_arc_modify_swap,
	undo_arc_modify_swap,
	undo_arc_modify_print
};


void csch_arc_modify(csch_sheet_t *sheet, csch_arc_t *arc, csch_coord_t *cx, csch_coord_t *cy, csch_coord_t *r, double *start, double *delta, int undoable, int relative)
{
	undo_arc_modify_t utmp, *u = &utmp;

	if (undoable) u = uundo_append(&sheet->undo, &undo_arc_modify, sizeof(undo_arc_modify_t));

	u->arc = arc;
	u->spec = arc->spec; /* copy all fields */
	u->spec.c.x = (cx == NULL) ? arc->spec.c.x : (relative ? arc->spec.c.x + *cx : *cx);
	u->spec.c.y = (cy == NULL) ? arc->spec.c.y : (relative ? arc->spec.c.y + *cy : *cy);
	u->spec.r   = (r == NULL) ? arc->spec.r : (relative ? arc->spec.r + *r : *r);
	u->spec.start = (start == NULL) ? arc->spec.start : (relative ? arc->spec.start + *start : *start);
	u->spec.delta = (delta == NULL) ? arc->spec.delta : (relative ? arc->spec.delta + *delta : *delta);

	undo_arc_modify_swap(u);
	if (undoable) csch_undo_inc_serial(sheet);
}

