/*
 *
 *                            COPYRIGHT
 *
 *  pcb-rnd, interactive printed circuit board design
 *  Copyright (C) 2018,2019,2023 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/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"

#define TDL_DONT_UNDEF
#include "idpath.h"
#include <genlist/gentdlist_impl.c>
#undef TDL_DONT_UNDEF
#include <genlist/gentdlist_undef.h>

#include <assert.h>

#include "board.h"
#include "data.h"
#include "layer.h"
#include "layer_ui.h"
#include "obj_subc_parent.h"
#include <librnd/core/rnd_printf.h>

static int idpath_map(pcb_idpath_t *idp, pcb_any_obj_t *obj, int level, int *numlevels)
{
	pcb_data_t *data;
	int n;

	if (numlevels != 0)
		(*numlevels)++;

	if (idp != NULL) {
		if (level < 0)
			return -1;
		idp->id[level] = obj->ID;
	}

	switch(obj->parent_type) {
		case PCB_PARENT_INVALID:
		case PCB_PARENT_UI:
		case PCB_PARENT_SUBC:
		case PCB_PARENT_BOARD:
		case PCB_PARENT_NET:
			return -1;
		case PCB_PARENT_LAYER:
			assert(obj->parent.layer->parent_type = PCB_PARENT_DATA);
			data = obj->parent.layer->parent.data;
			if (pcb_is_uilayer(obj->parent.layer)) {
				long uilid = pcb_uilayer_get_id(obj->parent.layer);
				if (uilid <= 0) {
					if (idp != NULL)
						idp->uilayer_addr = 0;
					return -1;
				}
				if (idp != NULL)
					idp->uilayer_addr = uilid;

				return 0;
			}
			goto recurse;

		case PCB_PARENT_DATA:
			data = obj->parent.data;
			recurse:;
			switch(data->parent_type) {
				case PCB_PARENT_UI:
				case PCB_PARENT_LAYER:
				case PCB_PARENT_DATA:
				case PCB_PARENT_NET:
					return -1;
				case PCB_PARENT_BOARD:
					if (idp == NULL)
						return 0;
					if (data == PCB->Data) {
						idp->data_addr = 1;
						return 0;
					}
				case PCB_PARENT_INVALID:
					if (idp == NULL)
						return 0;
					for(n = 0; n < PCB_MAX_BUFFER; n++) {
						if (data == pcb_buffers[n].Data) {
							idp->data_addr = n+2;
							return 0;
						}
					}
					/* not the board, not any buffer -> unknown */
					return 0;
				case PCB_PARENT_SUBC: /* recurse */
					return idpath_map(idp, (pcb_any_obj_t *)data->parent.subc, level-1, numlevels);
			}
			break;
	}
	return -1;
}

pcb_idpath_t *pcb_idpath_alloc(int len)
{
	pcb_idpath_t *idp;
	idp = calloc(1, sizeof(pcb_idpath_t) + (sizeof(long int) * (len-1)));
	idp->len = len;
	return idp;
}

pcb_idpath_t *pcb_obj2idpath(pcb_any_obj_t *obj)
{
	pcb_idpath_t *idp;
	int len = 0;

	/* determine the length first */
	if (idpath_map(NULL, obj, 0, &len) != 0)
		return NULL;

	idp = pcb_idpath_alloc(len);
	if (idpath_map(idp, obj, len-1, NULL) != 0) {
		free(idp);
		return NULL;
	}

	return idp;
}

pcb_idpath_t *pcb_str2idpath(pcb_board_t *pcb, const char *str)
{
	const char *s, *ps;
	char *next;
	int data_addr, n, len = 1;
	long uilid = 0;
	pcb_idpath_t *idp;

	for(ps = str; *ps == '/'; ps++) ;

	if (strncmp(ps, "ui#", 3) == 0) {
		ps += 3;
		uilid = strtol(ps, &next, 10);
		ps = next;
		if (*ps == '/') /* the ui#xxx/ prefix shouldn't increase level because it is a header/root field, ps should point beyond the separator */
			ps++;
	}
	else
		data_addr = pcb_data_addr_by_name(pcb, &ps);

	for(s = ps; *s != '\0'; s++) {
		if ((s[0] == '/') && (s[1] != '/') && (s[1] != '\0'))
			len++;
		else if ((!isdigit(*s)) && (*s != '/')) {
			rnd_trace("bad idpath: '%s' in '%s'\n", s, ps);
			return NULL;
		}
	}

	idp = pcb_idpath_alloc(len);
	idp->data_addr = data_addr;
	idp->uilayer_addr = uilid;

	for(s = ps, n = 0; *s != '\0'; s++,n++) {
		while(*s == '/') s++;
		if (*s == '\0')
			break;
		idp->id[n] = strtol(s, &next, 10);
		s = (const char *)next;
		if (*s == '\0')
			break;
	}
	return idp;
}


void pcb_append_idpath(gds_t *dst, const pcb_idpath_t *idp, rnd_bool relative)
{
	int n;

	if (!relative) {
		size_t end = dst->used;
		gds_enlarge(dst, dst->used + 32);
		dst->used = end;
		if (idp->uilayer_addr != 0)
			sprintf(dst->array+end, "ui#%ld", idp->uilayer_addr);
		else
			pcb_data_name_by_addr(idp->data_addr, dst->array+end);
		dst->used += strlen(dst->array+end);
	}

	for(n = 0; n < idp->len; n++)
		rnd_append_printf(dst, "/%ld", idp->id[n]);
}

char *pcb_idpath2str(const pcb_idpath_t *idp, rnd_bool relative)
{
	gds_t tmp;

	gds_init(&tmp);
	gds_enlarge(&tmp, 32);
	tmp.used = 0;
	pcb_append_idpath(&tmp, idp, relative);
	return tmp.array;
}


static pcb_any_obj_t *idpath2obj(pcb_data_t *data, const pcb_idpath_t *path, int level)
{

	for(;;) {
		pcb_any_obj_t *obj = htip_get(&data->id2obj, path->id[level]);
		pcb_subc_t *sc = (pcb_subc_t *)obj;

		if (obj == NULL)
			return NULL;

		level++;
		if (level == path->len)
			return obj;

		if (obj->type != PCB_OBJ_SUBC) /* can descend only in subcircuits */
			return NULL;

		data = sc->data;
	}
}

pcb_any_obj_t *pcb_idpath2obj_in_only(pcb_data_t *data, const pcb_idpath_t *path)
{
	return idpath2obj(data, path, 0);
}

pcb_any_obj_t *pcb_idpath2obj_in(pcb_data_t *data, const pcb_idpath_t *path)
{
	if ((path->data_addr >= 2) && (path->data_addr < PCB_MAX_BUFFER+2))
		data = pcb_buffers[path->data_addr - 2].Data;
	return idpath2obj(data, path, 0);
}

pcb_any_obj_t *pcb_idpath2obj(pcb_board_t *pcb, const pcb_idpath_t *path)
{
	pcb_data_t *data;
	if (path->uilayer_addr > 0) {
		data = pcb->uilayer_data;
	}
	else if (path->data_addr == 1) {
		if (pcb == NULL)
			return NULL;
		data = pcb->Data;
	}
	else if ((path->data_addr >= 2) && (path->data_addr < PCB_MAX_BUFFER+2))
		data = pcb_buffers[path->data_addr - 2].Data;
	else
		return NULL;
	
	return idpath2obj(data, path, 0);
}

void pcb_idpath_destroy(pcb_idpath_t *path)
{
	pcb_idpath_list_remove(path);
	free(path);
}

void pcb_idpath_list_clear(pcb_idpath_list_t *lst)
{
	pcb_idpath_t *i, *next;
	for(i = pcb_idpath_list_first(lst); i != NULL; i = next) {
		next = pcb_idpath_list_next(i);
		pcb_idpath_destroy(i);
	}
}

pcb_idpath_t *pcb_idpath_dup(const pcb_idpath_t *path)
{
	pcb_idpath_t *res = pcb_idpath_alloc(path->len);
	memcpy(res->id, path->id, (sizeof(long int) * path->len));
	return res;
}


rnd_bool pcb_idpath_obj_match(pcb_board_t *pcb, const pcb_idpath_t *path, pcb_any_obj_t *obj)
{
	int n;
	for(n = path->len-1; n >= 0; n--) {
		if ((obj == NULL) || (obj->ID != path->id[n]))
			return 0;
		obj = (pcb_any_obj_t *)pcb_obj_parent_subc(obj);
	}

	return 1;
}
