/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019 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/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include <librnd/config.h>

#include <stdlib.h>
#include <stdio.h>

#include <genht/htsp.h>
#include <genht/hash.h>

#include "camv_typedefs.h"
#include "plug_io.h"
#include "data.h"
#include <librnd/core/error.h>
#include "parse.h"
#include <librnd/core/safe_fs.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/misc_util.h>
#include "obj_any.h"

typedef struct {
	camv_design_t *camv;
	FILE *f;
	char buf[514];
	char *argv[510];
	htsp_t grps; /* group name -> temporary layer */
	long lypos;
} read_ctx_t;

#define coord_conv(dst, argnum, err) \
do { \
	rnd_bool succ; \
	dst = rnd_get_value(argv[argnum], unit, NULL, &succ); \
	if (!succ) { \
		rnd_message(RND_MSG_ERROR, "invalid coordinate value\n"); \
		err; \
	} \
} while(0)

#define double_conv(dst, argnum, err) \
do { \
	char *end; \
	dst = strtod(argv[argnum], &end); \
	if (*end != '\0') { \
		rnd_message(RND_MSG_ERROR, "invalid numeric value\n"); \
		err; \
	} \
} while(0)

static void copy_grp(read_ctx_t *ctx, camv_grp_t *dst, camv_layer_t *src)
{
	rnd_cardinal_t n;
	camv_rtree_it_t it;
	void *o;

	dst->len = src->objs.size;
	dst->objs = malloc(sizeof(camv_any_obj_t) * dst->len);

	for(o = camv_rtree_all_first(&it, &src->objs), n = 0; o != NULL; o = camv_rtree_all_next(&it), n++) {
		camv_any_obj_t *sobj = o;
		assert(n < dst->len);
		sobj->proto.calls->copy(&dst->objs[n], sobj);
	}
}

static int tdx_parse_layer(read_ctx_t *ctx, camv_layer_t *ly_main, rnd_bool is_grp)
{
	char **argv = ctx->argv;
	int argc, poly_more = 0, res = 0;
	char unit[8];
	int clearing = 0, has_color = 0;
	camv_layer_t *ly = ly_main;
	vtc0_t vert = {0};

	strcpy(unit, "mm");

	for(;;) {
		if ((argc = tedax_getline(ctx->f, ctx->buf, sizeof(ctx->buf), ctx->argv, sizeof(ctx->argv) / sizeof(ctx->argv[0]))) < 0)
			goto err;

		if (strcmp(argv[0], "end") == 0) {
			if (!is_grp && (strcmp(argv[1], "camv_layer") == 0))
				return 0;
			if (is_grp && (strcmp(argv[1], "camv_grp") == 0))
				return 0;
			rnd_message(RND_MSG_ERROR, "invalid end tag\n");
			goto err;
		}

		else if (strcmp(argv[0], "unit") == 0) {
			if ((strcmp(argv[1], "m") == 0) || (strcmp(argv[1], "mm") == 0) || (strcmp(argv[1], "inch") == 0) || (strcmp(argv[1], "mil") == 0))
				strcpy(unit, argv[1]);
			else {
				rnd_message(RND_MSG_ERROR, "invalid unit\n");
				goto err;
			}
		}

		else if (strcmp(argv[0], "grp") == 0) {
			camv_layer_t *gl;
			camv_grp_t *grp;

			if (argc != 2) {
				rnd_message(RND_MSG_ERROR, "invalid number of grp arguments\n");
				goto err;
			}
			gl = htsp_get(&ctx->grps, argv[1]);
			if (gl == NULL) {
				rnd_message(RND_MSG_ERROR, "group '%s' does not exist ref\n", argv[1]);
				goto err;
			}
			grp = camv_grp_new();
			copy_grp(ctx, grp, gl);
			camv_obj_add_to_layer(ly, (camv_any_obj_t *)grp);
		}

		else if (strcmp(argv[0], "color") == 0) {
			if (argc != 2) {
				rnd_message(RND_MSG_ERROR, "invalid number of color arguments\n");
				goto err;
			}
			if (has_color) {
				rnd_message(RND_MSG_ERROR, "only one color per layer can be set\n");
				goto err;
			}
			if (is_grp) {
				rnd_message(RND_MSG_ERROR, "groups can not change color\n");
				goto err;
			}
			if (rnd_color_load_str(&ly->color, argv[1]) != 0) {
				rnd_message(RND_MSG_ERROR, "invalid color string\n");
				goto err;
			}
			has_color = 1;
		}

		else if (strcmp(argv[0], "polarity") == 0) {
			int new_clearing;
			if (argc != 2) {
				rnd_message(RND_MSG_ERROR, "invalid number of polarity arguments\n");
				goto err;
			}
			if (is_grp) {
				rnd_message(RND_MSG_ERROR, "groups can't change polarity\n");
				goto err;
			}
			if (strcmp(argv[1], "clear") == 0) new_clearing = 1;
			else if (strcmp(argv[1], "draw") == 0) new_clearing = 0;
			else {
				rnd_message(RND_MSG_ERROR, "invalid polarity value\n");
				goto err;
			}
			if (!has_color) {
				camv_layer_invent_color(ctx->camv, ly_main);
				has_color = 1;
			}
			if (clearing != new_clearing) {
				clearing = new_clearing;
				ly = camv_layer_new();
				ly->sub = 1;
				ly->clearing = clearing;
				ly->color = ly_main->color;
				ctx->lypos = camv_sublayer_append_after(ctx->camv, ly, ctx->lypos);
			}
		}

		else if (strcmp(argv[0], "line") == 0) {
			rnd_coord_t x1, y1, x2, y2, th;
			camv_line_t *line;

			if (argc != 6) {
				rnd_message(RND_MSG_ERROR, "invalid number of line arguments\n");
				goto err;
			}
			coord_conv(x1, 1, return -1);
			coord_conv(y1, 2, return -1);
			coord_conv(x2, 3, return -1);
			coord_conv(y2, 4, return -1);
			coord_conv(th, 5, return -1);
			line = camv_line_new();
			line->x1 = x1; line->y1 = y1;
			line->x2 = x2; line->y2 = y2;
			line->thick = th;
			camv_obj_add_to_layer(ly, (camv_any_obj_t *)line);
		}

		else if (strcmp(argv[0], "arc") == 0) {
			rnd_coord_t cx, cy, r, th;
			double start, delta;
			camv_arc_t *arc;

			if (argc != 7) {
				rnd_message(RND_MSG_ERROR, "invalid number of arc arguments\n");
				goto err;
			}
			coord_conv(cx, 1, return -1);
			coord_conv(cy, 2, return -1);
			coord_conv(r, 3, return -1);
			coord_conv(th, 4, return -1);
			double_conv(start, 5, return -1);
			double_conv(delta, 6, return -1);
			arc = camv_arc_new();
			arc->cx = cx; arc->cy = cy; arc->r = r;
			arc->thick = th;
			arc->start = start - 180;
			arc->delta = -delta;
			camv_obj_add_to_layer(ly, (camv_any_obj_t *)arc);
		}

		else if (strcmp(argv[0], "poly") == 0) {
			int i, n, numcrd;

			numcrd = argc-1;
			if ((numcrd % 2) != 0) {
				rnd_message(RND_MSG_ERROR, "need even number of coords in polygon\n");
				goto err;
			}

			poly_more = 0;
			for(n = 1, i = 0; n < argc; n += 2, i++) {
				rnd_coord_t x, y;
				if ((strcmp(argv[n], "more") == 0) && (strcmp(argv[n+1], "below") == 0)) {
				 poly_more = 1;
				 break;
				}
				coord_conv(x, n, goto poly_err);
				coord_conv(y, n+1, goto poly_err);
				vtc0_append(&vert, x);
				vtc0_append(&vert, y);
			}


			if (!poly_more) {
				camv_poly_t *poly;
				if (vert.used < 6) {
					rnd_message(RND_MSG_ERROR, "too few polygon coords\n");
					goto err;
				}

				poly = camv_poly_new();
				camv_poly_allocpts(poly, vert.used/2);
				for(n = 0, i = 0; n < vert.used; n += 2, i++) {
					poly->x[i] = vert.array[n];
					poly->y[i] = vert.array[n+1];
				}

				camv_obj_add_to_layer(ly, (camv_any_obj_t *)poly);
				vert.used = 0;
			}
		}
		
		else {
			rnd_message(RND_MSG_ERROR, "Invalid tEDAx line: '%s'\n", argv[0]);
			goto err;
		}
	}

	if (poly_more != 0) {
		rnd_message(RND_MSG_ERROR, "unfinished polygon (last poly in layer ends in 'more below')\n");
		res = -1;
	}
	if (0) {
		poly_err:;
		rnd_message(RND_MSG_ERROR, "polygon coordinate conversion error\n");
		err:;
		res = -1;
	}
	vtc0_uninit(&vert);
	return res;
}

int camv_tdx_load(camv_design_t *camv, const char *fn, FILE *f)
{
	read_ctx_t ctx;
	camv_layer_t *ly;
	int load_res = 0, found_one = 0;
	rnd_cardinal_t first_new;
	long n; /* must be signed */
	htsp_entry_t *e;

	ctx.camv = camv;
	ctx.f = f;

	htsp_init(&ctx.grps, strhash, strkeyeq);

	while(tedax_seek_block(ctx.f, "camv_grp", "v1", NULL, 1, ctx.buf, sizeof(ctx.buf), ctx.argv, sizeof(ctx.argv)/sizeof(ctx.argv[0])) >= 0) {
		if (htsp_has(&ctx.grps, ctx.argv[3])) {
			rnd_message(RND_MSG_ERROR, "error: duplicate group: '%s'\n", ctx.argv[3]);
			load_res = -1;
			goto error;
		}
		ly = camv_layer_new();
		ly->name = rnd_strdup(ctx.argv[3]);
		if (tdx_parse_layer(&ctx, ly, 1) != 0) {
			load_res = -1;
			goto error;
		}
		htsp_set(&ctx.grps, ly->name, ly);
	}

	rewind(ctx.f);

	first_new = camv->layers.used;
	while(tedax_seek_block(ctx.f, "camv_layer", "v1", NULL, 1, ctx.buf, sizeof(ctx.buf), ctx.argv, sizeof(ctx.argv)/sizeof(ctx.argv[0])) >= 0) {
		found_one = 1;
		ly = camv_layer_new();
		ly->name = rnd_strdup(ctx.argv[3]);
		ctx.lypos = camv_layer_append_to_design(camv, ly);
		if (tdx_parse_layer(&ctx, ly, 0) != 0) {
			load_res = -1;
			/* free all layers created by this load attempt */
			for(n = camv->layers.used - 1; n >= first_new; n--)
				camv_layer_destroy(camv->layers.array[n]);
			goto error;
		}
	}

	/* probably not a tedax file */
	if (!found_one) {
		load_res = -1;
		rnd_message(RND_MSG_ERROR, "error: no camv layer found in '%s'\n", fn);
	}

	error:;
	for(e = htsp_first(&ctx.grps); e != NULL; e = htsp_next(&ctx.grps, e))
		camv_layer_destroy(e->value);
	htsp_uninit(&ctx.grps);
	return load_res;
}

/*** write ***/

typedef struct {
	camv_design_t *camv;
	FILE *f;
	int err;
} write_ctx_t;

static int tdx_open_write_header(camv_design_t *camv, write_ctx_t *ctx, const char *fn)
{
	rnd_printf_slot[4] = "%.06mm";
	ctx->camv = camv;
	ctx->f = rnd_fopen((rnd_design_t *)ctx->camv, fn, "w");
	ctx->err = 0;
	if (ctx->f == NULL) {
		rnd_message(RND_MSG_ERROR, "Can not open '%s' for write\n", fn);
		return -1;
	}
	fprintf(ctx->f, "tEDAx v1\n\n");
	return 0;
}

static int tdx_close_write_header(write_ctx_t *ctx)
{
	fclose(ctx->f);
	return ctx->err;
}

static void tdx_write_obj(write_ctx_t *ctx, const camv_any_obj_t *obj)
{
	rnd_cardinal_t n;
	int len;

	switch(obj->proto.type) {
		case CAMV_OBJ_TEXT: break; /* shouldn't happen */
		case CAMV_OBJ_ARC:  rnd_fprintf(ctx->f, "\tarc %[4] %[4] %[4] %[4] %f %f\n", obj->arc.cx, obj->arc.cy, obj->arc.r, obj->arc.thick, obj->arc.start+180, -obj->arc.delta); break;
		case CAMV_OBJ_LINE: rnd_fprintf(ctx->f, "\tline %[4] %[4] %[4] %[4] %[4]\n", obj->line.x1, obj->line.y1, obj->line.x2, obj->line.y2, obj->line.thick); break;
		case CAMV_OBJ_GRP:  fprintf(ctx->f, "\tgrp %p\n", (void *)obj); break;
		case CAMV_OBJ_POLY:

			for(len = n = 0; n < obj->poly.len; n++) {
				if (len == 0)
					len = fprintf(ctx->f, "\tpoly");
				len += rnd_fprintf(ctx->f, " %[4] %[4]", obj->poly.x[n], obj->poly.y[n]);
				if (len > 480) {
					if (n < obj->poly.len-1)
						rnd_fprintf(ctx->f, " more below\n");
					len = 0;
				}
			}
			fprintf(ctx->f, "\n");
			break;
		case CAMV_OBJ_max:
		case CAMV_OBJ_invalid:
			assert(!"invalid object");
			break;
	}
}

static void tdx_write_grp(write_ctx_t *ctx, const camv_grp_t *grp)
{
	rnd_cardinal_t n;
	fprintf(ctx->f, "begin camv_grp v1 %p\n", (void *)grp);
	for(n = 0; n < grp->len; n++) {
		assert(grp->objs[n].proto.type != CAMV_OBJ_GRP); /* do not allow group-in-group */
		tdx_write_obj(ctx, &grp->objs[n]);
	}
	fprintf(ctx->f, "end camv_grp\n\n");
}

static void tdx_write_layer_grps(write_ctx_t *ctx, const camv_layer_t *ly)
{
	void *o;
	camv_rtree_it_t it;

	for(o = camv_rtree_all_first(&it, &ly->objs); o != NULL; o = camv_rtree_all_next(&it)) {
		camv_any_obj_t *obj = o;
		if (obj->proto.type != CAMV_OBJ_GRP)
			continue;
		tdx_write_grp(ctx, &obj->grp);
	}
}

static void tdx_write_layer_head(write_ctx_t *ctx, const camv_layer_t *ly)
{
	fprintf(ctx->f, "begin camv_layer v1 ");
	tedax_fprint_escape(ctx->f, ly->name);
	fprintf(ctx->f, "\n");
	fprintf(ctx->f, "\tcolor %s\n", ly->color.str);
}

static void tdx_write_layer_foot(write_ctx_t *ctx, const camv_layer_t *ly)
{
	fprintf(ctx->f, "end camv_layer\n\n");
}

static void tdx_write_layer(write_ctx_t *ctx, const camv_layer_t *ly)
{
	void *o;
	camv_rtree_it_t it;

	if (ly->clearing)
		fprintf(ctx->f, "\tpolarity clear\n");
	else
		fprintf(ctx->f, "\tpolarity draw\n");

	for(o = camv_rtree_all_first(&it, &ly->objs); o != NULL; o = camv_rtree_all_next(&it))
		tdx_write_obj(ctx, o);
}


static int camv_tdx_save_sublayer(camv_design_t *camv, const camv_layer_t *ly, const char *fn)
{
	write_ctx_t ctx;

	if (tdx_open_write_header(camv, &ctx, fn) != 0)
		return -1;

	tdx_write_layer_grps(&ctx, ly);

	tdx_write_layer_head(&ctx, ly);
	tdx_write_layer(&ctx, ly);
	tdx_write_layer_foot(&ctx, ly);

	return tdx_close_write_header(&ctx);
}


static int camv_tdx_save_layer(camv_design_t *camv, rnd_cardinal_t lid, const char *fn)
{
	write_ctx_t ctx;
	rnd_cardinal_t n;
	const camv_layer_t *ly;

	if (tdx_open_write_header(camv, &ctx, fn) != 0)
		return -1;

	for(n = lid; n < camv->layers.used; n++) {
		ly = camv->layers.array[n];
		if ((n > lid) && (!ly->sub)) break;
		tdx_write_layer_grps(&ctx, ly);
	}

	ly = camv->layers.array[lid];
	tdx_write_layer_head(&ctx, ly);
	for(n = lid; n < camv->layers.used; n++) {
		ly = camv->layers.array[n];
		if ((n > lid) && (!ly->sub)) break;

		tdx_write_layer(&ctx, ly);
	}
	tdx_write_layer_foot(&ctx, ly);

	return tdx_close_write_header(&ctx);
}


static int camv_tdx_save_design(camv_design_t *camv, const char *fn)
{
	write_ctx_t ctx;
	long n, end, i;
	int open_ly = 0;
	const camv_layer_t *ly;

	if (tdx_open_write_header(camv, &ctx, fn) != 0)
		return -1;

	for(n = 0; n < camv->layers.used; n++)
		tdx_write_layer_grps(&ctx, camv->layers.array[n]);

	/* write layers in reverse order so the file is top->bottom */
	end = camv->layers.used;
	for(n = camv->layers.used-1; n >= 0; n--) {
		ly = camv->layers.array[n];
		if (!ly->sub) {
			if (open_ly)
				tdx_write_layer_foot(&ctx, ly);
			tdx_write_layer_head(&ctx, ly);
			open_ly = 1;
			for(i = n; i < end; i++)
				tdx_write_layer(&ctx, camv->layers.array[i]);
			end = n;
		}
	}

	if (open_ly)
		tdx_write_layer_foot(&ctx, ly);

	return tdx_close_write_header(&ctx);
}

static int camv_tdx_test_load(camv_design_t *camv, const char *fn, FILE *f)
{
	char *line, line_[1024];

	while((line = fgets(line_, sizeof(line_), f)) != NULL) {
		while(isspace(*line)) line++;
		if (*line == '#')
			continue;

		if (strncmp(line, "tEDAx", 5) != 0)
			return 0;
		line += 5;
		while(isspace(*line)) line++;
		if (strncmp(line, "v1", 2) != 0)
			return 0;
		if (isalnum(line[2]))
			return 0;
		return 1;
	}
	return 0;
}

static int camv_tdx_save_prio(const char *fn, const char *fmt, camv_io_type_t type)
{
	if ((type & CAMV_IOTYP_DESIGN) == 0)
		return -1;

	if ((rnd_strcasecmp(fmt, "tdx") != 0) && (rnd_strcasecmp(fmt, "tedax") != 0))
		return -1;

	return 100;
}

static camv_io_t io_tdx = {
	"tEDAx camv layer", 100,
	camv_tdx_test_load,
	camv_tdx_load,
	camv_tdx_save_prio,
	camv_tdx_save_design,
	"tdx"
};


int pplg_check_ver_io_tedax(int ver_needed) { return 0; }

void pplg_uninit_io_tedax(void)
{
	camv_io_unreg(&io_tdx);
}

int pplg_init_io_tedax(void)
{
	camv_io_reg(&io_tdx);
	return 0;
}
