/*
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/pcb-rnd
 *    lead developer: http://repo.hu/projects/pcb-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include "config.h"

#include <genvector/gds_char.h>

#include "board.h"
#include "data.h"
#include "layer_ui.h"
#include <librnd/core/error.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/misc_util.h>

#include "layer_addr.h"

char *pcb_parse_layergrp_err = "";
char *pcb_parse_layergrp_address(char *curr, char **spk, char **spv, int *spc)
{
	char *s, *lasta, *eq;
	int level = 0, trmax = *spc;
	*spc = 0;

	for(s = curr; *s != '\0'; s++) {
		switch(*s) {
			case '(':
				if (level == 0) {
					*s = '\0';
					lasta = s+1;
				}
				level++;
				break;
			case ')':
				if (level > 0)
					level--;
				if (level == 0)
					goto append;
				break;
			case ',':
				if (level == 0)
					goto out;
				append:;
				*s = '\0';
				if (*spc >= trmax)
					return pcb_parse_layergrp_err;
				lasta = rnd_str_strip(lasta);
				spk[*spc] = lasta;
				eq = strchr(lasta, '=');
				if (eq != NULL) {
					*eq = '\0';
					eq++;
				}
				spv[*spc] = eq;
				(*spc)++;
				*s = '\0';
				lasta = s+1;
		}
	}

	if (level > 0)
		return pcb_parse_layergrp_err;

	out:;

	if (*s != '\0') {
		*s = '\0';
		s++;
		return s;
	}

	return NULL; /* no more layers */
}

static int parse_layer_type(const char *type, pcb_layer_type_t *lyt, int *offs, int *has_offs)
{
	const char *soffs, *nxt, *cur;
	char *end;
	pcb_layer_type_t l;

	*lyt = 0;
	*offs = *has_offs = 0;

	soffs = strchr(type, ':');
	if (soffs != NULL) {
		*offs = strtol(soffs+1, &end, 10);
		if (*end != '\0') {
			rnd_message(RND_MSG_ERROR, "CAM rule: invalid offset '%s'\n", soffs);
			return 1;
		}
		*has_offs = 1;
	}

	for(cur = type; cur != NULL; cur = nxt) {
		nxt = strpbrk(cur, "-:(/");
		if (nxt != NULL)
			l = pcb_layer_type_strn2bit(cur, nxt - cur);
		else
			l = pcb_layer_type_str2bit(cur);
		if (nxt != NULL) {
			if (*nxt != '-')
				nxt = NULL;
			else
				nxt++;
		}
		if (l == 0) {
			rnd_message(RND_MSG_ERROR, "CAM rule: invalid layer type '%s'\n", cur);
			return 1;
		}
		(*lyt) |= l;
	}
	return 0;
}

void pcb_parse_layer_supplements(char **spk, char **spv, int spc,   char **purpose, rnd_xform_t **xf, rnd_xform_t *xf_)
{
	int n;

	*purpose = NULL;
	if (xf_ != NULL)
		memset(xf_, 0, sizeof(rnd_xform_t));

	for(n = 0; n < spc; n++) {
		char *key = spk[n], *val = spv[n];

		if (strcmp(key, "purpose") == 0)
			*purpose = val;
		else if (strcmp(key, "bloat") == 0) {
			rnd_bool succ;
			double v = rnd_get_value(val, NULL, NULL, &succ);
			if (succ) {
				if (xf_ != NULL)
					xf_->bloat = v;
				if (xf != NULL)
					*xf = xf_;
			}
			else
				rnd_message(RND_MSG_ERROR, "CAM: ignoring invalid layer supplement value '%s' for bloat\n", val);
		}
		else if (strcmp(key, "faded") == 0) {
			if (xf_ != NULL) xf_->layer_faded = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "partial") == 0) {
			if (xf_ != NULL) xf_->partial_export = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "wireframe") == 0) {
			if (xf_ != NULL) xf_->wireframe = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "thin_draw") == 0) {
			if (xf_ != NULL) xf_->thin_draw = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "thin_draw_poly") == 0) {
			if (xf_ != NULL) xf_->thin_draw_poly = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "check_planes") == 0) {
			if (xf_ != NULL) xf_->check_planes = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "flag_color") == 0) {
			if (xf_ != NULL) xf_->flag_color = 1;
			if (xf != NULL) *xf = xf_;
		}
		else if (strcmp(key, "hide_floaters") == 0) {
			if (xf_ != NULL) xf_->hide_floaters = 1;
			if (xf != NULL) *xf = xf_;
		}
		else
			rnd_message(RND_MSG_ERROR, "CAM: ignoring unknown layer supplement key '%s'\n", key);
	}
}


int pcb_layergrp_list_by_addr(pcb_board_t *pcb, const char *curr, rnd_layergrp_id_t gids[PCB_MAX_LAYERGRP], char **spk, char **spv, int spc, int *vid, rnd_xform_t **xf, rnd_xform_t *xf_in, const char *err_prefix)
{
	rnd_layergrp_id_t gid, lgids[PCB_MAX_LAYERGRP];
	int gids_max = PCB_MAX_LAYERGRP;
	char *purpose;

	if (vid != NULL)
		*vid = -1;

	if (*curr == '#') {
		char *end;
		gid = strtol(curr+1, &end, 10);
		if ((*end == '\0') && (gid >= 0) && (gid < pcb->LayerGroups.len)) {
			gids[0] = gid;
			return 1;
		}
		if (err_prefix != NULL)
			rnd_message(RND_MSG_ERROR, "%sinvalid layer group number\n", err_prefix);
		return -1;
	}

	if (*curr == '@') {
		/* named layer group */
		curr++;
		gid = pcb_layergrp_by_name(pcb, curr);
		if (gid < 0) {
			if (vid != NULL) {
				const pcb_virt_layer_t *v;
				int n;
				pcb_parse_layer_supplements(spk, spv, spc, &purpose, xf, xf_in);
				for(n = 0, v = pcb_virt_layers; v->name != NULL; n++,v++) {
					if (strcmp(v->name, curr) == 0) {
						*vid = n;
						return 0;
					}
				}
			}
			return 0;
		}
		if (gid < 0) {
			if (err_prefix != NULL)
				rnd_message(RND_MSG_ERROR, "%sno such layer group '%s'\n", curr, err_prefix);
			return -1;
		}
		if (pcb->LayerGroups.grp[gid].len <= 0)
			return 0;
		pcb_parse_layer_supplements(spk, spv, spc, &purpose, xf, xf_in);
		gids[0] = gid;
		return 1;
	}
	else {
		/* by layer type */
		int offs, has_offs;
		pcb_layer_type_t lyt;
		const pcb_virt_layer_t *vl;

		if (parse_layer_type(curr, &lyt, &offs, &has_offs) != 0)
			return -1;

		pcb_parse_layer_supplements(spk, spv, spc, &purpose, xf, xf_in);

		vl = pcb_vlayer_get_first(lyt, purpose, -1);
		if ((lyt & PCB_LYT_VIRTUAL) && (vl == NULL)) {
			if (err_prefix != NULL)
				rnd_message(RND_MSG_ERROR, "%sno virtual layer with purpose '%s'\n", err_prefix, purpose);
			return -1;
		}
		if (vl == NULL) {
			if (has_offs) {
				int len = pcb_layergrp_listp(pcb, lyt, lgids, sizeof(lgids)/sizeof(lgids[0]), -1, purpose);
				if (offs < 0)
					offs = len + offs;
				else
					offs--;
				if ((offs >= 0) && (offs < len)) {
					gids[0] = lgids[offs];
					return 1;
				}
			}
			else {
				return pcb_layergrp_listp(pcb, lyt, gids, gids_max, -1, purpose);
			}
		}
		else {
			if (vid != NULL)
				*vid = vl->new_id - PCB_LYT_VIRTUAL - 1;
		}
	}
	return 0;
}

static rnd_layer_id_t layer_str2id_data(pcb_board_t *pcb, pcb_data_t *data, const char *str)
{
	char *end;
	rnd_layer_id_t id;

	if (*str == '#') {
		id = strtol(str+1, &end, 10);
		if ((*end == '\0') && (id >= 0) && (id < data->LayerN))
			return id;
		if (pcb_uilayer_get(id) != NULL)
			return id;
	}
	if (*str == '@') {
		for(id = 0; id < data->LayerN; id++) {
			pcb_layer_t *ly = pcb_get_layer(data, id);
			if (ly == NULL) continue;
			if (strcmp(ly->name, str+1) == 0)
				return id;
		}
	}
	if (*str == '&') {
		str++;
		if (rnd_strcasecmp(str, "current") == 0)
			return PCB_CURRLID(pcb);
	}
	return -1;
}

static rnd_layer_id_t layer_str2id_grp(pcb_board_t *pcb, pcb_layergrp_t *grp, const char *str)
{
	char *end;
	long id, n;

	if (*str == '#') {
		str++;
		id = strtol(str, &end, 10);
		if ((*end == '+') || (*end == '-')) { /* search the nth positive or negative layer, count from 1 */
			int pos = (id > 0);
			if (!pos) id = -id;
			n = pos ? 0 : grp->len-1;
			for(; (pos ? (n < grp->len) : (n >= 0));) {
				pcb_layer_t *ly = pcb_get_layer(pcb->Data, grp->lid[n]);
				if (ly == NULL) continue;
				if ((*end == '-') && (ly->comb & PCB_LYC_SUB)) id--;
				else if ((*end == '+') && !(ly->comb & PCB_LYC_SUB)) id--;
				if (id == 0)
					return grp->lid[n];
				if (pos) n++;
				else n--;
			}
			return -1; /* not enough layers */
		}
		/* simply the nth layer ID, counting from 1 */
		if ((*end == '\0') && (id > 0) && (id <= grp->len))
			return grp->lid[id - 1];
		if ((*end == '\0') && (id < 0) && (id >= -((long)grp->len)))
			return grp->lid[id + grp->len];
		return -1;
	}
	if (*str == '@') {
		for(n = 0; n < grp->len; n++) {
			pcb_layer_t *ly = pcb_get_layer(pcb->Data, grp->lid[n]);
			if (ly == NULL) continue;
			if (strcmp(ly->name, str+1) == 0)
				return grp->lid[n];
		}
	}
	return -1;
}

rnd_layer_id_t pcb_layer_str2id(pcb_data_t *data, const char *str)
{
	char *sep;
	pcb_board_t *pcb = NULL;

	if (data->parent_type == PCB_PARENT_BOARD)
		pcb = data->parent.board;

	sep = strchr(str, '/');
	if (sep != NULL) {
		rnd_layergrp_id_t gid;
		pcb_layergrp_t *grp;
		if (pcb == NULL)
			return -1; /* group addressing works only on a board */
		gid = pcb_layergrp_str2id(pcb, str);
		grp = pcb_get_layergrp(pcb, gid);
		if (grp == NULL)
			return -1; /* invalid group */
		return layer_str2id_grp(pcb, grp, sep+1);
	}

	return layer_str2id_data(pcb, data, str);
}

rnd_layergrp_id_t pcb_layergrp_str2id(pcb_board_t *pcb, const char *str)
{
	char *end, *tmp = NULL;
	const char *curr;
	rnd_layer_id_t gid = -1;
	char *spk[64], *spv[64];
	int spc = 0, numg;
	rnd_layergrp_id_t gids[PCB_MAX_LAYERGRP];

	if (*str == '#') {
		gid = strtol(str+1, &end, 10);
		if ((*end != '\0') || (gid < 0) || (gid >= pcb->LayerGroups.len))
			return -1;
		return gid;
	}

	if (strchr(str, '(') != NULL) {
		curr = tmp = rnd_strdup(str);
		spc = sizeof(spk) / sizeof(spk[0]);
		end = pcb_parse_layergrp_address(tmp, spk, spv, &spc);
		if (end != NULL) {
			free(tmp);
			return -1;
		}
	}
	else
		curr = str;

	numg = pcb_layergrp_list_by_addr(pcb, curr, gids, spk, spv, spc, NULL, NULL, NULL, NULL);
	if (numg > 0)
		gid = gids[0];

	free(tmp);
	return gid;
}

/*** layer/group to addr ***/

static void addr_append_loc_type(gds_t *res, pcb_layer_type_t mask)
{
	const char *tmp;
	tmp = pcb_layer_type_bit2str(mask & PCB_LYT_ANYWHERE);
	if (tmp != NULL) {
		gds_append_str(res, tmp);
		gds_append(res, '-');
	}
	tmp = pcb_layer_type_bit2str(mask & PCB_LYT_ANYTHING);
	if (tmp != NULL)
		gds_append_str(res, tmp);
}

/* append loc-type:offs */
static int pcb_layergrp_append_to_addr_offs(pcb_board_t *pcb, pcb_layergrp_t *grp, gds_t *dst)
{
	char buf[64];
	long n, np = 0, nt = 0, offs, found = 0;
	pcb_layer_type_t mask = grp->ltype & (PCB_LYT_ANYWHERE | PCB_LYT_ANYTHING);

	addr_append_loc_type(dst, grp->ltype);

	for(n = 0; n < pcb->LayerGroups.len; n++) {
		pcb_layergrp_t *ng = &pcb->LayerGroups.grp[n];
		if (ng == grp)
			found = 1;
		if ((ng->ltype & (PCB_LYT_ANYWHERE | PCB_LYT_ANYTHING)) == mask) {
			if (!found)
				np++;
			nt++;
		}
	}
	/* np is the number of matching layer groups before grp, nt is the number
	   of matching groups total */
	if (mask & PCB_LYT_BOTTOM)
		offs = -(nt-np); /* bottom groups are counted from the bottom */
	else if (mask & PCB_LYT_TOP)
		offs = np+1; /* top groups are counted from top */
	else if (mask & PCB_LYT_INTERN) { /* intern groups are counted from the closer side */
		if (np < nt/2)
			offs = np+1;
		else
			offs = -(nt-np);
	}
	else { /* global: count from "top" */
		offs = np+1;
	}
	sprintf(buf, ":%ld", offs);
	gds_append_str(dst, buf);
	return 0;
}

/* append loc-type(purpose=...) */
static int pcb_layergrp_append_to_addr_purp(pcb_board_t *pcb, pcb_layergrp_t *grp, gds_t *dst)
{
	addr_append_loc_type(dst, grp->ltype);
	if (grp->purpose != NULL) {
		gds_append_str(dst, "(purpose=");
		gds_append_str(dst, grp->purpose);
		gds_append(dst, ')');
	}
	return 0;
}

int pcb_layergrp_append_to_addr(pcb_board_t *pcb, pcb_layergrp_t *grp, gds_t *dst)
{
	if (PCB_LAYER_IN_STACK(grp->ltype))
		return pcb_layergrp_append_to_addr_offs(pcb, grp, dst);
	return pcb_layergrp_append_to_addr_purp(pcb, grp, dst);
}

int pcb_layer_append_to_addr(pcb_board_t *pcb, pcb_layer_t *ly, gds_t *dst)
{
	rnd_layer_id_t lid;
	pcb_layergrp_t *grp;
	long n;
	char buf[64];

	if (ly->is_bound)
		return -1;

	grp = pcb_get_layergrp(pcb, ly->meta.real.grp);
	if (grp == NULL)
		return -1;

	lid = ly - pcb->Data->Layer;

	for(n = 0; n < grp->len; n++) {
		if (grp->lid[n] == lid) {
			pcb_layergrp_append_to_addr(pcb, grp, dst);
			sprintf(buf, "/#%ld", n);
			gds_append_str(dst, buf);
			return 0;
		}
	}

	return -1;
}

char *pcb_layergrp_to_addr(pcb_board_t *pcb, pcb_layergrp_t *grp)
{
	gds_t res;
	gds_init(&res);

	if (pcb_layergrp_append_to_addr(pcb, grp, &res) == 0)
		return res.array;

	gds_uninit(&res);
	return NULL;
}

char *pcb_layer_to_addr(pcb_board_t *pcb, pcb_layer_t *ly)
{
	gds_t res;
	gds_init(&res);

	if (pcb_layer_append_to_addr(pcb, ly, &res) == 0)
		return res.array;

	gds_uninit(&res);
	return NULL;
}
