/////////////////////////////////////////////////////////////////////////////
// File:        configurationimpl.h
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Copyright:   (C) 2010-11 Cesar Mauri Loba - CREA Software Systems
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////
#ifdef SPCORE_CONFIGURATIONIMPL_H
#error __FILE__ included twice
#endif

#define SPCORE_CONFIGURATIONIMPL_H

#include "spcore/configuration.h"
#include "libconfig.h"
#include "spcore/coreruntime.h"

#include <string>

#ifdef WIN32
// TODO: remove the following line when boost gets updated
// See: https://svn.boost.org/trac/boost/ticket/4649
#pragma warning (disable:4127)
#endif
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>
#include <boost/static_assert.hpp>

namespace spcore {

/**
	Configuration implementation based on libconfig
**/
class ConfigurationLibconfig : public IConfiguration {
protected:
	virtual ~ConfigurationLibconfig() {
		config_destroy (&m_config);
	}

public:
	// Constructor
	ConfigurationLibconfig() {
		config_init (&m_config);
	}
	
	virtual bool Load (FILE* instream) {
		int retval= config_read (&m_config, instream);
		
		if (retval== CONFIG_FALSE) {
			getSpCoreRuntime()->LogMessage(
				ICoreRuntime::LOG_ERROR, config_error_text(&m_config), "configuration load");
			return false;
		}
		return true;
	}
	
	virtual bool Save (FILE* outstream) const {
		config_write (&m_config, outstream);

		return true;
	}

private:
	static
	bool IsSane(const char* path) {
		assert (path);
		while (	(*path>= 'a' && *path<= 'z') ||
				(*path>= 'A' && *path<= 'Z') ||
				(*path>= '0' && *path<= '9') ||
				*path== '-' || *path== '_' || *path== '/' )

				++path;

		return (*path== 0);
	}

	// Get effective path given an absolute path, relative path or '..'.
	// Return true if successful
	bool GetEffectivePathTranslate(const char* src, std::string& path) const {
		if (!src || !*src) return false;

		if (strcmp(src, "..")== 0) {
			size_t dotpos= m_currentPath.find_last_of('.');
			if (dotpos== std::string::npos) {
				// Not found
				if (!m_currentPath.empty())
					path.clear();
				else
					// Already in the root path
					return false;
			}
			else
				path= m_currentPath.substr(0, dotpos);
			return true;
		}
		if (!IsSane(src)) return false;

		if (src[0]== '/') {
			// Absolute path
			
			// Ignore slashes at the begining
			for (++src; *src== '/'; ++src);

			path= src;
		}
		else {
			// Relative path
			path= m_currentPath + '.';
			path+= src;
		}
		
		// Remove trailing slashes
		size_t pos= path.find_last_not_of('/');
		path= path.substr(0, pos + 1);

		// Replace slashes by dots
		for (size_t i= 0; i< path.size(); ++i) 
			if (path[i]== '/') path[i]= '.';

		return true;
	}

public:
	virtual bool ReadString (const char* path, const char** str) const {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;
		return (config_lookup_string (&m_config, epath.c_str(), str)== CONFIG_TRUE);
	}
	
	virtual bool ReadInt (const char* path, int* i) const {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;
		// Workaround for older versions of libconfig
#ifdef WIN32
		return (config_lookup_int (&m_config, epath.c_str(), i)== CONFIG_TRUE);
#else
		long tmp;
		int retval= config_lookup_int (&m_config, epath.c_str(), &tmp);
		if (retval!= CONFIG_TRUE) return false;
		*i= (int) tmp;
		return true;
#endif
	}
	
	virtual bool ReadInt64 (const char* path, long long* i) const {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;
		return (config_lookup_int64 (&m_config, epath.c_str(), i)== CONFIG_TRUE);
	}
	
	virtual bool ReadDouble (const char* path, double* d) const {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;
		return (config_lookup_float (&m_config, epath.c_str(), d)== CONFIG_TRUE);
	}
	
	virtual bool ReadBool (const char* path, bool* b) const {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;
		int value;
		if (config_lookup_bool (&m_config, epath.c_str(), &value)== CONFIG_TRUE) {
			*b= (value? true : false);
			return true;
		}
		return false;
	}

private:

	

	// Return a pointer to a setting with a given path of a certain type
	// NULL if the setting already exists and is a group or wrong path
	config_setting_t* GetCreateScalarSetting (const char* path, int type) {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return NULL;
		if (epath.empty()) return NULL;

		config_setting_t* cs= config_lookup (&m_config, path);
		if (cs) {
			// Setting exists

			// Do not overwrite group!
			if (config_setting_is_group(cs)) {
				std::string error_msg= "Setting " + epath;
				error_msg+= " won't be saved. A group has the same name.";
				getSpCoreRuntime()->LogMessage(
					ICoreRuntime::LOG_ERROR, error_msg.c_str(), "configuration");
				return NULL;
			}

			// Has the same type?
			if (config_setting_type (cs)== type) return cs;

			// OK. Is scalar/vector/list an has a different type. Remove and create
			// a new one of the requested type
			config_setting_t* parent= config_setting_parent (cs);
			assert (parent);	// An scalar/vector/list needs a parent

			// Keep setting name
			const char* cname= config_setting_name (cs);
			assert (cname);		// Anonymous settings shouldn't exists
			std::string cnames(cname);

			// Remove child
			int index= config_setting_index(cs);
			assert (index>= 0);

			if (config_setting_remove_elem (parent, (unsigned int) index)== CONFIG_FALSE) {
				assert (false);
				return NULL;
			}

			// Add new child
			cs= config_setting_add (parent, cnames.c_str(), type);
			if (!cs) {
				assert (false);
				return NULL;
			}

			return cs;
		}
		else {
			// Tokenize path elements
			std::vector<std::string> tokens;
			boost::char_separator<char> sep(".");
			boost::tokenizer<boost::char_separator<char> > tok(epath, sep);	
			BOOST_FOREACH(std::string t, tok) {
				tokens.push_back(t);
			}
			assert (!tokens.empty());


			config_setting_t* parent= config_root_setting (&m_config);
			config_setting_t* current= NULL;

			// Check and create intermediate paths when needed
			for (size_t level= 0; level< tokens.size()-1; ++level) {
				current= config_setting_get_member (parent, tokens[level].c_str());
				if (!current) {
					// Does not exists. Create group.
					current= config_setting_add (parent, tokens[level].c_str(), CONFIG_TYPE_GROUP);
					assert (current);
				}
				else {
					// Exists. Is not group?
					if (!config_setting_is_group (current)) return NULL;	// Fail
				}

				parent= current;
			}

			// Create leaf setting of the desired type
#ifndef NDEBUG
			current= config_setting_get_member (parent, tokens[tokens.size()-1].c_str());
			assert (!current);
#endif
			current= config_setting_add (parent, tokens[tokens.size()-1].c_str(), type);
			assert (current);

			return current;
		}
	}

public:
	virtual bool WriteString (const char* path, const char* str) {
		config_setting_t* cs= GetCreateScalarSetting (path, CONFIG_TYPE_STRING);
		if (!cs) return false;
		return (config_setting_set_string (cs, str)== CONFIG_TRUE);
	}
	
	virtual bool WriteInt (const char* path, int i) {
		config_setting_t* cs= GetCreateScalarSetting (path, CONFIG_TYPE_INT);
		if (!cs) return false;
		return (config_setting_set_int (cs, i)== CONFIG_TRUE);
	}
	
	virtual bool WriteInt64 (const char* path, long long i) {
		config_setting_t* cs= GetCreateScalarSetting (path, CONFIG_TYPE_INT64);
		if (!cs) return false;
		return (config_setting_set_int64 (cs, i)== CONFIG_TRUE);
	}
	
	virtual bool WriteDouble (const char* path, double d) {
		config_setting_t* cs= GetCreateScalarSetting (path, CONFIG_TYPE_FLOAT);
		if (!cs) return false;
		return (config_setting_set_float (cs, d)== CONFIG_TRUE);
	}
	
	virtual bool WriteBool (const char* path, bool b) {
		config_setting_t* cs= GetCreateScalarSetting (path, CONFIG_TYPE_BOOL);
		if (!cs) return false;
		return (config_setting_set_bool (cs, (int) b)== CONFIG_TRUE);
	}
	
	virtual bool Remove (const char* path) {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return NULL;

		config_setting_t* current= config_lookup (&m_config, path);
		if (!current) return false;

		int index= config_setting_index (current);
		if (index< 0) return false;

		config_setting_t* parent= config_setting_parent (current);
		assert (parent);

		return (config_setting_remove_elem (parent, index)== CONFIG_TRUE);
	}

	virtual bool SetPath (const char* path) {
		std::string epath;
		if (!GetEffectivePathTranslate(path, epath)) return false;

		// New path should point to a group
		config_setting_t* cs= config_lookup (&m_config, epath.c_str());
		//if (!cs || !config_setting_is_group(cs)) return false;
		if (cs && !config_setting_is_group(cs)) return false;

		m_currentPath= epath;
	
		return true;
	}

private:

	config_t m_config;
	std::string m_currentPath;
};

} // namespace spcore
