#define CPPCMS_SOURCE
//#define DEBUG_HTTP_PARSER
#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 "http_parser.h"
#include "config.h"
#include <string.h>
#include <iostream>
#include <algorithm>
#ifdef CPPCMS_USE_EXTERNAL_BOOST
#   include <boost/lexical_cast.hpp>
#else // Internal Boost
#   include <cppcms_boost/lexical_cast.hpp>
    namespace boost = cppcms_boost;
#endif



namespace cppcms {
namespace impl {
namespace cgi {
	class http : public connection {
	public:
		http(cppcms::service &srv) :
			connection(srv),
			socket_(srv.impl().get_io_service()),
			input_body_ptr_(0),
			input_parser_(input_body_,input_body_ptr_),
			output_body_ptr_(0),
			output_parser_(output_body_,output_body_ptr_),
			headers_done_(false),
			first_header_observerd_(false),
			total_read_(0)
		{

			env_["SERVER_SOFTWARE"]=PACKAGE_NAME "/" PACKAGE_VERSION;
			env_["SERVER_NAME"]=srv.settings().get("service.ip","127.0.0.1");
			env_["SERVER_PORT"]=boost::lexical_cast<std::string>(srv.settings().get("service.port",8080));
			env_["GATEWAY_INTERFACE"]="CGI/1.0";
			env_["SERVER_PROTOCOL"]="HTTP/1.0";

		}
		~http()
		{
			boost::system::error_code e;
			socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both,e);
		}
		struct binder {
			void operator()(boost::system::error_code const &e,size_t n) const
			{
				self_->some_headers_data_read(e,n,h_);
			}
			binder(intrusive_ptr<http> self,handler const &h) :
				self_(self),
				h_(h)
			{
			}
		private:
			intrusive_ptr<http> self_;
			handler h_;
		};
		virtual void async_read_headers(handler const &h)
		{
			input_body_.reserve(8192);
			input_body_.resize(8192,0);
			input_body_ptr_=0;
			socket_.async_read_some(boost::asio::buffer(input_body_),binder(self(),h));
		}

		void some_headers_data_read(boost::system::error_code const &e,size_t n,handler const &h)
		{
			if(e) { h(e); return; }

			total_read_+=n;
			if(total_read_ > 16384) {
				h(boost::system::error_code(errc::protocol_violation,cppcms_category));
				return;
			}

			input_body_.resize(n);

			for(;;) {
				using ::cppcms::http::impl::parser;
				switch(input_parser_.step()) {
				case parser::more_data:
					// Assuming body_ptr == body.size()
					async_read_headers(h);
					return;
				case parser::got_header:
					if(!first_header_observerd_) {
						first_header_observerd_=true;
						std::string::iterator rmethod,query=input_parser_.header_.end();
						rmethod=std::find(	input_parser_.header_.begin(),
									input_parser_.header_.end(),
									' ');
						if(rmethod!=input_parser_.header_.end()) 
							query=std::find(rmethod+1,input_parser_.header_.end(),' ');
						if(query!=input_parser_.header_.end()) {
							request_method_.assign(input_parser_.header_.begin(),rmethod);
							request_uri_.assign(rmethod+1,query);
							first_header_observerd_=true;
						}
						else {
							h(boost::system::error_code(errc::protocol_violation,cppcms_category));
							return;
						}
					}
					else { // Any other header
						std::string name;
						std::string value;
						if(!parse_single_header(input_parser_.header_,name,value))  {
							h(boost::system::error_code(errc::protocol_violation,cppcms_category));
							return;
						}
						if(name=="CONTENT_LENGTH" || name=="CONTENT_TYPE")
							env_[name]=value;
						else
							env_["HTTP_"+name]=value;
					}
					break;
				case parser::end_of_headers:
					process_request(h);				
					return;
				case parser::error_observerd:
					h(boost::system::error_code(errc::protocol_violation,cppcms_category));
					return;
				}
			}


		}

		// 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(input_body_ptr_==input_body_.size()) {
				input_body_.clear();
				input_body_ptr_=0;
			}
			if(!input_body_.empty()) {
				if(input_body_.size() - input_body_ptr_ < s) {
					s=input_body_.size() -  input_body_ptr_;
				}
				memcpy(p,&input_body_[input_body_ptr_],s);
				input_body_ptr_+=s;
				if(input_body_ptr_==input_body_.size()) {
					input_body_.clear();
					input_body_ptr_=0;
				}
				socket_.get_io_service().post(boost::bind(h,boost::system::error_code(),s));
				return;
			}
			socket_.async_read_some(boost::asio::buffer(p,s),h);
		}
		virtual void async_write_eof(handler const &h)
		{
			boost::system::error_code e;
			socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send,e);
			socket_.get_io_service().post(boost::bind(h,boost::system::error_code()));
		}
		virtual void async_write_some(void const *p,size_t s,io_handler const &h)
		{
			if(headers_done_)
				socket_.async_write_some(boost::asio::buffer(p,s),h);
			else
				process_output_headers(p,s,h);
		}
		virtual size_t write_some(void const *buffer,size_t n)
		{
			if(headers_done_)
				return socket_.write_some(boost::asio::buffer(buffer,n));
			return process_output_headers(buffer,n);
		}
		virtual boost::asio::io_service &get_io_service()
		{
			return socket_.get_io_service();
		}
		virtual bool keep_alive()
		{
			return false;
		}

		virtual void close()
		{
			boost::system::error_code e;
			socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_receive,e);
			socket_.close(e);
		}
		virtual void async_read_eof(callback const &h)
		{
			static char a;
			socket_.async_read_some(boost::asio::buffer(&a,1),boost::bind(h));
		}

	private:
		size_t process_output_headers(void const *p,size_t s,io_handler const &h=io_handler())
		{
			char const *ptr=reinterpret_cast<char const *>(p);
			output_body_.insert(output_body_.end(),ptr,ptr+s);

			using cppcms::http::impl::parser;

			for(;;) {
				switch(output_parser_.step()) {
				case parser::more_data:
					if(!h.empty())
						h(boost::system::error_code(),s);
					return s;
				case parser::got_header:
					{
						std::string name,value;
						if(!parse_single_header(output_parser_.header_,name,value))  {
							h(boost::system::error_code(errc::protocol_violation,cppcms_category),s);
							return s;
						}
						if(name=="STATUS") {
							response_line_ = "HTTP/1.0 "+value+"\r\n";

							return write_response(h,s);
						}
					}
					break;
				case parser::end_of_headers:
					response_line_ = "HTTP/1.0 200 Ok\r\n";

					return write_response(h,s);
				case parser::error_observerd:
					h(boost::system::error_code(errc::protocol_violation,cppcms_category),0);
					return 0;
				}
			}
	
		}
		size_t write_response(io_handler const &h,size_t s)
		{
			char const *addon = 
				"Server: CppCMS-Embedded/" PACKAGE_VERSION "\r\n"
				"Connection: close\r\n";

			response_line_.append(addon);

			boost::array<boost::asio::const_buffer,2> packet = {
				{
					boost::asio::buffer(response_line_),
					boost::asio::buffer(output_body_)
				}
			};
#ifdef DEBUG_HTTP_PARSER
			std::cerr<<"["<<response_line_<<std::string(output_body_.begin(),output_body_.end())
				<<"]"<<std::endl;
#endif
			headers_done_=true;
			if(h.empty()) {
				boost::system::error_code e;
				boost::asio::write(socket_,packet,boost::asio::transfer_all(),e);
				if(e) return 0;
				return s;
			}
			boost::asio::async_write(socket_,packet,
				boost::bind(&http::do_write,self(),_1,h,s));
			return s;
		}

		void do_write(boost::system::error_code const &e,io_handler const &h,size_t s)
		{
			if(e) { h(e,0); return; }
			h(boost::system::error_code(),s);
		}

		void process_request(handler const &h)
		{
			if(request_method_!="GET" && request_method_!="POST" && request_method_!="HEAD") {
				response("HTTP/1.0 501 Not Implemented\r\n\r\n",h);
				return;
			}

			env_["REQUEST_METHOD"]=request_method_;
			std::string remote_addr;
			if(service().settings().get("http.proxy.behind",false)) {
				remote_addr=socket_.remote_endpoint().address().to_string();
			}
			else {
				std::vector<std::string> default_headers;
				default_headers.push_back("X-Forwarded-For");

				std::vector<std::string> headers = 
					service().settings().get("http.proxy.remote_addr_headers",default_headers);

				for(unsigned i=0;i<headers.size();i++) {
					std::map<std::string,std::string>::const_iterator p;
					if((p=env_.find(headers[i]))!=env_.end()) {
						remote_addr=p->second;
						break;
					}
				}
			}
			env_["REMOTE_HOST"] = env_["REMOTE_ADDR"] = remote_addr;

			if(request_uri_.empty() || request_uri_[0]!='/') {
				response("HTTP/1.0 400 Bad Request\r\n\r\n",h);
				return;
			}
			

			std::vector<std::string> script_names = 
				service().settings().get("http.script_names",std::vector<std::string>());

			std::string additional=service().settings().get("http.script","");
			
			if(!additional.empty())
				script_names.push_back(additional);


			for(unsigned i=0;i<script_names.size();i++) {

				std::string const &name=script_names[i];

				if(	request_uri_.size() >= name.size() 
					&& memcmp(request_uri_.c_str(),name.c_str(),name.size())==0)
				{
					env_["SCRIPT_NAME"]=name;
					size_t pos=request_uri_.find('?');
					if(pos==std::string::npos)
						env_["PATH_INFO"]=request_uri_.substr(name.size());
					else {
						env_["PATH_INFO"]=request_uri_.substr(name.size(),pos-name.size());
						env_["QUERY_STRING"]=request_uri_.substr(pos+1);
					}
					h(boost::system::error_code());
					return;
				}
			}
			
			size_t pos=request_uri_.find('?');
			if(pos==std::string::npos)
				env_["PATH_INFO"]=request_uri_;
			else {
				env_["PATH_INFO"]=request_uri_.substr(0,pos);
				env_["QUERY_STRING"]=request_uri_.substr(pos+1);
			}
			
			h(boost::system::error_code());

		}
		void response(char const *message,handler const &h)
		{
			boost::asio::async_write(socket_,boost::asio::buffer(message,strlen(message)),
					boost::bind(h,boost::system::error_code()));
		}

		bool parse_single_header(std::string const &header,std::string &name,std::string &value)
		{
			std::string::const_iterator p=header.begin(),e=header.end(),name_end;

			p=cppcms::http::protocol::skip_ws(p,e);
			name_end=cppcms::http::protocol::tocken(p,e);
			if(name_end==p)
				return false;
			name.assign(p,name_end);
			p=name_end;
			p=cppcms::http::protocol::skip_ws(p,e);
			if(p==e || *p!=':')
				return false;
			++p;
			p=cppcms::http::protocol::skip_ws(p,e);
			value.assign(p,e);
			for(unsigned i=0;i<name.size();i++) {
				if(name[i] == '-') 
					name[i]='_';
				else if('a' <= name[i] && name[i] <='z')
					name[i]=name[i]-'a' + 'A';
			}
			return true;
		}


		intrusive_ptr<http> self()
		{
			return this;
		}
		
		friend class socket_acceptor<boost::asio::ip::tcp,http>;
		
		boost::asio::ip::tcp::socket socket_;

		std::vector<char> input_body_;
		unsigned input_body_ptr_;
		::cppcms::http::impl::parser input_parser_;
		std::vector<char> output_body_;
		unsigned output_body_ptr_;
		::cppcms::http::impl::parser output_parser_;


		std::map<std::string,std::string> env_;
		std::string script_name_;
		std::string response_line_;
		std::string request_method_;
		std::string request_uri_;
		bool headers_done_;
		bool first_header_observerd_;
		unsigned total_read_;
	};

	typedef tcp_socket_acceptor<http>    http_acceptor;

	std::auto_ptr<acceptor> http_api_factory(cppcms::service &srv,std::string ip,int port,int backlog)
	{
		std::auto_ptr<acceptor> a(new http_acceptor(srv,ip,port,backlog));
		return a;
	}



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


