/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - generic graph plotting
 *  Copyright (C) 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., 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 "plot_preview.h"

#include <librnd/core/color.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/hid_inlines.h>

#include <sch-rnd/font.h>

/* low level transformations for librnd; can't be 1 because of text rendering */
#define PL2PX(crd) ((crd)*4)
#define PX2PL(crd) ((crd)/4)

/* text height passed down to the text renderer */
#define TEXT_HGHT (plt->e->coord_per_pix < 1 ? (1.0/80000.0) : ((double)plt->e->coord_per_pix/80000.0))

/* when two text objects are closer than this on the axes, don't draw one of them */
#define TEXT_SPACING (20 * plt->e->coord_per_pix)

/* high level transformation: convert raw trace data to graphical plot data */
RND_INLINE double data2plot_y(plot_preview_t *ctx, double y_raw)
{
	if (ctx->zoom_y != 0)
		y_raw = y_raw * ctx->zoom_y;
	return y_raw;
}

RND_INLINE double data2plot_x(plot_preview_t *ctx, double x_raw)
{
	switch(ctx->type_x) {
		case PLAXTY_LINEAR: break;
		case PLAXTY_OCTAVE:
		case PLAXTY_DECADE: x_raw = x_raw * 5; break; /* stretch the graph a bit because there won't be too many points anyway */
	}
	return x_raw;
}

RND_INLINE double plot2data_x(plot_preview_t *ctx, double x_raw)
{
	switch(ctx->type_x) {
		case PLAXTY_LINEAR: break;
		case PLAXTY_OCTAVE:
		case PLAXTY_DECADE: x_raw = x_raw / 5; break; /* stretch the graph a bit because there won't be too many points anyway */
	}
	return x_raw;
}

#define F 255U
#define T 191U
#define H 127U
#define Q 64U
static rnd_color_t trace_colors[] = {
	{F, 0, 0}, {0, T, 0}, {0, 0, F},
	{F, H, H}, {Q, T, Q}, {Q, H, T},
	{H, H, H}, {H, Q, T}, {F, T, 0}
};
static int num_trace_colors = 0;
#undef F
#undef T
#undef H
#undef Q


static void plot_preview_color_init(void)
{
	int n;

	num_trace_colors = sizeof(trace_colors) / sizeof(trace_colors[0]);

	for(n = 0; n < num_trace_colors; n++)
		rnd_color_load_int(&trace_colors[n], trace_colors[n].r, trace_colors[n].g, trace_colors[n].b, 255);
}

typedef struct plot_info_s {
	rnd_hid_gc_t gc;
	void *font;
	rnd_hid_expose_ctx_t *e;
	const rnd_color_t *grid_color;

	double miny, maxy; /* untransformed y bounds, extended to 0 as needed, margin added as needed */
	double my;         /* y margin (per side); already added to miny and maxy */
	double laby, labdy;/* labels at the right side end of the x axis: current y coord and y increment */
	double labx;       /* labels at the right side end of the x axis: x start coord */
} plot_info_t;

static void plot_draw_trace(plot_preview_t *ctx, plot_info_t *plt, int tridx, plot_trace_t *tr, int level)
{
	double buff[1024];
	long n, x, lastx = -1, trx;
	double lasty;
	plot_trdata_t *td;
	plot_pos_t pos;
	long wsx = plot2data_x(ctx, PX2PL(plt->e->view.X2 - plt->e->view.X1))+5;
	long wox = plot2data_x(ctx, PX2PL(plt->e->view.X1));

	td = plot_trdata_get(tr, level, 1);
	trx = (wox < 0 ? 0 : wox);
	if (plot_read_init(&pos, tr, td, PLOT_MAIN, trx, wsx, buff, sizeof(buff) / sizeof(buff[0])) != 0)
		return;

	rnd_render->set_color(plt->gc, &trace_colors[tridx % num_trace_colors]);

	x = wox, n = 0;
	if (x < 0) {
		x -= wox;
		n -= wox;
	}

	for(; (n < wsx) && (trx < td->main.len); x++,n++,trx++) {
		double px, py, y_raw;
		if (plot_read(&pos, &y_raw) == 0) {
			py = data2plot_y(ctx, y_raw);
			px = data2plot_x(ctx, x);
			if (lastx >= 0)
				rnd_render->draw_line(plt->gc, PL2PX(lastx), PL2PX(lasty), PL2PX(px), PL2PX(py));
			lastx = px;
			lasty = py;
		}
	}

	if ((ctx->pdata.trace_name != NULL) && (ctx->pdata.trace_name[tridx] != 0)) {
		sch_rnd_render_text_string_scrotmir(plt->gc, plt->font, PL2PX(plt->labx), PL2PX(plt->laby), TEXT_HGHT, 0, 1, (const unsigned char *)ctx->pdata.trace_name[tridx]);
		plt->laby += plt->labdy;
	}

TODO("LIBRND41: when we get zoom and calculated level != 0, draw a polygon for min-max");
}

/* rnd_printf won't do %.03f, zero truncation works only on %m formats */
static void snprintf_trunc0(char *dst, int maxlen, const char *fmt, double val)
{
	int len = rnd_snprintf(dst, maxlen, fmt, val);
	if (len > 1) {
		len--;
		while((len > 0) && (dst[len] == '0')) {
			dst[len] = '\0';
			len--;
		}
		if (dst[len] == '.') {
			dst[len] = '\0';
			len--;
		}
	}
}


void plot_draw_marks_y(plot_preview_t *ctx, plot_info_t *plt)
{
	long n;
	double maxx, tx, ty, last_ty = -CSCH_COORD_MAX;

	maxx = data2plot_x(ctx, ctx->maxx * 1.1);

	rnd_render->set_color(plt->gc, plt->grid_color);

	for(n = 0; n < ctx->pdata.num_y_labels; n++) {
		double y = data2plot_y(ctx, ctx->pdata.y_labels[n].plot_val);
		rnd_render->draw_line(plt->gc, PL2PX(0), PL2PX(y), PL2PX(maxx), PL2PX(y));
	}

	tx = -TEXT_HGHT * 3000000;

	rnd_render->set_color(plt->gc, rnd_color_black);
	for(n = 0; n < ctx->pdata.num_y_labels; n++) {
		char tmp[256];
		double y = data2plot_y(ctx, ctx->pdata.y_labels[n].plot_val);

		ty = PL2PX(y+4);
		if ((ty - last_ty) < TEXT_SPACING)
			continue;

		snprintf_trunc0(tmp, sizeof(tmp), "%.9f", ctx->pdata.y_labels[n].print_val);
		sch_rnd_render_text_string_scrotmir(plt->gc, plt->font, tx, ty, TEXT_HGHT, 0, 1, (const unsigned char *)tmp);
		last_ty = ty;
	}
	rnd_hid_set_line_width(plt->gc, 1);
}

void plot_draw_marks_x(plot_preview_t *ctx, plot_info_t *plt)
{
	long n;
	double tx, last_tx = -CSCH_COORD_MAX, ty, miny, maxy;

	miny = PL2PX(data2plot_y(ctx, plt->miny));
	maxy = PL2PX(data2plot_y(ctx, plt->maxy));

	rnd_render->set_color(plt->gc, plt->grid_color);

	for(n = 0; n < ctx->pdata.num_x_labels; n++) {
		double x = PL2PX(data2plot_x(ctx, ctx->pdata.x_labels[n].plot_val));
		rnd_render->draw_line(plt->gc, x, miny, x, maxy);
	}


	ty = ctx->maxy < 0 ? 3 : -40;
	rnd_render->set_color(plt->gc, rnd_color_black);

	for(n = 0; n < ctx->pdata.num_x_labels; n++) {
		char tmp[256];
		double x = data2plot_x(ctx, ctx->pdata.x_labels[n].plot_val);

		tx = PL2PX(x-4);
		if ((tx - last_tx) < TEXT_SPACING)
			continue;

		snprintf_trunc0(tmp, sizeof(tmp), "%.9f", ctx->pdata.x_labels[n].print_val);
		sch_rnd_render_text_string_scrotmir(plt->gc, plt->font, tx, PL2PX(ty), TEXT_HGHT, 90, 1, (const unsigned char *)tmp);
		last_tx = tx;
	}
	rnd_hid_set_line_width(plt->gc, 1);
}


void plot_draw_axes(plot_preview_t *ctx, plot_info_t *plt)
{
	long maxx = ctx->maxx * 1.1;
	double ylaby, arl;

	/* arrow length */
	arl = 2.0 * plt->e->coord_per_pix;
	if (arl > 8)
		arl = 8;

	if (ctx->maxy > 0)
		ylaby = ctx->maxy;
	else
		ylaby = ctx->miny;

	rnd_render->set_color(plt->gc, rnd_color_black);

	rnd_render->draw_line(plt->gc, PL2PX(0), PL2PX(0), PL2PX(data2plot_x(ctx, maxx)), PL2PX(0));
	rnd_render->draw_line(plt->gc, PL2PX(0), PL2PX(data2plot_y(ctx, plt->miny)), PL2PX(0), PL2PX(data2plot_y(ctx, plt->maxy)));

	/* arrow on x */
	rnd_render->draw_line(plt->gc, PL2PX(data2plot_x(ctx, maxx)), PL2PX(data2plot_y(ctx, plt->maxy)-arl), PL2PX(data2plot_x(ctx, maxx)+arl), PL2PX(data2plot_y(ctx, plt->maxy)));
	rnd_render->draw_line(plt->gc, PL2PX(data2plot_x(ctx, maxx)), PL2PX(data2plot_y(ctx, plt->maxy)+arl), PL2PX(data2plot_x(ctx, maxx)+arl), PL2PX(data2plot_y(ctx, plt->maxy)));
	rnd_render->draw_line(plt->gc, PL2PX(data2plot_x(ctx, maxx)), PL2PX(data2plot_y(ctx, plt->maxy)+arl), PL2PX(data2plot_x(ctx, maxx)), PL2PX(data2plot_y(ctx, plt->maxy)-arl));

	if (ctx->pdata.x_axis_name != NULL)
		sch_rnd_render_text_string_scrotmir(plt->gc, plt->font, PL2PX(plt->labx), PL2PX(plt->laby), TEXT_HGHT, 0, 1, (const unsigned char *)ctx->pdata.x_axis_name);

	if (ctx->pdata.y_axis_name != NULL) {
		sch_rnd_render_text_string_scrotmir(plt->gc, plt->font, PL2PX(0), PL2PX(data2plot_y(ctx, ylaby)), TEXT_HGHT, 90, 1, (const unsigned char *)ctx->pdata.y_axis_name);
		plt->laby += plt->labdy;
	}
}

void plot_preview_expose_init(plot_preview_t *ctx, rnd_hid_attribute_t *attrib)
{
	plot_pos_t pos;
	double buff[1024];
	fgw_arg_t args[2];
	int i, n;

	ctx->miny = +10000000000000;
	ctx->maxy = -10000000000000;

	for(i = 0; i < ctx->pdata.num_traces; i++) {
		plot_trace_t *tr = &ctx->pdata.trace[i];
		plot_trdata_t *td = plot_trdata_get(tr, 0, 0);

		if (plot_read_init(&pos, tr, td, PLOT_MAIN, 0, 0, buff, sizeof(buff) / sizeof(buff[0])) == 0) {
			for(n = 0; n < td->main.len; n++) {
				double y;
				if (plot_read(&pos, &y) == 0) {
					if (y < ctx->miny) ctx->miny = y;
					if (y > ctx->maxy) ctx->maxy = y;
				}
			}
		}
	}

	ctx->inited = 1;

	args[0].type = FGW_STR; args[0].val.cstr = "min_zoom";
	args[1].type = FGW_INT; args[1].val.nat_int = 1;
	rnd_gui->attr_dlg_widget_poke(ctx->hid_ctx, ctx->widget_idx, 2, args);

	args[0].type = FGW_STR; args[0].val.cstr = "yflip";
	args[1].type = FGW_INT; args[1].val.nat_int = 1;
	rnd_gui->attr_dlg_widget_poke(ctx->hid_ctx, ctx->widget_idx, 2, args);

	{ /* set initial zoom for the flip settings */
		long mx = (ctx->maxx)/20;
		double my = (ctx->maxy - ctx->miny) / 20;
		plot_zoomto(attrib, ctx, 0-mx, ctx->miny-my, ctx->maxx+mx, ctx->maxy+my);
	}
}

RND_INLINE void plt_init(plot_preview_t *ctx, plot_info_t *plt)
{
	static rnd_color_t grid_color_tmp;
	static int grid_color_tmp_inited = 0;
	static void *font_cache = NULL;


	/* init y coords */
	plt->miny = ctx->miny;
	plt->maxy = ctx->maxy;
	plt->my = (ctx->maxy - ctx->miny) / 20.0;

	if (plt->miny > 0)
		plt->miny = 0;
	else
		plt->miny -= plt->my;

	if (plt->maxy < 0)
		plt->maxy = 0;
	else
		plt->maxy += plt->my;

	if (plt->maxy < 0) {
		plt->laby = +8;
		plt->labdy = +8;
	}
	else {
		plt->laby = -4;
		plt->labdy = -8;
	}

	plt->labdy *= plt->e->coord_per_pix;
	plt->labx = data2plot_x(ctx, ctx->maxx + 6);

	/* init colors */
	if (num_trace_colors == 0)
		plot_preview_color_init();

	if (ctx->grid_color == NULL) {
		if (!grid_color_tmp_inited) {
			rnd_color_load_str(&grid_color_tmp, "#AAAAAA");
			grid_color_tmp_inited = 1;
		}
		plt->grid_color = &grid_color_tmp;
	}
	else
		plt->grid_color = ctx->grid_color;

	/* init font */
	if (font_cache == NULL)
		font_cache = sch_rnd_font_lookup("sans", "");
	plt->font = font_cache;
}

void plot_preview_expose_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
	int n;
	plot_preview_t *ctx = prv->user_ctx;
	plot_info_t plt;
	int level = /*floor(PX2PL(e->coord_per_px));*/ 0;
	TODO("LIBRND41 e->coords_per_px should be part of e, and level should be calculated with a logarithm");

	if (!ctx->inited) {
		plot_preview_expose_init(ctx, attrib);
		return; /* plot_zoomto() in the above call already triggered a draw */
	}

	plt.gc = gc;
	plt.e = e;
	plt_init(ctx, &plt);

	rnd_hid_set_line_cap(gc, rnd_cap_round);
	rnd_hid_set_line_width(gc, 1);

	plot_draw_marks_y(ctx, &plt);
	plot_draw_marks_x(ctx, &plt);
	plot_draw_axes(ctx, &plt);

	for(n = 0; n < ctx->pdata.num_traces; n++)
		plot_draw_trace(ctx, &plt, n, &ctx->pdata.trace[n], level);
}


rnd_bool plot_mouse_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_mouse_ev_t kind, rnd_coord_t x, rnd_coord_t y)
{
	int n;
	plot_pos_t pos;
	double buff[1];
	plot_preview_t *ctx = prv->user_ctx;


	if ((x < 0) || (kind != RND_HID_MOUSE_PRESS) || (ctx->readout_cb == NULL))
		goto skip;

	x = rnd_round(plot2data_x(ctx, PX2PL(x)));

	if (ctx->readout_begin_cb != NULL)
		ctx->readout_begin_cb(ctx, x);

	for(n = 0; n < ctx->pdata.num_traces; n++) {
		plot_trace_t *tr = &ctx->pdata.trace[n];
		plot_trdata_t *td = plot_trdata_get(tr, 0, 0);

		if (plot_read_init(&pos, tr, td, PLOT_MAIN, x, 2, buff, sizeof(buff) / sizeof(buff[0])) == 0) {
			double y;
			if (plot_read(&pos, &y) == 0)
				ctx->readout_cb(ctx, n, x, y);
		}
	}

	if (ctx->readout_end_cb != NULL)
		ctx->readout_end_cb(ctx, x);


	skip:;
	return 0; /* no redraw is needed */
}

void plot_zoomto(rnd_hid_attribute_t *attrib, plot_preview_t *ctx, double x1, double y1, double x2, double y2)
{
	rnd_box_t view;

	view.X1 = PL2PX(data2plot_x(ctx, x1));
	view.X2 = PL2PX(data2plot_x(ctx, x2));
	view.Y2 = PL2PX(data2plot_y(ctx, y1));
	view.Y1 = PL2PX(data2plot_y(ctx, y2));

	rnd_dad_preview_zoomto(attrib, &view);
}

void plot_redraw(rnd_hid_attribute_t *attrib)
{
	rnd_dad_preview_zoomto(attrib, NULL);
}

