#define CPPCMS_SOURCE
#include "cgi_api.h"
#include "service.h"
#include "http_context.h"
#include "http_request.h"
#include "http_response.h"
#include "application.h"
#include "applications_pool.h"
#include "thread_pool.h"
#include "views_pool.h"
#include "cache_interface.h"
#include "session_interface.h"
#include "cppcms_error.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 http {

	struct context::data {
		std::locale locale;
		std::string skin;
		http::request request;
		std::auto_ptr<http::response> response;
		std::auto_ptr<cache_interface> cache;
		std::auto_ptr<session_interface> session;
		data(context &cntx) :
			locale(cntx.connection().service().locale()),
			request(cntx.connection())
		{
		}
	};

context::context(intrusive_ptr<impl::cgi::connection> conn) :
	conn_(conn)
{
	d.reset(new data(*this));
	d->response.reset(new http::response(*this));
	skin(service().views_pool().default_skin());
	d->cache.reset(new cache_interface(*this));
	d->session.reset(new session_interface(*this));
}

std::string context::skin()
{
	return d->skin;
}

cache_interface &context::cache()
{
	return *d->cache;
}

void context::skin(std::string const &skin)
{
	d->skin=skin;
}


void context::run()
{
	conn_->async_prepare_request(d->request,boost::bind(&context::on_request_ready,self(),_1));
}

void context::on_request_ready(bool error)
{
	if(error) return;
	
	std::string path_info = conn_->getenv("PATH_INFO");
	std::string script_name = conn_->getenv("SCRIPT_NAME");
	std::string matched;

	intrusive_ptr<application> app = service().applications_pool().get(script_name,path_info,matched);

	if(!app) {
		response().io_mode(http::response::asynchronous);
		response().make_error_response(http::response::not_found);
		async_complete_response();
		return;
	}

	app->assign_context(self());
	
	if(app->is_asynchronous()) {
		response().io_mode(http::response::asynchronous);
		app->service().post(boost::bind(&context::dispatch,app,matched,false));
	}
	else {
		app->service().thread_pool().post(boost::bind(&context::dispatch,app,matched,true));
	}
}
// static 
void context::dispatch(intrusive_ptr<application> app,std::string url,bool syncronous)
{
	try {
		if(syncronous)
			app->context().session().load();
		app->main(url);
	}
	catch(std::exception const &e){
		if(app->get_context() && !app->response().some_output_was_written()) {
			app->response().make_error_response(http::response::internal_server_error,e.what());
		}
		else {
			// TODO log it
			std::cerr<<"Catched excepion ["<<e.what()<<"]"<<std::endl;
		}
	}
}

namespace {
	void wrapper(context::handler const &h,bool r)
	{
		h(r ? context::operation_aborted : context::operation_completed);
	}
}


void context::async_flush_output(context::handler const &h)
{
	if(response().io_mode() != http::response::asynchronous) {
		throw cppcms_error("Can't use asynchronouse operations when I/O mode is synchronous");
	}
	conn_->async_write_response(
		response(),
		false,
		boost::bind(wrapper,h,_1));
}

void context::async_complete_response()
{
	if(response().io_mode() == http::response::asynchronous) {
		conn_->async_write_response(
			response(),
			true,
			boost::bind(&context::try_restart,self(),_1));
		return;
	}
	conn_->async_complete_response(boost::bind(&context::try_restart,self(),_1));
}

void context::try_restart(bool e)
{
	if(e) return;

	if(conn_->is_reuseable()) {
		intrusive_ptr<context> cont=new context(conn_);
		cont->run();
	}
	conn_=0;
}

intrusive_ptr<context> context::self()
{
	intrusive_ptr<context> ptr(this);
	return ptr;
}

context::~context()
{
}

void context::async_on_peer_reset(util::callback0 const &h)
{
	// For some wired can't go without bind on SunCC
	conn_->aync_wait_for_close_by_peer(boost::bind(h));
}

impl::cgi::connection &context::connection()
{
	return *conn_;
}

cppcms::service &context::service()
{
	return conn_->service();
}

http::request &context::request()
{
	return d->request;
}

http::response &context::response()
{
	return *d->response;
}

json::value const &context::settings()
{
	return conn_->service().settings();
}

std::locale context::locale()
{
	return d->locale;
}
void context::locale(std::locale const &new_locale)
{
	d->locale=new_locale;
	if(response().some_output_was_written())
		response().out().imbue(d->locale);
}
void context::locale(std::string const &name)
{
	locale(service().locale(name));
}
session_interface &context::session()
{
	return *d->session;
}

} // http
} // cppcms


