/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - attribute table CSV import/export
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 <stdio.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/plugins.h>
#include <librnd/core/error.h>
#include <librnd/core/hidlib.h>
#include <librnd/hid/hid.h>
#include <librnd/hid/hid_attrib.h>
#include <librnd/hid/hid_nogui.h>
#include <librnd/hid/hid_init.h>
#include <libcschem/config.h>
#include <libcschem/plug_io.h>
#include <libcschem/util_export.h>
#include <plugins/lib_attbl/lib_attbl.h>
#include <sch-rnd/export.h>

/*** format write ***/

/* not used anywhere else in the codebase and very small - compile in */
#include <libucsv/libucsv.c>

typedef struct csv_ctx_s {
	attbl_file_ctx_t fctx;
	ucsv_ctx_t uctx;
} csv_ctx_t;

/* print a cell using the syntax defined in RFC4148 */
RND_INLINE void csv_print_cell(csv_ctx_t *pctx, const char *str, int is_first)
{
	ucsv_print_cell(&pctx->uctx, pctx->fctx.f, str, is_first);
}

static void csv_print_tbl_head(void *uctx, const char *type, const char **hdr)
{
	csv_ctx_t *pctx = uctx;
	const char **h;

	attbl_file_open(&pctx->fctx, type);
	if (pctx->fctx.f == NULL)
		return;

	for(h = hdr; *h != NULL; h++)
		csv_print_cell(pctx, *h, h == hdr);

	ucsv_print_endline(pctx->fctx.f);
}

static void csv_print_tbl_foot(void *uctx, const char *type, const char **hdr)
{
	csv_ctx_t *pctx = uctx;

	if (!pctx->fctx.multi_file) {
		/* produce an empty line to sepparate tables in an aggregate file */
		ucsv_print_endline(pctx->fctx.f);
	}
}

static void csv_print_row(void *uctx, const char *type, const char *uuid, const char **cols, const char **hdr)
{
	csv_ctx_t *pctx = uctx;
	const char **h, **c;

	if (pctx->fctx.f == NULL)
		return;

	for(h = hdr, c = cols; *h != NULL; h++,c++)
		csv_print_cell(pctx, *c, h == hdr);

	ucsv_print_endline(pctx->fctx.f);
}

static int attbl_write_csv(rnd_design_t *design, const char *fn, int is_prj, attbl_model_t model, csch_abstract_t *abst, int multi)
{
	csv_ctx_t pctx = {0};

	if (attbl_file_init(&pctx.fctx, design, fn, multi) != 0)
		return 1;

	csch_attbl_export(design, is_prj, model, abst, csv_print_tbl_head, csv_print_row, csv_print_tbl_foot, &pctx);

	attbl_file_uninit(&pctx.fctx);
	return 0;
}

/*** Import ***/

static int attbl_csv_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	long comma = -1, endl = 0;
	char tmp[16], *s;
	int c;

	if (!(type & CSCH_IOTYP_ATTBL))
		return -1;

	/* CSV has no format header, but we always have <uuid:type> as last column
	   table header, look for that; read char-by-char, remember the position
	   of the last "," and stop at the first newline */
	while((c = fgetc(f)) != EOF) {
		if (c == ',')
			comma = ftell(f);
		if ((c == '\r') || (c == '\n')) {
			endl = 1;
			break;
		}
	}

	if ((!endl) || (comma < 0))
		return -1; /* refuse */

	fseek(f, comma, SEEK_SET);
	fgets(tmp, sizeof(tmp), f);
	s = tmp;
	if (*s == ',') s++;
	if (*s == '"') s++;

	if (strncmp(s, "<uuid:", 6) != 0)
		return -1; /* refuse */

	return 0; /* accept */
}

static int attbl_csv_import(FILE *f, const char *fn, const char *fmt, csch_sheet_t *sheet)
{
	attbl_import_t ictx = {0};
	ucsv_ctx_t uctx = {0};
	int res = 0, table_open = 0, last_endline = 0;


	for(;;) {
		int c;
		ucsv_token_t t;

		c = fgetc(f);
		t = ucsv_parser_char(&uctx, c);
		if (t == UCSV_END)
			break;
		if (t == UCSV_TOK_TEXT)
			last_endline = 0;
		if ((t == UCSV_TOK_TEXT) || (t == UCSV_TOK_END_LINE)) {
			if (!table_open) {
				attbl_import_begin(&ictx, &sheet->hidlib);
				table_open = 1;
			}
			if (attbl_import_cell(&ictx, uctx.buf) != 0) {
				res = -1;
				break;
			}
			*uctx.buf = '\0';
		}
		if (t == UCSV_TOK_END_LINE) {
			if (last_endline) {
				/* empty line means new table ends (and a new table is open on the next row which is a header) */
				table_open = 0;
			}
			attbl_import_endline(&ictx);
			*uctx.buf = '\0';
			last_endline = 1;
		}
	}

	attbl_import_uninit(&ictx);
	ucsv_parser_uninit(&uctx);

	return res;
}

/*** HID and import/exporter glue ***/

static int attbl_csv_export_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (!(type & CSCH_IOTYP_ATTBL))
		return 0;

	if ((fmt != NULL) && (rnd_strcasecmp(fmt, "csv") == 0))
		return 100;

	return 0;
}

static int attbl_csv_import_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (!(type & CSCH_IOTYP_ATTBL))
		return 0;

	if ((fmt == NULL) || (*fmt == '\0'))
		return 50; /* autodetect */

	if (rnd_strcasecmp(fmt, "csv") == 0)
		return 100;

	return 0; /* requested a different format explicitly */
}


static const char attbl_csv_cookie[] = "attbl_csv export hid";

static rnd_export_opt_t attbl_csv_options[] = {
	{"outfile", "Name of the CSV output file",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_outfile 0

	{"model", "Name of the model to export",
	 RND_HATT_ENUM, 0, 0, {0, 0, 0}, attbl_models},
#define HA_model 1

	{"multi", "Write multiple files: save one file per table, inserting table name in the file name",
	 RND_HATT_BOOL, 0, 0, {0, 0, 0}, 0},
#define HA_multi 2

	{"view", "Name of the view to export (use first view when not specified); only in project's abstract model export",
	 RND_HATT_STRING, 0, 0, {0, 0, 0}, 0},
#define HA_view 3

	{"", "EXTEDIT",
	 RND_HATT_BEGIN_VBOX, 0, 0, {0, "csv", 0, 0, {0}, attbl_extedit_dad}, 0}
#define HA_extedit 4
};

#define NUM_OPTIONS (sizeof(attbl_csv_options)/sizeof(attbl_csv_options[0]))

static rnd_hid_attr_val_t attbl_csv_values[NUM_OPTIONS];

static sch_rnd_export_appspec_t appspec0;

static const rnd_export_opt_t *attbl_csv_get_export_options(rnd_hid_t *hid, int *n, rnd_design_t *dsg, void *appspec_)
{
	const char *val = attbl_csv_values[HA_outfile].str;

	if ((dsg != NULL) && ((val == NULL) || (*val == '\0')))
		csch_derive_default_filename(dsg, 1, &attbl_csv_values[HA_outfile], ".csv");

	if (n)
		*n = NUM_OPTIONS;
	return attbl_csv_options;
}

static void attbl_csv_do_export(rnd_hid_t *hid, rnd_design_t *design, rnd_hid_attr_val_t *options, void *appspec_)
{
	sch_rnd_export_appspec_t *appspec = appspec_ == NULL ? &appspec0 : appspec_;
	csch_abstract_t abst;
	int is_abst, is_prj;

	if (!options) {
		attbl_csv_get_export_options(hid, 0, design, appspec_);
		options = attbl_csv_values;
	}

	is_abst = ATTBL_MODEL_IS_ABST(options[HA_model].lng);
	if (attbl_do_export_begin(design, options[HA_view].str, options[HA_model].lng, &abst) != 0)
		return;

	is_prj = appspec->exp_prj || is_abst; /* abstract always needs to be exported as project */
	attbl_write_csv(design, options[HA_outfile].str, is_prj, options[HA_model].lng, (is_abst ? &abst : NULL), !!options[HA_multi].lng);

	attbl_do_export_end(&abst, options[HA_model].lng);
}

static int attbl_csv_usage(rnd_hid_t *hid, const char *topic)
{
	fprintf(stderr, "\nAttribute table CSV exporter command line arguments:\n\n");
	rnd_hid_usage(attbl_csv_options, sizeof(attbl_csv_options) / sizeof(attbl_csv_options[0]));
	fprintf(stderr, "\nUsage: sch-rnd [generic_options] -x attbl_csv [options] foo.rs\n\n");
	return 0;
}


static int attbl_csv_parse_arguments(rnd_hid_t *hid, int *argc, char ***argv)
{
	rnd_export_register_opts2(hid, attbl_csv_options, sizeof(attbl_csv_options) / sizeof(attbl_csv_options[0]), attbl_csv_cookie, 0);
	return rnd_hid_parse_command_line(argc, argv);
}

rnd_hid_t attbl_csv_hid = {0};


static int attbl_csv_export_project_csv(const char *fn, const char *fmt, csch_abstract_t *abst, rnd_hid_attr_val_t *options)
{
	return attbl_write_csv(rnd_multi_get_current(), fn, 1, ATTBLM_ABSTRACT, abst, !!options[HA_multi].lng);
}

/***/

static csch_plug_io_t ecsv;

int pplg_check_ver_attbl_csv(int ver_needed) { return 0; }

void pplg_uninit_attbl_csv(void)
{
	csch_plug_io_unregister(&ecsv);
	rnd_export_remove_opts_by_cookie(attbl_csv_cookie);
	rnd_hid_remove_hid(&attbl_csv_hid);
}

int pplg_init_attbl_csv(void)
{
	RND_API_CHK_VER;

	ecsv.name = "export/import attribute table CSV";
	ecsv.export_prio = attbl_csv_export_prio;
	ecsv.import_prio = attbl_csv_import_prio;
	ecsv.export_project_abst = attbl_csv_export_project_csv;
	ecsv.test_parse = attbl_csv_test_parse;
	ecsv.import_attbl = attbl_csv_import;
	ecsv.ext_export_project = ".csv";
	csch_plug_io_register(&ecsv);

	rnd_hid_nogui_init(&attbl_csv_hid);

	attbl_csv_hid.struct_size = sizeof(rnd_hid_t);
	attbl_csv_hid.name = "attbl_csv";
	attbl_csv_hid.description = "Export attribute table in CSV";
	attbl_csv_hid.exporter = 1;

	attbl_csv_hid.get_export_options = attbl_csv_get_export_options;
	attbl_csv_hid.do_export = attbl_csv_do_export;
	attbl_csv_hid.parse_arguments = attbl_csv_parse_arguments;
	attbl_csv_hid.argument_array = attbl_csv_values;

	attbl_csv_hid.usage = attbl_csv_usage;

	rnd_hid_register_hid(&attbl_csv_hid);
	rnd_hid_load_defaults(&attbl_csv_hid, attbl_csv_options, NUM_OPTIONS);

	return 0;
}

