///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#include <cppcms/url_mapper.h>
#include <cppcms/application.h>
#include <cppcms/service.h>
#include <cppcms/cppcms_error.h>
#include <cppcms/steal_buf.h>
#include <booster/log.h>
#include "cached_settings.h"
#include <map>

#include <stdlib.h>

#include <iostream>

namespace cppcms {

	template<typename Data,typename Entry>
	struct stream_it {
		Data const *self;
		Entry const *formatting;
		filters::streamable const * const *params;
		size_t params_no;
		std::map<string_key,std::string> const *data_helpers_default;
		std::map<string_key,std::string> const *data_helpers_override;
		void write(std::ostream &out) const
		{
			self->write(*formatting,params,params_no,*data_helpers_default,*data_helpers_override,out);
		}
	};

	template<typename Data,typename Entry>
	std::ostream &operator<<(std::ostream &out,stream_it<Data,Entry> const &wr)
	{
		wr.write(out);
		return out;
	}


	struct url_mapper::data 
	{

		data() : throws(true),parent(0),this_application(0) {}

		bool throws;

		std::string this_name;
		application *parent;
		application *this_application;

		struct entry {
			std::vector<std::string> parts;
			std::vector<int> indexes;
			std::vector<std::string> keys;
			application *child;
			entry() : child(0) {}
		};
		
		template<typename Data,typename Entry>
		friend struct stream_it;

		typedef std::map<size_t,entry> by_size_type;
		typedef std::map<string_key,by_size_type> by_key_type;
		typedef std::map<string_key,std::string> helpers_type;

		by_key_type by_key;
		helpers_type helpers;
		std::string root;

		entry const &get_entry(string_key const &key,size_t params_no,string_key const &full_url) const
		{
			by_key_type::const_iterator kp = by_key.find(key);
			if(kp == by_key.end())
				throw cppcms_error("url_mapper: key `" + key.str() + "' not found for "
						"url `" + full_url.str() + "'");
			by_size_type::const_iterator sp = kp->second.find(params_no);
			if(sp == kp->second.end())
				throw cppcms_error("url_mapper: invalid number of parameters for " + key.str() +
						"in url `" + full_url.str() + "'");
			return sp->second;
		}
		
		url_mapper *is_app(string_key const &key) const
		{
			by_key_type::const_iterator kp = by_key.find(key);
			if(kp == by_key.end())
				return 0;
			by_size_type::const_iterator sp = kp->second.begin();
			if(sp==kp->second.end())
				return 0;
			if(sp->second.child) 
				return &sp->second.child->mapper();
			return 0;
		}

		url_mapper &child(string_key const &name,string_key const &full_url)
		{
			entry const &fmt = get_entry(name,1,full_url);
			if(!fmt.child) {
				throw cppcms_error("url_mapper: the key " + name.str() + " is not child application key"
						" in url `" + full_url.str() + "'");
			}
			return fmt.child->mapper();
		}

		void write(	entry const &formatting,
				filters::streamable const * const *params,
				size_t params_no,
				std::map<string_key,std::string> const &data_helpers_default,
				std::map<string_key,std::string> const &data_helpers_override,
				std::ostream &out) const
		{
			for(size_t i=0;i<formatting.parts.size();i++) {
				out << formatting.parts[i];
				if( i < formatting.indexes.size() ) {
					if(formatting.indexes[i]==0) {
						string_key hkey  = string_key::unowned(formatting.keys[i]);
						std::map<string_key,std::string>::const_iterator p;
						p = data_helpers_override.find(string_key::unowned(hkey));
						if(p != data_helpers_override.end()) {
							out << p->second;
						}
						else {
							p = data_helpers_default.find(hkey);
							if(p!=data_helpers_default.end())
								out << p->second;
						}
					}
					else {
						size_t index = formatting.indexes[i] - 1;
						if(index >= params_no) {
							throw cppcms_error("url_mapper: Index of parameter out of range");
						}
						(*params[index])(out);
					}
				}
			}
		}

		void map(	string_key const &key,
				string_key const &full_url,
				filters::streamable const * const *params,
				size_t params_no,
				std::map<string_key,std::string> const &data_helpers_default,
				std::map<string_key,std::string> const &data_helpers_override,
				std::ostream &out) const
		{
			entry const &formatting = get_entry(key,params_no,full_url);

			if(parent) {
				stream_it<data,entry> url = 
				{ 
					this,
					&formatting,
					params,
					params_no,
					&data_helpers_default,
					&data_helpers_override
				};

				filters::streamable stream_url(url);
				filters::streamable const *par_ptr=&stream_url;
				parent->mapper().d->map(this_name,full_url,&par_ptr,1,	data_helpers_default,
											data_helpers_override,out);
			}
			else {
				out << root;
				write(formatting,params,params_no,data_helpers_default,data_helpers_override,out);
			}

		}


	};

	void url_mapper::assign(std::string const &key,std::string const &url)
	{
		if(	key.empty() 
			|| key.find('/') != std::string::npos 
			|| key.find(';') != std::string::npos 
			|| key.find(',') != std::string::npos 
			|| key ==".." 
			|| key == "." )
		{
			throw cppcms_error("cppcms::url_mapper: key may not be '' , '.' or '..' and must not include '/' in it");
		}
		real_assign(key,url,0);
	}
	void url_mapper::assign(std::string const &url)
	{
		real_assign(std::string(),url,0);
	}
	void url_mapper::real_assign(std::string const &key,std::string const &url,application *child)
	{

		data::entry e;
		std::string::const_iterator prev = url.begin(), p = url.begin();

		int max_index = 0;

		while(p!=url.end()) {
			if(*p=='{') {
				e.parts.push_back(std::string(prev,p));
				prev = p;
				while(p!=url.end()) {
					if(*p=='}') {
						std::string const hkey(prev+1,p);
						prev = p+1;
						if(hkey.size()==0) {
							throw cppcms_error("cppcms::url_mapper: empty index between {}");
						}
						bool all_digits = true;
						for(unsigned i=0;all_digits && i<hkey.size();i++) {
							if(hkey[i] < '0' || '9' <hkey[i])
								all_digits = false;
						}
						if(!all_digits) {
							e.indexes.push_back(0);
							e.keys.push_back(hkey);
						}
						else {
							int index = atoi(hkey.c_str());
							if(index == 0)
								throw cppcms_error("cppcms::url_mapper: index 0 is invalid");
							max_index = std::max(index,max_index);
							e.indexes.push_back(index);
							e.keys.resize(e.keys.size()+1);
						}
						break;
					}
					else
						p++;
				}
				if(p==url.end())
					throw cppcms_error("cppcms::url_mapper: '{' in url without '}'");
				p++;
			}
			else if(*p=='}') {
				throw cppcms_error("cppcms::url_mapper: '}' in url without '{'");
			}
			else
				p++;
		}
		if(child && max_index!=1) {
			throw cppcms_error("cppcms::url_mapper the application mapping should use only 1 parameter");
		}
		e.parts.push_back(std::string(prev,p));

		e.child = child;

		if(child) {
			if(d->by_key.find(key)!=d->by_key.end()) 
				throw cppcms_error(	"cppcms::url_mapper: mounted application key `" + key +
							"' can't be shared with ordinary url key");
		}
		else {
			data::by_key_type::iterator p = d->by_key.find(key);
			if(p!=d->by_key.end()) {
				data::by_size_type::const_iterator p2 = p->second.find(1);
				if(p2!=p->second.end() && p2->second.child) 
				{
					throw cppcms_error(	"cppcms::url_mapper: ordinary url key `"+key+
								"can't be shared with mounted application key");
				}
			}
		}
		d->by_key[key][max_index] = e;
	}

	void url_mapper::set_value(std::string const &key,std::string const &value)
	{
		root_mapper()->d->helpers[key]=value;
	}
	void url_mapper::clear_value(std::string const &key)
	{
		root_mapper()->d->helpers.erase(key);
	}

	url_mapper &url_mapper::child(std::string const &name)
	{
		return d->child(name,name);
	}
	
	url_mapper *url_mapper::root_mapper()
	{
		if(d->parent)
			return &d->parent->root()->mapper();
		else
			return this;
	}

	void url_mapper::mount(std::string const &name,std::string const &url,application &app)
	{
		app.mapper().d->parent = d->this_application;
		app.mapper().d->this_name = name;
		real_assign(name,url,&app);
		// Copy all values to root most one
		std::map<string_key,std::string> &values = app.mapper().d->helpers;
		std::map<string_key,std::string>::const_iterator p;
		for(p=values.begin();p!=values.end();++p) {
			set_value(p->first,p->second);
		}
		values.clear();
	}

	url_mapper::url_mapper(cppcms::application *my_app) : d(new url_mapper::data())
	{
		d->this_application = my_app;
		d->throws = my_app->service().cached_settings().misc.invalid_url_throws;
	}
	url_mapper::~url_mapper()
	{
	}

	std::string url_mapper::root()
	{
		return d->root;
	}

	void url_mapper::root(std::string const &r)
	{
		d->root = r;
	}


	url_mapper &url_mapper::get_mapper_for_key(string_key const &key,string_key &real_key,std::vector<string_key> &keywords)
	{
		url_mapper *mapper = this;
		size_t pos = 0;
		if(key.empty()) {
			real_key.clear();
			return *mapper;
		}
		if(key[0]=='/') {
			mapper = &mapper->topmost();
			pos = 1;
		}
		for(;;) {
			size_t end = key.find('/',pos);
			if(end == std::string::npos) {
				size_t separator = key.find(';',pos);
				if(separator == std::string::npos) {
					real_key = key.unowned_substr(pos);
				}
				else {
					keywords.reserve(6);
					real_key = key.unowned_substr(pos,separator - pos);
					pos = separator + 1;
					for(;;){
						end = key.find(',',pos);
						size_t size = end - pos;
						keywords.push_back(key.unowned_substr(pos,size));
						if(end == std::string::npos)
							break;
						pos = end + 1;
					}
					
				}
				if(real_key == ".")
					real_key.clear();
				else if(real_key == "..") {
					mapper = &mapper->parent();
					real_key.clear();
				}
					
				url_mapper *tmp = mapper->d->is_app(real_key);
				if(tmp) { 
					// empty special key
					real_key.clear();
					return *tmp;
				}
				return *mapper;
			}
			size_t chunk_size = end - pos;
			string_key subapp = key.unowned_substr(pos,chunk_size);
			if(subapp == ".")
				; // Just continue where we are
			else if(subapp == "..") {
				mapper = &mapper->parent();
			}
			else {
				mapper = &mapper->d->child(subapp,key);
			}
			pos = end + 1;
		}
	}


	void url_mapper::real_map(	char const *ckey,
					filters::streamable const * const *params,
					size_t params_no,
					std::ostream &output)
	{
		util::steal_buffer<> temp_buf;
		if(!d->throws)
			temp_buf.steal(output);
		try {
			string_key key=string_key::unowned(ckey);
			string_key real_key;
			std::vector<string_key> direct;
			url_mapper &mp = get_mapper_for_key(key,real_key,direct);
			if(params_no < direct.size())
				throw cppcms_error("url_mapper: number of keywords is larger then number of actual parameters");
			std::map<string_key,std::string> mappings;
			if(direct.size() == 0) {
				mp.d->map(real_key,key,params,params_no,topmost().d->helpers,mappings,output);
			}
			else {
				size_t direct_size = direct.size();
				filters::streamable const * const *key_params = params;
				params += direct_size;
				params_no -= direct_size;
				for(unsigned i=0;i<direct.size();i++) {
					std::ostringstream ss;
					ss.copyfmt(output);
					(*key_params[i])(ss);
					mappings[direct[i]]=ss.str();
				}
				mp.d->map(real_key,key,params,params_no,topmost().d->helpers,mappings,output);
			}
		}
		catch(cppcms_error const &e) {
			if(d->throws) {
				throw;
			}
			BOOSTER_ERROR("cppcms") << e.what() <<'\n' << booster::trace(e);
			temp_buf.release();
			output<<"/this_is_an_invalid_url_generated_by_url_mapper";
			return;
		}
		if(!d->throws) {
			temp_buf.release();
			output << temp_buf.c_str();
		}
	}
	
	void url_mapper::map(	std::ostream &out,	
				std::string const &key)
	{
		map(out,key.c_str());
	}

	void url_mapper::map(	std::ostream &out,	
				std::string const &key,
				filters::streamable const &p1)
	{
		map(out,key.c_str(),p1);
	}

	void url_mapper::map(	std::ostream &out,
				std::string const &key,
				filters::streamable const &p1,
				filters::streamable const &p2)
	{
		map(out,key.c_str(),p1,p2);
	}

	void url_mapper::map(	std::ostream &out,
				std::string const &key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3)
	{
		map(out,key.c_str(),p1,p2,p3);
	}

	void url_mapper::map(	std::ostream &out,
				std::string const &key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4)
	{
		map(out,key.c_str(),p1,p2,p3,p4);
	}

	void url_mapper::map(	std::ostream &out,
				std::string const &key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4,
				filters::streamable const &p5)
	{
		map(out,key.c_str(),p1,p2,p3,p4,p5);
	}

	void url_mapper::map(	std::ostream &out,
				std::string const &key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4,
				filters::streamable const &p5,
				filters::streamable const &p6)
	{
		map(out,key.c_str(),p1,p2,p3,p4,p5,p6);
	}

	void url_mapper::map(	std::ostream &out,	
				char const *key)
	{
		real_map(key,0,0,out);
	}

	void url_mapper::map(	std::ostream &out,	
				char const *key,
				filters::streamable const &p1)
	{
		filters::streamable const *params[1]= { &p1 };
		real_map(key,params,1,out);
	}

	void url_mapper::map(	std::ostream &out,
				char const *key,
				filters::streamable const &p1,
				filters::streamable const &p2)
	{
		filters::streamable const *params[2] = { &p1,&p2 };
		real_map(key,params,2,out);
	}

	void url_mapper::map(	std::ostream &out,
				char const *key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3)
	{
		filters::streamable const *params[3] = { &p1,&p2,&p3 };
		real_map(key,params,3,out);
	}

	void url_mapper::map(	std::ostream &out,
				char const *key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4)
	{
		filters::streamable const *params[4] = { &p1,&p2,&p3,&p4 };
		real_map(key,params,4,out);
	}

	void url_mapper::map(	std::ostream &out,
				char const *key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4,
				filters::streamable const &p5)
	{
		filters::streamable const *params[5] = { &p1,&p2,&p3,&p4,&p5 };
		real_map(key,params,5,out);
	}

	void url_mapper::map(	std::ostream &out,
				char const *key,
				filters::streamable const &p1,
				filters::streamable const &p2,
				filters::streamable const &p3,
				filters::streamable const &p4,
				filters::streamable const &p5,
				filters::streamable const &p6)
	{
		filters::streamable const *params[6] = { &p1,&p2,&p3,&p4,&p5,&p6 };
		real_map(key,params,6,out);
	}

	url_mapper &url_mapper::parent()
	{
		if(d->parent) {
			return d->parent->mapper();
		}
		throw cppcms_error("url_mapper: no parent found");
	}
	
	url_mapper &url_mapper::topmost()
	{
		application *app = d->this_application;
		if(!app)
			return *this;
		while(app->mapper().d->parent)
			app = app->mapper().d->parent;
		return app->mapper();
	}

	
}
