#include <stdlib.h>
#include "cli/quoting.h"
#include "lib/mlrutil.h"
#include "lib/mlr_globals.h"
#include "containers/mixutil.h"
#include "output/lrec_writers.h"

typedef void     quoted_output_func_t(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void     quote_all_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void    quote_none_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void quote_minimal_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void quote_numeric_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);

typedef struct _lrec_writer_csv_state_t {
	int   onr;
	char *ors;
	char *ofs;
	int   orslen;
	int   ofslen;
	quoted_output_func_t* pquoted_output_func;
	long long num_header_lines_output;
	slls_t* plast_header_output;
	int headerless_csv_output;
} lrec_writer_csv_state_t;

// ----------------------------------------------------------------
static void lrec_writer_csv_process(FILE* output_stream, lrec_t* prec, void* pvstate);
static void lrec_writer_csv_free(void* pvstate);
static void quote_all_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void quote_none_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void quote_minimal_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);
static void quote_numeric_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen);

// ----------------------------------------------------------------
lrec_writer_t* lrec_writer_csv_alloc(char* ors, char* ofs, int oquoting, int headerless_csv_output) {
	lrec_writer_t* plrec_writer = mlr_malloc_or_die(sizeof(lrec_writer_t));

	lrec_writer_csv_state_t* pstate = mlr_malloc_or_die(sizeof(lrec_writer_csv_state_t));
	pstate->onr    = 0;
	pstate->ors    = ors;
	pstate->ofs    = ofs;
	pstate->orslen = strlen(pstate->ors);
	pstate->ofslen = strlen(pstate->ofs);
	pstate->headerless_csv_output = headerless_csv_output;

	switch(oquoting) {
	case QUOTE_ALL:     pstate->pquoted_output_func = quote_all_output_func;     break;
	case QUOTE_NONE:    pstate->pquoted_output_func = quote_none_output_func;    break;
	case QUOTE_MINIMAL: pstate->pquoted_output_func = quote_minimal_output_func; break;
	case QUOTE_NUMERIC: pstate->pquoted_output_func = quote_numeric_output_func; break;
	default:
		fprintf(stderr, "%s: internal coding error: output-quoting style 0x%x unrecognized.\n",
			MLR_GLOBALS.argv0, oquoting);
		exit(1);
	}

	pstate->num_header_lines_output = 0LL;
	pstate->plast_header_output     = NULL;

	plrec_writer->pvstate       = (void*)pstate;
	plrec_writer->pprocess_func = lrec_writer_csv_process;
	plrec_writer->pfree_func    = lrec_writer_csv_free;

	return plrec_writer;
}

static void lrec_writer_csv_free(void* pvstate) {
	lrec_writer_csv_state_t* pstate = pvstate;
	if (pstate->plast_header_output != NULL) {
		slls_free(pstate->plast_header_output);
		pstate->plast_header_output = NULL;
	}
}

// ----------------------------------------------------------------
static void lrec_writer_csv_process(FILE* output_stream, lrec_t* prec, void* pvstate) {
	if (prec == NULL)
		return;
	lrec_writer_csv_state_t* pstate = pvstate;
	char *ors = pstate->ors;
	char *ofs = pstate->ofs;

	if (pstate->plast_header_output != NULL) {
		if (!lrec_keys_equal_list(prec, pstate->plast_header_output)) {
			slls_free(pstate->plast_header_output);
			pstate->plast_header_output = NULL;
			if (pstate->num_header_lines_output > 0LL)
				fputs(ors, output_stream);
		}
	}

	if (pstate->plast_header_output == NULL) {
		int nf = 0;
		if (!pstate->headerless_csv_output) {
			for (lrece_t* pe = prec->phead; pe != NULL; pe = pe->pnext) {
				if (nf > 0)
					fputs(ofs, output_stream);
				pstate->pquoted_output_func(output_stream, pe->key, pstate->ors, pstate->ofs,
					pstate->orslen, pstate->ofslen);
				nf++;
			}
			fputs(ors, output_stream);
		}
		pstate->plast_header_output = mlr_copy_keys_from_record(prec);
		pstate->num_header_lines_output++;
	}

	int nf = 0;
	for (lrece_t* pe = prec->phead; pe != NULL; pe = pe->pnext) {
		if (nf > 0)
			fputs(ofs, output_stream);
		pstate->pquoted_output_func(output_stream, pe->value, pstate->ors, pstate->ofs,
			pstate->orslen, pstate->ofslen);
		nf++;
	}
	fputs(ors, output_stream);
	pstate->onr++;

	// See ../README.md for memory-management conventions
	lrec_free(prec);
}

// ----------------------------------------------------------------
static void quote_all_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen) {
	fputc('"', fp);
	fputs(string, fp);
	fputc('"', fp);
}

static void quote_none_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen) {
	fputs(string, fp);
}

static void quote_minimal_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen) {
	int output_quotes = FALSE;
	for (char* p = string; *p; p++) {
		if (streqn(p, ors, orslen) || streqn(p, ofs, ofslen)) {
			output_quotes = TRUE;
			break;
		}
	}
	if (output_quotes) {
		fputc('"', fp);
		fputs(string, fp);
		fputc('"', fp);
	} else {
		fputs(string, fp);
	}
}

static void quote_numeric_output_func(FILE* fp, char* string, char* ors, char* ofs, int orslen, int ofslen) {
	double temp;
	if (mlr_try_float_from_string(string, &temp)) {
		fputc('"', fp);
		fputs(string, fp);
		fputc('"', fp);
	} else {
		fputs(string, fp);
	}
}
