#include "Alerts.h"
#include "../Interface/Database.h"
#include "database.h"
#include "../Interface/Server.h"
#include "server_settings.h"
#include "../stringtools.h"
#include "../luaplugin/ILuaInterpreter.h"
#include "../urlplugin/IUrlFactory.h"
#include "Mailer.h"
#include "ClientMain.h"
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include "../common/miniz.h"

extern ILuaInterpreter* lua_interpreter;
extern IUrlFactory *url_fak;

namespace
{
#include "alert_lua.h"
#include "alert_pulseway_lua.h"

        std::string get_alert_lua(unsigned char* data, unsigned int data_len)
        {
                size_t out_len;
                void* cdata = tinfl_decompress_mem_to_heap(data, data_len, &out_len, TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32);
                if (cdata == NULL)
                {
                        return std::string();
                }

                std::string ret(reinterpret_cast<char*>(cdata), reinterpret_cast<char*>(cdata) + out_len);
                mz_free(cdata);
                return ret;
        }

}

std::string get_alert_script(IDatabase* db, int script_id)
{
	db_results res_script = db->Read("SELECT script FROM alert_scripts WHERE id="+convert(script_id));

        if (res_script.empty())
        {
        	Server->Log("Cannot find alert script with id " + convert(script_id), LL_ERROR);
                return std::string();
        }

        std::string ret = res_script[0]["script"];

        if (script_id == 1
              && FileExists("urbackupserver/alert.lua"))
        {
        	ret = getFile("urbackupserver/alert.lua");
        }

		if (script_id == 100000
			&& FileExists("urbackupserver/alert_pulseway.lua"))
		{
			ret = getFile("urbackupserver/alert_pulseway.lua");
		}

		if (ret.empty())
		{
			switch (script_id)
			{
			case 1: ret = get_alert_lua(alert_lua_z, alert_lua_z_len); break;
			case 100000: ret = get_alert_lua(alert_pulseway_lua_z, alert_pulseway_lua_z_len); break;
			}
		}

	return ret;
}


namespace
{
	struct SScriptParam
	{
		std::string name;
		std::string default_value;
		std::string type;
	};

	struct SScript
	{
		std::string code;
		std::vector<SScriptParam> params;
		std::string global;
		std::string global_mem;
		std::map<int, std::string> state_mem;
	};

	SScript prepareScript(IDatabase* db, ILuaInterpreter* lua_interpreter, int script_id)
	{
		SScript ret;
		ret.code = get_alert_script(db, script_id);

		if (!ret.code.empty()
			&& ret.code.find("--disable_lua_compile")==std::string::npos)
		{
			std::string compiled_code = lua_interpreter->compileScript(ret.code);
			if (!compiled_code.empty())
			{
				ret.code = compiled_code;
			}
		}

		if (ret.code.empty())
		{
			return SScript();
		}

		db_results res_params = db->Read("SELECT name, default_value, type FROM alert_script_params WHERE script_id="+convert(script_id));

		for (size_t i = 0; i < res_params.size(); ++i)
		{
			SScriptParam param = { res_params[i]["name"], res_params[i]["default_value"], res_params[i]["type"] };
			ret.params.push_back(param);
		}

		db_results res_script = db->Read("SELECT global_state FROM alert_scripts WHERE id=" + convert(script_id));

		if (!res_script.empty())
		{
			ret.global = res_script[0]["global_state"];
		}

		return ret;
	}

	class MailBridge : public ILuaInterpreter::IEMailFunction
	{
	public:
		// Inherited via IEMailFunction
		virtual bool mail(const std::string & send_to, const std::string & subject, const std::string & message)
		{
			return Mailer::sendMail(send_to, subject, message);
		}
	};

	class UrlBridge : public ILuaInterpreter::IUrlFunction
	{
	public:
		// Inherited via IUrlFunction
		virtual bool requestUrl(const std::string & url, str_map & params, std::string & ret, long & http_code, std::string * errmsg = NULL)
		{
			return url_fak->requestUrl(url, params, ret, http_code, errmsg);
		}
	};
}

void Alerts::operator()()
{
	if (lua_interpreter == NULL)
	{
		return;
	}

	IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);

	db->Write("UPDATE clients SET alerts_next_check=NULL");

	IQuery* q_get_alert_clients = db->Prepare("SELECT c.id AS clientid, c.name AS clientname, c.capa AS capa, file_ok, image_ok, alerts_state, strftime('%s', lastbackup) AS lastbackup, "
		"strftime('%s', lastseen) AS lastseen, strftime('%s', lastbackup_image) AS lastbackup_image, created, os_simple, groupid, cg.name AS groupname "
		"FROM "
		" (clients c LEFT OUTER JOIN settings_db.si_client_groups cg ON c.groupid = cg.id) "
		" WHERE alerts_next_check IS NULL OR alerts_next_check<=?");
	IQuery* q_update_client = db->Prepare("UPDATE clients SET  file_ok=?, image_ok=?, alerts_next_check=?, alerts_state=? WHERE id=?");
	IQuery* q_update_global_state = db->Prepare("UPDATE alert_scripts SET global_state=? WHERE id=?");

	std::map<int, SScript> alert_scripts;

	ILuaInterpreter::SInterpreterFunctions funcs;
	funcs.mail_func = new MailBridge;
	funcs.url_func = new UrlBridge;


	bool first_run = true;
	while (true)
	{
#ifdef _DEBUG
		if(!first_run)
#endif
		Server->wait(60*1000);

#ifdef _DEBUG
		first_run = false;
#endif

		q_get_alert_clients->Bind(Server->getTimeMS());
		db_results res = q_get_alert_clients->Read();
		q_get_alert_clients->Reset();

		for (size_t i = 0; i < res.size(); ++i)
		{
			int clientid = watoi(res[i]["clientid"]);
			ServerSettings server_settings(db, clientid);
			int script_id = server_settings.getSettings()->alert_script;
			std::map<int, SScript>::iterator it = alert_scripts.find(script_id);
			if (it == alert_scripts.end())
			{
				alert_scripts[script_id] = prepareScript(db, lua_interpreter, script_id);
				it = alert_scripts.find(script_id);
			}			

			if (!it->second.code.empty())
			{
				ILuaInterpreter::Param params_raw;
				ILuaInterpreter::Param::params_map& params = *params_raw.u.params;
				params["clientid"] = clientid;
				params["clientname"] = res[i]["clientname"];
				int update_freq_file_incr = server_settings.getUpdateFreqFileIncr();
				int update_freq_file_full = server_settings.getUpdateFreqFileFull();
				params["incr_file_interval"] = update_freq_file_incr;
				params["full_file_interval"] = update_freq_file_full;
				int update_freq_image_incr = server_settings.getUpdateFreqImageIncr();
				int update_freq_image_full = server_settings.getUpdateFreqImageFull();
				params["incr_image_interval"] = update_freq_image_incr;
				params["full_image_interval"] = update_freq_image_full;
				params["no_images"] = server_settings.getSettings()->no_images;
				params["no_file_backups"] = server_settings.getSettings()->no_file_backups;
				params["os_simple"] = res[i]["os_simple"];
				params["groupid"] = watoi(res[i]["groupid"]);
				params["groupname"] = res[i]["groupname"];

				int64 times = Server->getTimeSeconds();
				int64 created = watoi64(res[i]["created"]);
				int64 lastbackup_file = watoi64(res[i]["lastbackup"]);
				int64 lastbackup_image = watoi64(res[i]["lastbackup_image"]);

				params["passed_time_lastseen"] = times - watoi64(res[i]["lastseen"]);
				params["passed_time_lastbackup_file"] = (std::min)(times - lastbackup_file, times - created);
				params["passed_time_lastbackup_image"] = (std::min)(times - lastbackup_image, times - created);
				params["lastbackup_file"] = lastbackup_file;
				params["lastbackup_image"] = lastbackup_image;
				params["image_support"] = (watoi(res[i]["capa"]) & CAPA_NO_IMAGE_BACKUPS) == 0;

				SSettings* settings = server_settings.getSettings();

				bool complex_file_interval = settings->update_freq_full.find(";") != std::string::npos
					|| settings->update_freq_incr.find(";") != std::string::npos;

				bool complex_image_interval = settings->update_freq_image_full.find(";") != std::string::npos
					|| settings->update_freq_image_incr.find(";") != std::string::npos;

				if ( complex_file_interval
					|| complex_image_interval )
				{
					params["complex_interval"] = true;
				}
				else
				{
					params["complex_interval"] = false;
				}

				bool file_ok = res[i]["file_ok"] == "1";
				params["file_ok"] = file_ok;
				bool image_ok = res[i]["image_ok"] == "1";
				params["image_ok"] = image_ok;

				str_map nondefault_params;
				ParseParamStrHttp(server_settings.getSettings()->alert_params, &nondefault_params);

				for (size_t j = 0; j < it->second.params.size(); ++j)
				{
					SScriptParam& param = it->second.params[j];
					str_map::iterator it_param = nondefault_params.find(param.name);
					std::string val;
					if (it_param != nondefault_params.end())
					{
						val = it_param->second;
					}
					else
					{
						val = param.default_value;
					}

					if (param.type == "int")
					{
						params[param.name] = watoi(val);
					}
					else if (param.type == "num")
					{
						params[param.name] = atof(val.c_str());
					}
					else if (param.type == "bool")
					{
						params[param.name] = val != "0";
					}
					else if (param.type == "interval")
					{
						params[param.name] = watoi64(val);
					}
					else
					{
						params[param.name] = val;
					}
				}

				std::string state = res[i]["alerts_state"];
				std::string global_state = it->second.global;
				int64 ret2;
				int64 ret = lua_interpreter->runScript(it->second.code, params_raw, ret2, state, 
					it->second.state_mem[clientid], global_state, it->second.global_mem, funcs);
				bool needs_update = false;
				
				if (ret>=0)
				{
					file_ok = !(ret & 1);
					image_ok = !(ret & 2);

					if (file_ok != params["file_ok"].u.b
						|| image_ok != params["image_ok"].u.b)
					{
						needs_update = true;
					}
				}
				else
				{
					Server->Log("Error executing alert script id " + convert(script_id) + ". Return value " + convert(ret) + ".", LL_ERROR);
				}

				int64 next_check;
				if (ret2 >= 0)
				{
					next_check = Server->getTimeMS() + ret2;
					needs_update = true;
				}
				else
				{
					if (!file_ok
						&& !image_ok)
					{
						next_check = Server->getTimeMS() + 1*60*60*1000;
						needs_update = true;
					}
					else
					{
						next_check = Server->getTimeMS();
					}
				}

				if (state != res[i]["alerts_state"])
				{
					needs_update = true;
				}

				int i_file_ok = file_ok ? 1 : 0;
				int i_image_ok = image_ok ? 1 : 0;

				if (settings->no_file_backups)
				{
					i_file_ok = -1;
					if (res[i]["file_ok"] != "-1")
					{
						needs_update = true;
					}
				}
				else if (!complex_file_interval
					&& update_freq_file_incr < 0
					&& update_freq_file_full < 0)
				{
					i_file_ok = -1;
					if (res[i]["file_ok"] != "-1")
					{
						needs_update = true;
					}
				}

				if (settings->no_images)
				{
					i_image_ok = -1;
					if (res[i]["image_ok"] != "-1")
					{
						needs_update = true;
					}
				}
				else if (!complex_image_interval
					&& update_freq_image_full < 0
					&& update_freq_image_incr < 0)
				{
					i_image_ok = -1;
					if (res[i]["image_ok"] != "-1")
					{
						needs_update = true;
					}
				}

				if (needs_update)
				{
					q_update_client->Bind(i_file_ok);
					q_update_client->Bind(i_image_ok);
					q_update_client->Bind(next_check);
					q_update_client->Bind(state.c_str(), state.size());
					q_update_client->Bind(clientid);
					q_update_client->Write();
					q_update_client->Reset();
				}

				if (global_state != it->second.global)
				{
					it->second.global = global_state;

					q_update_global_state->Bind(global_state.c_str(), global_state.size());
					q_update_global_state->Bind(script_id);
					q_update_global_state->Write();
					q_update_global_state->Reset();
				}
			}
		}
	}
}
