/*
 * 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 <string.h>
#include <fnmatch.h>

#include "signal-composer.hpp"

extern "C" void setSignalValueHandle(const char* aName, long long int timestamp, struct SignalValue value)
{
	std::shared_ptr<Signal> sig = bindingApp::instance().searchSignal(aName);
	if(sig)
		{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;
}

static struct pluginCBT pluginHandle = {
	.setSignalValue = setSignalValueHandle,
};

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

bindingApp::bindingApp()
{}

bindingApp::~bindingApp()
{
	free(ctlConfig_);
}

int bindingApp::loadConfig(const std::string& filepath)
{
	ctlConfig_ = CtlConfigLoad(filepath.c_str(), ctlSections_);
	if(ctlConfig_ != nullptr) {return 0;}
	return -1;
}

bindingApp& bindingApp::instance()
{
	static bindingApp bApp;
	return bApp;
}

SourceAPI* bindingApp::getSourceAPI(const std::string& api)
{
	for(auto& source: sourcesListV_)
	{
		if (source.api() == api)
			{return &source;}
	}
	return nullptr;
}

std::vector<std::string> bindingApp::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;
}

CtlActionT* bindingApp::convert2Action(const std::string& name, json_object* actionJ)
{
	json_object *functionArgsJ = nullptr, *action = nullptr;
	char *function;
	const char *plugin;

	if(actionJ &&
		!wrap_json_unpack(actionJ, "{ss,s?s,s?o !}", "function", &function,
			"plugin", &plugin,
			"args", &functionArgsJ))
	{
		action = nullptr;
		if(startsWith(function, "lua://"))
		{
			std::string fName = std::string(function).substr(6);
			wrap_json_pack(&action, "{ss,ss,so*}",
			"label", name.c_str(),
			"lua", fName,
			"args", functionArgsJ);
		}
		else if(startsWith(function, "api://"))
		{
			std::string uri = std::string(function).substr(6);
			std::vector<std::string> uriV = bindingApp::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(&action, "{ss,ss,ss,so*}",
			"label", name.c_str(),
			"api", uriV[0].c_str(),
			"verb", uriV[1].c_str(),
			"args", functionArgsJ);
		}
		else if(startsWith(function, "plugin://"))
		{
			std::string uri = std::string(function).substr(9);
			std::vector<std::string> uriV = bindingApp::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;
			}
			json_object *callbackJ = nullptr;
			wrap_json_pack(&callbackJ, "{ss,ss,so*}",
				"plugin", uriV[0].c_str(),
				"function", uriV[1].c_str(),
				"args", functionArgsJ);
			wrap_json_pack(&action, "{ss,so}",
				"label", name.c_str(),
				"callback", callbackJ);
		}
		else
		{
			AFB_ERROR("Wrong function uri specified. You have to specified 'lua://', 'plugin://' or 'api://'. (%s)", function);
			return nullptr;
		}
	}
	if(action) {return ActionLoad(action);}
	return nullptr;
}

int bindingApp::loadOneSourceAPI(json_object* sourceJ)
{
	json_object *initJ = nullptr, *getSignalsJ = nullptr;
	CtlActionT *initCtl = nullptr, *getSignalsCtl = nullptr;
	const char *api, *info;

	int err = wrap_json_unpack(sourceJ, "{ss,s?s,s?o,s?o !}",
			"api", &api,
			"info", &info,
			"init", &initJ,
			"getSignals", &getSignalsJ);
	if (err)
	{
		AFB_ERROR("Missing something api|[info]|[init]|[getSignals] in %s", json_object_get_string(sourceJ));
		return err;
	}

	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);}
	}

	if(initJ) {initCtl = convert2Action("init", initJ);}
	if(getSignalsJ) {getSignalsCtl = convert2Action("getSignals", getSignalsJ);}

	sourcesListV_.push_back(SourceAPI(api, info, initCtl, getSignalsCtl));

	return err;
}

int bindingApp::loadSourcesAPI(CtlSectionT* section, json_object *sourcesJ)
{
	int err = 0;
	bindingApp& bApp = instance();
	json_object *sigCompJ = nullptr;

	// add the signal composer itself as source
	if(sourcesJ)
	{
		wrap_json_pack(&sigCompJ, "{ss,ss}",
		"api", "signal-composer",
		"info", "Api on behalf the virtual signals are sent");

		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 = bApp.loadOneSourceAPI(sourceJ);
				if (err) return err;
			}
		}
		else
		{
			if ((err = bApp.loadOneSourceAPI(sourcesJ))) return err;
			if (sigCompJ && (err = bApp.loadOneSourceAPI(sigCompJ))) return err;
		}
}

	return err;
}

int bindingApp::loadOneSignal(json_object* signalJ)
{
	json_object *onReceivedJ = nullptr, *dependsJ = nullptr, *getSignalsArgs = nullptr;
	CtlActionT* onReceivedCtl;
	const char *id = nullptr,
			   *event = nullptr,
			   *sClass = nullptr,
			   *unit = nullptr;
	double frequency=0.0;
	std::string api;
	std::vector<std::string> dependsV;
	ssize_t sep;

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

	// Set default sClass is not specified
	sClass = !sClass ? "state" : sClass;
	unit = !unit ? "" : unit;

	// Get an action handler
	onReceivedCtl = onReceivedJ ? convert2Action("onReceived", onReceivedJ) : nullptr;

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

	// 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);
			}
			api = sourcesListV_.rbegin()->api();
		}
		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);
			api = sourcesListV_.rbegin()->api();
		}
	}

	// 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;
		}
		api = eventStr.substr(0, sep);
	}
	else
	{
		event = "";
	}

	SourceAPI* src = getSourceAPI(api) ? getSourceAPI(api):getSourceAPI("signal-composer");
	if(src != nullptr)
	{src->addSignal(id, event, dependsV, sClass, unit, frequency, onReceivedCtl, getSignalsArgs);}
	else
		{err = -1;}

	return err;
}

int bindingApp::loadSignals(CtlSectionT* section, json_object *signalsJ)
{
	int err = 0;
	bindingApp& bApp = 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 = bApp.loadOneSignal(signalJ);
				if (err) return err;
			}
		}
		else
			{err = bApp.loadOneSignal(signalsJ);}
	}

	return err;
}

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

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

std::vector<std::shared_ptr<Signal>> bindingApp::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;
}

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

int bindingApp::execSubscription()
{
	int err = 0;
	for(SourceAPI& srcAPI: sourcesListV_)
	{
		if (srcAPI.api() != std::string(ctlConfig_->api))
		{
			err = srcAPI.makeSubscription();
		}
	}
	return err;
}

json_object* bindingApp::getSignalValue(const std::string& sig, json_object* options)
{
	const char **opts = nullptr;
	json_object *response = nullptr;

	wrap_json_unpack(options, "{s?s?s?s?!}",
		&opts[0],
		&opts[1],
		&opts[2],
		&opts[3]);

	std::shared_ptr<Signal> sigP = searchSignal(sig);
	wrap_json_pack(&response, "{ss}",
		"signal", sigP->id().c_str());
	for(int idx=0; idx < sizeof(opts); idx++)
	{
		bool avg = false, min = false, max = false, last = false;
		if (strcasestr(opts[idx], "average") && !avg)
		{
			avg = true;
			double value = sigP->average();
			json_object_object_add(response, "average",
				json_object_new_double(value));
		}
		if (strcasestr(opts[idx], "min") && !min)
		{
			min = true;
			double value = sigP->minimum();
			json_object_object_add(response, "min",
				json_object_new_double(value));
		}
		if (strcasestr(opts[idx], "max") && !max)
		{
			max = true;
			double value = sigP->maximum();
			json_object_object_add(response, "max",
				json_object_new_double(value));
		}
		if (strcasestr(opts[idx], "last") && !last)
		{
			last = true;
			struct SignalValue value = sigP->last();
			if(value.hasBool)
			{
				json_object_object_add(response, "last",
					json_object_new_boolean(value.boolVal));
			}
			if(value.hasNum)
			{
				json_object_object_add(response, "last",
					json_object_new_double(value.numVal));
			}
			if(value.hasStr)
			{
				json_object_object_add(response, "last",
					json_object_new_string(value.strVal.c_str()));
			}
		}
	}

	if (!opts)
	{
		struct SignalValue value = sigP->last();
		if(value.hasBool)
		{
			json_object_object_add(response, "last",
				json_object_new_boolean(value.boolVal));
		}
		if(value.hasNum)
		{
			json_object_object_add(response, "last",
				json_object_new_double(value.numVal));
		}
		if(value.hasStr)
		{
			json_object_object_add(response, "last",
				json_object_new_string(value.strVal.c_str()));
		}
	}

	return response;
}