/*
 * link-names.cc
 *
 * Translate AtomSpace word-pairs to LG link names.
 *
 * Copyright (c) 2022 Linas Vepstas <linasvepstas@gmail.com>
 */
#ifdef HAVE_ATOMESE

#include <cmath>
#include <cstdlib>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/nlp/types/atom_types.h>

#undef STRINGIFY

extern "C" {
#include "../link-includes.h"            // For Dictionary
#include "../memory-pool.h"              // For Pool_desc
#include "lookup-atomese.h"
};

//#include "dict-atomese.h"
#include "local-as.h"

using namespace opencog;

// ===============================================================
/// Mapping LG connectors to AtomSpace connectors, and v.v.
///
/// An LG connector is a string of capital letters. It's generated on
/// the fly. It's associated with `(List (Word ..) (Word ...))`. There
/// are two lookup tasks. In this blob of code, when given the List, we
/// need to find the matching LG string. For users of LG, we have the
/// inverse problem: given the LG string, what's the List?
///
/// There are several possible solutionhs:
///
///    (Edge (BondNode "ABC") (List (Word ..) (Word ...)))
///
///    (Edge (LgLinkNode "ABC") (List (Word ..) (Word ...)))
///
///    (Edge (Predicate "*-LG connector string-*")
///       (LgConnNode "ABC") (List (Word ..) (Word ...)))
///
///     (Grant (List (Word ...) (Word ...))
///         (Predicate "*-LG connector string-*")
///         (LgLinkNode "ASDF"))
///
/// The first seems to be the best, and is implemented here.
///
/// As of just right now, this pair is held only in the local AtomSpace;
/// it is never written back to storage. This is fine casual use with
/// a private local AtomSpace. Users working with a shared attached
/// AtomSpace will probably keep counts, and store the pair. This will
/// then require taking care to avoid issuing duplicate LG link strings.

/// int to base-26 capital letters. zero = 'A'.
static std::string idtostr(uint64_t aid)
{
	std::string s;
	aid ++;
	do
	{
		aid --;
		char c = (aid % 26) + 'A';
		// Slightly more human-readable than s.push_back()
		s.insert(s.begin(), c);
	}
	while (0 < (aid /= 26));

	// Generated strings always end with star.
	// s.push_back('*');
	return s;
}

/// Given a `(List (Word ...) (Word ...))` denoting a link, find the
/// corresponding BondNode, if it exists. The string name of that
/// BondNode is the name of the LG link for that word-pair.
Handle get_lg_conn(Local* local, const Handle& wpair)
{
	for (const Handle& edge : wpair->getIncomingSetByType(EDGE_LINK))
	{
		const Handle& bond = edge->getOutgoingAtom(0);
		if (bond != local->bany)
			return bond;
	}
	return Handle::UNDEFINED;
}

/// Return a cached LG-compatible link string.
/// Assigns a new name, if one does not exist.
/// The Handle `lnk` is an ordered pair, left-right, two words/classes.
/// That is, `lnk` is `(List (Word ...) (Word ...))`
///
std::string cached_linkname(Local* local, const Handle& lnk)
{
	// If we've already cached a connector string for this,
	// just return it.  Else build and cache a string.
	Handle lgc = get_lg_conn(local, lnk);
	if (lgc)
		return lgc->get_name();

	// Use a lock, to make sure that link ID's are issued only
	// once, ever.  The GrantLink could also be used for this;
	// the GrantLink was invented to solve this problem. But
	// GrantLink is awkward for edges, because the arguments are
	// "backwards". We really need a UniqueEdgeLink, instead.
	//
	// Also: FYI, we could use Atom::incremenetCount() instead
	// of doing the last_id++ but, again, for now, it seems
	// easier to do it as below.
	std::lock_guard<std::mutex> guard(local->pair_mutex);

	// Maybe it's in storage?
	if (local->stnp)
		local->stnp->fetch_incoming_by_type(lnk, EDGE_LINK);

	// Try again, under the lock, in case threads are racing.
	lgc = get_lg_conn(local, lnk);
	if (lgc)
		return lgc->get_name();

	// idtostr(1064) is "ANY" and we want to reserve "ANY"
	if (1064 == local->last_id) local->last_id++;
	store_link_id(local);

	std::string slnk = idtostr(local->last_id++);
	lgc = createNode(BOND_NODE, slnk);
	Handle he = local->asp->add_link(EDGE_LINK, lgc, lnk);

	// Store, if there is storage.
	if (local->stnp)
		local->stnp->store_atom(he);

	return slnk;
}

// ===============================================================

// Get the last issued link ID from the data store.
void fetch_link_id(Local* local)
{
	if (not local->using_external_as) return;

	// Location where issued link ID's are stored.
	if (local->stnp)
		local->stnp->fetch_atom(local->idanch);

	Handle key(createNode(PREDICATE_NODE, "*-LG-last-id-*"));
	const ValuePtr& tv = local->idanch->getValue(key);
	const FloatValuePtr ctv(FloatValueCast(tv));
	if (ctv)
		local->last_id = std::round(ctv->value()[0]);
}

// Record the last unissued id.
void store_link_id(Local* local)
{
	if (not local->using_external_as) return;

	Handle key(createNode(PREDICATE_NODE, "*-LG-last-id-*"));
	ValuePtr tvp(createFloatValue((double) local->last_id));
	local->asp->set_value(local->idanch, key, tvp);

	// Store, if there is storage.
	if (local->stnp)
		local->stnp->store_atom(local->idanch);
}

// ===============================================================
#endif /* HAVE_ATOMESE */
