/* sdb - MIT - Copyright 2011-2022 - pancake */

#include "sdb/sdb.h"

SDB_API void sdb_ns_lock(Sdb *s, int lock, int depth) {
	SdbListIter *it;
	SdbNs *ns;
	if (!s) {
		return;
	}
	s->ns_lock = lock;
	if (depth) { // handles -1 as infinite
		ls_foreach_cast (s->ns, it, SdbNs*, ns) {
			sdb_ns_lock (ns->sdb, lock, depth - 1);
		}
	}
}

static int in_list(SdbList *list, void *item) {
	SdbNs *ns;
	SdbListIter *it;
	if (list && item) {
		ls_foreach_cast (list, it, SdbNs*, ns) {
			if (item == ns) {
				return 1;
			}
		}
	}
	return 0;
}

static void ns_free(Sdb *s, SdbList *list) {
	SdbListIter next;
	SdbListIter *it;
	int deleted;
	SdbNs *ns;
	if (!list || !s) {
		return;
	}
	// TODO: Implement and use ls_foreach_safe
	if (in_list (list, s)) {
		return;
	}
	ls_append (list, s);
	ls_foreach_cast (s->ns, it, SdbNs*, ns) {
		deleted = 0;
		next.n = it->n;
		if (!in_list (list, ns)) {
			ls_delete (s->ns, it); // free (it)
			deleted = 1;
			ls_append (list, ns);
			if (ns->sdb) {
				Sdb *child = ns->sdb;
				ns->sdb = NULL;
				if (!in_list (list, child)) {
					ls_append (list, child);
					ns_free (child, list);
					// force refs to 1 so sdb_free will actually free it
					if (child->refs > 1) {
						child->refs = 1;
					}
					sdb_free (child);
				}
			}
			sdb_gh_free (ns->name);
			ns->name = NULL;
		}
		if (!deleted) {
			Sdb *child = ns->sdb;
			ns->sdb = NULL;
			if (child) {
				if (!in_list (list, child)) {
					ls_append (list, child);
					if (child->refs > 1) {
						child->refs = 1;
					}
					sdb_free (child);
				}
			}
			s->ns->free = NULL;
			ls_delete (s->ns, it); // free (it)
			sdb_gh_free (ns->name);
			ns->name = NULL;
		}
		sdb_gh_free (ns);
		it = &next;
	}
	ls_free (s->ns);
	s->ns = NULL;
}

SDB_API void sdb_ns_free(Sdb *s) {
	SdbList *list;
	if (!s) {
		return;
	}
	list = ls_new ();
	if (list) {
		list->free = NULL;
		ns_free (s, list);
		ls_free (list);
	}
	ls_free (s->ns);
	s->ns = NULL;
}

// clear namespace references, freeing child sdbs whose refs drop to zero
SDB_API void sdb_ns_reset(Sdb *s) {
	SdbListIter *it;
	SdbNs *ns;
	if (!s || !s->ns) {
		return;
	}
	ls_foreach_cast (s->ns, it, SdbNs*, ns) {
		if (ns->sdb) {
			if (ns->sdb->refs > 1) {
				ns->sdb->refs--;
			} else if (ns->sdb->refs == 1) {
				// This SDB is only owned by this namespace, so we need to free it.
				// First recursively reset its namespaces to avoid freeing externally owned SDbs.
				sdb_ns_reset (ns->sdb);
				// Now safe to free since namespaces are already cleared (ns is NULL)
				sdb_free (ns->sdb);
			}
		}
		sdb_gh_free (ns->name);
		sdb_gh_free (ns);
	}
	ls_free (s->ns);
	s->ns = NULL;
}

static SdbNs *sdb_ns_new (Sdb *s, const char *name, ut32 hash) {
	char dir[SDB_MAX_PATH];
	SdbNs *ns;
	if (s->dir && *s->dir && name && *name) {
		int dir_len = strlen (s->dir);
		int name_len = strlen (name);
		if ((dir_len+name_len+3)>SDB_MAX_PATH) {
			return NULL;
		}
		memcpy (dir, s->dir, dir_len);
		memcpy (dir + dir_len, ".", 1);
		memcpy (dir + dir_len + 1, name, name_len + 1);
	} else {
		dir[0] = 0;
	}
	ns = (SdbNs *)sdb_gh_malloc (sizeof (SdbNs));
	if (!ns) {
		return NULL;
	}
	ns->hash = hash;
	ns->name = name? sdb_strdup (name): NULL;
	//ns->sdb = sdb_new (dir, ns->name, 0);
	ns->sdb = sdb_new0 ();
	// TODO: generate path

	if (ns->sdb) {
		sdb_gh_free (ns->sdb->path);
		ns->sdb->path = NULL;
		if (*dir) {
			ns->sdb->path = sdb_strdup (dir);
		}
		sdb_gh_free (ns->sdb->name);
		if (name && *name) {
			ns->sdb->name = sdb_strdup (name);
		}
	} else {
		sdb_gh_free (ns->name);
		sdb_gh_free (ns);
		ns = NULL;
	}
	return ns;
}

SDB_API bool sdb_ns_unset(Sdb *s, const char *name, Sdb *r) {
	SdbNs *ns;
	SdbListIter *it;
	if (s && (name || r)) {
		ls_foreach_cast (s->ns, it, SdbNs*, ns) {
			if ((name && !strcmp (name, ns->name)) || (r && ns->sdb == r)) {
				ls_delete (s->ns, it);
				// drop the reference taken by sdb_ns or sdb_ns_set
				if (ns->sdb && ns->sdb->refs == 1) {
					sdb_ns_reset (ns->sdb);
				}
				sdb_free (ns->sdb);
				sdb_gh_free (ns->name);
				sdb_gh_free (ns);
				return true;
			}
		}
	}
	return false;
}

SDB_API bool sdb_ns_set(Sdb *s, const char *name, Sdb *r) {
	SdbNs *ns;
	SdbListIter *it;
	ut32 hash = sdb_hash (name);
	if (!s || !r || !name) {
		return false;
	}
	ls_foreach_cast (s->ns, it, SdbNs*, ns) {
		if (ns->hash == hash) {
			if (ns->sdb == r) {
				return false;
			}
			sdb_free (ns->sdb);
			r->refs++; // sdb_ref / sdb_unref //
			ns->sdb = r;
			return true;
		}
	}
	if (s->ns_lock) {
		return false;
	}
	ns = R_NEW (SdbNs);
	if (!ns) {
		return false;
	}
	ns->name = sdb_strdup (name);
	if (!ns->name) {
		sdb_gh_free (ns);
		return false;
	}
	ns->hash = hash;
	ns->sdb = r;
	r->refs++;
	ls_append (s->ns, ns);
	return true;
}

SDB_API Sdb *sdb_ns(Sdb *s, const char *name, int create) {
	SdbListIter *it;
	SdbNs *ns;
	ut32 hash;
	if (!s || !name || !*name) {
		return NULL;
	}
	hash = sdb_hash (name);
	ls_foreach_cast (s->ns, it, SdbNs*, ns) {
		if (ns->hash == hash) {
			return ns->sdb;
		}
	}
	if (!create) {
		return NULL;
	}
	if (s->ns_lock) {
		return NULL;
	}
	ns = sdb_ns_new (s, name, hash);
	if (!ns) {
		return NULL;
	}
	ls_append (s->ns, ns);
	return ns->sdb;
}

SDB_API Sdb *sdb_ns_path(Sdb *s, const char *path, int create) {
	char *ptr, *str;
	char *slash;

	if (!s || !path || !*path) {
		return s;
	}
	ptr = str = sdb_strdup (path);
	do {
		slash = strchr (ptr, '/');
		if (slash)
			*slash = 0;
		s = sdb_ns (s, ptr, create);
		if (!s) break;
		if (slash)
			ptr = slash+1;
	} while (slash);
	sdb_gh_free (str);
	return s;
}

static bool ns_sync(Sdb *s, SdbList *list) {
	SdbNs *ns;
	SdbListIter *it;
	bool ret = true;
	ls_foreach_cast (s->ns, it, SdbNs*, ns) {
		if (in_list (list, ns)) {
			continue;
		}
		ls_append (list, ns);
		ret &= ns_sync (ns->sdb, list);
		ret &= sdb_sync (ns->sdb);
	}
	ret &= sdb_sync (s);
	return ret;
}

SDB_API bool sdb_ns_sync(Sdb *s) {
	if (!s) {
		return false;
	}
	SdbList *list = ls_new ();
	if (!list) {
		return false;
	}
	bool ret = ns_sync (s, list);
	list->free = NULL;
	ls_free (list);
	return ret;
}
