/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - sch-rnd (executable)
 *  Copyright (C) 2019,2020,2022,2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2023)
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 <librnd/hid/hid.h>
#include <librnd/hid/hid_inlines.h>
#include <librnd/core/color.h>
#include <librnd/core/vtc0.h>
#include <librnd/core/compat_misc.h>

#include <libcschem/abstract.h>
#include <libcschem/concrete.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_conn.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_arc.h>
#include <libcschem/cnc_pen.h>
#include <libcschem/non_graphical.h>
#include <libcschem/util_wirenet.h>
#include <libcschem/event.h>
#include <libcschem/project.h>

#include <gengeo2d/xform.h>

#include "conf_core.h"
#include "font.h"
#include "draw.h"
#include "search.h"

static const char draw_cookie[] = "sch-rnd/draw.c";

typedef struct {
	csch_sheet_t *sheet;
	csch_abstract_t *abst;
	rnd_hid_gc_t gc;
	vtp0_t objlist;
	rnd_xform_t *xform;
} draw_ctx_t;

TODO("code dup with pcb-rnd")
static void pcb_draw_dashed_line(rnd_hid_gc_t GC, rnd_coord_t x1, rnd_coord_t y1, rnd_coord_t x2, rnd_coord_t y2, unsigned int segs, rnd_bool_t cheap)
{
/* TODO: we need a real geo lib... using double here is plain wrong */
	double dx = x2-x1, dy = y2-y1;
	double len_mnt = RND_ABS(dx) + RND_ABS(dy);
	int n;
	rnd_coord_t minlen = rnd_render->coord_per_pix * 8;

	/* Ignore info->xform->bloat because a dashed line is always thin */

	if (len_mnt < minlen*2) {
		/* line too short, just draw it */
		rnd_render->draw_line(GC, x1, y1, x2, y2);
		return;
	}

	segs = (segs << 1) + 1; /* must be odd */

	if (cheap) {
		if ((segs > 3) && (len_mnt < minlen * segs))
			segs = 3;
		else if ((segs > 5) && (len_mnt < minlen * 2 * segs))
			segs = 5;
	}

	/* first seg is drawn from x1, y1 with no rounding error due to n-1 == 0 */
	for(n = 1; n < segs; n+=2)
		rnd_render->draw_line(GC,
			x1 + (dx * (double)(n-1) / (double)segs), y1 + (dy * (double)(n-1) / (double)segs),
			x1 + (dx * (double)n / (double)segs), y1 + (dy * (double)n / (double)segs));


	/* make sure the last segment is drawn properly to x2 and y2, don't leave
	   room for rounding errors */
	rnd_render->draw_line(GC,
		x2 - (dx / (double)segs), y2 - (dy / (double)segs),
		x2, y2);
}

RND_INLINE rnd_cap_style_t pen_cap(csch_cpen_t *pen)
{
	switch(pen->shape) {
		case CSCH_PSHP_ROUND: return rnd_cap_round;
		case CSCH_PSHP_SQUARE: return rnd_cap_square;
	}

	/* fallback for invalid */
	return rnd_cap_round;
}

static void lighten_color(const rnd_color_t *orig, rnd_color_t *dst, double factor)
{
	rnd_color_load_int(dst, MIN(255, orig->r * factor), MIN(255, orig->g * factor), MIN(255, orig->b * factor), 255);
}


RND_INLINE const rnd_color_t *sch_rnd_stroke_color(draw_ctx_t *ctx, const csch_chdr_t *obj, const csch_cpen_t *stroke, int selected)
{
	const rnd_color_t *res;
	int no_render_select = (ctx->xform != NULL) && (ctx->xform->no_render_select);
	int no_render_hilight = (ctx->xform != NULL) && (ctx->xform->no_render_hilight);

	if (obj->hilight && !no_render_hilight)
		return &conf_core.appearance.color.hilight_stroke;

	if (no_render_select)
		selected = 0;

	res = selected ? &conf_core.appearance.color.selected_stroke : &stroke->color;
	if ((ctx->xform != NULL) && (ctx->xform->faded)) {
		static rnd_color_t tmp;
		lighten_color(res, &tmp, 0.5);
		res = &tmp;
	}
	return res;
}

RND_INLINE const rnd_color_t *sch_rnd_fill_color(draw_ctx_t *ctx, const csch_chdr_t *obj, const csch_cpen_t *fill, int selected)
{
	const rnd_color_t *res;
	if (obj->hilight)
		return &conf_core.appearance.color.hilight_stroke;
	res = selected ? &conf_core.appearance.color.selected_fill : &fill->color;
	if ((ctx->xform != NULL) && (ctx->xform->faded)) {
		static rnd_color_t tmp;
		lighten_color(res, &tmp, 0.5);
		res = &tmp;
	}
	return res;
}

#include "draw_poly.c"

RND_INLINE void draw_layer_line(draw_ctx_t *ctx, csch_line_t *l, csch_cpen_t *pen, int selected)
{
	rnd_render->set_color(ctx->gc, sch_rnd_stroke_color(ctx, &l->hdr, pen, selected));
	rnd_hid_set_line_cap(ctx->gc, pen_cap(pen));
	rnd_hid_set_line_width(ctx->gc, C2P(pen->size));
	rnd_render->draw_line(ctx->gc, C2P(l->inst.c.p1.x), C2P(l->inst.c.p1.y), C2P(l->inst.c.p2.x), C2P(l->inst.c.p2.y));
}

RND_INLINE void draw_layer_conn(draw_ctx_t *ctx, csch_conn_t *c)
{
	long n, *crd;

	if (c->coords.used == 0)
		return;

	rnd_render->set_color(ctx->gc, &conf_core.appearance.color.connection_good);
	rnd_hid_set_line_cap(ctx->gc, rnd_cap_round);
	rnd_hid_set_line_width(ctx->gc, -2);

	for(n = 0, crd = c->coords.array; n < c->coords.used; n += 4, crd += 4) {
		long slop = sch_rnd_slop;
		long dx = crd[0] - crd[2], dy = crd[1] - crd[3];
		dx = RND_ABS(dx); dy = RND_ABS(dy);
	
		if (dx + dy <= slop)
			rnd_render->draw_arc(ctx->gc, C2P((crd[0] + crd[2])/2), C2P((crd[1] + crd[3])/2), C2P(256), C2P(256), 0, 360);
		else
			rnd_render->draw_line(ctx->gc, C2P(crd[0]), C2P(crd[1]), C2P(crd[2]), C2P(crd[3]));
	}
}

RND_INLINE void draw_vectors(draw_ctx_t *ctx, g2d_vect_t base, g2d_vect_t origin, g2d_xform_t mx)
{
		g2d_vect_t u[6];
		int n;

		u[0] = g2d_vect(1000, 0);
		u[1] = g2d_vect(900, +100);
		u[2] = g2d_vect(900, -100);
		u[3] = g2d_vect(0, 1000);
		u[4] = g2d_vect(+100, 900);
		u[5] = g2d_vect(-100, 900);
		for(n = 0; n < 6; n++) {
			u[n].x += base.x;
			u[n].y += base.y;
			u[n] = g2d_xform_vect2vect(mx, u[n]);
		}
		rnd_render->draw_line(ctx->gc, C2P(origin.x), C2P(origin.y), C2P(u[0].x), C2P(u[0].y));
		rnd_render->draw_line(ctx->gc, C2P(origin.x), C2P(origin.y), C2P(u[3].x), C2P(u[3].y));
		rnd_render->draw_line(ctx->gc, C2P(u[0].x), C2P(u[0].y), C2P(u[1].x), C2P(u[1].y));
		rnd_render->draw_line(ctx->gc, C2P(u[0].x), C2P(u[0].y), C2P(u[2].x), C2P(u[2].y));
		rnd_render->draw_line(ctx->gc, C2P(u[3].x), C2P(u[3].y), C2P(u[4].x), C2P(u[4].y));
		rnd_render->draw_line(ctx->gc, C2P(u[3].x), C2P(u[3].y), C2P(u[5].x), C2P(u[5].y));
}

RND_INLINE void print_mx(const char *name, g2d_xform_t mx)
{
	int n;
	rnd_trace("MX %s\n", name);
	for(n = 0; n < 9; n++) {
		rnd_trace(" %.2f", mx.v[n]);
		if ((n % 3) == 2)
			rnd_trace("\n");
	}
	rnd_trace("\n");
}

RND_INLINE void draw_layer_text(draw_ctx_t *ctx, csch_text_t *t, csch_cpen_t *pen, int selected)
{
	int use_local = (ctx->xform != NULL) && ctx->xform->use_local_vis;
	int vis_meta = use_local ? ctx->xform->local_vis[CSCH_DSPLY_TEXT_META] : csch_layer_vis[CSCH_DSPLY_TEXT_META];

	if (vis_meta) {
		rnd_render->set_color(ctx->gc, sch_rnd_stroke_color(ctx, &t->hdr, pen, selected));
		rnd_hid_set_line_cap(ctx->gc, pen_cap(pen));
		rnd_hid_set_line_width(ctx->gc, -1);

		rnd_render->draw_line(ctx->gc, C2P(t->inst1.x), C2P(t->inst1.y), C2P(t->inst15.x), C2P(t->inst15.y));
		rnd_render->draw_line(ctx->gc, C2P(t->inst1.x), C2P(t->inst1.y), C2P(t->inst25.x), C2P(t->inst25.y));
		rnd_render->draw_line(ctx->gc, C2P(t->inst2.x), C2P(t->inst2.y), C2P(t->inst15.x), C2P(t->inst15.y));
		rnd_render->draw_line(ctx->gc, C2P(t->inst2.x), C2P(t->inst2.y), C2P(t->inst25.x), C2P(t->inst25.y));
	}
	if (conf_core.rc.debug.draw_text_xform) {
		rnd_render->set_color(ctx->gc, sch_rnd_stroke_color(ctx, &t->hdr, pen, selected));
		rnd_hid_set_line_cap(ctx->gc, pen_cap(pen));
		rnd_hid_set_line_width(ctx->gc, -1);
		draw_vectors(ctx, t->spec1, t->inst1, t->hdr.parent->xform.mx);

#if 0
		{
		g2d_xform_t imx;
		print_mx("orig:", t->hdr.parent->xform.mx);
		rnd_render->set_color(ctx->gc, rnd_color_red);
		csch_cgrp_inverse_matrix(&imx, t->hdr.parent);
		imx.v[2] = imx.v[2] < 0 ? -1000 : 1000;
		imx.v[5] = imx.v[5] < 0 ? -1000 : 1000;
		draw_vectors(ctx, t->inst1, t->inst1, imx);
		print_mx("inv:", imx);
		}
#endif
	}


	rnd_render->set_color(ctx->gc, sch_rnd_stroke_color(ctx, &t->hdr, pen, selected));
	sch_rnd_font_text_render(ctx->sheet, ctx->gc, t, pen);

	if (vis_meta) {
		double vx, vy, len;

		rnd_render->set_color(ctx->gc, rnd_color_red);
		rnd_hid_set_line_cap(ctx->gc, pen_cap(pen));
		rnd_hid_set_line_width(ctx->gc, -3);

		vx = t->inst15.x - t->inst1.x;
		vy = t->inst15.y - t->inst1.y;
		len = sqrt(vx*vx+vy*vy);
		if (len > 0) {
			vx /= len; vy /= len;
			rnd_render->draw_line(ctx->gc, C2P(t->inst1.x), C2P(t->inst1.y), C2P((csch_coord_t)(t->inst1.x + vx*3000)), C2P((csch_coord_t)(t->inst1.y + vy*3000)));
		}

		rnd_render->set_color(ctx->gc, rnd_color_magenta);
		vx = t->inst25.x - t->inst1.x;
		vy = t->inst25.y - t->inst1.y;
		len = sqrt(vx*vx+vy*vy);
		if (len > 0) {
			vx /= len; vy /= len;
			rnd_render->draw_line(ctx->gc, C2P(t->inst1.x), C2P(t->inst1.y), C2P((csch_coord_t)(t->inst1.x + vx*3000)), C2P((csch_coord_t)(t->inst1.y + vy*3000)));
		}
	}
}

RND_INLINE void draw_layer_arc(draw_ctx_t *ctx, csch_arc_t *a, csch_cpen_t *pen, int selected)
{
	rnd_render->set_color(ctx->gc, sch_rnd_stroke_color(ctx, &a->hdr, pen, selected));
	rnd_hid_set_line_cap(ctx->gc, pen_cap(pen));
	rnd_hid_set_line_width(ctx->gc, C2P(pen->size));
	rnd_render->draw_arc(ctx->gc, C2P(a->inst.c.c.x), C2P(a->inst.c.c.y), C2P(a->inst.c.r), C2P(a->inst.c.r), (180.0 - a->inst.c.start * RND_RAD_TO_DEG), -(a->inst.c.delta * RND_RAD_TO_DEG));

	if (conf_core.rc.debug.draw_arc_bbox) {
		rnd_hid_set_line_width(ctx->gc, -1);
		rnd_render->draw_line(ctx->gc, C2P(a->hdr.bbox.x1), C2P(a->hdr.bbox.y1), C2P(a->hdr.bbox.x2), C2P(a->hdr.bbox.y1));
		rnd_render->draw_line(ctx->gc, C2P(a->hdr.bbox.x1), C2P(a->hdr.bbox.y1), C2P(a->hdr.bbox.x1), C2P(a->hdr.bbox.y2));
		rnd_render->draw_line(ctx->gc, C2P(a->hdr.bbox.x2), C2P(a->hdr.bbox.y2), C2P(a->hdr.bbox.x2), C2P(a->hdr.bbox.y1));
		rnd_render->draw_line(ctx->gc, C2P(a->hdr.bbox.x2), C2P(a->hdr.bbox.y2), C2P(a->hdr.bbox.x1), C2P(a->hdr.bbox.y2));
	}
}

static csch_rtree_dir_t predraw_layer_obj(void *ctx_, void *obj_, const csch_rtree_box_t *box)
{
	draw_ctx_t *ctx = (draw_ctx_t *)ctx_;
	csch_chdr_t *obj = obj_;

	switch(obj->type) {
		case CSCH_CTYPE_TEXT: /* need to update font in a separate pass because it changes the rtree we are searching */
			{
				csch_text_t *t = (csch_text_t *)obj;
				if ((rnd_render->draw_pixmap != NULL) && (t->pixmap == NULL))
					vtp0_append(&ctx->objlist, t);
			}
			break;
		default: break;
	}

	return csch_RTREE_DIR_FOUND_CONT;

}

static void draw_grp_meta(draw_ctx_t *ctx, csch_cgrp_t *grp, int selected)
{
	int use_local = (ctx->xform != NULL) && ctx->xform->use_local_vis;
	int vis_meta_sym = use_local ? ctx->xform->local_vis[CSCH_DSPLY_SYMBOL_META] : csch_layer_vis[CSCH_DSPLY_SYMBOL_META];

	if (vis_meta_sym && (grp->role == CSCH_ROLE_SYMBOL) && (grp->hdr.bbox.x1 < grp->hdr.bbox.x2)) {
		int embed = grp->hdr.type == CSCH_CTYPE_GRP;
		rnd_render->set_color(ctx->gc, (embed ? &conf_core.appearance.color.symbol_meta_embed : &conf_core.appearance.color.symbol_meta_loclib));
		rnd_hid_set_line_cap(ctx->gc, rnd_cap_round);
		rnd_hid_set_line_width(ctx->gc, -1);

		pcb_draw_dashed_line(ctx->gc, C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y1), C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y1), 5, rnd_true);
		pcb_draw_dashed_line(ctx->gc, C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y1), C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y2), 5, rnd_true);
		pcb_draw_dashed_line(ctx->gc, C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y2), C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y2), 5, rnd_true);
		pcb_draw_dashed_line(ctx->gc, C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y2), C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y1), 5, rnd_true);

		/* draw E or L for embed or local */
		rnd_hid_set_line_width(ctx->gc, -2);
		rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1 + 1000), C2P(grp->hdr.bbox.y2 - 1000), C2P(grp->hdr.bbox.x1 + 1000), C2P(grp->hdr.bbox.y2 - 3000));
		rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1 + 1000), C2P(grp->hdr.bbox.y2 - 3000), C2P(grp->hdr.bbox.x1 + 2000), C2P(grp->hdr.bbox.y2 - 3000));
		if (embed) {
			rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1 + 1000), C2P(grp->hdr.bbox.y2 - 1000), C2P(grp->hdr.bbox.x1 + 2000), C2P(grp->hdr.bbox.y2 - 1000));
			rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1 + 1000), C2P(grp->hdr.bbox.y2 - 2000), C2P(grp->hdr.bbox.x1 + 1500), C2P(grp->hdr.bbox.y2 - 2000));
		}
	}

	/* mark origin */
	if (vis_meta_sym && (grp->role == CSCH_ROLE_SYMBOL)) {
		rnd_render->set_color(ctx->gc, &conf_core.appearance.color.symbol_meta_embed);
		rnd_hid_set_line_cap(ctx->gc, rnd_cap_round);
		rnd_hid_set_line_width(ctx->gc, -1);
		rnd_render->draw_line(ctx->gc, C2P(grp->x - 100), C2P(grp->y - 100), C2P(grp->x + 100), C2P(grp->y + 100));
		rnd_render->draw_line(ctx->gc, C2P(grp->x + 100), C2P(grp->y - 100), C2P(grp->x - 100), C2P(grp->y + 100));
	}

	/* draw omit and dnp */
	if ((ctx->abst != NULL) && (grp->role == CSCH_ROLE_SYMBOL) && (grp->hdr.bbox.x1 < grp->hdr.bbox.x2) && (grp->aid.used > 0)) {
		int omit = 0, dnp = 0;
		csch_ahdr_t *aobj;

		TODO("hierarchic: revise this");
		/* for now take only the first abstract's flag; in a more complicated
		   situation in hierarchic design, the attributes may differ from
		   abstract obj to abstract obj derived from the same concrete! */
		aobj = htip_get(&ctx->abst->aid2obj, grp->aid.array[0]);
		omit = aobj != NULL ? aobj->omit : 0;
		dnp = aobj != NULL ? aobj->dnp : 0;

		if (omit || dnp) {
			rnd_render->set_color(ctx->gc, (selected ? &conf_core.appearance.color.selected_stroke : &conf_core.appearance.color.symbol_meta_embed));
			rnd_hid_set_line_cap(ctx->gc, rnd_cap_round);
			rnd_hid_set_line_width(ctx->gc, -2);
		}

		if (dnp) {
			static void *f = NULL;
			csch_coord_t cx = (grp->hdr.bbox.x1 + grp->hdr.bbox.x2)/2;
			csch_coord_t cy = (grp->hdr.bbox.y1 + grp->hdr.bbox.y2)/2;

			if (f == NULL)
				f = sch_rnd_font_lookup("sans", "regular");

			sch_rnd_render_text_string(ctx->gc, f, C2P(cx-2500), C2P(cy+2000), 2, (unsigned char *)"DNP");
		}

		if (omit) {
			rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y1), C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y2));
			rnd_render->draw_line(ctx->gc, C2P(grp->hdr.bbox.x1), C2P(grp->hdr.bbox.y2), C2P(grp->hdr.bbox.x2), C2P(grp->hdr.bbox.y1));
		}
	}

	if ((ctx->abst != NULL) && (grp->role == CSCH_ROLE_WIRE_NET) && (grp->aid.used > 0)) {
		int omit = 0;
		csch_ahdr_t *aobj;

		TODO("hierarchic: revise this");
		/* for now take only the first abstract's flag; in a more complicated
		   situation in hierarchic design, the attributes may differ from
		   abstract obj to abstract obj derived from the same concrete! */
		aobj = htip_get(&ctx->abst->aid2obj, grp->aid.array[0]);
		omit = (aobj != NULL) ? aobj->omit : 0;

		if (omit) {
			htip_entry_t *e;

			rnd_render->set_color(ctx->gc, (selected ? &conf_core.appearance.color.selected_stroke : &conf_core.appearance.color.symbol_meta_embed));
			rnd_hid_set_line_cap(ctx->gc, rnd_cap_round);
			rnd_hid_set_line_width(ctx->gc, -2);

			for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
				csch_line_t *line = e->value;
				csch_coord_t cx, cy, len = 500;

				if (line->hdr.type != CSCH_CTYPE_LINE) continue;
				if ((line->inst.c.p1.x == line->inst.c.p2.x) && (line->inst.c.p1.y == line->inst.c.p2.y)) continue;

				cx = (line->inst.c.p1.x + line->inst.c.p2.x)/2;
				cy = (line->inst.c.p1.y + line->inst.c.p2.y)/2;
				rnd_render->draw_line(ctx->gc, C2P(cx-len), C2P(cy-len), C2P(cx+len), C2P(cy+len));
				rnd_render->draw_line(ctx->gc, C2P(cx-len), C2P(cy+len), C2P(cx+len), C2P(cy-len));
			}
		}
	}
}

static csch_rtree_dir_t collect_layer_obj_fill(void *ctx_, void *obj_, const csch_rtree_box_t *box)
{
	draw_ctx_t *ctx = (draw_ctx_t *)ctx_;
	csch_chdr_t *obj = obj_;

	switch(obj->type) {
		case CSCH_CTYPE_POLY:
			{
				csch_cpoly_t *poly = (csch_cpoly_t *)obj;
				if (poly->has_fill) {
					if (poly->area < 0)
						draw_poly_calc_area(ctx, poly);
					vtp0_append(&ctx->objlist, obj);
				}
			}
			break;

		case CSCH_CTYPE_ARC:
		case CSCH_CTYPE_LINE:
		case CSCH_CTYPE_TEXT:
		case CSCH_CTYPE_CONN:
		case CSCH_CTYPE_BITMAP:
		case CSCH_CTYPE_GRP:
		case CSCH_CTYPE_GRP_REF:
			/* no fill */
			break;

		case CSCH_CTYPE_PEN:
		case CSCH_CTYPE_invalid:
		case CSCH_CTYPE_max:
			/* no graphical representation */
			break;
	}

	return csch_RTREE_DIR_FOUND_CONT;
}


static void draw_layer_obj_fill(draw_ctx_t *ctx, csch_chdr_t *obj)
{
	int sel = csch_chdr_is_selected(obj);

	switch(obj->type) {
		case CSCH_CTYPE_POLY: draw_layer_poly_fill(ctx, (csch_cpoly_t *)obj, sel); break;

		case CSCH_CTYPE_ARC:
		case CSCH_CTYPE_LINE:
		case CSCH_CTYPE_TEXT:
		case CSCH_CTYPE_CONN:
		case CSCH_CTYPE_BITMAP:
		case CSCH_CTYPE_GRP:
		case CSCH_CTYPE_GRP_REF:
			/* no fill */
			break;

		case CSCH_CTYPE_PEN:
		case CSCH_CTYPE_invalid:
		case CSCH_CTYPE_max:
			/* no graphical representation */
			break;
	}
}


static csch_rtree_dir_t draw_layer_obj_stroke(void *ctx_, void *obj_, const csch_rtree_box_t *box)
{
	draw_ctx_t *ctx = (draw_ctx_t *)ctx_;
	csch_chdr_t *obj = obj_;
	csch_cpen_t *stroke = csch_stroke_fallback_(obj, ctx->xform == NULL ? NULL : ctx->xform->fallback_pen);
	int sel = csch_chdr_is_selected(obj);

	switch(obj->type) {
		case CSCH_CTYPE_LINE: draw_layer_line(ctx, (csch_line_t *)obj, stroke, sel); break;
		case CSCH_CTYPE_POLY: draw_layer_poly_stroke(ctx, (csch_cpoly_t *)obj, stroke, sel); break;
		case CSCH_CTYPE_ARC:  draw_layer_arc(ctx, (csch_arc_t *)obj, stroke, sel); break;
		case CSCH_CTYPE_TEXT: draw_layer_text(ctx, (csch_text_t *)obj, stroke, sel); break;
		case CSCH_CTYPE_CONN: draw_layer_conn(ctx, (csch_conn_t *)obj); break;
		case CSCH_CTYPE_BITMAP: /* not supported yet */ break;

		case CSCH_CTYPE_GRP:
		case CSCH_CTYPE_GRP_REF:
			/* no need to draw groups since we are working from rtree, but need to draw meta */
			draw_grp_meta(ctx, (csch_cgrp_t *)obj, sel);
			break;

		case CSCH_CTYPE_PEN:
		case CSCH_CTYPE_invalid:
		case CSCH_CTYPE_max:
			/* no graphical representation */
			break;
	}

	return csch_RTREE_DIR_FOUND_CONT;
}

static csch_rtree_dir_t draw_layer_obj_nojunc(void *ctx_, void *obj_, const csch_rtree_box_t *box)
{
	if (!csch_obj_is_junction(obj_))
		return draw_layer_obj_stroke(ctx_, obj_, box);
	return csch_RTREE_DIR_NOT_FOUND_CONT;
}

static csch_rtree_dir_t draw_layer_obj_junc(void *ctx_, void *obj_, const csch_rtree_box_t *box)
{
	if (csch_obj_is_junction(obj_))
		draw_layer_obj_stroke(ctx_, obj_, box);
	return csch_RTREE_DIR_NOT_FOUND_CONT;
}

static int fill_area_cmp(const void *a_, const void *b_)
{
	const csch_cpoly_t **a = (const csch_cpoly_t **)a_, **b = (const csch_cpoly_t **)b_;
	if ((*a)->area < (*b)->area) return 1;
	return -1;
}

RND_INLINE void draw_layer_fill(draw_ctx_t *ctx, csch_displayer_t layer, const csch_rtree_box_t *bbox)
{
	long n;

	if ((layer == CSCH_DSPLY_SYMBOL_META) || (layer == CSCH_DSPLY_TEXT_META))
		return;

	ctx->objlist.used = 0;

	csch_rtree_search_obj(&ctx->sheet->dsply_fill[layer], bbox, collect_layer_obj_fill, ctx);

	qsort(ctx->objlist.array, ctx->objlist.used, sizeof(void *), fill_area_cmp);
	for(n = 0; n < ctx->objlist.used; n++)
		draw_layer_obj_fill(ctx, ctx->objlist.array[n]);
}


RND_INLINE void draw_layer_stroke(draw_ctx_t *ctx, csch_displayer_t layer, const csch_rtree_box_t *bbox)
{
	long n;

	if ((layer == CSCH_DSPLY_SYMBOL_META) || (layer == CSCH_DSPLY_TEXT_META))
		return;

	ctx->objlist.used = 0;

	csch_rtree_search_obj(&ctx->sheet->dsply[layer], bbox, predraw_layer_obj, ctx);

	for(n = 0; n < ctx->objlist.used; n++) {
		csch_chdr_t *obj = ctx->objlist.array[n];
		sch_rnd_font_text_update(ctx->sheet, (csch_text_t *)obj, csch_stroke_fallback_(obj, ctx->xform == NULL ? NULL : ctx->xform->fallback_pen));
	}

	if (layer == CSCH_DSPLY_WIRE) {
		/* have to draw in two steps so junctions are always above wires */
		csch_rtree_search_obj(&ctx->sheet->dsply[layer], bbox, draw_layer_obj_nojunc, ctx);
		csch_rtree_search_obj(&ctx->sheet->dsply[layer], bbox, draw_layer_obj_junc, ctx);
	}
	else
		csch_rtree_search_obj(&ctx->sheet->dsply[layer], bbox, draw_layer_obj_stroke, ctx);
}

RND_INLINE void sch_rnd_draw_sheet_graphical(csch_sheet_t *sheet, rnd_hid_gc_t gc, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	int n;
	csch_rtree_box_t bb;
	csch_project_t *prj = (csch_project_t *)sheet->hidlib.project;
	draw_ctx_t ctx = {0};

	ctx.sheet = sheet;
	ctx.gc = gc;
	ctx.xform = xform_caller;
	if (prj != NULL)
		ctx.abst = prj->abst;

	bb.x1 = P2C(region->view.X1); bb.y1 = P2C(region->view.Y1);
	bb.x2 = P2C(region->view.X2); bb.y2 = P2C(region->view.Y2);

	for(n = 0; n < CSCH_DSPLY_max; n++) {
		int use_local = (xform_caller != NULL) && xform_caller->use_local_vis;
		int vis = use_local ? xform_caller->local_vis[n] : csch_layer_vis[n];
		if (vis)
			draw_layer_fill(&ctx, n, &bb);
	}

	for(n = 0; n < CSCH_DSPLY_max; n++) {
		int use_local = (xform_caller != NULL) && xform_caller->use_local_vis;
		int vis = use_local ? xform_caller->local_vis[n] : csch_layer_vis[n];
		if (vis)
			draw_layer_stroke(&ctx, n, &bb);
	}

	vtp0_uninit(&ctx.objlist);
}

RND_INLINE void sch_rnd_draw_sheet_non_graphical(csch_sheet_t *sheet, rnd_hid_gc_t gc, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	static csch_text_t t;
	static csch_cpen_t pen;
	static int inited = 0;
	draw_ctx_t ctx = {0};
	void *cookie = NULL;
	long cnt = 0;
	csch_coord_t y, step = 4000;
	char *line;

	if (!inited) {
		inited = 1;

		pen.size = 250;
		pen.color = conf_core.appearance.color.non_graphical_text;
		pen.font_height = 3000;
		pen.font_family = "sans";
		t.inst1.x = 0;
		t.inst1.y = 0;
	}

	ctx.sheet = sheet;
	ctx.gc = gc;
	ctx.xform = NULL;

	rnd_render->set_color(gc, sch_rnd_stroke_color(&ctx, &t.hdr, &pen, 0));

	if ((sheet->non_graphical_impl == NULL) || (sheet->non_graphical_impl->draw_getline == NULL)) {
		t.bbox_calced = 0;
		t.rtext = "non-graphical sheet.";
		sch_rnd_font_text_render(sheet, gc, &t, &pen);
		return;
	}

	y = sheet->bbox.y2 - step;
	while((line = sheet->non_graphical_impl->draw_getline(sheet, &cnt, &cookie)) != NULL) {
		t.bbox_calced = 0;
		t.inst1.y = y;
		t.rtext = line;
		sch_rnd_font_text_render(sheet, gc, &t, &pen);
		y -= step;
	}
}

void sch_rnd_draw_sheet(csch_sheet_t *sheet, rnd_hid_gc_t gc, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	if (sheet->non_graphical)
		sch_rnd_draw_sheet_non_graphical(sheet, gc, region, xform_caller);
	else
		sch_rnd_draw_sheet_graphical(sheet, gc, region, xform_caller);
}

rnd_box_t sch_rnd_last_main_expose_region;
void sch_rnd_expose_main(rnd_hid_t *hid, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	rnd_hid_gc_t gc;
	int want_layer;
	csch_sheet_t *sheet = (csch_sheet_t *)region->design;
	rnd_hid_t *save = rnd_render;

	sch_rnd_last_main_expose_region.X1 = region->view.X1;
	sch_rnd_last_main_expose_region.Y1 = region->view.Y1;
	sch_rnd_last_main_expose_region.X2 = region->view.X2;
	sch_rnd_last_main_expose_region.Y2 = region->view.Y2;

	if ((rnd_render != NULL) && (!rnd_render->override_render))
		rnd_render = hid;

	gc = rnd_render->make_gc(rnd_render);

	rnd_render->render_burst(rnd_render, RND_HID_BURST_START, &region->view);

	/* the HID needs a group set, even if we don't do groups */
	if (rnd_render->set_layer_group != NULL)
		want_layer = rnd_render->set_layer_group(rnd_render, region->design, 1, NULL, 0, 0, 0, 0, NULL);
	else
		want_layer = 0;

	if (want_layer) {
		rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, 1, &region->view);
		rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, 1, &region->view);

		sch_rnd_draw_sheet(sheet, gc, region, xform_caller);

		rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, 1, &region->view);
		rnd_render->end_layer(rnd_render);
	}
	rnd_render->render_burst(rnd_render, RND_HID_BURST_END, &region->view);

	rnd_render->destroy_gc(gc);
	rnd_render = save;
}


void sch_rnd_expose_preview(rnd_hid_t *hid, rnd_hid_expose_ctx_t *e)
{
	rnd_hid_gc_t gc = rnd_render->make_gc(rnd_render);

	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, 1, &e->view);
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, 1, &e->view);
	e->expose_cb(gc, e);
	rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, 1, &e->view);
	rnd_render->end_layer(rnd_render);

	rnd_render->destroy_gc(gc);
}

int sch_draw_setup_non_graphical(csch_sheet_t *sheet, long lines)
{
	sheet->bbox.x2 = 210*1000;
	sheet->bbox.y2 = lines * 4000;
	sheet->bbox_changed = 1;
	rnd_conf_set(RND_CFR_DESIGN, "appearance/color/background", -1, conf_core.appearance.color.non_graphical_background.str, RND_POL_OVERWRITE);
	rnd_conf_set(RND_CFR_DESIGN, "editor/draw_grid", -1, "0", RND_POL_OVERWRITE);
	rnd_event(&sheet->hidlib, CSCH_EVENT_SHEET_EDITED, NULL);
	return 0;
}

void sch_rnd_redraw(rnd_design_t *target)
{
	rnd_design_t *curr = rnd_multi_get_current();

	if ((target == NULL) || (target == curr))
		rnd_render->invalidate_all(rnd_render);
}

void sch_rnd_box_redraw_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	csch_rtree_box_t *box = argv[1].d.p;
	rnd_design_t *curr = rnd_multi_get_current();

	if (curr != hidlib)
		return;

	assert(argv[1].type == RND_EVARG_PTR);
	if (rnd_render->gui && (rnd_render->invalidate_lr != NULL)) {
		rnd_hid_notify_crosshair_change(hidlib, 0);
		rnd_render->invalidate_lr(rnd_render, C2P(box->x1), C2P(box->x2), C2P(box->y1), C2P(box->y2));
		rnd_hid_notify_crosshair_change(hidlib, 1);
	}

	/* rnd_trace("LR: %rc %rc - %rc %rc\n", box->x1, box->y1, box->x2, box->y2); */
}

void sch_rnd_draw_init2(void)
{
	rnd_event_bind(CSCH_EVENT_BOX_NEEDS_REDRAW, sch_rnd_box_redraw_ev, NULL, draw_cookie);
}

void sch_rnd_draw_uninit(void)
{
	draw_poly_uninit();
	rnd_event_unbind_allcookie(draw_cookie);
}
