/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019,2023,2024 Tibor 'Igor2' Palinkas
 *
 *  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/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include <librnd/hid/hid.h>
#include <librnd/hid/hid_inlines.h>
#include <librnd/core/xform_mx.h>
#include "obj_any.h"
#include "conf_core.h"
#include "draw.h"

static rnd_coord_t MIN4(rnd_coord_t a, rnd_coord_t b, rnd_coord_t c, rnd_coord_t d)
{
	rnd_coord_t min = a;
	if (b < min) min = b;
	if (c < min) min = c;
	if (d < min) min = d;
	return min;
}

static rnd_coord_t MAX4(rnd_coord_t a, rnd_coord_t b, rnd_coord_t c, rnd_coord_t d)
{
	rnd_coord_t max = a;
	if (b > max) max = b;
	if (c > max) max = c;
	if (d > max) max = d;
	return max;
}

void camv_draw_layer(camv_layer_t *ly, rnd_hid_gc_t gc, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	camv_rtree_it_t it;
	void *o;
	rnd_xform_mx_t *mx = NULL;
	camv_rtree_box_t vtmp, *view = (camv_rtree_box_t *)&region->view;

	if (ly->enable_mx) {
		rnd_coord_t x1, y1, x2, y2, x3, y3, x4, y4, sx, sy;
		mx = &ly->mx;
		ly->mx_rotdeg = rnd_xform_mx_extract_rot(*mx);
		rnd_xform_mx_extract_scale(*mx, &ly->mx_sx, &ly->mx_sy);
		x1 = rnd_xform_x((*mx), view->x1, view->y1);
		y1 = rnd_xform_y((*mx), view->x1, view->y1);
		x2 = rnd_xform_x((*mx), view->x2, view->y2);
		y2 = rnd_xform_y((*mx), view->x2, view->y2);
		x3 = rnd_xform_x((*mx), view->x1, view->y2);
		y3 = rnd_xform_y((*mx), view->x1, view->y2);
		x4 = rnd_xform_x((*mx), view->x2, view->y1);
		y4 = rnd_xform_y((*mx), view->x2, view->y1);

/*		rnd_trace("orig: %mm;%mm %mm;%mm\n", view->x1, view->y1, view->x1, view->y2);*/
		sx = (view->x2 - view->x1)*2; sy = (view->y2 - view->y1)*2;

		vtmp.x1 = MIN4(x1, x2, x3, x4) - sx; vtmp.y1 = MIN4(y1, y2, y3, y4) - sy;
		vtmp.x2 = MAX4(x1, x2, x3, x4) + sx; vtmp.y2 = MAX4(y1, y2, y3, y4) + sy;

		view = &vtmp;

/*		rnd_trace("      %mm;%mm %mm;%mm\n", vtmp.x1, vtmp.y1, vtmp.x2, vtmp.y2);*/
	}

	for(o = camv_rtree_first(&it, &ly->objs, view); o != NULL; o = camv_rtree_next(&it)) {
		camv_any_obj_t *obj = o;
		obj->proto.calls->draw(obj, gc, mx);
		if (conf_core.appearance.bbox_debug) {
			rnd_coord_t x1, y1, x2, y2;

			if (mx != NULL) {
				x1 = rnd_xform_x((*mx), obj->proto.bbox.x1, obj->proto.bbox.y1); y1 = rnd_xform_y((*mx), obj->proto.bbox.x1, obj->proto.bbox.y1);
				x2 = rnd_xform_x((*mx), obj->proto.bbox.x2, obj->proto.bbox.y2); y2 = rnd_xform_y((*mx), obj->proto.bbox.x2, obj->proto.bbox.y2);
			}
			else {
				x1 = obj->proto.bbox.x1; y1 = obj->proto.bbox.y1;
				x2 = obj->proto.bbox.x2; y2 = obj->proto.bbox.y2;
			}

			rnd_hid_set_line_cap(gc, rnd_cap_round);
			rnd_hid_set_line_width(gc, -1);
			rnd_render->draw_line(gc, x1, y1, x2, y1);
			rnd_render->draw_line(gc, x2, y1, x2, y2);
			rnd_render->draw_line(gc, x2, y2, x1, y2);
			rnd_render->draw_line(gc, x1, y2, x1, y1);
		}
	}
}

rnd_cardinal_t camv_draw_inhibit = 0;
rnd_box_t camv_last_main_expose_region;

void camv_expose_main(rnd_hid_t *hid, const rnd_hid_expose_ctx_t *region, rnd_xform_t *xform_caller)
{
	rnd_cardinal_t lid;
	rnd_hid_gc_t gc;
	int clearing = -1, direct = 1, need_flush = 0;
	rnd_hid_t *save = rnd_render;

	if (camv_draw_inhibit)
		return;

	camv_last_main_expose_region.X1 = region->view.X1;
	camv_last_main_expose_region.Y1 = region->view.Y1;
	camv_last_main_expose_region.X2 = region->view.X2;
	camv_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);

	/* calculate whether the cheaper direct-draw mechanism can be done */
	for(lid = 0; lid < camv.layers.used; lid++) {
		camv_layer_t *ly = camv.layers.array[lid];
		if (!ly->vis) continue;
		if (ly->clearing) {
			direct = 0;
			break;
		}
	}

	/* announce start of rendering */
	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) {
		camv_layer_t *ly = camv.layers.array[0];
		rnd_render->set_layer_group(rnd_render, &camv.hidlib, 0, ly->name, 0, 0, 0, 0, NULL);
	}

	for(lid = 0; lid < camv.layers.used; lid++) {
		camv_layer_t *ly = camv.layers.array[lid];
		if (!ly->vis) continue;


		if (!ly->sub) {
			if (need_flush) {
				rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, direct, &region->view);
				rnd_render->end_layer(rnd_render);
				if ((xform_caller != NULL) && xform_caller->announce_layers) {
					if (rnd_render->set_layer_group != NULL)
						rnd_render->set_layer_group(rnd_render, &camv.hidlib, lid, ly->name, 0, 0, 0, 0, NULL);
				}
			}
			rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_RESET, direct, &region->view);
			need_flush = 1;
		}

		if (((int)ly->clearing != clearing) || (!ly->sub)) {
			if (ly->prefill) {
				rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_POSITIVE, direct, &region->view);
				rnd_render->set_color(gc, &ly->color);
				rnd_render->fill_rect(gc, region->view.X1, region->view.Y1, region->view.X2, region->view.Y2);
			}
			clearing = ly->clearing;
			rnd_render->set_drawing_mode(rnd_render, ly->clearing ? RND_HID_COMP_NEGATIVE : RND_HID_COMP_POSITIVE, direct, &region->view);
		}
		rnd_render->set_color(gc, &ly->color);
		camv_draw_layer(ly, gc, region, xform_caller);
	}

	if (need_flush) {
		rnd_render->set_drawing_mode(rnd_render, RND_HID_COMP_FLUSH, direct, &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 camv_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->render_burst(rnd_render, RND_HID_BURST_END, &e->view);

	rnd_render->destroy_gc(gc);
}
