#define CPPCMS_SOURCE
#include "asio_config.h"

#include "url_dispatcher.h"
#include "http_request.h"
#include "http_response.h"
#include "http_protocol.h"
#include "service.h"
#include "service_impl.h"
#include "json.h"
#include "cgi_api.h"
#include "util.h"

#include <stdlib.h>
#include "config.h"
#ifdef CPPCMS_USE_EXTERNAL_BOOST
#   include <boost/bind.hpp>
#else // Internal Boost
#   include <cppcms_boost/bind.hpp>
    namespace boost = cppcms_boost;
#endif


namespace cppcms { namespace impl { namespace cgi {
/*
class multipart_separator {
public:
	multipart_separator(std::vector<char> &body,unsigned &body_ptr,std::string boundary) :
		body_(body),
		body_ptr_(body_ptr)
	{
		boundary_ = "--" + boundary;
		pos_ = 0;
		last_ = false;
	}
	
	int getc()
	{
		if(body_ptr_ < body_.size()) {
			return body_[body_ptr_++];
		}
		else {
			body_.clear();
			body_ptr_=0;
			return -1;
		}
	}

	enum { incomplete, last_chunk, chunk };
	int next()
	{
		for(;;){
			int c=getc();
			if(c < 0)
				return incomplete;
			if(pos_ < bodundary_.size()) {
				if(c == boundary_[pos_]) {
					pos_++;
				}
				else {
					if(pos_ != 0)
						output_.append(boundary_.substr(0,pos_));
					output_.append(char(c));
					pos_ = 0;
				}
			}
			else if(pos_ == boundary_.size()) {
				c == '-';
				last_ = true;
				pos_ = 0x10001;
			}
			else {
				unsigned diff = pos_ & 0xFFFF;
				if(last_){
					if(last_ending[diff]==c) {
						pos_ ++;
						diff ++ ;
					}
					else {
						output_.append(last_ending,diff);
						output_.append(char(c));
						pos_ = 0;
						last_ = false;
					}
					if(diff == 4)
						return last_chunk;
				}
				else {
					if(ending[diff] == c) {
						pos_ ++;
						diff ++;
					}
					else {
						output_.append(ending,diff);
						output_.append(char(c));
						pos_ = 0;
					}
					if(diff == 2) {
						pos_ = 0;
						return chunk;
					}
				}
			}
		}
	}

private:
	static char const last_ending[]="--\r\n"
	static char const ending[]="\r\n"
	
};


class multipart_parser : public util::noncopyable {
public:
	multipart_parser(std::vector<char> &body, unsigned &ptr) :
		separator_(body,ptr),
		parser_(body,ptr)
	{
	}

	struct none{};
	struct eof{};
	typedef std::pair<std::string,std::string> pair_type;
	typedef boost::shared_ptr<http::file> file_type;
	typedef enum { none, eof, error } result_type;
	typedef boost::variant<result_type,pair_type,file_type,eof> variant_type;

	void append(bool final = false)
	{
		if(result_.which() == 1) {
			std::string &last=boost::get<pair_type>(result_).second;
			last.append(separator_.output());
		}
		else if(result_.which() == 2) {
			file_type &file == boost::get<file_type>(result_);
			file.write(separator_.output());
			if(final)
				file.close();
		}
	}

	variant_type next()
	{
		switch(state:) {
		case done:
			return eof;
		case reading_data:
			switch(separator_.next()) {
			case multipart_separator::incomplete:
				append();
				return none;
			case multipart_separator::chunk;
				{
					append(final);
					variant_type tmp = result_;
					result_=none;
					state_ = reading_headers;
					return tmp;
				}
			case multipart_separator::last_chunk;
				{
					append(final);
					variant_type tmp = result_;
					result_=none;
					state_ = done;
					return tmp;
				}
			default:
				throw cppcms_error(
					(boost::format("Internal error at " __FILE__ "line %d") % __LINE__).str());
			}
			break;
		case reading_headers:
			switch(parser_.step())
			case parset::mode_data:
				return none;
			case parser::error_observerd:
				return error;
			case parser::end_of_headers:
				if(result_.which() == 0)
					return error;
				state_ = reading_data;
				return none;
			case parser::got_header:
				{
					std::string const header = parser.header_;
					parser.header_.clean();
					std::string::const_iterator m,p=header.begin();
					std::string::const_iterator e=header.end();
					p=http::protocol::skip_ws(p,e);
					m=p;
					p=http::protocol::tocken(p,e);
					std::string type(m,p); 
					if(http::protocol::compare("Content-Disposition",type)==0) 
					{
						while(p!=e) {
							if(http::protocol::separator(*p)) {
								++p;
								continue;
							}
							m=p;
							if((p=http::protocol::tocken(p,e))!=m) {
								if(http::protocol::compare(std::string(m,p),"name"))
							}
						}
									
					}
					
				}
		}
	}


private:
	multipart_separator separator_;
	cppcms::http::impl::parser parser_;
};
*/


connection::connection(cppcms::service &srv) :
	service_(&srv),
	request_in_progress_(true)
{
}

connection::~connection()
{
}


cppcms::service &connection::service()
{
	return *service_;
}
intrusive_ptr<connection> connection::self()
{
	return this;
}

void connection::async_prepare_request(	http::request &request,
					boost::function<void(bool)> const &h)
{
	async_read_headers(boost::bind(&connection::load_content,self(),_1,&request,h));
}

void connection::aync_wait_for_close_by_peer(boost::function<void()> const &on_eof)
{
	async_read_eof(boost::bind(&connection::handle_eof,self(),on_eof));
}

void connection::handle_eof(callback const &on_eof)
{
	if(request_in_progress_) {
		on_eof();
	}
}

void connection::set_error(ehandler const &h,std::string s)
{
	error_=s;
	h(true);
}

void connection::load_content(boost::system::error_code const &e,http::request *request,ehandler const &h)
{
	if(e)  {
		set_error(h,e.message());
		return;
	}

	std::string content_type = getenv("CONTENT_TYPE");
	std::string s_content_length=getenv("CONTENT_LENGTH");

	long long content_length = s_content_length.empty() ? 0 : atoll(s_content_length.c_str());

	if(content_length < 0)  {
		set_error(h,"Incorrect content length");
		return;
	}

	if(http::protocol::is_prefix_of("multipart/form-data",content_type)) {
		// 64 MB
		long long allowed=service().settings().get("security.multipart_form_data_limit",64*1024)*1024;
		if(content_length > allowed) { 
			set_error(h,"security violation: multipart/form-data content length too big");
			return;
		}
		// FIXME
		return;
	}

	long long allowed=service().settings().get("security.content_length_limit",1024)*1024;
	if(content_length > allowed) {
		set_error(h,"security violation POST content length too big");
		// TODO log
		return;
	}

	content_.clear();

	if(content_length > 0) {
		content_.resize(content_length,0);
		async_read(	&content_.front(),
				content_.size(),
				boost::bind(&connection::on_post_data_loaded,self(),_1,request,h));
	}
	else  {
		on_post_data_loaded(boost::system::error_code(),request,h);
	}
}

void connection::on_post_data_loaded(boost::system::error_code const &e,http::request *request,ehandler const &h)
{
	if(e) { set_error(h,e.message()); return; }

	request->set_post_data(content_);

	if(!request->prepare()) {
		set_error(h,"Bad Request");
		return;
	}
	h(false);
}

bool connection::is_reuseable()
{
	return error_.empty() && keep_alive();
}

std::string connection::last_error()
{
	return error_;
}

void connection::async_write_response(	http::response &response,
					bool complete_response,
					ehandler const &h)
{
	async_chunk_=response.get_async_chunk();
	if(!async_chunk_.empty()) {
		async_write(	async_chunk_.data(),
				async_chunk_.size(),
				boost::bind(	&connection::on_async_write_written,
						self(),
						_1,
						complete_response,
						h));
		return;
	}
	if(complete_response) {
		on_async_write_written(boost::system::error_code(),complete_response,h);
		return;
	}
	service().impl().get_io_service().post(boost::bind(h,false));
}

void connection::on_async_write_written(boost::system::error_code const &e,bool complete_response,ehandler const &h)
{
	if(complete_response) {
		async_write_eof(boost::bind(&connection::on_eof_written,self(),_1,h));
		request_in_progress_=false;
		return;
	}
	service().impl().get_io_service().post(boost::bind(h,false));
}
void connection::async_complete_response(ehandler const &h)
{
	async_write_eof(boost::bind(&connection::on_eof_written,self(),_1,h));
	request_in_progress_=false;
}

void connection::on_eof_written(boost::system::error_code const &e,ehandler const &h)
{
	if(e) { set_error(h,e.message()); return; }
	h(false);
}


struct connection::reader {
	reader(connection *C,io_handler const &H,size_t S,char *P) : h(H), s(S), p(P),conn(C)
	{
		done=0;
	}
	io_handler h;
	size_t s;
	size_t done;
	char *p;
	connection *conn;
	void operator() (boost::system::error_code const &e=boost::system::error_code(),size_t read = 0)
	{
		if(e) {
			h(e,done+read);
		}
		s-=read;
		p+=read;
		done+=read;
		if(s==0)
			h(boost::system::error_code(),done);
		else
			conn->async_read_some(p,s,*this);
	}
};
struct connection::writer {
	writer(connection *C,io_handler const &H,size_t S,char const *P) : h(H), s(S), p(P),conn(C)
	{
		done=0;
	}
	io_handler h;
	size_t s;
	size_t done;
	char const *p;
	connection *conn;
	void operator() (boost::system::error_code const &e=boost::system::error_code(),size_t wr = 0)
	{
		if(e) {
			h(e,done+wr);
		}
		s-=wr;
		p+=wr;
		done+=wr;
		if(s==0)
			h(boost::system::error_code(),done);
		else
			conn->async_write_some(p,s,*this);
	}
};

void connection::async_read(void *p,size_t s,io_handler const &h)
{
	reader r(this,h,s,(char*)p);
	r();
}

void connection::async_write(void const *p,size_t s,io_handler const &h)
{
	writer w(this,h,s,(char const *)p);
	w();
}

size_t connection::write(void const *data,size_t n)
{
	char const *p=reinterpret_cast<char const *>(data);
	size_t wr=0;
	while(n > 0) {
		size_t d=write_some(p,n);
		if(d == 0)
			return wr;
		p+=d;
		wr+=d;
		n-=d;
	}
	return wr;
}

} // cgi
} // impl
} // cppcms
