/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2020 Tibor 'Igor2' Palinkas
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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 "config.h"

#include <liblihata/lihata.h>
#include <liblihata/dom.h>
#include <liblihata/tree.h>
#include <load_cache/load_cache.h>
#include <librnd/core/compat_misc.h>

#include "cnc_any_obj.h"
#include "engine.h"
#include "project.h"

#include "util_parse.h"

/*** load_cache low levels ***/

 /* called when the file is first loaded; should allocate (ldch_file_t *) */
static ldch_file_t *ldlht_parse_alloc(ldch_low_parser_t *parser, void *call_ctx, const char *fn)
{
	return ldch_file_alloc(parser->ctx, parser, sizeof(lht_doc_t *));
}

 /* called after parse_alloc() on initial load or after free_payload() on reload */
static int ldlht_parse(ldch_low_parser_t *parser, void *call_ctx, ldch_file_t *file, const char *fn)
{
	char *errmsg;
	lht_doc_t **payload = (lht_doc_t **)&file->low_payload;

	*payload = lht_dom_load(fn, &errmsg);
	if (*payload == NULL) {
		rnd_message(RND_MSG_ERROR, "Failed to parse lihata file '%s': %s\n", fn, errmsg);
		return -1;
	}
	return 0;
}

 /* should free the in-memory document/parse-tree; a call to parse() may follow when reloading */
static void ldlht_free_payload(ldch_low_parser_t *low, ldch_file_t *file)
{
	lht_doc_t **payload = (lht_doc_t **)&file->low_payload;
	if (*payload != NULL)
		lht_dom_uninit(*payload);
}

lht_doc_t *ldch_lht_get_doc(ldch_file_t *file)
{
	lht_doc_t **payload = (lht_doc_t **)&file->low_payload;
	return *payload;
}

ldch_low_parser_t *ldch_lht_reg_low_parser(ldch_ctx_t *ctx)
{
	ldch_low_parser_t *p = ldch_reg_low_parser(ctx, "lihata");

	p->parse_alloc  = ldlht_parse_alloc;
	p->parse        = ldlht_parse;
	p->free_payload = ldlht_free_payload;

	return p;
}


/*** lihata parsers ***/

static void csch_lht_parse_attribs_val(csch_sheet_t *sheet, csch_cgrp_t *dstg, csch_attribs_t *dsta, const char *key, lht_node_t *n, void (*error)(void *ectx, lht_node_t *n, const char *msg), void *ectx, int prio)
{
	lht_node_t *i, *np, *nv;
	long idx;
	const char *val;
	csch_source_arg_t *src;

	if (dstg != NULL) {
		assert(csch_obj_is_grp(&dstg->hdr));
	}

	switch(n->type) {
		case LHT_TEXT: /* plain text: scalar attrib */
			val = n->data.text.value;
			src = csch_attrib_src_c(n->file_name, n->line, n->col, NULL);
			if (dstg != NULL)
				csch_cobj_attrib_set(sheet, dstg, prio, key, val, src);
			else
				csch_attrib_set(dsta, prio, key, val, src, NULL);
			break;
		case LHT_LIST: /* array attrib */
			/* first create the attribute as an empty array, just in case it is indeed an empty arrya with no child */
			src = csch_attrib_src_c(n->file_name, n->line, n->col, NULL);
			if (dstg != NULL)
				csch_cobj_attrib_set(sheet, dstg, prio, key, NULL, src);
			else
				csch_attrib_set(dsta, prio, key, NULL, src, NULL);

			for(i = n->data.list.first, idx = 0; i != NULL; i = i->next) {
				if (i->type != LHT_TEXT) {
					error(ectx, i, "invalid node type for an attribute list item");
					continue;
				}
				val = i->data.text.value;
				src = csch_attrib_src_c(i->file_name, i->line, i->col, NULL);
				if (dstg != NULL)
					csch_cobj_attrib_seti(sheet, dstg, prio, key, idx, val, src);
				else
					csch_attrib_seti(dsta, prio, key, idx, val, src, NULL);
				idx++;
			}
			break;
		case LHT_HASH: /* subtree with explicit prio value */
			nv = lht_dom_hash_get(n, "value");
			if (nv == NULL) {
				error(ectx, n, "missing attribute value subtree");
				return;
			}
			if ((nv->type != LHT_TEXT) && (nv->type != LHT_LIST)) {
				error(ectx, n, "wrong attribute value subtree type (must be text or list)");
				return;
			}
			np = lht_dom_hash_get(n, "prio");
			if (np != NULL) {
				if (np->type == LHT_TEXT) {
					char *end;
					prio = strtol(np->data.text.value, &end, 10);
					if (*end != '\0') {
						error(ectx, n, "prio value is not integer");
						return;
					}
					if ((prio < 0) || (prio > 32767)) {
						error(ectx, n, "attribute prio is out of range");
						return;
					}
				}
				else {
					error(ectx, n, "wrong attribute prio type (must be text)");
					return;
				}
			}
			csch_lht_parse_attribs_val(sheet, dstg, dsta, key, nv, error, ectx, prio);
			break;
		default:
			error(ectx, n, "invalid node type for an attribute");
	}
}

int csch_lht_parse_attribs_(csch_sheet_t *sheet, csch_chdr_t *dsth, csch_attribs_t *dsta, lht_node_t *subtree, void (*error)(void *ectx, lht_node_t *n, const char *msg), void *ectx)
{
	lht_node_t *n;
	lht_dom_iterator_t it;
	csch_cgrp_t *dstg = (csch_cgrp_t *)dsth;

	if ((subtree == NULL) || (subtree->type != LHT_HASH))
		return 0;

	if (dstg != NULL) {
		assert(csch_obj_is_grp(dsth));
		assert(dstg->extobj_inhibit >= 0);
		dstg->extobj_inhibit++;
	}

	for(n = lht_dom_first(&it, subtree); n != NULL; n = lht_dom_next(&it))
		csch_lht_parse_attribs_val(sheet, dstg, dsta, n->name, n, error, ectx, CSCH_ATP_USER_DEFAULT);

	if (dstg != NULL) {
		dstg->extobj_inhibit--;
		assert(dstg->extobj_inhibit >= 0);
	}

	return 0;
}

int csch_lht_parse_attribs(csch_attribs_t *dst, lht_node_t *subtree, void (*error)(void *ectx, lht_node_t *n, const char *msg), void *ectx)
{
	return csch_lht_parse_attribs_(NULL, NULL, dst, subtree, error, ectx);
}

static lht_node_t *csch_view2lht_engines_in_(lht_node_t *nv, const csch_view_t *view)
{
	lht_node_t *ne, *neng, *nd;
	long n;

	ne = lht_dom_node_alloc(LHT_LIST, "engines");
	lht_dom_hash_put(nv, ne);
	for(n = 0; n < view->engines.used; n++) {
		csch_view_eng_t *e = view->engines.array[n];
		neng = lht_dom_node_alloc(LHT_HASH, e->obj->name);
		lht_dom_list_append(ne, neng);

		nd = lht_dom_node_alloc(LHT_TEXT, "plugin");
		nd->data.text.value = rnd_strdup(e->obj->engine->name);
		lht_dom_hash_put(neng, nd);

		if (e->options != NULL) {
			nd = lht_dom_node_alloc(LHT_TEXT, "options");
			nd->data.text.value = rnd_strdup(e->options);
			lht_dom_hash_put(neng, nd);
		}
	}
	return nv;
}

lht_node_t *csch_view2lht_in(lht_node_t *nv, const csch_view_t *view)
{
	lht_node_t *old;

	/* purge old nodes under nv so they can be replaced with newly allocated */
	old = lht_dom_hash_get(nv, "engines");
	if (old != NULL)
		lht_tree_del(old);

	return csch_view2lht_engines_in_(nv, view);
}

lht_node_t *csch_view2lht(const csch_view_t *view)
{
	lht_node_t *nv = lht_dom_node_alloc(LHT_HASH, view->fgw_ctx.name);
	return csch_view2lht_engines_in_(nv, view);
}

