/*
cpptempl
=================
This is a template engine for C++.

Syntax
=================
Variables: {$variable_name}
Loops: {% for person in people %}Name: {$person.name}{% endfor %}
If: {% for person.name == "Bob" %}Full name: Robert{% endif %}

Copyright
==================
Author: Ryan Ginstrom
MIT License

Usage
=======================
	wstring text = L"{% if item %}{$item}{% endif %}\n"
		L"{% if thing %}{$thing}{% endif %}" ;
	cpptempl::data_map data ;
	data[L"item"] = cpptempl::make_data(L"aaa") ;
	data[L"thing"] = cpptempl::make_data(L"bbb") ;

	wstring result = cpptempl::parse(text, data) ;

Handy Functions
========================
make_data() : Feed it a string, data_map, or data_list to create a data entry.
Example:
	data_map person ;
	person[L"name"] = make_data(L"Bob") ;
	person[L"occupation"] = make_data(L"Plumber") ;
	data_map data ;
	data[L"person"] = make_data(person) ;
	wstring result = parse(templ_text, data) ;

*/
#pragma once

#ifdef _WIN32
#pragma warning( disable : 4996 ) // 'std::copy': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'
#pragma warning( disable : 4512 ) // 'std::copy': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'
#endif

#include <string>
#include <vector>
#include <map>							
#include <boost/shared_ptr.hpp>
#ifndef _MSC_VER
#include <boost/locale.hpp>
#include <utf8.h>
#else
#include <boost/scoped_array.hpp>
#include "windows.h"
#include "winnls.h" // unicode-multibyte conversion
#endif
#include <boost/unordered_map.hpp>
#include <boost/lexical_cast.hpp>

#include <iostream>

namespace cpptempl
{
	using std::wstring ;
	// various typedefs

	class data_ptr;
	typedef std::vector<data_ptr> data_list ;

	class data_map {
	public:
		data_ptr& operator [](const std::wstring& key);
		data_ptr& operator [](const std::string& key);
		bool empty();
		bool has(const wstring& key);
	private:
		boost::unordered_map<wstring, data_ptr> data;
	};

	// data classes
	class Data ;
	class DataValue ;
	class DataList ;
	class DataMap ;

	class data_ptr {
	public:
		data_ptr() {}
		template<typename T> data_ptr(const T& data) {
			this->operator =(data);
		}
		data_ptr(DataValue* data) : ptr(data) {}
		data_ptr(DataList* data) : ptr(data) {}
		data_ptr(DataMap* data) : ptr(data) {}
		data_ptr(const data_ptr& data) {
			ptr = data.ptr;
		}
		template<typename T> void operator = (const T& data);
		void push_back(const data_ptr& data);
		virtual ~data_ptr() {}
		Data* operator ->() {
			return ptr.get();
		}
	private:
		boost::shared_ptr<Data> ptr;
	};

	template<> inline void data_ptr::operator = (const data_ptr& data);
	template<> void data_ptr::operator = (const std::string& data);
	template<> void data_ptr::operator = (const std::wstring& data);
	template<> void data_ptr::operator = (const data_map& data);
	template<typename T>
	void data_ptr::operator = (const T& data) {
#ifndef _MSC_VER
		std::wstring data_str = boost::lexical_cast<std::wstring>(data);
#else

#endif
		this->operator =(data_str);
	}

	// convenience functions for recoding utf8 string to wstring and back
	inline std::wstring utf8_to_wide(const std::string& text) {
#ifndef _MSC_VER
        std::wstring result;
        if (sizeof(wchar_t) == 2) {
            utf8::utf8to16(text.begin(), text.end(), std::back_inserter(result));
        } else {
            assert(sizeof(wchar_t) == 4);
            utf8::utf8to32(text.begin(), text.end(), std::back_inserter(result));
        }
        return result;
        //return boost::locale::conv::to_utf<wchar_t>(text, "UTF-8");
#else
		// Calculate the required length of the buffer
		const size_t len_needed = ::MultiByteToWideChar(CP_UTF8, 0, text.c_str(), (UINT)(text.length()) , NULL, 0 );
		boost::scoped_array<wchar_t> buff(new wchar_t[len_needed+1]) ;
		const size_t num_copied = ::MultiByteToWideChar(CP_UTF8, 0, text.c_str(), text.size(), buff.get(), len_needed+1) ;
		return std::wstring(buff.get(), num_copied) ;
#endif
	}

	inline std::string wide_to_utf8(const std::wstring& text) {
#ifndef _MSC_VER
        std::string result;
        if (sizeof(wchar_t) == 2) {
            utf8::utf16to8(text.begin(), text.end(), std::back_inserter(result));
        } else {
            assert(sizeof(wchar_t) == 4);
            utf8::utf32to8(text.begin(), text.end(), std::back_inserter(result));
        }
        return result;
        //return boost::locale::conv::from_utf<>(text, "UTF-8");
#else
		const size_t len_needed = ::WideCharToMultiByte(CP_UTF8, 0, text.c_str(), (UINT)(text.length()) , NULL, 0, NULL, NULL) ;
		boost::scoped_array<char> buff(new char[len_needed+1]) ;
		const size_t num_copied = ::WideCharToMultiByte(CP_UTF8, 0, text.c_str(), (UINT)(text.length()) , buff.get(), len_needed+1, NULL, NULL) ;
		return std::string(buff.get(), num_copied) ;
#endif
	}

	// token classes
	class Token ;
	typedef boost::shared_ptr<Token> token_ptr ;
	typedef std::vector<token_ptr> token_vector ;

	// Custom exception class for library errors
	class TemplateException : public std::exception
	{
	public:
		TemplateException(std::string reason) : m_reason(reason){}
		~TemplateException() throw() {}
		const char* what() const throw() {
			return m_reason.c_str();
		}
	private:
		std::string m_reason;
	};

	// Data types used in templates
	class Data
	{
	public:
        virtual ~Data() {
            // Intentionally left empty.
        }
		virtual bool empty() = 0 ;
		virtual wstring getvalue();
		virtual data_list& getlist();
		virtual data_map& getmap() ;
	};

	class DataValue : public Data
	{
		wstring m_value ;
	public:
		DataValue(wstring value) : m_value(value){}
		wstring getvalue();
		bool empty();
	};

	class DataList : public Data
	{
		data_list m_items ;
	public:
		DataList(const data_list &items) : m_items(items){}
		data_list& getlist() ;
		bool empty();
	};

	class DataMap : public Data
	{
		data_map m_items ;
	public:
		DataMap(const data_map &items) : m_items(items){}
		data_map& getmap();
		bool empty();
	};

	// convenience functions for making data objects
	inline data_ptr make_data(wstring val)
	{
		return data_ptr(new DataValue(val)) ;
	}
	inline data_ptr make_data(data_list &val)
	{
		return data_ptr(new DataList(val)) ;
	}
	inline data_ptr make_data(data_map &val)
	{
		return data_ptr(new DataMap(val)) ;
	}
	// get a data value from a data map
	// e.g. foo.bar => data["foo"]["bar"]
	data_ptr parse_val(wstring key, data_map &data) ;

	typedef enum 
	{
		TOKEN_TYPE_NONE,
		TOKEN_TYPE_TEXT,
		TOKEN_TYPE_VAR,
		TOKEN_TYPE_IF,
		TOKEN_TYPE_FOR,
		TOKEN_TYPE_ENDIF,
		TOKEN_TYPE_ENDFOR,
	} TokenType;

	// Template tokens
	// base class for all token types
	class Token
	{
	public:
		virtual TokenType gettype() = 0 ;
		virtual void gettext(std::wostream &stream, data_map &data) = 0 ;
		virtual void set_children(token_vector &children);
		virtual token_vector & get_children();
	};

	// normal text
	class TokenText : public Token
	{
		wstring m_text ;
	public:
		TokenText(wstring text) : m_text(text){}
		TokenType gettype();
		void gettext(std::wostream &stream, data_map &data);
	};

	// variable
	class TokenVar : public Token
	{
		wstring m_key ;
	public:
		TokenVar(wstring key) : m_key(key){}
		TokenType gettype();
		void gettext(std::wostream &stream, data_map &data);
	};

	// for block
	class TokenFor : public Token 
	{
	public:
		wstring m_key ;
		wstring m_val ;
		token_vector m_children ;
		TokenFor(wstring expr);
		TokenType gettype();
		void gettext(std::wostream &stream, data_map &data);
		void set_children(token_vector &children);
		token_vector &get_children();
	};

	// if block
	class TokenIf : public Token
	{
	public:
		wstring m_expr ;
		token_vector m_children ;
		TokenIf(wstring expr) : m_expr(expr){}
		TokenType gettype();
		void gettext(std::wostream &stream, data_map &data);
		bool is_true(wstring expr, data_map &data);
		void set_children(token_vector &children);
		token_vector &get_children();
	};

	// end of block
	class TokenEnd : public Token // end of control block
	{
		wstring m_type ;
	public:
		TokenEnd(wstring text) : m_type(text){}
		TokenType gettype();
		void gettext(std::wostream &stream, data_map &data);
	};

	wstring gettext(token_ptr token, data_map &data) ;

	void parse_tree(token_vector &tokens, token_vector &tree, TokenType until=TOKEN_TYPE_NONE) ;
	token_vector & tokenize(wstring text, token_vector &tokens) ;

	// The big daddy. Pass in the template and data, 
	// and get out a completed doc.
	void parse(std::wostream &stream, wstring templ_text, data_map &data) ;
	wstring parse(wstring templ_text, data_map &data);
	std::string parse(std::string templ_text, data_map &data);
}