#define CPPCMS_SOURCE
#include "asio_config.h"
#include "cgi_api.h"
#include "cgi_acceptor.h"
#include "service.h"
#include "service_impl.h"
#include "cppcms_error_category.h"
#include "json.h"
#include "cstdint.h"
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include "config.h"
#ifdef CPPCMS_USE_EXTERNAL_BOOST
#   include <boost/array.hpp>
#   if defined(CPPCMS_WIN32) && _WIN32_WINNT <= 0x0501 && !defined(BOOST_ASIO_DISABLE_IOCP)
#   define NO_CANCELIO
#   endif
#else // Internal Boost
#   include <cppcms_boost/array.hpp>
    namespace boost = cppcms_boost;
#   if defined(CPPCMS_WIN32) && _WIN32_WINNT <= 0x0501 && !defined(CPPCMS_BOOST_ASIO_DISABLE_IOCP)
#   define NO_CANCELIO
#   endif
#endif



namespace cppcms {
namespace impl {
namespace cgi {


	template<typename Proto,typename API> class socket_acceptor;
	template<typename Proto>
	class fastcgi : public connection {
	public:
		fastcgi(cppcms::service &srv) :
			connection(srv),
			socket_(srv.impl().get_io_service())
		{
			reset_all();
			int procs=srv.procs_no();
			if(procs < 1) procs=1;
			int threads=srv.threads_no();
			cuncurrency_hint_=srv.settings().get("fastcgi.cuncurrency_hint",procs*threads);
		}
		~fastcgi()
		{
			boost::system::error_code e;
			socket_.shutdown(boost::asio::basic_stream_socket<Proto>::shutdown_both,e);
		}
		virtual void async_read_headers(handler const &h)
		{
			reset_all();
			async_read_record(boost::bind(&fastcgi::on_start_request,self(),_1,h));
		}

		// should be called only after headers are read
		virtual std::string getenv(std::string const &key)
		{
			std::map<std::string,std::string>::const_iterator p;
			p=env_.find(key);
			if(p==env_.end())
				return std::string();
			return p->second;
		}
		virtual std::map<std::string,std::string> const &getenv()
		{
			return env_;
		}
		virtual void async_read_some(void *p,size_t s,io_handler const &h)
		{
			if(read_length_ == content_length_) {
				socket_.get_io_service().post(boost::bind(
						h,
						boost::system::error_code(errc::protocol_violation,cppcms_category),
						0));
				return;
			}
			if(body_ptr_ < body_.size()) {
				size_t rest=body_.size() - body_ptr_;
				if(s > rest)
					s=rest;
				memcpy(p,&body_[body_ptr_],s);
				body_ptr_ += s;
				read_length_ += s;

				if(body_ptr_ == body_.size()) {
					body_ptr_ = 0;
					body_.clear();
				}

				if(read_length_ >= content_length_) {
					async_read_record(boost::bind(
						&fastcgi::on_read_stdin_eof_expected,
						self(),
						_1,
						h,
						s));
					return;
				}
				socket_.get_io_service().post(boost::bind(h,boost::system::error_code(),s));
				return;
			}
			else {
				async_read_record(boost::bind(
					&fastcgi::on_some_input_recieved,
					self(),
					_1,h,p,s));
				return;
			}
		}
	private:
		void on_some_input_recieved(boost::system::error_code const &e,io_handler const &h,void *p,size_t s)
		{
			if(e) { h(e,0); return; }
			if(	header_.type!=fcgi_stdin 
				|| header_.request_id!=request_id_ 
				|| header_.content_length==0)
			{
				h(boost::system::error_code(errc::protocol_violation,cppcms_category),0);
				return;
			}
			async_read_some(p,s,h);
		}
		void on_read_stdin_eof_expected(boost::system::error_code const &e,io_handler const &h,size_t s)
		{
			if(e) { h(e,s); return; }
			if(	header_.type!=fcgi_stdin 
				|| header_.request_id!=request_id_ 
				|| header_.content_length!=0)
			{
				h(boost::system::error_code(errc::protocol_violation,cppcms_category),s);
				return;
			}
			h(boost::system::error_code(),s);
		}
	public:	
		virtual void async_write_some(void const *p,size_t s,io_handler const &h)
		{
			do_write_some(p,s,h,true);
		}
		
		virtual size_t write_some(void const *buffer,size_t n)
		{
			return do_write_some(buffer,n,io_handler(),false);
		}
		
		virtual size_t do_write_some(void const *p,size_t s,io_handler const &h,bool async)
		{
			if(s==0) {
				if(async)
					socket_.get_io_service().post(boost::bind(h,boost::system::error_code(),0));
				return 0;
			}
			memset(&header_,0,sizeof(header_));
			header_.version=fcgi_version_1;
			header_.type=fcgi_stdout;
			header_.request_id=request_id_;
			if(s > 65535) s=65535;
			header_.content_length =s;
			header_.padding_length =7 - (s % 8);
			static char pad[8];
			
			header_.to_net();

			boost::array<boost::asio::const_buffer,3> packet = {
				{
					boost::asio::buffer(&header_,sizeof(header_)),
					boost::asio::buffer(p,s),
					boost::asio::buffer(pad,header_.padding_length)
				}
			};
			if(async) {
				boost::asio::async_write(socket_,
						packet,
						boost::bind(	h,
								boost::asio::placeholders::error,
								s));
				return s;
			}
			
			boost::asio::write(socket_,packet);
			return s;
		}
		virtual boost::asio::io_service &get_io_service()
		{
			return socket_.get_io_service();
		}
		virtual bool keep_alive()
		{
			reset_all();
			return keep_alive_;
		}

		virtual void close()
		{
			boost::system::error_code e;
			socket_.close(e);
		}
		
		virtual void async_write_eof(handler const &h)
		{
			memset(&eof_,0,sizeof(eof_));
			for(unsigned i=0;i<3;i++) {
				eof_.headers_[i].version=fcgi_version_1;
				eof_.headers_[i].request_id=request_id_;
			}
			eof_.headers_[0].type=fcgi_stdout;
			eof_.headers_[1].type=fcgi_stderr;
			eof_.headers_[2].type=fcgi_end_request;
			eof_.headers_[2].content_length=8;
			eof_.record_.protocol_status=fcgi_request_complete;
			eof_.headers_[0].to_net();
			eof_.headers_[1].to_net();
			eof_.headers_[2].to_net();
			eof_.record_.to_net();
			
			#if !defined(NO_CANCELIO)
			// Stop reading EOF from socket
			socket_.cancel();
			#endif


			boost::asio::async_write(	socket_,
							boost::asio::buffer(&eof_,sizeof(eof_)),
							boost::bind(	&fastcgi::on_written_eof,
									self(),
									_1,
									h));
		}

		virtual void on_written_eof(boost::system::error_code const &e,handler const &h)
		{
			if(e) { h(e); return; }

			#if defined(NO_CANCELIO)
			
			// Windows XP and below not support CancelIO... So we just close
			// socket, because we can't stop asyncronous EOF reading...

			keep_alive_=false;
			boost::system::error_code err;
			socket_.shutdown(boost::asio::basic_stream_socket<Proto>::shutdown_both,err);
			socket_.close(err);

			#else

			// Stop reading from socket
			if(!keep_alive_) {
				boost::system::error_code err;
				socket_.shutdown(boost::asio::basic_stream_socket<Proto>::shutdown_both,err);
			}

			#endif

			h(boost::system::error_code());
		}

		// This is not really correct because server may try
		// to multiplex or ask control... But meanwhile it is good enough
		virtual void async_read_eof(callback const &h)
		{
			static char a;
			socket_.async_read_some(boost::asio::buffer(&a,1),boost::bind(h));
		}
	
	private:

		// 
		//
		// defs
		//
		//

		struct fcgi_header {
			unsigned char version;
			unsigned char type;
			uint16_t request_id;
			uint16_t content_length;
			unsigned char padding_length;
			unsigned char reserverd;
			void to_host() { 
				request_id = ntohs(request_id);
				content_length = ntohs(content_length);
			}
			void to_net() { 
				request_id = htons(request_id);
				content_length = htons(content_length);
			}
		};

		enum {
			fcgi_header_len = 8,
			fcgi_version_1 = 1,
						

			fcgi_begin_request     = 1,
			fcgi_abort_request     = 2,
			fcgi_end_request       = 3,
			fcgi_params            = 4,
			fcgi_stdin             = 5,
			fcgi_stdout            = 6,
			fcgi_stderr            = 7,
			fcgi_data              = 8,
			fcgi_get_values        = 9,
			fcgi_get_values_result =10,
			fcgi_unknown_type      =11,
			fcgi_maxtype 	       =11,

			fcgi_null_request_id = 0
		};
		
		struct fcgi_request_body {
			uint16_t role;
			unsigned char flags;
			unsigned char reserved [5];
			void to_host() { role = ntohs(role); }
			void to_net() { role = htons(role); }
		};

		struct fcgi_begin_request_record {
			fcgi_header header;
			fcgi_request_body body;
			void to_host() { header.to_host(); body.to_host(); }
			void to_net() {  header.to_net(); body.to_net(); }
		};
		enum {
			fcgi_keep_conn = 1
		};
		enum {
			fcgi_responder = 1,
			fcgi_authorizer = 2,
			fcgi_filter = 3
		};

		struct fcgi_end_request_body {
			uint32_t app_status;
			unsigned char protocol_status;
			unsigned char reserved[3];
			void to_host() {  app_status = ntohl(app_status); }
			void to_net() { app_status = htonl(app_status); }
		}; 

		struct fcgi_end_request_record {
			fcgi_header header;
			fcgi_end_request_body body;
			void to_host() { header.to_host(); body.to_host(); }
			void to_net() {  header.to_net(); body.to_net(); }
		};

		enum {
			fcgi_request_complete = 0,
			fcgi_cant_mpx_conn    = 1,
			fcgi_overloaded       = 2,
			fcgi_unknown_role     = 3
		};


		struct fcgi_unknown_type_body {
			unsigned char type;
			unsigned char reserverd[7];
		};

		struct fcgi_unknown_type_record {
			fcgi_header header;
			fcgi_unknown_type_record body;
			void to_host() { header.to_host();}
			void to_net() {  header.to_net(); }
		};

		union fcgi_record {
			fcgi_header header;
			fcgi_begin_request_record begin ;
			fcgi_end_request_record  end;
			fcgi_unknown_type_record unknown;
		};
		
		//
		//
		// end of defs
		//
		//
		//
		//
	
		void on_start_request(boost::system::error_code const &e,handler const &h)
		{

			if(header_.version!=fcgi_version_1)
			{
				h(boost::system::error_code(errc::protocol_violation,cppcms_category));
				return;
			}
			if(header_.type==fcgi_get_values) {
				header_.type = fcgi_get_values_result;
				std::vector<std::pair<std::string,std::string> > pairs;
				if(!parse_pairs(pairs)) {
					h(boost::system::error_code(errc::protocol_violation,cppcms_category));
					return;
				}
				body_.clear();
				std::locale l("C");
				std::ostringstream ss;
				ss.imbue(l);
				ss<<cuncurrency_hint_;
				for(unsigned i=0;i<pairs.size();i++) {
					std::string name=pairs[i].first;
					if(name=="FCGI_MAX_CONNS" || name=="FCGI_MAX_REQS")
						add_pair(name,ss.str());
					else if(name=="FCGI_MPXS_CONNS")
						add_pair(name,"0");
				}
				async_send_respnse(boost::bind(	&fastcgi::on_params_response_sent,
								self(),
								_1,
								h));
			}
			else if(header_.type!=fcgi_begin_request) {
				async_read_headers(h);
				return;
			}
			
			if(body_.size()!=sizeof(fcgi_request_body)) {
				h(boost::system::error_code(errc::protocol_violation,cppcms_category));
				return;
			}
			fcgi_request_body *body=reinterpret_cast<fcgi_request_body*>(&body_.front());
			body->to_host();
			keep_alive_=body->flags & fcgi_keep_conn;
			
			if(body->role!=fcgi_responder) {
				header_.type=fcgi_end_request;
				body_.assign(0,8);
				fcgi_end_request_body *body=reinterpret_cast<fcgi_end_request_body*>(&body_.front());
				body->protocol_status=fcgi_unknown_role;
				body->to_net();
				async_send_respnse(boost::bind(	&fastcgi::on_params_response_sent,
								self(),
								_1,
								h));
				return;
			}
			request_id_=header_.request_id;
			
			body_.clear();
			async_read_record(boost::bind(&fastcgi::params_record_expected,self(),_1,h));
		}

		uint32_t read_len(unsigned char const *&p,unsigned char const *e)
		{
			if (p<e && *p < 0x80) {
				return *p++;
			}
			else if(e-p >= 4) {
				return	(((*p++) & 0x7F) << 24)
					| ((*p++) << 16)
					| ((*p++) << 8)
					| (*p++);
			}
			else {
				return 0xFFFFFFFFu;
			}
		}

		template<typename Container>
		bool parse_pairs(Container &container)
		{
			unsigned char const *p=reinterpret_cast<unsigned char const *>(&body_.front());
			unsigned char const *e=p + body_.size();
			while(p<e) {
				uint32_t nlen=read_len(p,e);
				uint32_t vlen=read_len(p,e);
				if(nlen == 0xFFFFFFFFu || vlen == 0xFFFFFFFFu)
					return false;
				std::string name;
				if(uint32_t(e - p) >= nlen) { // don't chage order -- prevent integer overflow
					name.assign(p,p+nlen);
					p+=nlen;
				}
				else {
					return false;
				}
				std::string value;
				if(uint32_t(e - p) >= vlen) { // don't chage order -- prevent integer overflow
					value.assign(p,p+vlen);
					p+=vlen;
				}
				else {
					return false;
				}
				container.insert(container.end(),std::make_pair(name,value));
			}
			return true;
		}

		void params_record_expected(boost::system::error_code const &e,handler const &h)
		{
			if(header_.type!=fcgi_params || header_.request_id!=request_id_)
				h(boost::system::error_code(errc::protocol_violation,cppcms_category));
			if(header_.content_length!=0) { // eof
				if(body_.size() < 16384) {
					async_read_record(boost::bind(	&fastcgi::params_record_expected,
									self(),
									_1,
									h));
				}
				else {
					h(boost::system::error_code(errc::protocol_violation,cppcms_category));
				}
				return;
			}


			parse_pairs(env_);

			body_.clear();

			std::string content_length=getenv("CONTENT_LENGTH");
			if(content_length.empty() || (content_length_ =  atoll(content_length.c_str())) <=0)
				content_length_=0;
			if(content_length_==0) {
				async_read_record(boost::bind(
					&fastcgi::stdin_eof_expected,
					self(),
					_1,
					h));
				return;
			}
			h(boost::system::error_code());
		}

		void stdin_eof_expected(boost::system::error_code const &e,handler const &h)
		{
			if(e) { h(e); return; }
			if(header_.type!=fcgi_stdin || header_.content_length!=0) {
				std::cerr<<int(header_.type) <<" "<<header_.content_length<<std::endl;
				h(boost::system::error_code(errc::protocol_violation,cppcms_category));
				return;				
			}
			h(boost::system::error_code());
		}

		void on_params_response_sent(boost::system::error_code const &e,handler const &h)
		{
			if(e) { h(e); return; }
			async_read_headers(h);
		}

		void async_send_respnse(handler const &h)
		{
			header_.content_length=body_.size();
			if(body_.size() % 8 != 0) {
				header_.padding_length=8 - (body_.size() % 8);
				body_.resize(body_.size() + header_.padding_length);
			}
			boost::array<boost::asio::const_buffer,2> packet = {
				{
					boost::asio::buffer(&header_,sizeof(header_)),
					boost::asio::buffer(body_)
				}
			};
			header_.to_net();
			boost::asio::async_write(socket_,packet,boost::bind(h,boost::asio::placeholders::error));

		}

		void async_read_record(handler const &h)
		{
			boost::asio::async_read(socket_,
						boost::asio::buffer(&header_,sizeof(header_)),
						boost::bind(	&fastcgi::on_header_read,
								self(),
								boost::asio::placeholders::error,
								h));
		}
		void on_header_read(boost::system::error_code const &e,handler const &h)
		{
			if(e) { h(e); return; }
			header_.to_host();
			size_t cur_size=body_.size();
			size_t rec_size=header_.content_length+header_.padding_length;
			if(rec_size==0) {
				h(boost::system::error_code());
				return;
			}
			body_.resize(cur_size + rec_size);
			boost::asio::async_read(socket_,
						boost::asio::buffer(&body_[cur_size],rec_size),
						boost::bind(	&fastcgi::on_body_read,
								self(),
								boost::asio::placeholders::error,
								h));
		}
		void on_body_read(boost::system::error_code const &e,handler const &h)
		{
			if(e) { h(e);  return; }
			body_.resize(body_.size() - header_.padding_length);
			h(boost::system::error_code());
		}

		void add_value(std::string const &value)
		{
			if(value.size() <128) {
				body_.push_back(char(value.size()));
			}
			else {
				uint32_t size=value.size();
				size |= 0x80000000u;
				size=htonl(size);
				char *p=(char*)&size;
				body_.insert(body_.end(),p,p+sizeof(size));
				body_.insert(body_.end(),value.begin(),value.end());
			}
		}
		void add_pair(std::string const &name,std::string const &value)
		{
			add_value(name);
			add_value(value);
		}


		intrusive_ptr<fastcgi<Proto> > self()
		{
			return this;
		}
		
		
		friend class socket_acceptor<Proto,fastcgi<Proto> >;
		boost::asio::basic_stream_socket<Proto> socket_;

		fcgi_header header_;
		std::vector<char> body_;


		long long read_length_;
		long long content_length_;
		unsigned body_ptr_;
		int request_id_;
		bool keep_alive_;
		int cuncurrency_hint_;


		std::map<std::string,std::string> env_;
		struct eof_str {
			fcgi_header headers_[3];
			fcgi_end_request_body record_;
		} eof_;

		void reset_all()
		{
			memset(&header_,0,sizeof(header_));
			body_.clear();
			read_length_=content_length_=0;
			body_ptr_=0;
			request_id_=0;
			keep_alive_=false;
			env_.clear();
			memset(&eof_,0,sizeof(eof_));
		}
	};

	typedef fastcgi<boost::asio::ip::tcp> tcp_socket_fastcgi;
	typedef tcp_socket_acceptor<tcp_socket_fastcgi>    tcp_socket_fastcgi_acceptor;
	std::auto_ptr<acceptor> fastcgi_api_tcp_socket_factory(cppcms::service &srv,std::string ip,int port,int backlog)
	{
		std::auto_ptr<acceptor> a(new tcp_socket_fastcgi_acceptor(srv,ip,port,backlog));
		return a;
	}

#if !defined(CPPCMS_WIN32)
	typedef fastcgi<boost::asio::local::stream_protocol> unix_socket_fastcgi;
	typedef unix_socket_acceptor<unix_socket_fastcgi>    unix_socket_fastcgi_acceptor;

	std::auto_ptr<acceptor> fastcgi_api_unix_socket_factory(cppcms::service &srv,std::string socket,int backlog)
	{
		std::auto_ptr<acceptor> a(new unix_socket_fastcgi_acceptor(srv,socket,backlog));
		return a;
	}
	std::auto_ptr<acceptor> fastcgi_api_unix_socket_factory(cppcms::service &srv,int backlog)
	{
		std::auto_ptr<acceptor> a(new unix_socket_fastcgi_acceptor(srv,backlog));
		return a;
	}
#endif


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

