/*
 *                            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
 */

#ifndef RND_INLINE
#include <librnd/core/config.h>
#endif

#include <stdio.h>
#include <genht/htip.h>

typedef struct plot_raw_s {
	fpos_t start_offs;        /* in bytes, from the start of the cache file */
	long len;                 /* in number of doubles */
} plot_raw_t;

typedef enum plot_which_e {
	PLOT_MAIN, PLOT_MIN, PLOT_MAX
} plot_which_t;

typedef struct plot_trdata_s {
	long level;           /* 0 means original input; else zommed out, number of data is len/(1<<level), keeping one main:min:max for each 'level' points */
	plot_raw_t main;      /* input data (on level 0) or average (on other levels) */
	plot_raw_t min, max;  /* minimum/maximum values around the main data when level > 0 */
} plot_trdata_t;

typedef enum plot_axis_type_e {
	PLAXTY_LINEAR,
	PLAXTY_DECADE, /* logarithmic, 10 tick per div, 10, 100, 1000 */
	PLAXTY_OCTAVE  /* logarithmic, 8 tick per div */
} plot_axis_type_t;

typedef struct plot_trace_s {
	FILE *f;       /* cache; binary file with doubles */
	htip_t trdata; /* key is level, data is (plot_trdata_t *) */

	/* user data: used and specified by the caller */
	plot_axis_type_t type_y;
	double zoom_y; /* per trace y zoom factor */
} plot_trace_t;

/* labels on an axis */
typedef struct plot_alabel_s {
	double plot_val;
	double print_val; /* not printed if -> text != NULL */
	char *text;      /* may be NULL */
} plot_alabel_t;

/* A plot is a collection of traces drawn on the same diagram */
typedef struct plot_data_s {
	int num_traces;
	plot_trace_t *trace;
	char **trace_name;

	long num_x_labels, num_y_labels;
	plot_alabel_t *x_labels, *y_labels;

	char *x_axis_name, *y_axis_name;
} plot_data_t;

/* _alloc: Allocate trace data for num_pts points and store trdata in trace's
   for level. If level > 0 also allocates min/max within trdata.
   _free: Frees all memory used by the trace data (typically no need to call;
   called from plot_data_free()) */
plot_trdata_t *plot_trdata_alloc(plot_trace_t *trace, long level, long num_pts);
void plot_trdata_free(plot_trdata_t *trdata);

/* _init: Initialize trace memory, remember user provided cache file for the trace
   _uninit: Frees all memory used by the trace (typically no need to call;
   called from plot_data_free()) */
void plot_trace_init(plot_trace_t *tr, FILE *f);
void plot_trace_uninit(plot_trace_t *trace);

/* _alloc: allocate plot data for a number of traces; each ->trace[]
   needs to be initialized using plot_trace_init()
   _free: free all traces of the plot */
plot_data_t *plot_data_alloc(int num_traces);
void plot_data_free(plot_data_t *data);

void plot_data_init(plot_data_t *pd, int num_traces);
void plot_data_uninit(plot_data_t *pd);


/* Unbuffered read/write an array of data from/into a specific
   trace's main/min/max storage. Return 0 on success. */
int plot_trdata_set_arr(plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, const double *data, long from, long len);
int plot_trdata_get_arr(plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, double *data, long from, long len);

/* Return the trace data for a trace:level. If trace data does not exist
   on the given level: if alloc_gen is 1, allocate and generate it (from
   level 0) automatically, else return NULL. */
plot_trdata_t *plot_trdata_get(plot_trace_t *trace, long level, int alloc_gen);

/* Generate and insert a given trace:level from level 0. Normally no need
   to call this direclty: plot_trdata_get() with alloc_gen==1 handles this. */
plot_trdata_t *plot_trdata_generate(plot_trace_t *trace, long level);


/*** Buffered sequential read/write ***/

/* Buffer data in user provided memory to batch reads/writes (with their
   associated seek) */
typedef struct plot_pos_s {
	plot_trace_t *tr;
	plot_raw_t *raw;
	long idx, remaining;
	double *buffer;
	long bufflen, buffi;
} plot_pos_t;


/* Prepare for read at most "len" points from trace starting at index "from";
   if len is 0, read the whole trace. Return 0 on success */
RND_INLINE int plot_read_init(plot_pos_t *pos, plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, long from, long len, double *buff, long bufflen);

/* Read next data into dst. Return 0 on success, -1 on error or eof */
RND_INLINE int plot_read(plot_pos_t *pos, double *dst);


/* Prepare for write at most "len" points from trace starting at index "from";
   if len is 0, write the whole trace. Return 0 on success */
RND_INLINE int plot_write_init(plot_pos_t *pos, plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, long from, long len, double *buff, long bufflen);

/* Write next data from src. Return 0 on success, -1 on error or eof */
RND_INLINE int plot_write(plot_pos_t *pos, double src);

/* Write out pending data from the pos buffer */
RND_INLINE void plot_flush(plot_pos_t *pos);


/*** internal and implementation ***/
void plot_raw_seek(plot_raw_t *raw, FILE *f, long idx);

RND_INLINE int plot_rw_init(plot_pos_t *pos, plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, long from, long len, double *buff, long bufflen)
{
	pos->tr = tr;

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

	pos->remaining = ((len == 0) ? trdata->main.len : len);
	pos->idx = from;
	if (from >= pos->raw->len)
		return -1;

	pos->buffer = buff;
	pos->bufflen = bufflen;
	return 0;
}

RND_INLINE int plot_read_init(plot_pos_t *pos, plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, long from, long len, double *buff, long bufflen)
{
	int res = plot_rw_init(pos, tr, trdata, which, from, len, buff, bufflen);
	pos->buffi = bufflen;
	return res;
}

RND_INLINE int plot_write_init(plot_pos_t *pos, plot_trace_t *tr, plot_trdata_t *trdata, plot_which_t which, long from, long len, double *buff, long bufflen)
{
	int res = plot_rw_init(pos, tr, trdata, which, from, len, buff, bufflen);
	pos->buffi = 0;
	return res;
}


RND_INLINE int plot_read(plot_pos_t *pos, double *dst)
{
	/* end of query */
	if (pos->remaining <= 0)
		return -1;

	/* read next batch if needed */
	if (pos->buffi >= pos->bufflen) {
		long fill_len = pos->remaining < pos->bufflen ? pos->remaining : pos->bufflen;
		plot_raw_seek(pos->raw, pos->tr->f, pos->idx);
		if (fread(pos->buffer, sizeof(double), fill_len, pos->tr->f) < 1)
			return -1;
		pos->buffi = 0;
	}

	/* serve next data from the buffer */
	*dst = pos->buffer[pos->buffi];
	pos->buffi++;
	pos->idx++;
	pos->remaining--;
	return 0;
}


RND_INLINE void plot_flush(plot_pos_t *pos)
{
	long fill_len = pos->buffi;
	if (fill_len <= 0)
		return;
	plot_raw_seek(pos->raw, pos->tr->f, pos->idx - fill_len);
	fwrite(pos->buffer, sizeof(double), fill_len, pos->tr->f);
	pos->buffi = 0;
}

RND_INLINE int plot_write(plot_pos_t *pos, double src)
{
	/* end of query */
	if (pos->remaining <= 0)
		return -1;

	/* write next batch if needed */
	if (pos->buffi >= pos->bufflen)
		plot_flush(pos);

	/* serve next data from the buffer */
	pos->buffer[pos->buffi] = src;
	pos->buffi++;
	pos->idx++;
	pos->remaining--;
	return 0;
}
