/*
 *                            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 <stdlib.h>
#include <genht/hash.h>
#include <genht/ht_utils.h>
#include "plot_data.h"

static void raw_alloc(plot_raw_t *dst, FILE *f, long num_pts)
{
	double d = 0;

	fseek(f, 0, SEEK_END);
	fgetpos(f, &dst->start_offs);
	fseek(f, (num_pts-1) * sizeof(double), SEEK_CUR);
	fwrite(&d, sizeof(d), 1, f);

	dst->len = num_pts;
}

void plot_raw_seek(plot_raw_t *raw, FILE *f, long idx)
{
	fsetpos(f, &raw->start_offs);
	if (idx > 0)
		fseek(f, idx * sizeof(double), SEEK_CUR);
}


plot_trdata_t *plot_trdata_alloc(plot_trace_t *trace, long level, long num_pts)
{
	plot_trdata_t *td = malloc(sizeof(plot_trdata_t));

	td->level = level;
	raw_alloc(&td->main, trace->f, num_pts);
	if (level > 0) {
		raw_alloc(&td->min, trace->f, num_pts);
		raw_alloc(&td->max, trace->f, num_pts);
	}
	else
		td->min.len = td->max.len = 0;

	htip_set(&trace->trdata, level, td);
	return td;
}

void plot_trace_init(plot_trace_t *tr, FILE *f)
{
	tr->f = f;
	htip_init(&tr->trdata, longhash, longkeyeq);
}

void plot_data_init(plot_data_t *pd, int num_traces)
{
	pd->num_traces = num_traces;
	pd->trace = calloc(sizeof(plot_trace_t), num_traces);
}

plot_data_t *plot_data_alloc(int num_traces)
{
	plot_data_t *pd = malloc(sizeof(plot_data_t));
	plot_data_init(pd, num_traces);
	return pd;
}

void plot_trdata_free(plot_trdata_t *td)
{
	free(td);
}

void plot_trace_uninit(plot_trace_t *tr)
{
	genht_uninit_deep(htip, &tr->trdata, {
		plot_trdata_free(htent->value);
	});
}

void plot_data_uninit(plot_data_t *pd)
{
	int n;
	for(n = 0; n < pd->num_traces; n++) {
		plot_trace_uninit(&pd->trace[n]);
		if (pd->trace_name != NULL)
			free(pd->trace_name[n]);
	}
	free(pd->trace);
	pd->trace = NULL;

	free(pd->trace_name);
	pd->trace_name = NULL;

	if (pd->x_labels != NULL) {
		for(n = 0; n < pd->num_x_labels; n++)
			free(pd->x_labels[n].text);
		free(pd->x_labels);
		pd->x_labels = NULL;
	}

	if (pd->y_labels != NULL) {
		for(n = 0; n < pd->num_y_labels; n++)
			free(pd->y_labels[n].text);
		free(pd->y_labels);
		pd->y_labels = NULL;
	}

	free(pd->x_axis_name);
	free(pd->y_axis_name);
	pd->x_axis_name = pd->y_axis_name = NULL;
}

void plot_data_free(plot_data_t *pd)
{
	plot_data_uninit(pd);
	free(pd);
}

int plot_trdata_set_arr(plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, const double *data, long from, long len)
{
	plot_raw_t *raw;

	switch(which) {
		case PLOT_MAIN: raw = &trdata->main; break;
		case PLOT_MIN:  raw = &trdata->min; break;
		case PLOT_MAX:  raw = &trdata->max; break;
		default:        return -1;
	}

	if (from+len > raw->len)
		return -1;
	plot_raw_seek(raw, tr->f, from);
	fwrite(data, sizeof(double), len, tr->f);
	return 0;
}

int plot_trdata_get_arr(plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, double *data, long from, long len)
{
	plot_raw_t *raw;

	switch(which) {
		case PLOT_MAIN: raw = &trdata->main; break;
		case PLOT_MIN:  raw = &trdata->min; break;
		case PLOT_MAX:  raw = &trdata->max; break;
		default:        return -1;
	}

	if (from+len > raw->len)
		return -1;
	plot_raw_seek(raw, tr->f, from);
	if (fread(data, sizeof(double), len, tr->f) < 1)
		return -1;
	return 0;
}

plot_trdata_t *plot_trdata_get(plot_trace_t *trace, long level, int alloc_gen)
{
	plot_trdata_t *td = htip_get(&trace->trdata, level);

	if ((level == 0) || (td != NULL) || !alloc_gen)
		return td;

	/* allocate and generate zoom level */
	return plot_trdata_generate(trace, level);
}

#define BUFSIZE 1024

plot_trdata_t *plot_trdata_generate(plot_trace_t *trace, long level)
{
	plot_trdata_t *src, *dst;
	long num_pts, runlen, got, n;
	double br[BUFSIZE], bw[BUFSIZE], bmin[BUFSIZE], bmax[BUFSIZE], min, max, avg, pt;
	plot_pos_t pr, pwmain, pwmin, pwmax;

	src = plot_trdata_get(trace, 0, 0);
	if (src == NULL)
		return NULL;

	if (plot_read_init(&pr, trace, src, PLOT_MAIN, 0, 0, br, sizeof(br)/sizeof(br[0])) != 0)
		return NULL;

	runlen = (1 << level);
	num_pts = src->main.len / runlen + 1;
	dst = plot_trdata_alloc(trace, level, num_pts);

	if (plot_write_init(&pwmain, trace, dst, PLOT_MAIN, 0, 0, bw, sizeof(bw)/sizeof(bw[0])) != 0)
		return NULL;
	if (plot_write_init(&pwmin, trace, dst, PLOT_MIN, 0, 0, bmin, sizeof(bmin)/sizeof(bmin[0])) != 0)
		return NULL;
	if (plot_write_init(&pwmax, trace, dst, PLOT_MAX, 0, 0, bmax, sizeof(bmax)/sizeof(bmax[0])) != 0)
		return NULL;

	n = 0;
	while(plot_read(&pr, &pt) == 0) {
		if ((n % runlen) == 0) {
			if (n > 0) {
				plot_write(&pwmain, avg / got);
				plot_write(&pwmin, min);
				plot_write(&pwmax, max);
			}
			avg = min = max = pt;
			got = 1;
		}
		else {
			avg += pt;
			if (pt < min) min = pt;
			if (pt > max) max = pt;
			got++;
		}
		n++;
	}

	/* The last block may be partial, write it if there's any pending data */
	if (got > 0) {
		plot_write(&pwmain, avg / got);
		plot_write(&pwmin, min);
		plot_write(&pwmax, max);
	}

	/* flush output buffers */
	plot_flush(&pwmain);
	plot_flush(&pwmin);
	plot_flush(&pwmax);

	return dst;
}


