/*
 *                            COPYRIGHT
 *
 *  cschem - modular/flexible schematics editor - libcschem (core library)
 *  Copyright (C) 2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust in 2023)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.*
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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
 */

/* included from abstract.c */

/* Search component for v/name in all hlev parents. Return NULL if not found. */
RND_INLINE csch_acomp_t *comp_search_subtree_up(csch_abstract_t *abs, csch_hlevel_t *hlev, const char *name)
{
	for(hlev = hlev->parent; hlev != NULL; hlev = hlev->parent) {
		csch_acomp_t *comp = htsp_get(&hlev->comps, name);
		if ((comp != NULL) && (comp->scope == CSCH_ASCOPE_SUBTREE_LOCAL))
			return comp;
	}

	return NULL;
}

csch_acomp_t *csch_acomp_get_at(csch_abstract_t *abs, csch_hlevel_t *hlev, csch_ascope_t scope, const char *name)
{
	csch_acomp_t *comp = NULL;
	csch_ascope_t prefix_scope;
	const char *orig_name = name;

	if (name == NULL)
		return NULL;

	prefix_scope = csch_split_name_scope(&name);
	if (scope != CSCH_ASCOPE_unknown) {
		if ((prefix_scope != CSCH_ASCOPE_AUTO) && (prefix_scope != scope)) {
			rnd_message(RND_MSG_ERROR, "Failed to resolve component '%s': conflicting scopes\n", orig_name);
			return NULL;
		}
	}
	else
		scope = prefix_scope;


	switch(scope) {
		case CSCH_ASCOPE_AUTO:
			/* first search on sheet local and parents for v/ components */
			if (hlev != NULL) {
				comp = htsp_get(&hlev->comps, name);
				if (comp != NULL)
					return comp;
				comp = comp_search_subtree_up(abs, hlev, name);
				if (comp != NULL)
					return comp;
			}
			return htsp_get(&abs->comps, name); /* fall back to global */

		case CSCH_ASCOPE_GLOBAL:           return htsp_get(&abs->comps, name);
		case CSCH_ASCOPE_SHEET_LOCAL:      return htsp_get(&hlev->comps, name);
		case CSCH_ASCOPE_SUBTREE_LOCAL:
			/* search in sheet only so that multuple v/foo are merged together;
			   sub-sheets are compiled later so they can find this comp searching upward */
			return htsp_get(&hlev->comps, name);

		case CSCH_ASCOPE_SUBTREE_AUTO: /* The ^/ prefix */
			if (hlev == NULL) {
				rnd_message(RND_MSG_ERROR, "Failed to resolve component '%s': scope requires hierarchy which is not available\n", orig_name);
				return NULL;
			}
			/* search on sheet local and parents for v/ comps */
			return comp_search_subtree_up(abs, hlev, name);

		case CSCH_ASCOPE_unknown:
			rnd_message(RND_MSG_ERROR, "Failed to resolve component '%s': unknown scope\n", orig_name);
			return NULL;
	}
	return NULL;
}

csch_acomp_t *csch_acomp_get(csch_abstract_t *abs, const char *name)
{
	return csch_acomp_get_at(abs, NULL, CSCH_ASCOPE_unknown, name);
}

csch_acomp_t *csch_acomp_new(csch_abstract_t *abs, csch_hlevel_t *hlev, csch_ascope_t scope, const char *name_glob, const char *name_loc)
{
	csch_acomp_t *comp = calloc(sizeof(csch_acomp_t), 1);
	int short_name;

	if (scope == CSCH_ASCOPE_SUBTREE_AUTO) {
		rnd_message(RND_MSG_ERROR, "Can not find subtree-local component v/%s\n", name_loc);
		return NULL;
	}

	/* create global comps with the short name; AUTO is taken GLOBAL: if we got
	   here no other binding worked for it */
	short_name = (scope == CSCH_ASCOPE_GLOBAL) || (scope == CSCH_ASCOPE_AUTO);


	csch_aobj_init(abs, &comp->hdr, CSCH_ATYPE_COMP);
	comp->name = rnd_strdup(short_name ? name_loc : name_glob);
	comp->name_loc = rnd_strdup(name_loc);
	comp->scope = scope;
	comp->hlev = hlev;
	comp->hdepth = hlev->hdepth;
	htsp_init(&comp->ports, strhash, strkeyeq);

	htsp_set(&abs->comps, comp->name, comp);

	if (hlev != NULL) {
		switch(scope) {
			case CSCH_ASCOPE_AUTO:       /* if we got here, we are adding it as global */
			case CSCH_ASCOPE_GLOBAL:
			case CSCH_ASCOPE_unknown:
				break; /* has no impact on local */

			case CSCH_ASCOPE_SUBTREE_LOCAL:
			case CSCH_ASCOPE_SHEET_LOCAL:
				htsp_set(&hlev->comps, comp->name_loc, comp);
				break;

			case CSCH_ASCOPE_SUBTREE_AUTO:
				return NULL; /* can't get here */
		}
	}

	return comp;
}
