#ifndef LIBGRBS_GRBS_H
#define LIBGRBS_GRBS_H

#include <genlist/gendlist.h>
#include <libualloc/stacks_api.h>
#include <libgrbs/rtree.h>
#include <genvector/vtp0.h>

#define GRBS_MAX_SEG 4

typedef struct grbs_point_s grbs_point_t;
typedef struct grbs_arc_s grbs_arc_t;
typedef struct grbs_line_s grbs_line_t;
typedef struct grbs_2net_s grbs_2net_t;
typedef struct grbs_s grbs_t;
typedef struct grbs_addr_s grbs_addr_t;

typedef enum { GRBS_POINT = 1, GRBS_ARC, GRBS_LINE, GRBS_2NET } grbs_obj_type_t;

typedef enum grbs_arc_dir_e { /* the exact values are important because of array indexing */
	GRBS_ADIR_INC = -1,
	GRBS_ADIR_CONVEX_CCW  = 0,
	GRBS_ADIR_CONVEX_CW   = 1,
	GRBS_ADIR_VCONCAVE    = 20
} grbs_arc_dir_t;

#define grbs_adir_is_CW(adir)      (((adir) == GRBS_ADIR_CONVEX_CW))
#define grbs_adir_is_CCW(adir)     (((adir) == GRBS_ADIR_CONVEX_CCW))
#define grbs_adir_is_convex(adir)  (((adir) == GRBS_ADIR_CONVEX_CW) || ((adir) == GRBS_ADIR_CONVEX_CCW))
#define grbs_adir_is_arc(adir)     ((adir) >= 0)

struct grbs_line_s {
	grbs_rtree_box_t bbox;
	long uid;

	grbs_arc_t *a1, *a2;    /* line is between a1's endpoint and a2's start point */
	double x1, y1, x2, y2;

	unsigned immutable:1;

	/* the user is free to use these flag bits to mark the object; set to 0 on obj creation */
	unsigned user_flg1:1;
	unsigned user_flg2:1;
	unsigned user_flg3:1;
	unsigned user_flg4:1;

	gdl_elem_t link_lines;

	void *user_data;
};

struct grbs_arc_s {
	grbs_rtree_box_t bbox;
	long uid;

	double r;      /* in case of incident line: 0 */
	double sa, da; /* start and delta angle; in case of incident line only sa is used */
	int segi;      /* segment index around pt */
	double copper, clearance;

	/* new is a newly inserted, yet non-realized path being routed with grbs_next */
	double new_r;            /* in case of incident line: 0 */
	double new_sa, new_da;   /* start and delta angle; in case of incident line only sa is used */
/*	int new_segi;             not needed: can't differ from segi */
	int new_adir;            /* if non-zero: direction the arc can progress from sa (+1 or -1, sign of new_da) */

	/* old is a snapshot of r/sa/da before a change made during realize; this is
	   how after the change the code knows which objects changed */
	double old_r;            /* in case of incident line: 0 */
	double old_sa, old_da;   /* start and delta angle; in case of incident line only sa is used */

	double min_r;            /* when wrong_r is set, this is the pre-calculated minimum radius under which the convex go-around would turn into concave */

	unsigned in_use:1;      /* whether r, sa and da are valid */
	unsigned new_in_use:1;  /* whether new_r, new_sa and new_da are valid */
	unsigned old_in_use:1;  /* whether old_r, old_sa and old_da are valid */
	unsigned vconcave:1;    /* if 1, both current and new are concave */
	unsigned registered:1;  /* if 1, arc is regeistered in the rtree */
	unsigned wrong_r:1;     /* current ->r is wrong, recalculate it on realize */

	/* the user is free to use these flag bits to mark the object; set to 0 on obj creation */
	unsigned user_flg1:1;
	unsigned user_flg2:1;
	unsigned user_flg3:1;
	unsigned user_flg4:1;

	grbs_point_t *parent_pt; /* can't be deduced from link_arcs because it can be on either of 2 lists */
	grbs_line_t *sline;      /* start side line (only when realized, for r/sa/da); this arc is sline->a2; NULL on two-net first point */
	grbs_line_t *eline;      /* end side line (only when realized, for r/sa/da); this arc is sline->a1 */

	gdl_elem_t link_2net;
	gdl_elem_t link_point;
	gdl_elem_t link_arcs;

	void *user_data;
};

struct grbs_point_s {
	grbs_rtree_box_t bbox;
	long uid;

	double x, y;
	double copper, clearance;
	gdl_list_t incs;                   /* of grbs_arc_t; incident line endpoints */
	gdl_list_t arcs[GRBS_MAX_SEG];     /* of grbs_arc_t; attached arcs, per curvature (convex=0), per segment */

	gdl_list_t vconcs;                 /* of grbs_arc_t; virtual concave arcs; used only in arc->new */

	grbs_2net_t *temp_tn;              /* temporary two-net association for collision detection */

	gdl_elem_t link_points;

	/* the user is free to use these flag bits to mark the object; set to 0 on obj creation */
	unsigned user_flg1:1;
	unsigned user_flg2:1;
	unsigned user_flg3:1;
	unsigned user_flg4:1;

	void *user_data;
};

struct grbs_2net_s {
	long uid;

	gdl_list_t arcs;   /* ordered from start to end */
	double copper;     /* trace width/2 */
	double clearance;

	gdl_elem_t link_2nets;

	/* the user is free to use these flag bits to mark the object; set to 0 on obj creation */
	unsigned user_flg1:1;
	unsigned user_flg2:1;
	unsigned user_flg3:1;
	unsigned user_flg4:1;

	void *user_data;
};

struct grbs_s {
	long uids; /* next uid to use is is ->uids+1 */

	/* user config */
	unsigned force_new_alloc:1;  /* do not reuse free'd objects (useful for snapshot compare) */
	unsigned ignore_arc_incident_cross:1; /* do not refuse new arcs that cross incident lines */

	/* user settable callbacks (optional) */
	void (*coll_report_arc_cb)(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn, grbs_arc_t *coll_arc); /* routing tn happened to collide with coll_tn's coll_arc */
	void (*coll_report_line_cb)(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn, grbs_line_t *coll_line); /* routing tn happened to collide with coll_tn's coll_line */
	void (*coll_report_pt_cb)(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *coll_pt); /* routing tn happened to collide with coll_pt */
	void (*coll_report_check_cb)(grbs_t *grbs, grbs_2net_t *tn, grbs_2net_t *coll_tn); /* routing tn happened to collide with coll_tn, figured by the coll_check callbacks */
	grbs_2net_t *(*coll_check_arc)(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *arc, int new); /* called on outmost arc proposals (->new if new is non-zero); should return a net on collision or NULL on no collision */
	grbs_2net_t *(*coll_check_line)(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *pt1, double x1, double y1, grbs_point_t *pt2, double x2, double y2); /* called on new line proposals (->new). pt1 and pt2 are the points around the endpoints are (precise endpoints are in x and y parameters) */
	int (*coll_ingore_tn_line)(grbs_t *grbs, grbs_2net_t *tn, grbs_line_t *l); /* optional: called on a obj-line intersection while routing; if returns 1, the collision is ignored (useful for self-colliding nets) */
	int (*coll_ingore_tn_arc)(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *a); /* optional: called on a obj-arc intersection while routing; if returns 1, the collision is ignored (useful for self-colliding nets) */
	int (*coll_ingore_tn_point)(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *p); /* optional: called on a obj-point intersection while routing; if returns 1, the collision is ignored (useful for self-colliding nets) */
	void (*auto_created_arc)(grbs_t *grbs, grbs_2net_t *tn, grbs_arc_t *a); /* called when an arc is automatically created elsewhere (e.g. force attach) */

	/* rtree of realized objects */
	grbs_rtree_t line_tree;
	grbs_rtree_t arc_tree;
	grbs_rtree_t point_tree;

	/* keep track on all objects so they can be freed or quickly reallocated */
	gdl_list_t all_2nets, free_2nets;
	gdl_list_t all_points, free_points;
	gdl_list_t all_arcs, free_arcs;
	gdl_list_t all_lines, free_lines;

	/* caches */
	uall_sysalloc_t sys;
	uall_stacks_t stk_points;
	uall_stacks_t stk_2nets;
	uall_stacks_t stk_arcs;
	uall_stacks_t stk_lines;
	uall_stacks_t stk_addrs;
	vtp0_t collobjs; /* list of collided objects */

	void *user_data;
};

typedef enum {
	/* base addr */
	ADDR_ARC_CONVEX   = 0x01, /* convex go-around: ((*)) ; use obj.arc */
	ADDR_POINT        = 0x03, /* incident, without an arc allocated; use obj.pt */
	ADDR_ARC_VCONCAVE = 0x04, /* virtual concave go-around: the final path will not touch this point */

	/* modifier: arc start or endpoint */
	ADDR_ARC_START   = 0x10,
	ADDR_ARC_END     = 0x20
} grbs_addr_type_t;

struct grbs_addr_s {
	grbs_addr_type_t type;
	union {
		grbs_arc_t *arc;
		grbs_point_t *pt;
	} obj;
	grbs_addr_t *last_real; /* last known non-vconcave address on this path */
	void *user_data;
} ;


/* A detached address is a safe address to store while running an A*; this
   trick is required because when visiting new points the grbs_addr_t often
   will be based on a newly created sentinel but after rollback that sentinel
   won't exist. So a detached address saves arc only if it's permanent. Plus
   it saves the "new" fields so they can be reproduced */
typedef struct grbs_detached_addr_s {
	grbs_addr_type_t type;
	grbs_arc_t *arc;   /* if NULL, try to create a new sentinel */
	grbs_point_t *pt;
	void *user_data;   /* copied from grbs_addr_t */
	long user_long;    /* spare field for the caller to use */

	/* these fields are the same as in grbs_arc_t */
	double new_r;
	double new_sa, new_da;
	int new_adir;
} grbs_detached_addr_t;

#define grbs_addr_is_convex(addr)    (((addr)->type & 0x0F) == ADDR_ARC_CONVEX)
#define grbs_addr_is_vconcave(addr)  (((addr)->type & 0x0F) == ADDR_ARC_VCONCAVE)

void grbs_init(grbs_t *grbs);
void grbs_uninit(grbs_t *grbs);

/* object allocation */
grbs_point_t *grbs_point_new(grbs_t *grbs, double x, double y, double copper, double clearance);
grbs_arc_t *grbs_arc_new(grbs_t *grbs, grbs_point_t *parent, int seg, double r, double sa, double da);
grbs_2net_t *grbs_2net_new(grbs_t *grbs, double copper, double clearance);
void grbs_point_free(grbs_t *grbs, grbs_point_t *p);
void grbs_arc_free(grbs_t *grbs, grbs_arc_t *a);
void grbs_2net_free(grbs_t *grbs, grbs_2net_t *tn);
grbs_addr_t *grbs_addr_new(grbs_t *grbs, grbs_addr_type_t type, void *obj);
void grbs_addr_free_last(grbs_t *grbs);
grbs_line_t *grbs_line_new(grbs_t *grbs);
void grbs_line_free(grbs_t *grbs, grbs_line_t *l);

/* object binding */
void grbs_line_attach(grbs_t *grbs, grbs_line_t *line, grbs_arc_t *arc, int linend);
void grbs_line_reg(grbs_t *grbs, grbs_line_t *l);
void grbs_arc_reg(grbs_t *grbs, grbs_arc_t *a);
void grbs_line_bbox(grbs_line_t *l);
void grbs_arc_bbox(grbs_arc_t *a);



#define grbs_arc_parent_2net(arc) \
	((((arc)->link_2net.parent) == NULL) ? NULL :  \
	(((grbs_2net_t *)(((char *)((arc)->link_2net.parent)) - offsetof(grbs_2net_t, arcs)))))

/* Convert an address into a portable detached addr. Does _not_ free addr */
void grbs_detach_addr(grbs_t *grbs, grbs_detached_addr_t dst[3], grbs_addr_t *addr);

/* Convert a detached address into an address, creating sentinel if needed
   and setting the returned addr's ->new_* fields. A path can be continued
   from the returned address */
grbs_addr_t *grbs_reattach_addr(grbs_t *grbs, grbs_detached_addr_t src[3]);

/*** collision checks ***/

/* returns 1 if an incident line of tn would be able to hit pt without an
   end cap collision in pt. It is really an end-cap-circle vs. rtree objects
   check. Makes collision reports on movable objects. */
int grbs_is_target_pt_routable(grbs_t *grbs, grbs_2net_t *tn, grbs_point_t *pt);

/* maintain point rtree if point geometry changes */
void grbs_point_reg(grbs_t *grbs, grbs_point_t *p);
void grbs_point_unreg(grbs_t *grbs, grbs_point_t *p);

/*** misc util ***/
int grbs_get_seg_idx(grbs_t *grbs, grbs_point_t *pt, double ang, int alloc);

/* Sanity check (and fix if !dry) a model so that arc angles are precize */
int grbs_sanity(grbs_t *grbs, int dry);


#endif /* LIBGRBS_GRBS_H */
