/*******************************************************************************
 * libretroshare/src/pqi: p3cfgmgr.cc                                          *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 *                                                                             *
 * Copyright 2007-2008 by Robert Fernie, Retroshare Team.                      *
 *                                                                             *
 * This program is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU Lesser General Public License as              *
 * published by the Free Software Foundation, either version 3 of the          *
 * License, or (at your option) any later version.                             *
 *                                                                             *
 * This program is distributed in the hope that it will be useful,             *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                *
 * GNU Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/
#include "util/rsdir.h"
//#include "retroshare/rspeers.h"
#include "pqi/p3cfgmgr.h"
#include "pqi/authssl.h"
#include "pqi/pqibin.h"
#include "pqi/pqistore.h"
#include <errno.h>
#include <rsserver/p3face.h>
#include <util/rsdiscspace.h>
#include "util/rsstring.h"

#include "rsitems/rsconfigitems.h"

/*
#define CONFIG_DEBUG 1
*/
#define BACKEDUP_SAVE


p3ConfigMgr::p3ConfigMgr(std::string dir)
        :basedir(dir), cfgMtx("p3ConfigMgr"),
	mConfigSaveActive(true)
{
}

void	p3ConfigMgr::tick(CheckPriority T)
{
    /* disable saving before exit */

    if(!mConfigSaveActive)
        return;

    if(!RsDiscSpace::checkForDiscSpace(RS_CONFIG_DIRECTORY))
    {
        RsErr() << "Cannot save configuration: disk full!";
        return;
    }

    saveConfig(T);
}


void p3ConfigMgr::saveConfiguration()
{
	if(!RsDiscSpace::checkForDiscSpace(RS_CONFIG_DIRECTORY))
		return ;

    saveConfig(CheckPriority::SAVE_WHEN_CLOSING);
}

void p3ConfigMgr::saveConfig(CheckPriority t)
{
	bool ok= true;

	RsStackMutex stack(cfgMtx);  /***** LOCK STACK MUTEX ****/

	std::list<pqiConfig *>::iterator it;
	for(it = mConfigs.begin(); it != mConfigs.end(); ++it)
        if ((*it)->HasConfigChanged(t))
		{
#ifdef CONFIG_DEBUG
			std::cerr << "p3ConfigMgr::globalSaveConfig() Saving Element: ";
			std::cerr << *it;
			std::cerr << std::endl;
#endif
			ok &= (*it)->saveConfiguration();
		}
}


void p3ConfigMgr::loadConfiguration()
{
	loadConfig();

	return;
}

void p3ConfigMgr::loadConfig()
{
	std::list<pqiConfig *>::iterator cit;
	RsFileHash dummyHash ;
	for (cit = mConfigs.begin(); cit != mConfigs.end(); ++cit)
	{
#ifdef CONFIG_DEBUG
		std::cerr << "p3ConfigMgr::loadConfig() Element: ";
		std::cerr << *cit <<" Dummy Hash: " << dummyHash;
		std::cerr << std::endl;
#endif

		(*cit)->loadConfiguration(dummyHash);

		/* force config to NOT CHANGED */
        (*cit)->resetChanges();
	}

	return;
}


void	p3ConfigMgr::addConfiguration(std::string file, pqiConfig *conf)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/

	/* construct filename */
	std::string filename = basedir;
	if (basedir != "")
	{
		filename += "/";
	}
	filename += "config/";
	filename += file;

	std::list<pqiConfig *>::iterator cit = std::find(mConfigs.begin(),mConfigs.end(),conf);
	if (cit != mConfigs.end())
	{
		std::cerr << "p3Config::addConfiguration() Config already added";
		std::cerr << std::endl;
		std::cerr << "\tOriginal filename " << (*cit)->Filename();
		std::cerr << std::endl;
		std::cerr << "\tIgnoring new filename " << filename;
		std::cerr << std::endl;
		return;
	}
	// Also check that the filename is not already registered for another config

	for(std::list<pqiConfig*>::iterator it = mConfigs.begin(); it!= mConfigs.end();)
		if((*it)->filename == filename)
		{
			std::cerr << "(WW) Registering a config for file \"" << filename << "\" that is already registered. Replacing previous component." << std::endl;
			it = mConfigs.erase(it);
		}
		else
			++it;

	conf->setFilename(filename);// (cyril) this is quite terrible. The constructor of pqiConfig should take the filename as parameter and hold the information.
	mConfigs.push_back(conf);
}


void    p3ConfigMgr::completeConfiguration()
{
	saveConfiguration();

	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
	mConfigSaveActive = false;
}



p3Config::p3Config()
	:pqiConfig()
{
	return;
}


bool p3Config::loadConfiguration(RsFileHash& /* loadHash */)
{
	return loadConfig();
}

bool p3Config::loadConfig()
{

#ifdef CONFIG_DEBUG
		std::cerr << "p3Config::loadConfig() loading Configuration\n File: " << Filename() << std::endl;
#endif

	bool pass = true;
	std::string cfgFname = Filename();
	std::string cfgFnameBackup = cfgFname + ".tmp";

	std::string signFname = Filename() +".sgn";
	std::string signFnameBackup = signFname + ".tmp";

	std::list<RsItem *> load;
	std::list<RsItem *>::iterator it;

	// try 1st attempt
	if(!loadAttempt(cfgFname, signFname, load))
	{

#ifdef CONFIG_DEBUG
		std::cerr << "p3Config::loadConfig() Failed to Load" << std::endl;
#endif

		/* bad load */
		for(it = load.begin(); it != load.end(); ++it)
		{
			delete (*it);
		}
		pass = false;

		load.clear();
	}

	// try 2nd attempt with backup files if first failed
	if(!pass)
	{
		if(!loadAttempt(cfgFnameBackup, signFnameBackup, load))
		{

#ifdef CONFIG_DEBUG
			std::cerr << "p3Config::loadConfig() Failed on 2nd Pass" << std::endl;
#endif

			/* bad load */
			for(it = load.begin(); it != load.end(); ++it)
			{
				delete (*it);
			}
			pass = false;
		}
		else
			pass = true;
	}



	if(pass)
		loadList(load);
	else
		return false;

	return pass;
}

bool p3Config::loadAttempt(const std::string& cfgFname,const std::string& signFname, std::list<RsItem *>& load)
{

#ifdef CONFIG_DEBUG
	std::cerr << "p3Config::loadAttempt() \nFilename: " << cfgFname <<  std::endl;
#endif


	uint32_t bioflags = BIN_FLAGS_HASH_DATA | BIN_FLAGS_READABLE;
	uint32_t stream_flags = BIN_FLAGS_READABLE;

	BinEncryptedFileInterface *bio = new BinEncryptedFileInterface(cfgFname.c_str(), bioflags);
	pqiSSLstore stream(setupSerialiser(), RsPeerId(), bio, stream_flags);

	if(!stream.getEncryptedItems(load))
	{
#ifdef CONFIG_DEBUG
		std::cerr << "p3Config::loadAttempt() Error occurred trying to load Item" << std::endl;
#endif
		return false;
	}

	/* set hash */
	setHash(bio->gethash());

    // In order to check the signature that is stored on disk, we compute the hash of the current data (which should match the hash of the data on disc because we just read it),
    // and validate the signature from the disk on this data. The config file data is therefore hashed twice. Not a security issue, but
    // this is a bit inelegant.

	std::string signatureRead;
	RsFileHash strHash(Hash());

	BinFileInterface bfi(signFname.c_str(), BIN_FLAGS_READABLE);

    if(bfi.getFileSize() == 0)
        return false ;

    RsTemporaryMemory mem(bfi.getFileSize()) ;

    if(!bfi.readdata(mem,mem.size()))
		return false;

    // signature is stored as ascii so we need to convert it back to binary

    RsTemporaryMemory mem2(bfi.getFileSize()/2) ;

    if(!RsUtil::HexToBin(std::string((char*)(unsigned char*)mem,mem.size()),mem2,mem2.size()))
    {
        std::cerr << "Input string is not a Hex string!!"<< std::endl;
        return false ;
	}

	bool signature_checks = AuthSSL::getAuthSSL()->VerifyOwnSignBin(strHash.toByteArray(), RsFileHash::SIZE_IN_BYTES,mem2,mem2.size());

    std::cerr << "(II) checked signature of config file " << cfgFname << ": " << (signature_checks?"OK":"Wrong!") << std::endl;

    return signature_checks;
}

bool p3Config::saveConfiguration()
{
		return saveConfig();
}

bool p3Config::saveConfig()
{
	bool cleanup = true;
	std::list<RsItem *> toSave;
	saveList(cleanup, toSave);

	// temporarily append new to files as these will replace current configuration
	std::string newCfgFname = Filename() + "_new";
	std::string newSignFname = Filename() + ".sgn" + "_new";

	std::string tmpCfgFname = Filename() + ".tmp";
	std::string tmpSignFname = Filename() + ".sgn" + ".tmp";

	std::string cfgFname = Filename();
	std::string signFname = Filename() + ".sgn";

    std::cerr << "(II) Saving configuration file " << cfgFname << std::endl;

	uint32_t bioflags = BIN_FLAGS_HASH_DATA | BIN_FLAGS_WRITEABLE;
	uint32_t stream_flags = BIN_FLAGS_WRITEABLE;
	bool written = true;

	if (!cleanup)
		stream_flags |= BIN_FLAGS_NO_DELETE;

	BinEncryptedFileInterface *cfg_bio = new BinEncryptedFileInterface(newCfgFname.c_str(), bioflags);
	pqiSSLstore *stream = new pqiSSLstore(setupSerialiser(), RsPeerId(), cfg_bio, stream_flags);

	written = written && stream->encryptedSendItems(toSave);

	if(!written)
		std::cerr << "(EE) Error while writing config file " << Filename() << ": file dropped!!" << std::endl;

	/* store the hash */
	setHash(cfg_bio->gethash());

	// bio is taken care of in stream's destructor, also forces file to close
	delete stream;

	/* sign data */
	std::string signature;
	RsFileHash strHash(Hash());
	AuthSSL::getAuthSSL()->SignData(strHash.toByteArray(),strHash.SIZE_IN_BYTES, signature);

    /* write signature to configuration */
    BinMemInterface *signbio = new BinMemInterface(signature.c_str(),
    		signature.length(), BIN_FLAGS_READABLE);

    signbio->writetofile(newSignFname.c_str());

    delete signbio;

    // now rewrite current files to temp files
	// rename back-up to current file
	if(!RsDirUtil::renameFile(cfgFname, tmpCfgFname)  || !RsDirUtil::renameFile(signFname, tmpSignFname)){
#ifdef CONFIG_DEBUG
		std::cerr << "p3Config::backedUpFileSave() Failed to rename backup meta files: " << std::endl
				<< cfgFname << " to " << tmpCfgFname << std::endl
				<< signFname << " to " << tmpSignFname << std::endl;
#endif
			written = false;
		}



	// now rewrite current files to temp files
	// rename back-up to current file
	if(!RsDirUtil::renameFile(newCfgFname, cfgFname)  || !RsDirUtil::renameFile(newSignFname, signFname)){
	#ifdef CONFIG_DEBUG
				std::cerr << "p3Config::() Failed to rename meta files: " << std::endl
						<< newCfgFname << " to " << cfgFname << std::endl
						<< newSignFname << " to " << signFname << std::endl;
	#endif

				written = false;
			}



	saveDone(); // callback to inherited class to unlock any Mutexes protecting saveList() data

	return written;

}


/**************************** CONFIGURATION CLASSES ********************/

p3GeneralConfig::p3GeneralConfig()
	:p3Config()
{
	return;
}

		// General Configuration System
std::string     p3GeneralConfig::getSetting(const std::string &opt)
{
#ifdef CONFIG_DEBUG
	std::cerr << "p3GeneralConfig::getSetting(" << opt << ")";
	std::cerr << std::endl;
#endif
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/

	/* extract from config */
	std::map<std::string, std::string>::iterator it;
	if (settings.end() == (it = settings.find(opt)))
	{
		std::string nullstring;
		return nullstring;
	}
	return it->second;
}

void            p3GeneralConfig::setSetting(const std::string &opt, const std::string &val)
{
#ifdef CONFIG_DEBUG
	std::cerr << "p3GeneralConfig::setSetting(" << opt << " = " << val << ")";
	std::cerr << std::endl;
#endif
	{
		RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/

		/* extract from config */
		std::map<std::string, std::string>::iterator it;
		if (settings.end() != (it = settings.find(opt)))
		{
			if (it->second == val)
			{
				/* no change */
				return;
			}
		}

		settings[opt] = val;
	}
	/* outside mutex */
	IndicateConfigChanged();

	return;
}

RsSerialiser *p3GeneralConfig::setupSerialiser()
{
	RsSerialiser *rss = new RsSerialiser();
	rss->addSerialType(new RsGeneralConfigSerialiser());
	return rss;
}

bool p3GeneralConfig::saveList(bool &cleanup, std::list<RsItem *>& savelist)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/

#ifdef CONFIG_DEBUG
	std::cerr << "p3GeneralConfig::saveList() KV sets: " << settings.size();
	std::cerr << std::endl;
#endif

	cleanup = true;


	RsConfigKeyValueSet *item = new RsConfigKeyValueSet();
	std::map<std::string, std::string>::iterator it;
	for(it = settings.begin(); it != settings.end(); ++it)
	{
		RsTlvKeyValue kv;
		kv.key = it->first;
		kv.value = it->second;
		item->tlvkvs.pairs.push_back(kv);

		/* make sure we don't overload it */
		if (item->tlvkvs.TlvSize() > 4000)
		{
			savelist.push_back(item);
			item = new RsConfigKeyValueSet();
		}
	}

	if (item->tlvkvs.pairs.size() > 0)
	{
		savelist.push_back(item);
	} else
		delete item;

	return true;
}


bool    p3GeneralConfig::loadList(std::list<RsItem *>& load)
{
#ifdef CONFIG_DEBUG
	std::cerr << "p3GeneralConfig::loadList() count: " << load.size();
	std::cerr << std::endl;
#endif

	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/

	/* add into settings */
	RsConfigKeyValueSet *item = NULL;
	std::list<RsItem *>::iterator it;
	std::list<RsTlvKeyValue>::iterator kit;

	for(it = load.begin(); it != load.end();)
	{
		item = dynamic_cast<RsConfigKeyValueSet *>(*it);
		if (item)
		{
			for(kit = item->tlvkvs.pairs.begin();
				kit != item->tlvkvs.pairs.end(); ++kit)
			{
				settings[kit->key] = kit->value;
			}
		}

		/* cleanup */
		delete (*it);
		it = load.erase(it);
	}

	return true;
}


/**** MUTEX NOTE:
 * have protected all, but think that
 * only the Indication and hash really need it
 */

pqiConfig::pqiConfig()
    : cfgMtx("pqiConfig")
{
}

pqiConfig::~pqiConfig()
{
}

const std::string& pqiConfig::Filename()
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
	return filename;
}

const RsFileHash& pqiConfig::Hash()
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
	return hash;
}

void	pqiConfig::IndicateConfigChanged(RsConfigMgr::CheckPriority t)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
    ConfInd.IndicateChanged(uint8_t(t));
}

bool	pqiConfig::HasConfigChanged(RsConfigMgr::CheckPriority t)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
    return  ConfInd.Changed(uint8_t(t));
}
bool	pqiConfig::resetChanges()
{
    RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
    ConfInd.Reset();
    return true;
}

void    pqiConfig::setFilename(const std::string& name)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
	filename = name;
}

void	pqiConfig::setHash(const RsFileHash& h)
{
	RsStackMutex stack(cfgMtx); /***** LOCK STACK MUTEX ****/
	hash = h;
}

