#include "lib/mlrutil.h"
#include "lib/string_builder.h"
#include "containers/lrec.h"
#include "lib/string_array.h"
#include "containers/mixutil.h"
#include "mapping/mappers.h"
#include "cli/argparse.h"

typedef struct _mapper_bar_state_t {
	ap_state_t* pargp;
	string_array_t*  pfield_names;
	char    fill_char;
	char    oob_char;
	char    blank_char;
	double  lo;
	double  hi;
	int     width;
	char**  bars;
	sllv_t* precords; // only for auto mode
} mapper_bar_state_t;

static void      mapper_bar_usage(FILE* o, char* argv0, char* verb);
static mapper_t* mapper_bar_parse_cli(int* pargi, int argc, char** argv,
	cli_reader_opts_t* _, cli_writer_opts_t* __);
static mapper_t* mapper_bar_alloc(ap_state_t* pargp, string_array_t* pfield_names,
	char fill_char, char oob_char, char blank_char, double lo, double hi,
	int width, int do_auto);
static void      mapper_bar_free(mapper_t* pmapper, context_t* _);
static sllv_t*   mapper_bar_process_no_auto(lrec_t* pinrec, context_t* pctx, void* pvstate);
static sllv_t*   mapper_bar_process_auto(lrec_t* pinrec, context_t* pctx, void* pvstate);

#define DEFAULT_FILL_CHAR  '*'
#define DEFAULT_OOB_CHAR   '#'
#define DEFAULT_BLANK_CHAR '.'
#define DEFAULT_LO         0.0
#define DEFAULT_HI         100.0
#define DEFAULT_WIDTH      40

#define SB_ALLOC_LENGTH    128

// ----------------------------------------------------------------
mapper_setup_t mapper_bar_setup = {
	.verb = "bar",
	.pusage_func = mapper_bar_usage,
	.pparse_func = mapper_bar_parse_cli,
	.ignores_input = FALSE,
};

// ----------------------------------------------------------------
static void mapper_bar_usage(FILE* o, char* argv0, char* verb) {
	fprintf(o, "Usage: %s %s [options]\n", argv0, verb);
	fprintf(o, "Replaces a numeric field with a number of asterisks, allowing for cheesy\n");
	fprintf(o, "bar plots. These align best with --opprint or --oxtab output format.\n");
	fprintf(o, "Options:\n");
	fprintf(o, "-f   {a,b,c}      Field names to convert to bars.\n");
	fprintf(o, "-c   {character}  Fill character: default '%c'.\n", DEFAULT_FILL_CHAR);
	fprintf(o, "-x   {character}  Out-of-bounds character: default '%c'.\n", DEFAULT_OOB_CHAR);
	fprintf(o, "-b   {character}  Blank character: default '%c'.\n", DEFAULT_BLANK_CHAR);
	fprintf(o, "--lo {lo}         Lower-limit value for min-width bar: default '%lf'.\n", DEFAULT_LO);
	fprintf(o, "--hi {hi}         Upper-limit value for max-width bar: default '%lf'.\n", DEFAULT_HI);
	fprintf(o, "-w   {n}          Bar-field width: default '%d'.\n", DEFAULT_WIDTH);
	fprintf(o, "--auto            Automatically computes limits, ignoring --lo and --hi.\n");
	fprintf(o, "                  Holds all records in memory before producing any output.\n");
}

// ----------------------------------------------------------------
static mapper_t* mapper_bar_parse_cli(int* pargi, int argc, char** argv,
	cli_reader_opts_t* _, cli_writer_opts_t* __)
{
	string_array_t*  pfield_names = NULL;
	char*  fill_string  = NULL;
	char*  oob_string   = NULL;
	char*  blank_string = NULL;
	double lo           = DEFAULT_LO;
	double hi           = DEFAULT_HI;
	int    width        = DEFAULT_WIDTH;
	int    do_auto      = FALSE;

	char* verb = argv[(*pargi)++];

	ap_state_t* pstate = ap_alloc();
	ap_define_string_array_flag(pstate, "-f",     &pfield_names);
	ap_define_string_flag(pstate,       "-c",     &fill_string);
	ap_define_string_flag(pstate,       "-x",     &oob_string);
	ap_define_string_flag(pstate,       "-b",     &blank_string);
	ap_define_float_flag(pstate,        "--lo",   &lo);
	ap_define_float_flag(pstate,        "--hi",   &hi);
	ap_define_int_flag(pstate,          "-w",     &width);
	ap_define_true_flag(pstate,         "--auto", &do_auto);

	if (!ap_parse(pstate, verb, pargi, argc, argv)) {
		mapper_bar_usage(stderr, argv[0], verb);
		return NULL;
	}

	if (pfield_names == NULL) {
		mapper_bar_usage(stderr, argv[0], verb);
		return NULL;
	}

	char fill_char  = DEFAULT_FILL_CHAR;
	char oob_char   = DEFAULT_OOB_CHAR;
	char blank_char = DEFAULT_BLANK_CHAR;

	if (fill_string != NULL) {
		if (strlen(fill_string) != 1) {
			mapper_bar_usage(stderr, argv[0], verb);
			return NULL;
		}
		fill_char = fill_string[0];
	}

	if (oob_string != NULL) {
		if (strlen(oob_string) != 1) {
			mapper_bar_usage(stderr, argv[0], verb);
			return NULL;
		}
		oob_char = oob_string[0];
	}

	if (blank_string != NULL) {
		if (strlen(blank_string) != 1) {
			mapper_bar_usage(stderr, argv[0], verb);
			return NULL;
		}
		blank_char = blank_string[0];
	}

	return mapper_bar_alloc(pstate, pfield_names, fill_char, oob_char, blank_char,
		lo, hi, width, do_auto);
}

// ----------------------------------------------------------------
static mapper_t* mapper_bar_alloc(ap_state_t* pargp, string_array_t* pfield_names,
	char fill_char, char oob_char, char blank_char, double lo, double hi,
	int width, int do_auto)
{
	mapper_t* pmapper = mlr_malloc_or_die(sizeof(mapper_t));

	mapper_bar_state_t* pstate = mlr_malloc_or_die(sizeof(mapper_bar_state_t));
	pstate->pargp        = pargp;
	pstate->pfield_names = pfield_names;
	pstate->fill_char    = fill_char;
	pstate->oob_char     = oob_char;
	pstate->blank_char   = blank_char;
	pstate->lo           = lo;
	pstate->hi           = hi;
	pstate->width        = width;
	pstate->bars         = mlr_malloc_or_die((pstate->width + 1) * sizeof(char*));
	for (int i = 0; i <= pstate->width; i++) {
		pstate->bars[i] = mlr_malloc_or_die(pstate->width + 1);
		char* bar = pstate->bars[i];
		memset(bar, pstate->blank_char, pstate->width);
		bar[pstate->width] = 0;
		if (i == 0) {
			bar[0] = pstate->oob_char;
		} else if (i < pstate->width) {
			memset(bar, pstate->fill_char, i);
		} else {
			memset(bar, pstate->fill_char, pstate->width);
			bar[pstate->width-1] = pstate->oob_char;
		}
		pstate->bars[i] = bar;
	}
	pstate->precords = do_auto ? sllv_alloc() : NULL;

	pmapper->pprocess_func = do_auto
		? mapper_bar_process_auto
		: mapper_bar_process_no_auto;
	pmapper->pvstate    = (void*)pstate;
	pmapper->pfree_func = mapper_bar_free;

	return pmapper;
}

static void mapper_bar_free(mapper_t* pmapper, context_t* _) {
	mapper_bar_state_t* pstate = (mapper_bar_state_t*)pmapper->pvstate;
	string_array_free(pstate->pfield_names);
	for (int i = 0; i <= pstate->width; i++)
		free(pstate->bars[i]);
	free(pstate->bars);
	ap_free(pstate->pargp);
	free(pstate);
	free(pmapper);
}

// ----------------------------------------------------------------
static sllv_t* mapper_bar_process_no_auto(lrec_t* pinrec, context_t* pctx, void* pvstate) {
	if (pinrec == NULL) // end of stream
		return sllv_single(NULL);

	mapper_bar_state_t* pstate = (mapper_bar_state_t*)pvstate;
	int n = pstate->pfield_names->length;
	for (int i = 0; i < n; i++) {
		char* name = pstate->pfield_names->strings[i];
		char* sval = lrec_get(pinrec, name);
		if (sval == NULL)
			continue;
		double dval = mlr_double_from_string_or_die(sval);
		int idx = (int)(pstate->width * (dval - pstate->lo) / (pstate->hi - pstate->lo));
		if (idx < 0)
			idx = 0;
		if (idx > pstate->width)
			idx = pstate->width;
		lrec_put(pinrec, name, pstate->bars[idx], NO_FREE);
	}
	return sllv_single(pinrec);
}

// ----------------------------------------------------------------
static sllv_t* mapper_bar_process_auto(lrec_t* pinrec, context_t* pctx, void* pvstate) {
	mapper_bar_state_t* pstate = (mapper_bar_state_t*)pvstate;

	if (pinrec != NULL) { // not end of stream
		sllv_append(pstate->precords, pinrec);
		return NULL;
	}

	// end of stream
	int n = pstate->pfield_names->length;
	string_builder_t* psb = sb_alloc(SB_ALLOC_LENGTH);

	// Loop over field names to be barred
	for (int i = 0; i < n; i++) {
		char* name = pstate->pfield_names->strings[i];
		double lo = 0.0;
		double hi = 0.0;

		// First pass computes lo and hi from the data
		int j = 0;
		for (sllve_t* pe = pstate->precords->phead; pe != NULL; pe = pe->pnext, j++) {
			lrec_t* prec = pe->pvvalue;
			char* sval = lrec_get(prec, name);
			if (sval == NULL)
				continue;
			double dval = mlr_double_from_string_or_die(sval);
			if (j == 0 || dval < lo)
				lo = dval;
			if (j == 0 || dval > hi)
				hi = dval;
		}

		// Second pass applies the bars. There is some redundant computation
		// which could be hoisted out of the loop for performance ... but this
		// verb computes data solely for visual inspection and I take the
		// nominal use case to be tens or hundreds of records. So, optimization
		// isn't worth the effort here.
		char* slo = mlr_alloc_string_from_double(lo, "%g");
		char* shi = mlr_alloc_string_from_double(hi, "%g");

		for (sllve_t* pe = pstate->precords->phead; pe != NULL; pe = pe->pnext) {
			lrec_t* prec = pe->pvvalue;
			char* sval = lrec_get(prec, name);
			if (sval == NULL)
				continue;
			double dval = mlr_double_from_string_or_die(sval);

			int idx = (int)(pstate->width * (dval - lo) / (hi - lo));
			if (idx < 0)
				idx = 0;
			if (idx > pstate->width)
				idx = pstate->width;
			sb_append_string(psb, "[");
			sb_append_string(psb, slo);
			sb_append_string(psb, "]");
			sb_append_string(psb, pstate->bars[idx]);
			sb_append_string(psb, "[");
			sb_append_string(psb, shi);
			sb_append_string(psb, "]");
			lrec_put(prec, name, sb_finish(psb), FREE_ENTRY_VALUE);
		}

		free(slo);
		free(shi);

	}

	sb_free(psb);
	sllv_append(pstate->precords, NULL);
	return pstate->precords;
}
