/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - tEDAx non-graphical sheet support
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2023)
 *
 *  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
 */

/* High level tEDAx parser and glue to core and comp.c */

#include <libcschem/config.h>
#include <ctype.h>

#include <librnd/core/error.h>

#include <libcschem/concrete.h>
#include <libcschem/non_graphical.h>
#include <libcschem/event.h>

#include <plugins/lib_ngrp/lib_ngrp.h>
#include <plugins/lib_tedax/parse.h>

#include "read.h"

static void ngrp_tedax_free(csch_sheet_t *sheet)
{
	sch_ngrp_free(sheet->non_graphical_data);
}

static int ngrp_tedax_load_file(csch_sheet_t *sheet, const char *fn)
{
	return sch_ngrp_load_file(sheet, sheet->non_graphical_data, fn);
}

static char *ngrp_tedax_draw_getline(csch_sheet_t *sheet, long *cnt, void **cookie)
{
	return sch_ngrp_draw_getline(sheet->non_graphical_data, cnt, cookie);
}

static int ngrp_tedax_compile_sheet(csch_abstract_t *dst, int viewid, csch_hier_path_t *hpath, const csch_sheet_t *src)
{
	return sch_ngrp_compile_sheet(dst, viewid, hpath, src, src->non_graphical_data);
}

static int io_ngrp_tedax_parse_acompact_attrs(sch_ngrp_t *ctx, sch_ngrp_comp_t *dst, int argc, char *argv[], long lineno)
{
	int n;
	for(n = 0; n < argc; n++) {
		char *key = argv[n], *val;
		val = strchr(key, '=');
		if (val != NULL) {
			*val = '\0';
			val++;
		}
		else
			val = "";
		sch_ngrp_add_attr(&dst->attr_head, key, val, -1, lineno);
	}
	return 0;
}

/* Parse a line of an acompact block */
static int io_ngrp_tedax_parse_acompact(csch_sheet_t *sheet, int argc, char *argv[], long lineno)
{
	sch_ngrp_t *ctx = sheet->non_graphical_data;

	if (strcmp(argv[0], "comp") == 0) {
		sch_ngrp_obj_t *c = sch_ngrp_add_obj(&ctx->comps, argv[1], lineno);
		return io_ngrp_tedax_parse_acompact_attrs(ctx, c, argc-2, argv+2, lineno);
	}

	if (strcmp(argv[0], "net") == 0) {
		sch_ngrp_obj_t *n = sch_ngrp_add_obj(&ctx->nets, argv[1], lineno);
		return io_ngrp_tedax_parse_acompact_attrs(ctx, n, argc-2, argv+2, lineno);
	}

	else if (strcmp(argv[0], "conn") == 0) {
		const char *netname = argv[1];
		int n;
		for(n = 2; n < argc; n++) {
			char *comp= argv[n], *port;
			port = strchr(comp, '=');
			if (port == NULL) {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact conn '%s': missing port name\n", sheet->hidlib.fullpath, lineno, argv[n]);
				return -1;
			}
			*port = '\0';
			port++;
			sch_ngrp_add_conn(&ctx->conns, netname, comp, port, lineno);
		}
		return 0;
	}

	rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact instruction %s\n", sheet->hidlib.fullpath, lineno, argv[0]);
	return -1;
}

static int parse_long_attr(sch_ngrp_obj_t *parent, int argc, char *argv[], const char *rest, int rest_len, long lineno)
{
	sch_ngrp_add_attr(&parent->attr_head, argv[0], rest, rest_len, lineno);
	return 0;
}

/* Parse a line of an acomp block */
static int io_ngrp_tedax_parse_acomp(csch_sheet_t *sheet, sch_ngrp_comp_t *parent, int argc, char *argv[], long lineno, const char *rest, int rest_len)
{
	sch_ngrp_t *ctx = sheet->non_graphical_data;

	if ((argv[0][0] == '-') && (argv[0][1] == '\0')) {
		if (argc != 3) {
			rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact conn '%s': need 3 fields for an acomp connection\n", sheet->hidlib.fullpath, lineno);
			return -1;
		}
		sch_ngrp_add_conn(&ctx->conns, argv[2], parent->name, argv[1], lineno);
		return 0;
	}

	if (argc < 2) {
		rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact conn '%s': need 2+ fields for attributes\n", sheet->hidlib.fullpath, lineno);
		return -1;
	}

	return parse_long_attr(parent, argc, argv, rest, rest_len, lineno);
}

/* Parse a line of an anet block */
static int io_ngrp_tedax_parse_anet(csch_sheet_t *sheet, sch_ngrp_net_t *parent, int argc, char *argv[], long lineno, const char *rest, int rest_len)
{
	sch_ngrp_t *ctx = sheet->non_graphical_data;

	if ((argv[0][0] == '-') && (argv[0][1] == '\0')) {
		if (argc != 3) {
			rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact conn '%s': need 3 fields for an anet connection\n", sheet->hidlib.fullpath, lineno);
			return -1;
		}
		sch_ngrp_add_conn(&ctx->conns, parent->name, argv[1], argv[2], lineno);
		return 0;
	}

	if (argc < 2) {
		rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: invalid acompact conn '%s': need 2+ fields for attributes\n", sheet->hidlib.fullpath, lineno);
		return -1;
	}

	return parse_long_attr(parent, argc, argv, rest, rest_len, lineno);
}

int io_ngrp_tedax_parse(csch_sheet_t *sheet)
{
	sch_ngrp_t *ctx = sheet->non_graphical_data;
	char *argv[128], buf[515], **s, *orig;
	int gothdr = 0;
	long lineno;
	enum { NONE, ACOMPACT, ACOMP, ANET } blocktype = NONE;
	sch_ngrp_obj_t *parent;

	sch_ngrp_clean_cache(ctx);

	orig = ctx->text.array;
	for(s = &orig, lineno = 1; **s != '\0'; lineno++) {
		const char *rest; /* rest of the arguments after argv[0] */
		int argc, rest_offs, rest_len;

		rest = *s;
		argc = tedax_getline_mem(s, buf, sizeof(buf), argv, sizeof(argv)/sizeof(argv[0]));
		if (argc < 0) {
			rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld\n", sheet->hidlib.fullpath, lineno);
			return -1;
		}

		if (argc > 1) {
			rest_offs = argv[1] - buf;
			rest += rest_offs;
			rest_len = *s - rest;
			if (rest[rest_len-1] == '\n')
				rest_len--;
		}
		else {
			rest_offs = -1;
			rest_len = 0;
			rest = NULL;
		}


		if (argv[0] == NULL)
			continue; /* skip empty lines */

		if (strcmp(argv[0], "tEDAx") == 0) {
			if (gothdr) {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: multiple tEDAx headers\n", sheet->hidlib.fullpath, lineno);
				return -1;
			}
			gothdr = 1;
		}
		else if (strcmp(argv[0], "begin") == 0) {
			if (!gothdr) {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: 'begin' before tEDAx header\n", sheet->hidlib.fullpath, lineno);
				return -1;
			}
			if (blocktype != NONE) {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: nested blocks\n", sheet->hidlib.fullpath, lineno);
				return -1;
			}
			if (strcmp(argv[1], "cschem_acompact") == 0) {
				blocktype = ACOMPACT;
			}
			else if (strcmp(argv[1], "cschem_acomp") == 0) {
				blocktype = ACOMP;
				parent = sch_ngrp_add_obj(&ctx->comps, argv[2], lineno);
			}
			else if (strcmp(argv[1], "cschem_anet") == 0) {
				blocktype = ANET;
				parent = sch_ngrp_add_obj(&ctx->nets, argv[2], lineno);
			}
			else {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: unknown block type %s\n", sheet->hidlib.fullpath, lineno, argv[1]);
				return -1;
			}
		}
		else if (strcmp(argv[0], "end") == 0) {
			if (blocktype == NONE) {
				rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: END with no block open\n", sheet->hidlib.fullpath, lineno);
				return -1;
			}
			blocktype = NONE;
			parent = NULL;
		}
		else {
			int res;

			switch(blocktype) {
				case ACOMPACT: res = io_ngrp_tedax_parse_acompact(sheet, argc, argv, lineno); break;
				case ACOMP:    res = io_ngrp_tedax_parse_acomp(sheet, parent, argc, argv, lineno, rest, rest_len); break;
				case ANET:     res = io_ngrp_tedax_parse_anet(sheet, parent, argc, argv, lineno, rest, rest_len); break;
				default:
					rnd_message(RND_MSG_ERROR, "tEDAx syntax error in '%s':%ld: instruction outside of blocks\n", sheet->hidlib.fullpath, lineno);
					return -1;
			}
			if (res != 0)
				return -1;
		}
	}

	return 0;
}


csch_non_graphical_impl_t io_ngrp_tedax_impl;

int io_ngrp_tedax_load_sheet(FILE *f, const char *fn, const char *fmt, csch_sheet_t *dst)
{
	sch_ngrp_t *ctx = calloc(sizeof(sch_ngrp_t), 1);

	if (ctx == NULL)
		return -1;

	ctx->sheet = dst;
	dst->non_graphical = 1;
	dst->non_graphical_data = ctx;
	dst->non_graphical_impl = &io_ngrp_tedax_impl;

	io_ngrp_tedax_impl.compile_sheet = ngrp_tedax_compile_sheet;
	io_ngrp_tedax_impl.free_sheet = ngrp_tedax_free;
	io_ngrp_tedax_impl.draw_getline = ngrp_tedax_draw_getline;

	if (ngrp_tedax_load_file(dst, fn) != 0) {
		rnd_message(RND_MSG_ERROR, "Failed to load content of file '%s'\n", fn);
		goto error;
	}

	io_ngrp_tedax_parse(dst);

	return 0;

	error:;
	ngrp_tedax_free(dst);
	return -1;
}

int io_ngrp_tedax_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	char line[515], *s;
	int n, fmthdr = 0;

	for(n = 0; n < 2048; n++) {
		s = fgets(line, sizeof(line), f);
		if (s == NULL)
			return -1;
		while(isspace(*s)) s++;
		if (*s == '#')
			continue;
		if (strncmp(s, "tEDAx", 5) == 0) {
			s += 5;
			while(isspace(*s)) s++;
			if ((s[0] == 'v') && (s[1] == '1'))
				fmthdr = 1;
		}

		if (!fmthdr && (n > 32))
			return -1; /* expect file header in the first few lines */

		if (fmthdr && (strncmp(s, "begin", 5) == 0)) {
			/* if we got the file header, look for a known block */
			s += 5;
			while(isspace(*s)) s++;
			if (strncmp(s, "cschem_acompact", 15) == 0) return 0;
			if (strncmp(s, "cschem_acomp", 12) == 0) return 0;
			if (strncmp(s, "cschem_anet", 11) == 0) return 0;
		}
	}

	return -1;
}


