/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - attribute table json 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., 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 <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>

#include <libnanojson/nanojson.h>
#include <libnanojson/semantic.h>

/*** format write ***/

typedef struct json_ctx_s {
	attbl_file_ctx_t fctx;
	unsigned main_hdr:1;
	unsigned tbl_first:1;
} json_ctx_t;

static void json_print_col(json_ctx_t *pctx, const char *str, int is_last)
{
	const char *s;

	fprintf(pctx->fctx.f, "   \"");
	if (str != NULL) {
		for(s = str; *s != '\0'; s++) {
			if ((*s == '\"') || (*s == '\\'))
				fputc('\\', pctx->fctx.f);
			fputc(*s, pctx->fctx.f);
		}
	}
	fputc('"', pctx->fctx.f);
	if (!is_last)
		fputc(',', pctx->fctx.f);
	fputc('\n', pctx->fctx.f);
}


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

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

	if (!pctx->main_hdr) {
		if (!pctx->fctx.multi_file)
			pctx->main_hdr = 1;
		fprintf(pctx->fctx.f, "{ \"sch-rnd-attbl-v1\": [\n");
		pctx->tbl_first = 1;
	}

	if (!pctx->tbl_first)
		fprintf(pctx->fctx.f, " ,\n");
	pctx->tbl_first = 0;
	fprintf(pctx->fctx.f, " [\n");

	fprintf(pctx->fctx.f, "  [\n");
	for(h = hdr; *h != NULL; h++)
		json_print_col(pctx, *h, (h[1] == NULL));
	fprintf(pctx->fctx.f, "  ]\n");
}

static void json_print_tbl_foot(void *uctx, const char *type, const char **hdr)
{
	json_ctx_t *pctx = uctx;
	fprintf(pctx->fctx.f, " ]\n");
	if (pctx->fctx.multi_file)
		fprintf(pctx->fctx.f, "]}\n");
}

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

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

	fprintf(pctx->fctx.f, "  [\n");
	for(h = hdr, c = cols; *h != NULL; h++,c++)
		json_print_col(pctx, *c, (h[1] == NULL));
	fprintf(pctx->fctx.f, "  ]\n");
}

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

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

	csch_attbl_export(design, is_prj, model, abst, json_print_tbl_head, json_print_row, json_print_tbl_foot, &pctx);

	if (!pctx.fctx.multi_file)
		fprintf(pctx.fctx.f, "]}\n");

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

/*** Import ***/

static int attbl_json_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	int n;
	njson_sem_ctx_t jctx = {0};

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

	for(n = 0; n < 1024; n++) {
		int c = fgetc(f);
		njson_sem_ev_t ev;

		if (c == EOF)
			break;  /* refuse - premature EOF */

		ev = njson_sem_push(&jctx, c);
		if (ev == NJSON_SEM_EV_ARRAY_BEGIN) {
			if (strncmp(jctx.name, "sch-rnd-attbl-v", 15) == 0) {
				njson_sem_uninit(&jctx);
				return 0; /* accept */
			}
			break; /* refuse - wrong array name */
		}
		if (ev == NJSON_SEM_EV_ATOMIC)
			break; /* refuse - wrong array name */
		if ((ev == NJSON_SEM_EV_ARRAY_END) || (ev == NJSON_SEM_EV_OBJECT_END))
			break; /* refuse - can't close anything before the header */
	}

	njson_sem_uninit(&jctx);
	return -1; /* refuse */
}

static int attbl_json_import(FILE *f, const char *fn, const char *fmt, csch_sheet_t *sheet)
{
	attbl_import_t ictx = {0};
	njson_sem_ctx_t jctx = {0};
	int c, res = -1, level = 0;

	for(;;) {
		int c = fgetc(f);
		njson_sem_ev_t ev = njson_sem_push(&jctx, c);;
		if (ev == NJSON_SEM_EV_eof)
			break;
		switch(ev) {
			case NJSON_SEM_EV_OBJECT_BEGIN:
				if (level != 0) {
					rnd_message(RND_MSG_ERROR, "error 'the only object shall be the root object (open)' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				level++;
				break;
			case NJSON_SEM_EV_OBJECT_END:
				level--;
				if (level != 0) {
					rnd_message(RND_MSG_ERROR, "error 'the only object shall be the root object (close)' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				break;
			case NJSON_SEM_EV_ARRAY_BEGIN:
				if ((level < 1) || (level > 3)) {
					rnd_message(RND_MSG_ERROR, "error 'array nesting error (open)' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				switch(level) {
					case 1: /* file */
						break;
					case 2: /* table */
						attbl_import_begin(&ictx, &sheet->hidlib);
						break;
					case 3: /* row */
						break;
				}
				level++;
				break;
			case NJSON_SEM_EV_ARRAY_END:
				level--;
				if ((level < 1) || (level > 3)) {
					rnd_message(RND_MSG_ERROR, "error 'array nesting error (close)' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				switch(level) {
					case 1: /* file */
						break;
					case 2: /* table */
						break;
					case 3: /* row */
						attbl_import_endline(&ictx);
						break;
				}
				break;

			case NJSON_SEM_EV_ATOMIC:
				if (level != 4) {
					rnd_message(RND_MSG_ERROR, "error 'atom on the wrong level' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				if (jctx.type != NJSON_SEM_TYPE_STRING) {
					rnd_message(RND_MSG_ERROR, "error 'cell type must be string' at %ld:%ld\n", jctx.njs.lineno, jctx.njs.col);
					goto error;
				}
				attbl_import_cell(&ictx, jctx.value.string);
				break;
				
			case NJSON_SEM_EV_error:
				rnd_message(RND_MSG_ERROR, "error '%s' at %ld:%ld\n", jctx.njs.error, jctx.njs.lineno, jctx.njs.col);
				goto error;
			case NJSON_SEM_EV_eof:
			case NJSON_SEM_EV_more:
				break;
		}
	}

	res = 0;
	error:;
	attbl_import_uninit(&ictx);

	return res;
}

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

static int attbl_json_export_prio(const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	njson_sem_ctx_t jctx = {0};

	if (!(type & CSCH_IOTYP_ATTBL))
		return 0;

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

	return 0;
}

static int attbl_json_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, "json") == 0) || (rnd_strcasecmp(fmt, "json") == 0))
		return 100;

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


static const char attbl_json_cookie[] = "attbl_json export hid";

static rnd_export_opt_t attbl_json_options[] = {
	{"outfile", "Name of the json 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, "json", 0, 0, {0}, attbl_extedit_dad}, 0}
#define HA_extedit 4
};

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

static rnd_hid_attr_val_t attbl_json_values[NUM_OPTIONS];

static sch_rnd_export_appspec_t appspec0;

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

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

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

static void attbl_json_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_json_get_export_options(hid, 0, design, appspec_);
		options = attbl_json_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_json(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_json_usage(rnd_hid_t *hid, const char *topic)
{
	fprintf(stderr, "\nAttribute table json exporter command line arguments:\n\n");
	rnd_hid_usage(attbl_json_options, sizeof(attbl_json_options) / sizeof(attbl_json_options[0]));
	fprintf(stderr, "\nUsage: sch-rnd [generic_options] -x attbl_json [options] foo.rs\n\n");
	return 0;
}


static int attbl_json_parse_arguments(rnd_hid_t *hid, int *argc, char ***argv)
{
	rnd_export_register_opts2(hid, attbl_json_options, sizeof(attbl_json_options) / sizeof(attbl_json_options[0]), attbl_json_cookie, 0);
	return rnd_hid_parse_command_line(argc, argv);
}

rnd_hid_t attbl_json_hid = {0};


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

/***/

static csch_plug_io_t ejson;

int pplg_check_ver_attbl_json(int ver_needed) { return 0; }

void pplg_uninit_attbl_json(void)
{
	csch_plug_io_unregister(&ejson);
	rnd_export_remove_opts_by_cookie(attbl_json_cookie);
	rnd_hid_remove_hid(&attbl_json_hid);
}

int pplg_init_attbl_json(void)
{
	RND_API_CHK_VER;

	ejson.name = "export/import attribute table json";
	ejson.export_prio = attbl_json_export_prio;
	ejson.import_prio = attbl_json_import_prio;
	ejson.export_project_abst = attbl_json_export_project_json;
	ejson.test_parse = attbl_json_test_parse;
	ejson.import_attbl = attbl_json_import;
	ejson.ext_export_project = ".json";
	csch_plug_io_register(&ejson);

	rnd_hid_nogui_init(&attbl_json_hid);

	attbl_json_hid.struct_size = sizeof(rnd_hid_t);
	attbl_json_hid.name = "attbl_json";
	attbl_json_hid.description = "Export attribute table in json";
	attbl_json_hid.exporter = 1;

	attbl_json_hid.get_export_options = attbl_json_get_export_options;
	attbl_json_hid.do_export = attbl_json_do_export;
	attbl_json_hid.parse_arguments = attbl_json_parse_arguments;
	attbl_json_hid.argument_array = attbl_json_values;

	attbl_json_hid.usage = attbl_json_usage;

	rnd_hid_register_hid(&attbl_json_hid);
	rnd_hid_load_defaults(&attbl_json_hid, attbl_json_options, NUM_OPTIONS);

	return 0;
}

