#define CPPCMS_SOURCE
#include "defs.h"
#ifndef CPPCMS_WIN32
#if defined(__sun)
#define _POSIX_PTHREAD_SEMANTICS
#endif
#include <signal.h>
#endif
#include "asio_config.h"
#include "service.h"
#include "service_impl.h"
#include "applications_pool.h"
#include "thread_pool.h"
#include "cppcms_error.h"
#include "cgi_acceptor.h"
#include "cgi_api.h"
#ifdef CPPCMS_HAS_SCGI
# include "scgi_api.h"
#endif
#ifdef CPPCMS_HAS_HTTP
# include "http_api.h"
#endif
#ifdef CPPCMS_HAS_FCGI
# include "fastcgi_api.h"
#endif

#include "cache_pool.h"
#include "internal_file_server.h"
#include "json.h"
#include "localization.h"
#include "views_pool.h"
#include "session_pool.h"


#ifdef CPPCMS_POSIX
#include <sys/wait.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include "config.h"
#ifdef CPPCMS_USE_EXTERNAL_BOOST
#   include <boost/lexical_cast.hpp>
#   include <boost/regex.hpp>
#else // Internal Boost
#   include <cppcms_boost/lexical_cast.hpp>
#   include <cppcms_boost/regex.hpp>
    namespace boost = cppcms_boost;
#endif


namespace cppcms {

service::service(json::value const &v) :
	impl_(new impl::service())
{
	impl_->settings_.reset(new json::value(v));
	setup();
}

service::service(int argc,char *argv[]) :
	impl_(new impl::service())
{
	impl_->settings_.reset(new json::value());
	load_settings(argc,argv);
	setup();
}

void service::load_settings(int argc,char *argv[])
{
	using std::string;
	std::string file_name;
	
	for(int i=1;i<argc;i++) {
		if(argv[i]==std::string("-c")) {
			if(!file_name.empty()) {
				throw cppcms_error("Switch -c can't be used twice");
			}
			if(i+1 < argc)
				file_name=argv[i+1];
			else
				throw cppcms_error("Switch -c requires configuration file parameters");
			i++;
		}
		else if(argv[i]==std::string("-U")) 
			break;
	}
	
	if(file_name.empty()) {
		char const *e = getenv("CPPCMS_CONFIG");
		if(e) file_name = e;
	}
	
	json::value &val=*impl_->settings_;


	if(!file_name.empty()) {
		std::ifstream fin(file_name.c_str());
		if(!fin)
			throw cppcms_error("Failed to open filename:"+file_name);
		int line_no=0;
		if(!val.load(fin,true,&line_no)) {
			std::ostringstream ss;
			ss<<"Error reading configurarion file "<<file_name<<" in line:"<<line_no;
			throw cppcms_error(ss.str());
		}
	}
	
	boost::regex r("^--((\\w+)(-\\w+)*)=((true)|(false)|(null)|(-?\\d+(\\.\\d+)?([eE][\\+-]?\\d+)?)|([\\[\\{].*[\\]\\}])|(.*))$");

	for(int i=1;i<argc;i++) {
		std::string arg=argv[i];
		if(arg=="-c") {
			i++;
			continue;
		}
		if(arg=="-U")
			break;
		if(arg.substr(0,2)=="--") {
			boost::cmatch m;
			if(!boost::regex_match(arg.c_str(),m,r))
				throw cppcms_error("Invalid switch: "+arg);
			std::string path=m[1];
			for(unsigned i=0;i<path.size();i++)
				if(path[i]=='-')
					path[i]='.';
			if(!m[5].str().empty())
				val.set(path,true);
			else if(!m[6].str().empty())
				val.set(path,false);
			else if(!m[7].str().empty())
				val.set(path,json::null());
			else if(!m[8].str().empty())
				val.set(path,boost::lexical_cast<double>(m[8].str()));
			else if(!m[11].str().empty()) {
				std::istringstream ss(m[11].str());
				json::value tmp;
				if(!tmp.load(ss,true))
					throw cppcms_error("Invadid json expresson: " + m[11].str());
				val.set(path,tmp);
			}
			else
				val.set(path,m[4].str());
		}
	}

	if(val.is_undefined()) {
		throw cppcms_error("No configuration defined");
	}

}

void service::setup()
{
	impl_->id_=0;
	int apps=settings().get("service.applications_pool_size",threads_no()*2);
	impl_->applications_pool_.reset(new cppcms::applications_pool(*this,apps));
	impl_->views_pool_.reset(new cppcms::views_pool(settings()));
	impl_->cache_pool_.reset(new cppcms::cache_pool(settings()));
	impl_->session_pool_.reset(new cppcms::session_pool(*this));
}

cppcms::session_pool &service::session_pool()
{
	return *impl_->session_pool_;
}

cppcms::cache_pool &service::cache_pool()
{
	return *impl_->cache_pool_;
}

cppcms::views_pool &service::views_pool()
{
	return *impl_->views_pool_;
}


service::~service()
{
}

int service::threads_no()
{
	return settings().get("service.worker_threads",5);
}

namespace {
	cppcms::service *the_service;

#if defined(CPPCMS_WIN32)
	void make_socket_pair(boost::asio::ip::tcp::socket &s1,boost::asio::ip::tcp::socket &s2)
	{
		boost::asio::ip::tcp::acceptor acceptor(s1.get_io_service(),
			boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
		boost::asio::ip::tcp::endpoint server_endpoint = acceptor.local_endpoint();
		server_endpoint.address(boost::asio::ip::address_v4::loopback());
		s1.lowest_layer().connect(server_endpoint);
		acceptor.accept(s2.lowest_layer());
	}

	BOOL WINAPI handler(DWORD ctrl_type)
	{
		switch (ctrl_type)
		{
		case CTRL_C_EVENT:
		case CTRL_BREAK_EVENT:
		case CTRL_CLOSE_EVENT:
		case CTRL_SHUTDOWN_EVENT:
			the_service->shutdown();
			return TRUE;
		default:
			return FALSE;
		}
	}

#else
	void make_socket_pair(boost::asio::local::stream_protocol::socket &s1,boost::asio::local::stream_protocol::socket &s2)
	{
		boost::asio::local::connect_pair(s1,s2);
	}

	void handler(int nothing)
	{
		the_service->shutdown();
	}

#endif
} // anon


void service::setup_exit_handling()
{
	make_socket_pair(*impl_->sig_,*impl_->breaker_);

	static char c;

	impl_->breaker_->async_read_some(boost::asio::buffer(&c,1),
					boost::bind(&service::stop,this));

	impl_->notification_socket_=impl_->sig_->native();

	if(settings().get("service.disable_global_exit_handling",false))
		return;

	the_service=this;

	#ifdef CPPCMS_WIN32
	SetConsoleCtrlHandler(handler, TRUE);
	#else
	struct sigaction sa;

	memset(&sa,0,sizeof(sa));
	sa.sa_handler=handler;
	
	sigaction(SIGINT,&sa,0);
	sigaction(SIGTERM,&sa,0);
	sigaction(SIGUSR1,&sa,0);
	#endif
}



void service::shutdown()
{
	char c='A';
#ifdef CPPCMS_WIN32
	if(send(impl_->notification_socket_,&c,1,0) <= 0) {
		perror("notification failed");
		exit(1);
	}
#else
	for(;;){
		int res=::write(impl_->notification_socket_,&c,1);
		if(res<0 && errno == EINTR)
			continue;
		if(res<=0) {
			perror("shudown notification failed");
			exit(1);
		}
		return;
	}
#endif
}

void service::after_fork(util::callback0 const &cb)
{
	impl_->on_fork_.push_back(cb);
}

void service::run()
{
	generator();
	session_pool().init();
	start_acceptor();

	if(settings().get("file_server.enable",false))
		applications_pool().mount(applications_factory<cppcms::impl::file_server>(),"");

	if(prefork()) {
		return;
	}
	thread_pool(); // make sure we start it
	
	for(unsigned i=0;i<impl_->on_fork_.size();i++)
		impl_->on_fork_[i]();

	impl_->on_fork_.clear();
	
	impl_->acceptor_->async_accept();

	setup_exit_handling();

	impl_->get_io_service().run();
}

int service::procs_no()
{
	int procs=settings().get("service.worker_processes",0);
	if(procs < 0)
		procs = 0;
	#ifdef CPPCMS_WIN32
	if(procs > 0)
		throw cppcms_error("Prefork is not supported under Windows");
	#endif
	return procs;
}

#ifdef CPPCMS_WIN32
bool service::prefork()
{
	procs_no();
	impl_->id_ = 1;
	return false;
}
#else // UNIX
static void  dummy(int n) {}
bool service::prefork()
{
	int procs=procs_no();
	if(procs==0) {
		impl_->id_ = 1;
		return false;
	}
	std::vector<int> pids(procs,0);
	
	for(int i=0;i<procs;i++) {
		int pid=::fork();
		if(pid < 0) {
			int err=errno;
			for(int j=0;j<i;j++) {
				::kill(pids[j],SIGTERM);
				int stat;
				::waitpid(pids[j],&stat,0);
			}
			throw cppcms_error(err,"fork failed");
		}
		else if(pid==0) {
			impl_->id_ = i+1;
			return false; // chaild
		}
		else {
			pids[i]=pid;
		}
	}

	sigset_t set,prev;
	sigemptyset(&set);
	sigaddset(&set,SIGTERM);
	sigaddset(&set,SIGINT);
	sigaddset(&set,SIGCHLD);
	sigaddset(&set,SIGQUIT);

	sigprocmask(SIG_BLOCK,&set,&prev);

	// Enable delivery of SIGCHLD
	struct sigaction sa_new,sa_old;
	memset(&sa_new,0,sizeof(sa_new));
	sa_new.sa_handler=dummy;
	::sigaction(SIGCHLD,&sa_new,&sa_old);

	int sig;
	do {
		sig=0;
		sigwait(&set,&sig);
		if(sig==SIGCHLD) {
			int status;
			int pid = ::waitpid(0,&status,WNOHANG);
			if(pid > 0)  {
				std::vector<int>::iterator p;
				p = std::find(pids.begin(),pids.end(),pid);
				// Ingnore all processes that are not my own childrens
				if(p!=pids.end()) {
					// TODO: Make better error handling
					if(!WIFEXITED(status) || WEXITSTATUS(status)!=0){
						if(WIFEXITED(status)) {
							std::cerr<<"Chaild exited with "<<WEXITSTATUS(status)<<std::endl;
						}
						else if(WIFSIGNALED(status)) {
							std::cerr<<"Chaild killed by "<<WTERMSIG(status)<<std::endl;
						}
						else {
							std::cerr<<"Chaild exited for unknown reason"<<std::endl;
						}
						impl_->id_ = p - pids.begin() + 1;
						*p=-1;
						pid = ::fork();
						if(pid < 0) {
							int err=errno;
							std::cerr<<"Failed to create process: " <<strerror(err)<<std::endl;
						}
						else if(pid == 0) {
							::sigaction(SIGCHLD,&sa_old,NULL);
							sigprocmask(SIG_SETMASK,&prev,NULL);
							return false;
						}
						else {
							*p=pid;
							impl_->id_ = 0;
						}
					}
					else {
						*p=-1;
					}
				}
			}
		}
	}while(sig!=SIGINT && sig!=SIGTERM && sig!=SIGQUIT);

	sigprocmask(SIG_SETMASK,&prev,NULL);

	for(int i=0;i<procs;i++) {
		if(pids[i]<0)
			continue;
		::kill(pids[i],SIGTERM);
		int stat;
		::waitpid(pids[i],&stat,0);
	}
	return true;
}

#endif

int service::process_id()
{
	return impl_->id_;
}

void service::start_acceptor()
{
	using namespace impl::cgi;
	std::string api=settings().get<std::string>("service.api");
	std::string socket=settings().get("service.socket","");
	int backlog=settings().get("service.backlog",threads_no() * 2);

	std::string ip;
	int port=0;

	bool tcp;

	if(socket.empty()) {
		ip=settings().get("service.ip","127.0.0.1");
		port=settings().get("service.port",8080);
		tcp=true;
	}
	else {
		if(	!settings().find("service.port").is_undefined()
			|| !settings().find("service.ip").is_undefined())
		{
			throw cppcms_error("Can't define both UNIX socket and TCP port/ip");
		}
		tcp=false;
	}

	impl_->acceptor_.reset();

	if(tcp) {
		#ifdef CPPCMS_HAS_SCGI
		if(api=="scgi")
			impl_->acceptor_ = scgi_api_tcp_socket_factory(*this,ip,port,backlog);
		#endif		
		#ifdef CPPCMS_HAS_FCGI
		if(api=="fastcgi")
			impl_->acceptor_ = fastcgi_api_tcp_socket_factory(*this,ip,port,backlog);
		#endif
		#ifdef CPPCMS_HAS_HTTP
		if(api=="http")
			impl_->acceptor_ = http_api_factory(*this,ip,port,backlog);
		#endif
	}
	else {
#ifdef CPPCMS_WIN_NATIVE 
		throw cppcms_error("Unix domain sockets are not supported under Windows... (isn't it obvious?)");
#elif defined CPPCMS_CYGWIN
		throw cppcms_error("CppCMS uses native Win32 sockets under cygwin, so Unix sockets are not supported");
#else
		#ifdef CPPCMS_HAS_SCGI
		if(api=="scgi") {
			if(socket=="stdin")
				impl_->acceptor_ = scgi_api_unix_socket_factory(*this,backlog);
			else
				impl_->acceptor_ = scgi_api_unix_socket_factory(*this,socket,backlog);
		}
		#endif
		
		#ifdef CPPCMS_HAS_FCGI
		if(api=="fastcgi") {
			if(socket=="stdin")
				impl_->acceptor_ = fastcgi_api_unix_socket_factory(*this,backlog);
			else
				impl_->acceptor_ = fastcgi_api_unix_socket_factory(*this,socket,backlog);
		}
		#endif

		#ifdef CPPCMS_HAS_HTTP
		if(api=="http")
			throw cppcms_error("HTTP API is not supported over Unix Domain sockets");
		#endif
#endif
	}
	if(!impl_->acceptor_.get())
		throw cppcms_error("Unknown service.api: " + api);

}

cppcms::applications_pool &service::applications_pool()
{
	return *impl_->applications_pool_;
}
cppcms::thread_pool &service::thread_pool()
{
	if(!impl_->thread_pool_.get()) {
		impl_->thread_pool_.reset(new cppcms::thread_pool(threads_no()));
	}
	return *impl_->thread_pool_;
}

json::value const &service::settings()
{
	return *impl_->settings_;
}

cppcms::impl::service &service::impl()
{
	return *impl_;
}

void service::post(util::callback0 const &handler)
{
	impl_->get_io_service().post(handler);
}

void service::stop()
{
	if(impl_->acceptor_.get())
		impl_->acceptor_->stop();
	thread_pool().stop();
	impl_->get_io_service().stop();
}

locale::generator const &service::generator()
{
	if(impl_->locale_generator_.get())
		return *impl_->locale_generator_.get();

	typedef std::vector<std::string> vstr_type;
	impl_->locale_generator_.reset(new locale::generator());
	locale::generator &gen= *impl_->locale_generator_;
	gen.characters(locale::char_facet);
	std::string enc = settings().get("localization.encoding","");

	if(!enc.empty())
		gen.octet_encoding(enc);

	vstr_type paths= settings().get("localization.messages.paths",vstr_type());
	vstr_type domains = settings().get("localization.messages.domains",vstr_type());

	if(!paths.empty() && !domains.empty()) {
		unsigned i;
		for(i=0;i<paths.size();i++)
			gen.add_messages_path(paths[i]);
		for(i=0;i<domains.size();i++)
			gen.add_messages_domain(domains[i]);
	}

	vstr_type locales = settings().get("localization.locales",vstr_type());

	if(locales.empty()) {
		gen.preload("");
		impl_->default_locale_=gen("");
	}
	else {
		for(unsigned i=0;i<locales.size();i++)
			gen.preload(locales[i]);
		impl_->default_locale_=gen(locales[0]);
	}

	return *impl_->locale_generator_.get();

}

std::locale service::locale()
{
	generator();
	return impl_->default_locale_;
}
std::locale service::locale(std::string const &name)
{
	return generator().get(name);
}


namespace impl {
	service::service() :
		io_service_(new boost::asio::io_service()),
		sig_(new loopback_socket_type(*io_service_)),
		breaker_(new loopback_socket_type(*io_service_))
	{
	}
	service::~service()
	{
		acceptor_.reset();
		thread_pool_.reset();
		sig_.reset();
		breaker_.reset();
		io_service_.reset();
		// applications pool should be destroyed after
		// io_service, because soma apps may try unregister themselfs
		applications_pool_.reset();
		locale_generator_.reset();
		settings_.reset();

	}
} // impl


} // cppcms
