#include "internal.hpp"

#include <boost/nowide/iostream.hpp>
#include <boost/tokenizer.hpp>

#include <atomic>
#include <iostream>
#include <mutex>
#include <regex>
#include <vector>

class dv_attribute_updater {
private:
	dvConfigNode node;
	std::string key;
	enum dvConfigAttributeType type;
	dvConfigAttributeUpdater updater;
	void *userData;
	bool runOnce;

public:
	dv_attribute_updater(dvConfigNode _node, const std::string &_key, enum dvConfigAttributeType _type,
		dvConfigAttributeUpdater _updater, void *_userData, bool _runOnce) :
		node(_node),
		key(_key),
		type(_type),
		updater(_updater),
		userData(_userData),
		runOnce(_runOnce) {
	}

	dvConfigNode getNode() const noexcept {
		return (node);
	}

	const std::string &getKey() const noexcept {
		return (key);
	}

	enum dvConfigAttributeType getType() const noexcept {
		return (type);
	}

	dvConfigAttributeUpdater getUpdater() const noexcept {
		return (updater);
	}

	void *getUserData() const noexcept {
		return (userData);
	}

	bool getRunOnce() const noexcept {
		return (runOnce);
	}

	// Comparison operators.
	bool operator==(const dv_attribute_updater &rhs) const noexcept {
		// Only remove can pass in a null updater, in which case it means we
		// want to remove all attrUpdaters that match node+key, so we reduce
		// the comparison to only look at those parts.
		if ((updater == nullptr) || (rhs.updater == nullptr)) {
			return ((node == rhs.node) && (key == rhs.key) && (type == rhs.type));
		}

		return ((node == rhs.node) && (key == rhs.key) && (type == rhs.type) && (updater == rhs.updater)
				&& (userData == rhs.userData) && (runOnce == rhs.runOnce));
	}

	bool operator!=(const dv_attribute_updater &rhs) const noexcept {
		return (!this->operator==(rhs));
	}
};

// struct for C compatibility
struct dv_config_tree {
public:
	// Data root node. Cannot be deleted.
	dvConfigNode root = nullptr;
	// Global attribute updaters.
	std::vector<dv_attribute_updater> attributeUpdaters;
	std::mutex attributeUpdatersLock;
	// Global node listener.
	std::atomic<dvConfigNodeChangeListener> globalNodeListenerFunction = nullptr;
	std::atomic<void *> globalNodeListenerUserData                     = nullptr;
	// Global attribute listener.
	std::atomic<dvConfigAttributeChangeListener> globalAttributeListenerFunction = nullptr;
	std::atomic<void *> globalAttributeListenerUserData                          = nullptr;
	// Lock to serialize setting of global listeners.
	std::mutex globalListenersLock;

	dv_config_tree() {
		// Create root node.
		root = dvConfigNodeNew("", nullptr, this);
	}

	~dv_config_tree() noexcept {
		dvConfigNodeRemoveNode(root);
		dvConfigNodeDestroy(root);
	}

	// Don't allow move/copy of config trees, or assigning them around.
	// Usually only pointers are passed around.
	dv_config_tree(const dv_config_tree &t) = delete;
	dv_config_tree(dv_config_tree &&t)      = delete;

	dv_config_tree &operator=(const dv_config_tree &rhs) = delete;
	dv_config_tree &operator=(dv_config_tree &&rhs)      = delete;
};

static void dvConfigGlobalInitialize(void);
static void dvConfigGlobalErrorLogCallbackInitialize(void);
static void dvConfigGlobalErrorLogCallbackSetInternal(dvConfigTreeErrorLogCallback error_log_cb);
static void dvConfigDefaultErrorLogCallback(const char *msg, bool fatal);
static bool dvConfigCheckAbsoluteNodePath(const std::string &absolutePath);
static bool dvConfigCheckRelativeNodePath(const std::string &relativePath);

static dvConfigTree dvConfigGlobalTree = nullptr;
static std::once_flag dvConfigGlobalTreeIsInitialized;

static void dvConfigGlobalInitialize(void) {
	dvConfigGlobalTree = dvConfigTreeNew();
}

dvConfigTree dvConfigTreeGlobal(void) {
	std::call_once(dvConfigGlobalTreeIsInitialized, &dvConfigGlobalInitialize);

	return (dvConfigGlobalTree);
}

static dvConfigTreeErrorLogCallback dvConfigGlobalErrorLogCallback = nullptr;
static std::once_flag dvConfigGlobalErrorLogCallbackIsInitialized;

static void dvConfigGlobalErrorLogCallbackInitialize(void) {
	dvConfigGlobalErrorLogCallbackSetInternal(&dvConfigDefaultErrorLogCallback);
}

static void dvConfigGlobalErrorLogCallbackSetInternal(dvConfigTreeErrorLogCallback error_log_cb) {
	dvConfigGlobalErrorLogCallback = error_log_cb;
}

dvConfigTreeErrorLogCallback dvConfigTreeErrorLogCallbackGet(void) {
	std::call_once(dvConfigGlobalErrorLogCallbackIsInitialized, &dvConfigGlobalErrorLogCallbackInitialize);

	return (dvConfigGlobalErrorLogCallback);
}

/**
 * This is not thread-safe, and it's not meant to be.
 * Set the global error callback preferably only once, before using the configuration store.
 */
void dvConfigTreeErrorLogCallbackSet(dvConfigTreeErrorLogCallback error_log_cb) {
	std::call_once(dvConfigGlobalErrorLogCallbackIsInitialized, &dvConfigGlobalErrorLogCallbackInitialize);

	// If nullptr, set to default logging callback.
	if (error_log_cb == nullptr) {
		dvConfigGlobalErrorLogCallbackSetInternal(&dvConfigDefaultErrorLogCallback);
	}
	else {
		dvConfigGlobalErrorLogCallbackSetInternal(error_log_cb);
	}
}

dvConfigTree dvConfigTreeNew(void) {
	dvConfigTree newTree = new (std::nothrow) dv_config_tree{};
	dvConfigMemoryCheck(newTree, __func__);

	return (newTree);
}

void dvConfigTreeDelete(dvConfigTree tree) {
	// Not allowed to destroy global tree.
	if (tree == dvConfigTreeGlobal()) {
		return;
	}

	delete tree;
}

bool dvConfigTreeExistsNode(dvConfigTreeConst st, const char *nodePathC) {
	const std::string nodePath(nodePathC);

	if (!dvConfigCheckAbsoluteNodePath(nodePath)) {
		errno = EINVAL;
		return (false);
	}

	// First node is the root.
	dvConfigNodeConst curr = st->root;

	// Optimization: the root node always exists.
	if (nodePath == "/") {
		return (true);
	}

	boost::tokenizer<boost::char_separator<char>> nodePathTokens(nodePath, boost::char_separator<char>("/", nullptr));

	// Search (or create) viable node iteratively.
	for (const auto &tok : nodePathTokens) {
		dvConfigNodeConst next = dvConfigNodeGetChild(curr, tok.c_str());

		// If node doesn't exist, return that.
		if (next == nullptr) {
			errno = ENOENT;
			return (false);
		}

		curr = next;
	}

	// We got to the end, so the node exists.
	return (true);
}

dvConfigNode dvConfigTreeGetNode(dvConfigTree st, const char *nodePathC) {
	const std::string nodePath(nodePathC);

	if (!dvConfigCheckAbsoluteNodePath(nodePath)) {
		errno = EINVAL;
		return (nullptr);
	}

	// First node is the root.
	dvConfigNode curr = st->root;

	// Optimization: the root node always exists and is right there.
	if (nodePath == "/") {
		return (curr);
	}

	boost::tokenizer<boost::char_separator<char>> nodePathTokens(nodePath, boost::char_separator<char>("/", nullptr));

	// Search (or create) viable node iteratively.
	for (const auto &tok : nodePathTokens) {
		dvConfigNode next = dvConfigNodeGetChild(curr, tok.c_str());

		// Create next node in path if not existing.
		if (next == nullptr) {
			next = dvConfigNodeAddChild(curr, tok.c_str());
		}

		curr = next;
	}

	// 'curr' now contains the specified node.
	return (curr);
}

bool dvConfigNodeExistsRelativeNode(dvConfigNodeConst node, const char *nodePathC) {
	const std::string nodePath(nodePathC);

	if (!dvConfigCheckRelativeNodePath(nodePath)) {
		errno = EINVAL;
		return (false);
	}

	// Current node reference can be resolved right away, always valid.
	if (nodePath == "./") {
		return (true);
	}

	// Start with the given node.
	dvConfigNodeConst curr = node;

	boost::tokenizer<boost::char_separator<char>> nodePathTokens(nodePath, boost::char_separator<char>("/", nullptr));

	// Search (or create) viable node iteratively.
	for (const auto &tok : nodePathTokens) {
		dvConfigNodeConst next = nullptr;

		if (tok == "..") {
			// Go up.
			next = dvConfigNodeGetParent(curr);
		}
		else {
			// Go down.
			next = dvConfigNodeGetChild(curr, tok.c_str());
		}

		// If node doesn't exist, return that.
		if (next == nullptr) {
			errno = ENOENT;
			return (false);
		}

		curr = next;
	}

	// We got to the end, so the node exists.
	return (true);
}

dvConfigNode dvConfigNodeGetRelativeNode(dvConfigNode node, const char *nodePathC) {
	const std::string nodePath(nodePathC);

	if (!dvConfigCheckRelativeNodePath(nodePath)) {
		errno = EINVAL;
		return (nullptr);
	}

	// Current node reference can be resolved right away, always valid.
	if (nodePath == "./") {
		return (node);
	}

	// Start with the given node.
	dvConfigNode curr = node;

	boost::tokenizer<boost::char_separator<char>> nodePathTokens(nodePath, boost::char_separator<char>("/", nullptr));

	// Search (or create) viable node iteratively.
	for (const auto &tok : nodePathTokens) {
		dvConfigNode next = nullptr;

		if (tok == "..") {
			// Go up.
			next = dvConfigNodeGetParent(curr);

			if (next == nullptr) {
				// Cannot go up anymore, error!
				errno = EINVAL;
				return (nullptr);
			}
		}
		else {
			// Go down.
			next = dvConfigNodeGetChild(curr, tok.c_str());

			// Create next node in path if not existing.
			if (next == nullptr) {
				next = dvConfigNodeAddChild(curr, tok.c_str());
			}
		}

		curr = next;
	}

	// 'curr' now contains the specified node.
	return (curr);
}

void dvConfigNodeAttributeUpdaterAdd(dvConfigNode node, const char *key, enum dvConfigAttributeType type,
	dvConfigAttributeUpdater updater, void *updaterUserData, bool runOnce) {
	// Cannot work without an updater.
	if (updater == nullptr) {
		dvConfigNodeError("dvConfigNodeAttributeUpdaterAdd", key, type, "updater not specified");
	}

	dv_attribute_updater attrUpdater(node, key, type, updater, updaterUserData, runOnce);

	dvConfigTree tree = dvConfigNodeGetGlobal(node);
	std::scoped_lock lock(tree->attributeUpdatersLock);

	// Check no other updater already exists that matches this one.
	if (!dv::vectorContains(tree->attributeUpdaters, attrUpdater)) {
		// Verify referenced attribute actually exists.
		if (!dvConfigNodeExistsAttribute(node, key, type)) {
			dvConfigNodeErrorNoAttribute("dvConfigNodeAttributeUpdaterAdd", key, type);
		}

		tree->attributeUpdaters.push_back(attrUpdater);
	}
}

void dvConfigNodeAttributeUpdaterRemove(dvConfigNode node, const char *key, enum dvConfigAttributeType type,
	dvConfigAttributeUpdater updater, void *updaterUserData) {
	dv_attribute_updater attrUpdater(node, key, type, updater, updaterUserData, false);

	dvConfigTree tree = dvConfigNodeGetGlobal(node);
	std::scoped_lock lock(tree->attributeUpdatersLock);

	dv::vectorRemove(tree->attributeUpdaters, attrUpdater);
}

void dvConfigNodeAttributeUpdaterRemoveAll(dvConfigNode node) {
	dvConfigTree tree = dvConfigNodeGetGlobal(node);
	std::scoped_lock lock(tree->attributeUpdatersLock);

	dv::vectorRemoveIf(tree->attributeUpdaters, [&node](const dv_attribute_updater &up) {
		return (up.getNode() == node);
	});
}

void dvConfigTreeAttributeUpdaterRemoveAll(dvConfigTree tree) {
	std::scoped_lock lock(tree->attributeUpdatersLock);

	tree->attributeUpdaters.clear();
}

bool dvConfigTreeAttributeUpdaterRun(dvConfigTree tree) {
	std::scoped_lock lock(tree->attributeUpdatersLock);

	bool allSuccess = true;

	for (auto up = tree->attributeUpdaters.begin(); up != tree->attributeUpdaters.end();) {
		union dvConfigAttributeValue newValue
			= (*up->getUpdater())(up->getUserData(), up->getKey().c_str(), up->getType());

		if (!dvConfigNodePutAttribute(up->getNode(), up->getKey().c_str(), up->getType(), newValue)) {
			if (errno == EPERM) {
				// Trying to write a read-only value. Force update.
				if (!dvConfigNodeUpdateReadOnlyAttribute(
						up->getNode(), up->getKey().c_str(), up->getType(), newValue)) {
					allSuccess = false;
				}
			}
			else {
				allSuccess = false;
			}
		}

		if (up->getRunOnce()) {
			up = tree->attributeUpdaters.erase(up);
		}
		else {
			up++;
		}
	}

	return (allSuccess);
}

void dvConfigTreeGlobalNodeListenerSet(dvConfigTree tree, dvConfigNodeChangeListener node_changed, void *userData) {
	std::scoped_lock lock(tree->globalListenersLock);

	// Ensure function is never called with old user data.
	tree->globalNodeListenerUserData.store(nullptr);

	// Update function.
	tree->globalNodeListenerFunction.store(node_changed);

	// Associate new user data.
	tree->globalNodeListenerUserData.store(userData);
}

dvConfigNodeChangeListener dvConfigGlobalNodeListenerGetFunction(dvConfigTree tree) {
	return (tree->globalNodeListenerFunction.load(std::memory_order_relaxed));
}

void *dvConfigGlobalNodeListenerGetUserData(dvConfigTree tree) {
	return (tree->globalNodeListenerUserData.load(std::memory_order_relaxed));
}

void dvConfigTreeGlobalAttributeListenerSet(
	dvConfigTree tree, dvConfigAttributeChangeListener attribute_changed, void *userData) {
	std::scoped_lock lock(tree->globalListenersLock);

	// Ensure function is never called with old user data.
	tree->globalAttributeListenerUserData.store(nullptr);

	// Update function.
	tree->globalAttributeListenerFunction.store(attribute_changed);

	// Associate new user data.
	tree->globalAttributeListenerUserData.store(userData);
}

dvConfigAttributeChangeListener dvConfigGlobalAttributeListenerGetFunction(dvConfigTree tree) {
	return (tree->globalAttributeListenerFunction.load(std::memory_order_relaxed));
}

void *dvConfigGlobalAttributeListenerGetUserData(dvConfigTree tree) {
	return (tree->globalAttributeListenerUserData.load(std::memory_order_relaxed));
}

static const std::regex dvAbsoluteNodePathRegexp("^/([a-zA-Z-_\\d]+/)*$");
static const std::regex dvRelativeNodePathRegexp("^((\\./)|((\\.\\./)*([a-zA-Z-_\\d]+/)*))$");

static bool dvConfigCheckAbsoluteNodePath(const std::string &absolutePath) {
	if (absolutePath.empty()) {
		(*dvConfigTreeErrorLogCallbackGet())("Absolute node path cannot be empty.", false);
		return (false);
	}

	if (!std::regex_match(absolutePath, dvAbsoluteNodePathRegexp)) {
		(*dvConfigTreeErrorLogCallbackGet())(
			fmt::format(FMT_STRING("Invalid absolute node path format: '{:s}'."), absolutePath).c_str(), false);

		return (false);
	}

	return (true);
}

static bool dvConfigCheckRelativeNodePath(const std::string &relativePath) {
	if (relativePath.empty()) {
		(*dvConfigTreeErrorLogCallbackGet())("Relative node path cannot be empty.", false);
		return (false);
	}

	if (!std::regex_match(relativePath, dvRelativeNodePathRegexp)) {
		(*dvConfigTreeErrorLogCallbackGet())(
			fmt::format(FMT_STRING("Invalid relative node path format: '{:s}'."), relativePath).c_str(), false);

		return (false);
	}

	return (true);
}

static void dvConfigDefaultErrorLogCallback(const char *msg, bool fatal) {
	boost::nowide::cerr << msg << std::endl;

	if (fatal) {
		exit(EXIT_FAILURE);
	}
}
