/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019,2022 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 <stdio.h>
#include <ctype.h>
#include <genht/htip.h>
#include <genht/hash.h>

#include <librnd/core/hidlib.h>
#include <librnd/core/conf_multi.h>
#include <librnd/core/error.h>
#include <librnd/core/safe_fs.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/math_helper.h>
#include <librnd/core/rnd_printf.h>

#include "data.h"
#include "plug_io.h"
#include "obj_any.h"
#include "conf_core.h"

#include "gcode_vm.h"
#include "gcode_exec.h"

#include "import_gcode_conf.h"

conf_import_gcode_t conf_import_gcode;
static const char io_gcode_cookie[] = "io_gcode";

#include "conf_internal.c"

typedef struct {
	htip_t h2l; /* height-to-layer */
	const char *fn;
	FILE *fin;
	camv_design_t *camv;
} read_ctx_t;

static int ggetchar(gcode_prg_t *prg)
{
	read_ctx_t *ctx = prg->user_data;
	int chr = fgetc(ctx->fin);
	return chr;
}

static void error(gcode_prg_t *ctx, int runtime, const char *msg)
{
	fprintf(stderr, "g-code %s error:", runtime ? "runtime" : "compile");
	if (ctx->lineno >= 0) {
		fprintf(stderr, " (in N%d)\n", ctx->lineno);
		ctx->lineno = -1;
	}
	fprintf(stderr, "%s\n", msg);
}

static camv_layer_t *get_layer(gcode_prg_t *prg, double height_)
{
	read_ctx_t *ctx = prg->user_data;
	long height = height_ * 1000;
	camv_layer_t *ly;

	if ((height_ < -1000) || (height_ > +1000))
		error(prg, 1, "Error: board too thick");

	ly = htip_get(&ctx->h2l, height);
	if (ly == NULL) {
		const char *sh;
		ly = camv_layer_new();
		ly->name = rnd_strdup_printf("%s/%dum", ctx->fn, height);
		sh = strrchr(ctx->fn, '/');
		if (sh == NULL)
			sh = ctx->fn;
		else
			sh++;
		ly->short_name = rnd_strdup_printf("%s/%dum", sh, height);
		camv_layer_invent_color(ctx->camv, ly);
		camv_layer_append_to_design(ctx->camv, ly);
		htip_set(&ctx->h2l, height, ly);
	}

	return ly;
}

static void travel(gcode_prg_t *prg, double x1, double y1, double z1, double x2, double y2, double z2)
{
	printf("TRAVEL %f;%f;%f -> %f;%f;%f\n", x1, y1, z1,  x2, y2, z2);
}

static void linear(gcode_prg_t *prg, double x1, double y1, double z1, double x2, double y2, double z2)
{
/*	read_ctx_t *ctx = prg->user_data;*/
	int in_place = (x1 == x2) && (y1 == y2);
	camv_layer_t *ly;
	camv_any_obj_t *o;

	if (z1 != z2) {
		if (!in_place)
			error(prg, 1, "Error: only horizontal or vertical move allowed");
		return;
	}

	ly = get_layer(prg, z1);
	o = (camv_any_obj_t *)camv_line_new();
	o->line.x1 = RND_MM_TO_COORD(x1); o->line.y1 = RND_MM_TO_COORD(y1);
	o->line.x2 = RND_MM_TO_COORD(x2); o->line.y2 = RND_MM_TO_COORD(y2);
TODO("implement tools");
	o->line.thick = 1;
	camv_obj_add_to_layer(ly, o);
	
	printf("LINEAR %f;%f;%f -> %f;%f;%f\n", x1, y1, z1, x2, y2, z2);
}

static gcode_execute_op_t ops = {travel, linear};

static int camv_gcode_load(camv_design_t *camv, const char *fn, FILE *fin)
{
	gcode_prg_t prg;
	read_ctx_t ctx;

	memset(&prg, 0, sizeof(prg));
	prg.user_data = &ctx;
	ctx.camv = camv;
	ctx.fn = fn;
	ctx.fin = fin;
	htip_init(&ctx.h2l, longhash, longkeyeq);

	prg.get_char = ggetchar;
	prg.error = error;
	if (gcodeparse(&prg) != 0)
		return 1;

	prg.cfg.laser = conf_import_gcode.plugins.import_gcode.laser;
	gcode_execute_init(&prg, &ops);
	gcode_execute(&prg);
	gcode_execute_uninit(&prg);

	htip_uninit(&ctx.h2l);
	return 0;
}

static int camv_gcode_test_load(camv_design_t *camv, const char *fn, FILE *f)
{
	char *line, line_[1024];
	int bad = 0;
	
	while((line = fgets(line_, sizeof(line_), f)) != NULL) {
		while(isspace(*line)) line++;
		if (*line == '(')
			continue;
		if ((strstr(line, "G20") != NULL) || (strstr(line, "G21") != NULL))
			return 1;
		bad++;
		if (bad > 16)
			return 0;
	}
	return 0;
}


static camv_io_t io_gcode = {
	"gcode", 80,
	camv_gcode_test_load,
	camv_gcode_load,
	NULL
};


int pplg_check_ver_import_gcode(int ver_needed) { return 0; }

void pplg_uninit_import_gcode(void)
{
	rnd_conf_plug_unreg("plugins/import_gcode/", import_gcode_conf_internal, io_gcode_cookie);
	camv_io_unreg(&io_gcode);
}

int pplg_init_import_gcode(void)
{
	camv_io_reg(&io_gcode);

	rnd_conf_plug_reg(conf_import_gcode, import_gcode_conf_internal, io_gcode_cookie);

#define conf_reg(field,isarray,type_name,cpath,cname,desc,flags) \
	rnd_conf_reg_field(conf_import_gcode, field,isarray,type_name,cpath,cname,desc,flags);
#include "import_gcode_conf_fields.h"

	return 0;
}
