#ifndef CFG_ELEMENTS_H_
#define CFG_ELEMENTS_H_

#include <string>
#include <deque>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>

#include <boost/variant.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_vector.hpp>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/classification.hpp>

#include <boost/lexical_cast.hpp>
#include <boost/type_traits/is_arithmetic.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/foreach.hpp>

#include <boost/xpressive/xpressive.hpp>

namespace cfg {

typedef std::deque<boost::variant<std::string, unsigned int> > token_list;

struct util
{
	static token_list split(const std::string& s)
	{
		using namespace boost::xpressive;
	
		token_list result;
		std::deque<std::string> tokens;
		std::string stripped = boost::algorithm::erase_all_copy(s, "]");
		boost::algorithm::split(tokens, stripped, boost::algorithm::is_any_of(".["));
	
		sregex rexName = +_w;
		sregex rexNumber = +_d;
		smatch what;
	
		BOOST_FOREACH(const std::string& t, tokens)
		{
			if(t.empty())
				throw std::invalid_argument("Subsequent path separators found in config path (" + s + ")");
	
			if(regex_match(t, what, rexNumber)) {
				result.push_back(boost::lexical_cast<unsigned int>(t));
			}
			else if(regex_match(t, what, rexName)) {
				result.push_back(t);
			}
			else {
				throw std::invalid_argument("Failed to parse config path (" + s + "). Last token processed was " + t);
			}
		}
	
		return result;
	}
};

namespace atom_detail
{

template<typename T>
struct visitor : public boost::static_visitor<T>
{
	// note: this class is specialized for std::string below
	template <typename U>
	typename boost::enable_if<boost::is_arithmetic<U>, const T>::type
	operator()(const U& value) const { return boost::numeric_cast<T>(value); }

	template <typename U>
	typename boost::enable_if<boost::is_same<U, std::string>, const T>::type
	operator()(const U& value) const { return boost::lexical_cast<T>(value); }
};

template<>
struct visitor<std::string> : public boost::static_visitor<std::string>
{
	template <typename U>
	typename boost::enable_if<boost::is_arithmetic<U>, const std::string>::type
	operator()(const U& value) const { return boost::lexical_cast<std::string>(value); }

	template <typename U>
	typename boost::enable_if<boost::is_same<U, std::string>, const std::string>::type
	operator()(const U& value) const { return value; }
};

}

class group;
class list;
class atom;

// Need this forward declarations for friendship in list and group
namespace actions { struct insert_impl; struct append_impl; }

class element
{
public:
	virtual ~element() {}

	const group& as_group() const;
	const list& as_list() const;
	const atom& as_atom() const;

	const element& operator[](const std::string& key) const;
	const element& operator[](size_t index) const;

	template<typename T>
	const T lookup(const std::string& path) const;

	template<typename T>
	const T lookup(const std::string& path, const T& default_value) const;

	template<typename T>
	const T as() const;

	template<typename T>
	operator T() const
	{
		return as<T>();
	}

protected:
	element() {}
	element(const element&) {}

private:
	const element& recursive_lookup(const element& e, token_list tokens) const;

	class visitor : public boost::static_visitor<const element&>
	{
	public:
		visitor(const element& current) : current_(current) {}

		const element& operator()(const std::string& s) const { return current_[s]; }
		const element& operator()(unsigned int i) const { return current_[i]; }
	
	private:
		const element& current_;
	};

};

class group : public element
{
public:
	group() {}

	const element& get(const std::string& key) const { setting_map_t::const_iterator it = settings_.find(key); return *it->second; }

private:
	void insert(const std::string& key, element* value) { std::string key_(key); settings_.insert(key_, value); }

	typedef boost::ptr_map<std::string, element> setting_map_t;
	setting_map_t settings_;

public:
	typedef setting_map_t::const_iterator const_iterator;
	const_iterator begin() const { return settings_.begin(); }
	const_iterator end() const { return settings_.end(); }

	friend struct cfg::actions::insert_impl;
};

class list : public element
{
public:
	list() {}

	size_t size() const { return settings_.size(); }
	const element& get(size_t index) const { return settings_[index]; }

private:
	void append(element* value) { settings_.push_back(value); }

	typedef boost::ptr_vector<element> setting_list_t;
	setting_list_t settings_;

public:
	typedef setting_list_t::const_iterator const_iterator;
	const_iterator begin() const { return settings_.begin(); }
	const_iterator end() const { return settings_.end(); }

	friend struct cfg::actions::append_impl;
};

class atom : public element
{
public:
	explicit atom(const boost::variant<bool, long, double, std::string>& value) : value_(value) {}

	template<typename T>
	const T as() const
	{
		return boost::apply_visitor(atom_detail::visitor<T>(), value_);
	}

private:
	boost::variant<bool, long, double, std::string> value_;
};

inline const group& element::as_group() const { return dynamic_cast<const group&>(*this); }
inline const list& element::as_list() const { return dynamic_cast<const list&>(*this); }
inline const atom& element::as_atom() const { return dynamic_cast<const atom&>(*this); }

inline const element& element::operator[](const std::string& key) const
{
	if(key.find_first_of('.') != std::string::npos)
	{
		token_list tokens = util::split(key);
		return recursive_lookup(*this, tokens);
	}
	else
		return this->as_group().get(key);
}

inline const element& element::operator[](size_t index) const
{
	return this->as_list().get(index);
}

template<typename T>
inline const T element::lookup(const std::string& path) const
{
	token_list tokens = util::split(path);
	return recursive_lookup(*this, tokens).as<T>();
}

template<typename T>
inline const T element::lookup(const std::string& path, const T& default_value) const
{
	try {
		return lookup<T>(path);
	}
	catch(std::exception& e) {
		return default_value;
	}
}

inline const element& element::recursive_lookup(const element& e, token_list tokens) const
{
	if(tokens.empty())
		return e;

	boost::variant<std::string, unsigned int> t = tokens.front();
	tokens.pop_front();
	
	const element& next(boost::apply_visitor(visitor(e), t));
	return recursive_lookup(next, tokens);
}

template<typename T>
inline const T element::as() const
{
	return this->as_atom().as<T>();
}

}

#endif

