/*
 * Copyright (C) 2015, 2016 "IoT.bzh"
 * Author "Romain Forlot" <romain.forlot@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

#include <uuid.h>
#include <string.h>
#include <fnmatch.h>

#include "clientApp.hpp"

extern "C" void searchNsetSignalValueHandle(const char* aName, uint64_t timestamp, struct signalValue value)
{
	std::vector<std::shared_ptr<Signal>> signals = Composer::instance().searchSignals(aName);
	if(!signals.empty())
	{
		for(auto& sig: signals)
			{sig->set(timestamp, value);}
	}
}

extern "C" void setSignalValueHandle(void* aSignal, uint64_t timestamp, struct signalValue value)
{
	Signal* sig = static_cast<Signal*>(aSignal);
	sig->set(timestamp, value);
}

bool startsWith(const std::string& str, const std::string& pattern)
{
	size_t sep;
	if( (sep = str.find(pattern)) != std::string::npos && !sep)
		{return true;}
	return false;
}

void extractString(void* closure, json_object* object)
{
	std::vector<std::string> *files = (std::vector<std::string>*) closure;
	const char *oneFile = json_object_get_string(object);

	files->push_back(oneFile);
}

// aSignal member value will be initialized in sourceAPI->addSignal()
static struct signalCBT pluginHandle = {
	.searchNsetSignalValue = searchNsetSignalValueHandle,
	.setSignalValue = setSignalValueHandle,
	.aSignal = nullptr,
	.pluginCtx = nullptr,
};

CtlSectionT Composer::ctlSections_[] = {
	[0]={.key="plugins" , .uid="plugins", .info=nullptr,
		.loadCB=pluginsLoad,
		.handle=&pluginHandle,
		.actions=nullptr},
	[1]={.key="sources" , .uid="sources", .info=nullptr,
		 .loadCB=loadSourcesAPI,
		 .handle=nullptr,
		 .actions=nullptr},
	[2]={.key="signals" , .uid="signals", .info=nullptr,
		 .loadCB=loadSignals,
		 .handle=nullptr,
		 .actions=nullptr},
	[3]={.key=nullptr, .uid=nullptr, .info=nullptr,
		 .loadCB=nullptr,
		 .handle=nullptr,
		 .actions=nullptr}
};

///////////////////////////////////////////////////////////////////////////////
//                             PRIVATE METHODS                               //
///////////////////////////////////////////////////////////////////////////////

Composer::Composer()
{}

Composer::~Composer()
{
	for(auto& j: ctlActionsJ_)
	{
		json_object_put(j);
	}
	json_object_put(ctlConfig_->configJ);
	json_object_put(ctlConfig_->requireJ);
	free(ctlConfig_);
}

json_object* Composer::buildPluginAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *callbackJ = nullptr, *ctlActionJ = nullptr;
	std::string uri = std::string(function).substr(9);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() != 2)
	{
		AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: plugin://<plugin-name>/<function-name>");
		return nullptr;
	}
	wrap_json_pack(&callbackJ, "{ss,ss,so*}",
		"plugin", uriV[0].c_str(),
		"function", uriV[1].c_str(),
		"args", functionArgsJ);
	wrap_json_pack(&ctlActionJ, "{ss,so}",
		"uid", name.c_str(),
		"callback", callbackJ);

	return ctlActionJ;
}

json_object* Composer::buildApiAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *subcallJ = nullptr, *ctlActionJ = nullptr;
	std::string uri = std::string(function).substr(6);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() != 2)
	{
		AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: api://<plugin-name>/<function-name>");
		return nullptr;
	}
	wrap_json_pack(&subcallJ, "{ss,ss}",
		"api", uriV[0].c_str(),
		"verb", uriV[1].c_str());
	wrap_json_pack(&ctlActionJ, "{ss,so,so*}",
		"uid", name.c_str(),
		"subcall", subcallJ,
		"args", functionArgsJ);

	return ctlActionJ;
}

json_object* Composer::buildLuaAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *luaJ = nullptr, *ctlActionJ = nullptr;
	std::string fName, filepath;
	std::string uri = std::string(function).substr(6);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() > 2)
	{
		int i = 0;
		while(i < uriV.size()-1)
			{filepath += uriV[i] + "/";}
		fName = uriV[-1];
	}
	else if(uriV.size() == 2)
	{
		filepath = uriV[0];
		fName = uriV[2];
	}
	else if(uriV.size() == 1)
		{fName = uriV[0];}
	else
	{
		AFB_ERROR("Missing something in uri either lua filepath or function name. Uri has to be like: lua://file/path/file.lua/function_name with filepath optionnal. If not specified, search will be done in default directories");
		return nullptr;
	}

	wrap_json_pack(&luaJ, "{ss*,ss}",
		"load", filepath.empty() ? NULL:filepath.c_str(),
		"func", fName.c_str());
	wrap_json_pack(&ctlActionJ, "{ss,so,so*}",
		"uid", name.c_str(),
		"lua", luaJ,
		"args", functionArgsJ);

	return ctlActionJ;
}

CtlActionT* Composer::convert2Action(const std::string& name, json_object* actionJ)
{
	json_object *functionArgsJ = nullptr,
				*ctlActionJ = nullptr;
	char *function;
	const char *plugin;
	CtlActionT *ctlAction = new CtlActionT;
	memset(ctlAction, 0, sizeof(CtlActionT));

	if(actionJ &&
		!wrap_json_unpack(actionJ, "{ss,s?s,s?o !}", "function", &function,
			"plugin", &plugin,
			"args", &functionArgsJ))
	{
		if(startsWith(function, "lua://"))
		{
			ctlActionJ = buildLuaAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "api://"))
		{
			ctlActionJ = buildApiAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "plugin://"))
		{
			ctlActionJ = buildPluginAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "builtin://"))
		{
			std::string uri = std::string(function).substr(10);
			std::vector<std::string> uriV = Composer::parseURI(uri);
			if(uriV.size() > 1) {AFB_WARNING("Too many thing specified. Uri has to be like: builtin://<builtin-function-name>");}

			json_object *callbackJ = nullptr;
			wrap_json_pack(&callbackJ, "{ss,ss,so*}",
				"plugin", "builtin",
				"function", uriV[0].c_str(),
				"args", functionArgsJ);
			wrap_json_pack(&ctlActionJ, "{ss,so}",
				"uid", name.c_str(),
				"callback", callbackJ);
		}
		else
		{
			AFB_ERROR("Wrong function uri specified. You have to specified 'lua://', 'plugin://' or 'api://'. (%s)", function);
			return nullptr;
		}
	}

	// Register json object for later release
	ctlActionsJ_.push_back(ctlActionJ);
	if(ctlActionJ)
	{
		int err = ActionLoadOne(nullptr, ctlAction, ctlActionJ, 0);
		if(! err)
			{return ctlAction;}
	}

	return nullptr;
}

/// @brief Add the builtin plugin in the default plugins section definition
///
/// @param[in] section - Control Section structure
/// @param[in] pluginsJ - JSON object containing all plugins definition made in
///  JSON configuration file.
///
/// @return 0 if OK, other if not.
int Composer::pluginsLoad(AFB_ApiT apiHandle, CtlSectionT *section, json_object *pluginsJ)
{
	json_object* builtinJ = nullptr, * completePluginsJ = nullptr;

	if(pluginsJ)
	{
		wrap_json_pack(&builtinJ, "{ss,ss,ss,ss,s[s]}",
			"uid", "builtin",
			"ldpath", CONTROL_PLUGIN_PATH,
			"info", "Builtin routine for onReceived or getSignals routines",
			"basename", "builtin",
			"lua2c", "setSignalValueWrap");

		if (json_object_get_type(pluginsJ) == json_type_array)
		{
				json_object_array_add(pluginsJ, builtinJ);
				completePluginsJ = pluginsJ;
		}
		else
		{

			completePluginsJ = json_object_new_array();
			json_object_array_add(completePluginsJ, pluginsJ);
			json_object_array_add(completePluginsJ, builtinJ);
		}
	}

	return PluginConfig(nullptr, section, completePluginsJ);
}

int Composer::loadOneSourceAPI(json_object* sourceJ)
{
	json_object *initJ = nullptr,
				*getSignalsJ = nullptr,
				*onReceivedJ = nullptr;
	CtlActionT  *initCtl = nullptr,
				*getSignalsCtl = nullptr,
				*onReceivedCtl = nullptr;
	const char *uid, *api, *info;
	int retention = 0;

	int err = wrap_json_unpack(sourceJ, "{ss,s?s,s?o,s?o,s?o,s?i,s?o !}",
			"uid", &uid,
			"api", &api,
			"info", &info,
			"init", &initJ,
			"getSignals", &getSignalsJ,
			// Signals field to make signals conf by sources
			"onReceived", &onReceivedJ,
			"retention", &retention);
	if (err)
	{
		AFB_ERROR("Missing something api|[info]|[init]|[getSignals] in %s", json_object_get_string(sourceJ));
		return err;
	}

	// Checking duplicate entry and ignore if so
	for(auto& src: sourcesListV_)
	{
		if(*src == uid)
		{
			json_object_put(sourceJ);
			return 0;
		}
	}

	if(ctlConfig_ && ctlConfig_->requireJ)
	{
		const char* requireS = json_object_to_json_string(ctlConfig_->requireJ);
		if(!strcasestr(requireS, api))
			{AFB_WARNING("Caution! You don't specify the API source as required in the metadata section. This API '%s' may not be initialized", api);}
	}

	initCtl = initJ ? convert2Action("init", initJ) : nullptr;

	// Define default action to take to subscibe souce's signals if none
	// defined.
	if(!getSignalsJ)
	{
		std::string function = "api://" + std::string(api) + "/subscribe";
		getSignalsJ = buildApiAction("getSignals", function, nullptr);
		// Register json object for later release
		ctlActionsJ_.push_back(getSignalsJ);
	}
	getSignalsCtl = convert2Action("getSignals", getSignalsJ);

	onReceivedCtl = onReceivedJ ? convert2Action("onReceived", onReceivedJ) : nullptr;

	sourcesListV_.push_back(std::make_shared<SourceAPI>(uid, api, info, initCtl, getSignalsCtl, onReceivedCtl, retention));
	return err;
}

int Composer::loadSourcesAPI(AFB_ApiT apihandle, CtlSectionT* section, json_object *sourcesJ)
{
	int err = 0;
	Composer& composer = instance();

	if(sourcesJ)
	{
		json_object *sigCompJ = nullptr;
		// add the signal composer itself as source
		wrap_json_pack(&sigCompJ, "{ss,ss,ss}",
		"uid", "Signal-Composer-service",
		"api", afbBindingV2.api,
		"info", "Api on behalf the virtual signals are sent");

		if(json_object_is_type(sourcesJ, json_type_array))
			json_object_array_add(sourcesJ, sigCompJ);

		if (json_object_get_type(sourcesJ) == json_type_array)
		{
			int count = json_object_array_length(sourcesJ);

			for (int idx = 0; idx < count; idx++)
			{
				json_object *sourceJ = json_object_array_get_idx(sourcesJ, idx);
				err = composer.loadOneSourceAPI(sourceJ);
				if (err) return err;
			}
		}
		else
		{
			if ((err = composer.loadOneSourceAPI(sourcesJ))) return err;
			if (sigCompJ && (err = composer.loadOneSourceAPI(sigCompJ))) return err;
		}
	}
	else
		{Composer::instance().initSourcesAPI();}

	return err;
}

int Composer::loadOneSignal(json_object* signalJ)
{
	json_object *onReceivedJ = nullptr,
				*dependsJ = nullptr,
				*getSignalsArgs = nullptr;
	CtlActionT* onReceivedCtl;
	const char *id = nullptr,
			   *event = nullptr,
			   *unit = nullptr;
	int retention = 0;
	double frequency=0.0;
	std::vector<std::string> dependsV;
	ssize_t sep;
	std::shared_ptr<SourceAPI> src = nullptr;

	int err = wrap_json_unpack(signalJ, "{ss,s?s,s?o,s?o,s?i,s?s,s?F,s?o !}",
			"uid", &id,
			"event", &event,
			"depends", &dependsJ,
			"getSignalsArgs", &getSignalsArgs,
			"retention", &retention,
			"unit", &unit,
			"frequency", &frequency,
			"onReceived", &onReceivedJ);
	if (err)
	{
		AFB_ERROR("Missing something uid|[event|depends]|[getSignalsArgs]|[retention]|[unit]|[frequency]|[onReceived] in %s", json_object_get_string(signalJ));
		return err;
	}

	// event or depends field manadatory
	if( (!event && !dependsJ) || (event && dependsJ) )
	{
		AFB_ERROR("Missing something uid|[event|depends]|[getSignalsArgs]|[retention]|[unit]|[frequency]|[onReceived] in %s. Or you declare event AND depends, only one of them is needed.", json_object_get_string(signalJ));
		return -1;
	}

	// Declare a raw signal
	if(event)
	{
		std::string eventStr = std::string(event);
		if( (sep = eventStr.find("/")) == std::string::npos)
		{
			AFB_ERROR("Missing something in event declaration. Has to be like: <api>/<event>");
			return -1;
		}
		std::string api = eventStr.substr(0, sep);
		src = getSourceAPI(api);
	}
	else
	{
		event = "";
	}
	// Process depends JSON object to declare virtual signal dependencies
	if (dependsJ)
	{
		if(json_object_get_type(dependsJ) == json_type_array)
		{
			int count = json_object_array_length(dependsJ);
			for(int i = 0; i < count; i++)
			{
				std::string sourceStr = json_object_get_string(json_object_array_get_idx(dependsJ, i));
				if( (sep = sourceStr.find("/")) != std::string::npos)
				{
					AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name");
					return -1;
				}
				dependsV.push_back(sourceStr);
			}
		}
		else
		{
			std::string sourceStr = json_object_get_string(dependsJ);
			if( (sep = sourceStr.find("/")) != std::string::npos)
			{
				AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name");
				return -1;
			}
			dependsV.push_back(sourceStr);
		}
		if(!src) {src=*sourcesListV_.rbegin();}
	}

	// Set default retention if not specified
	if(!retention)
	{
		retention = src->signalsDefault().retention ?
			src->signalsDefault().retention :
			30;
	}
	unit = !unit ? "" : unit;

	// Set default onReceived action if not specified
	char uid[CONTROL_MAXPATH_LEN] = "onReceived_";
	strncat(uid, id, strlen(id));

	if(!onReceivedJ)
	{
		onReceivedCtl = src->signalsDefault().onReceived ?
			src->signalsDefault().onReceived :
			nullptr;
			// Overwrite uid to the signal one instead of the default
			if(onReceivedCtl)
				{onReceivedCtl->uid = uid;}
	}
	else {onReceivedCtl = convert2Action(uid, onReceivedJ);}

	if(src != nullptr)
		{src->addSignal(id, event, dependsV, retention, unit, frequency, onReceivedCtl, getSignalsArgs);}
	else
		{err = -1;}

	return err;
}

int Composer::loadSignals(AFB_ApiT apihandle, CtlSectionT* section, json_object *signalsJ)
{
	int err = 0;
	Composer& composer = instance();

	if(signalsJ)
	{
		if (json_object_get_type(signalsJ) == json_type_array)
		{
			int count = json_object_array_length(signalsJ);

			for (int idx = 0; idx < count; idx++)
			{
				json_object *signalJ = json_object_array_get_idx(signalsJ, idx);
				err += composer.loadOneSignal(signalJ);
			}
		}
		else
			{err = composer.loadOneSignal(signalsJ);}
	}

	return err;
}

void Composer::processOptions(const std::map<std::string, int>& opts, std::shared_ptr<Signal> sig, json_object* response) const
{
	for(const auto& o: opts)
	{
		bool avg = false, min = false, max = false, last = false;
		if (o.first.compare("average") && !avg)
		{
			avg = true;
			double value = sig->average(o.second);
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("minimum") && !min)
		{
			min = true;
			double value = sig->minimum();
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("maximum") && !max)
		{
			max = true;
			double value = sig->maximum();
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("last") && !last)
		{
			last = true;
			struct signalValue value = sig->last();
			if(value.hasBool)
			{
				json_object_object_add(response, "value",
					json_object_new_boolean(value.boolVal));
			}
			else if(value.hasNum)
			{
				json_object_object_add(response, "value",
					json_object_new_double(value.numVal));
			}
			else if(value.hasStr)
			{
				json_object_object_add(response, "value",
					json_object_new_string(value.strVal.c_str()));
			}
			else
			{
				json_object_object_add(response, "value",
					json_object_new_string("No recorded value so far."));
			}
		}
		else
		{
			json_object_object_add(response, "value",
				json_object_new_string("No recorded value so far."));
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//                             PUBLIC METHODS                                //
///////////////////////////////////////////////////////////////////////////////

Composer& Composer::instance()
{
	static Composer composer;
	return composer;
}

void* Composer::createContext(void* ctx)
{
	uuid_t x;
	char cuid[38];
	uuid_generate(x);
	uuid_unparse(x, cuid);
	clientAppCtx* ret = new clientAppCtx(cuid);

	return (void*)ret;
}

void Composer::destroyContext(void* ctx)
{
	delete(reinterpret_cast<clientAppCtx*>(ctx));
}

std::vector<std::string> Composer::parseURI(const std::string& uri)
{
	std::vector<std::string> uriV;
	std::string delimiters = "/";

	std::string::size_type start = 0;
	auto pos = uri.find_first_of(delimiters, start);
	while(pos != std::string::npos)
	{
		if(pos != start) // ignore empty tokens
			uriV.emplace_back(uri, start, pos - start);
		start = pos + 1;
		pos = uri.find_first_of(delimiters, start);
	}

	if(start < uri.length()) // ignore trailing delimiter
	uriV.emplace_back(uri, start, uri.length() - start); // add what's left of the string

	return uriV;
}

int Composer::loadConfig(const std::string& filepath)
{
	const char *dirList= getenv("CONTROL_CONFIG_PATH");
	if (!dirList) dirList=CONTROL_CONFIG_PATH;
	const char *configPath = CtlConfigSearch (nullptr, dirList, "control-");

	if (!configPath) {
		AFB_ApiError(apiHandle, "CtlPreInit: No control-* config found invalid JSON %s ", dirList);
		return -1;
	}

	// create one API per file
	ctlConfig_ = CtlLoadMetaData(nullptr, configPath);
	if (!ctlConfig_) {
		AFB_ApiError(apiHandle, "CtrlPreInit No valid control config file in:\n-- %s", configPath);
		return -1;
	}

	if (ctlConfig_->api) {
		int err = afb_daemon_rename_api(ctlConfig_->api);
		if (err) {
			AFB_ApiError(apiHandle, "Fail to rename api to:%s", ctlConfig_->api);
			return -1;
		}
	}

	int err= CtlLoadSections(nullptr, ctlConfig_, ctlSections_);
	return err;
}

int Composer::loadSources(json_object* sourcesJ)
{
	int err = loadSourcesAPI(nullptr, nullptr, sourcesJ);
	if(err)
	{
		AFB_ERROR("Loading sources failed. JSON: %s", json_object_to_json_string(sourcesJ));
		return err;
	}
	initSourcesAPI();
	return err;
}

int Composer::loadSignals(json_object* signalsJ)
{
	return loadSignals(nullptr, nullptr, signalsJ);
}

CtlConfigT* Composer::ctlConfig()
{
	return ctlConfig_;
}

void Composer::initSourcesAPI()
{
	for(int i=0; i < newSourcesListV_.size(); i++)
	{
		std::shared_ptr<SourceAPI> src = newSourcesListV_.back();
		newSourcesListV_.pop_back();
		src->init();
		sourcesListV_.push_back(src);
	}
}

void Composer::initSignals()
{
	for(int i=0; i < sourcesListV_.size(); i++)
	{
		std::shared_ptr<SourceAPI> src = sourcesListV_[i];
		src->initSignals();
	}
	execSignalsSubscription();
}

std::shared_ptr<SourceAPI> Composer::getSourceAPI(const std::string& api)
{
	for(auto& source: sourcesListV_)
	{
		if (source->api() == api)
			{return source;}
	}
	for(auto& source: newSourcesListV_)
	{
		if (source->api() == api)
			{return source;}
	}
	return nullptr;
}

std::vector<std::shared_ptr<Signal>> Composer::getAllSignals()
{
	std::vector<std::shared_ptr<Signal>> allSignals;
	for( auto& source : sourcesListV_)
	{
		std::vector<std::shared_ptr<Signal>> srcSignals = source->getSignals();
		allSignals.insert(allSignals.end(), srcSignals.begin(), srcSignals.end());
	}

	return allSignals;
}

std::vector<std::shared_ptr<Signal>> Composer::searchSignals(const std::string& aName)
{
	std::string api;
	std::vector<std::shared_ptr<Signal>> signals;
	size_t sep = aName.find_first_of("/");
	if(sep != std::string::npos)
	{
		api = aName.substr(0, sep);
		std::shared_ptr<SourceAPI> source = getSourceAPI(api);
		return source->searchSignals(aName);
	}
	else
	{
		std::vector<std::shared_ptr<Signal>> allSignals = getAllSignals();
		for (std::shared_ptr<Signal>& sig : allSignals)
		{
			if(*sig == aName)
				{signals.emplace_back(sig);}
		}
	}
	return signals;
}

json_object* Composer::getsignalValue(const std::string& sig, json_object* options)
{
	std::map<std::string, int> opts;
	json_object *response = nullptr, *finalResponse = json_object_new_array();

	wrap_json_unpack(options, "{s?i, s?i, s?i, s?i !}",
		"average", &opts["average"],
		"minimum", &opts["minimum"],
		"maximum", &opts["maximum"],
		"last", &opts["last"]);

	std::vector<std::shared_ptr<Signal>> sigP = searchSignals(sig);
	if(!sigP.empty())
	{
		for(auto& sig: sigP)
		{
			wrap_json_pack(&response, "{ss}",
				"signal", sig->id().c_str());
			if (opts.empty())
			{
				struct signalValue value = sig->last();
				if(value.hasBool)
				{
					json_object_object_add(response, "value",
						json_object_new_boolean(value.boolVal));
				}
				else if(value.hasNum)
				{
					json_object_object_add(response, "value",
						json_object_new_double(value.numVal));
				}
				else if(value.hasStr)
				{
					json_object_object_add(response, "value",
						json_object_new_string(value.strVal.c_str()));
				}
				else
				{
					json_object_object_add(response, "value",
						json_object_new_string("No recorded value so far."));
				}
			}
			else
				{processOptions(opts, sig, response);}
			json_object_array_add(finalResponse, response);
		}
	}

	return finalResponse;
}

void Composer::execSignalsSubscription()
{
	for(std::shared_ptr<SourceAPI> srcAPI: sourcesListV_)
	{
		if (srcAPI->api() != std::string(ctlConfig_->api))
		{
			srcAPI->makeSubscription();
		}
	}
}