#ifndef BOOST_NETWORK_PROTOCOL_HTTP_IMPL_HTTP_ASYNC_PROTOCOL_HANDLER_HPP_
#define BOOST_NETWORK_PROTOCOL_HTTP_IMPL_HTTP_ASYNC_PROTOCOL_HANDLER_HPP_

// Copyright 2010 (C) Dean Michael Berris
// Copyright 2011 Dean Michael Berris (dberris@google.com).
// Copyright 2011 Google, Inc.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <boost/network/detail/debug.hpp>
#include <boost/network/protocol/http/algorithms/linearize.hpp>

namespace boost { namespace network { namespace http { namespace impl {

  template <class Tag, unsigned version_major, unsigned version_minor>
  struct http_async_protocol_handler {
  protected:

    typedef typename string<Tag>::type string_type;

#ifdef BOOST_NETWORK_DEBUG
    struct debug_escaper {
      string_type & string;
      explicit debug_escaper(string_type & string_)
        : string(string_) {}
      debug_escaper(debug_escaper const & other)
        : string(other.string) {}
      void operator()(typename string_type::value_type input) {
      if (!algorithm::is_print()(input)) {
        typename ostringstream<Tag>::type escaped_stream;
        if (input == '\r') {
        string.append("\\r");
        } else if (input == '\n') {
        string.append("\\n");
        } else {
        escaped_stream << "\\x" << static_cast<int>(input);
        string.append(escaped_stream.str());
        }
      } else {
        string.push_back(input);
      }
      }
    };
#endif

    template <class ResponseType>
    void init_response(ResponseType & response_, bool get_body) {
      boost::shared_future<string_type> source_future(
        source_promise.get_future());
      source(response_, source_future);
      boost::shared_future<string_type> destination_future(
        destination_promise.get_future());
      destination(response_, destination_future);
      boost::shared_future<typename headers_container<Tag>::type>
        headers_future(headers_promise.get_future());
      headers(response_, headers_future);
      boost::shared_future<string_type> body_future(
        body_promise.get_future());
      body(response_, body_future);
      boost::shared_future<string_type> version_future(
        version_promise.get_future());
      version(response_, version_future);
      boost::shared_future<boost::uint16_t> status_future(
        status_promise.get_future());
      status(response_, status_future);
      boost::shared_future<string_type> status_message_future(
        status_message_promise.get_future());
      status_message(response_, status_message_future);
    }

    struct to_http_headers {
      typedef typename string<Tag>::type string_type;
      template <class U>
      string_type const operator() (U const & pair) const {
        typedef typename ostringstream<Tag>::type ostringstream_type;
        typedef constants<Tag> constants;
        ostringstream_type header_line;
        header_line << pair.first
          << constants::colon()
          << constants::space()
          << pair.second
          << constants::crlf();
        return header_line.str();
      }
    };

    template <class Delegate, class Callback>
    logic::tribool parse_version(Delegate & delegate_,
                   Callback callback,
                   size_t bytes) {
      logic::tribool parsed_ok;
      part_begin = part.begin();
      typename buffer_type::const_iterator part_end = part.begin();
      std::advance(part_end, bytes);
      typename boost::iterator_range<typename buffer_type::const_iterator>
        result_range,
        input_range = boost::make_iterator_range(part_begin, part_end);
      fusion::tie(parsed_ok, result_range) = response_parser_.parse_until(
        response_parser_type::http_version_done,
        input_range);
      if (parsed_ok == true) {
        string_type version;
        std::swap(version, partial_parsed);
        version.append(boost::begin(result_range),
                 boost::end(result_range));
        algorithm::trim(version);
        version_promise.set_value(version);
        part_begin = boost::end(result_range);
      } else if (parsed_ok == false) {
#ifdef BOOST_NETWORK_DEBUG
        string_type escaped;
        debug_escaper escaper(escaped);
        std::for_each(part_begin, part_end, escaper);
        BOOST_NETWORK_MESSAGE("[parser:"
                    << response_parser_.state()
                    << "] buffer contents: \""
                    << escaped
                    << "\"");
#endif
        std::runtime_error error("Invalid Version Part.");
        version_promise.set_exception(boost::copy_exception(error));
        status_promise.set_exception(boost::copy_exception(error));
        status_message_promise.set_exception(
          boost::copy_exception(error));
        headers_promise.set_exception(boost::copy_exception(error));
        source_promise.set_exception(boost::copy_exception(error));
        destination_promise.set_exception(boost::copy_exception(error));
        body_promise.set_exception(boost::copy_exception(error));
      } else {
        partial_parsed.append(
          boost::begin(result_range),
          boost::end(result_range)
          );
        part_begin = part.begin();
        delegate_->read_some(
          boost::asio::mutable_buffers_1(part.c_array(), part.size()),
          callback
          );
      }
      return parsed_ok;
    }

    template <class Delegate, class Callback>
    logic::tribool parse_status(Delegate & delegate_,
                  Callback callback,
                  size_t bytes) {
      logic::tribool parsed_ok;
      typename buffer_type::const_iterator part_end = part.begin();
      std::advance(part_end, bytes);
      typename boost::iterator_range<typename buffer_type::const_iterator>
        result_range,
        input_range = boost::make_iterator_range(part_begin, part_end);
      fusion::tie(parsed_ok, result_range) = response_parser_.parse_until(
        response_parser_type::http_status_done,
        input_range);
      if (parsed_ok == true) {
        string_type status;
        std::swap(status, partial_parsed);
        status.append(boost::begin(result_range),
                boost::end(result_range));
        trim(status);
        boost::uint16_t status_int =
          lexical_cast<boost::uint16_t>(status);
        status_promise.set_value(status_int);
        part_begin = boost::end(result_range);
      } else if (parsed_ok == false) {
#ifdef BOOST_NETWORK_DEBUG
        string_type escaped;
        debug_escaper escaper(escaped);
        std::for_each(part_begin, part_end, escaper);
        BOOST_NETWORK_MESSAGE("[parser:"
                    << response_parser_.state()
                    << "] buffer contents: \""
                    << escaped
                    << "\"");
#endif
        std::runtime_error error("Invalid status part.");
        status_promise.set_exception(boost::copy_exception(error));
        status_message_promise.set_exception(
          boost::copy_exception(error));
        headers_promise.set_exception(boost::copy_exception(error));
        source_promise.set_exception(boost::copy_exception(error));
        destination_promise.set_exception(boost::copy_exception(error));
        body_promise.set_exception(boost::copy_exception(error));
      } else {
        partial_parsed.append(
          boost::begin(result_range),
          boost::end(result_range)
          );
        part_begin = part.begin();
        delegate_->read_some(
          boost::asio::mutable_buffers_1(part.c_array(), part.size()),
          callback
          );
      }
      return parsed_ok;
    }

    template <class Delegate, class Callback>
    logic::tribool parse_status_message(Delegate & delegate_,
                      Callback callback,
                      size_t bytes) {
      logic::tribool parsed_ok;
      typename buffer_type::const_iterator part_end = part.begin();
      std::advance(part_end, bytes);
      typename boost::iterator_range<typename buffer_type::const_iterator>
        result_range,
        input_range = boost::make_iterator_range(part_begin, part_end);
      fusion::tie(parsed_ok, result_range) = response_parser_.parse_until(
        response_parser_type::http_status_message_done,
        input_range);
      if (parsed_ok == true) {
        string_type status_message;
        std::swap(status_message, partial_parsed);
        status_message.append(boost::begin(result_range),
                    boost::end(result_range));
        algorithm::trim(status_message);
        status_message_promise.set_value(status_message);
        part_begin = boost::end(result_range);
      } else if (parsed_ok == false) {
#ifdef BOOST_NETWORK_DEBUG
        string_type escaped;
        debug_escaper escaper(escaped);
        std::for_each(part_begin, part_end, escaper);
        BOOST_NETWORK_MESSAGE("[parser:"
                    << response_parser_.state()
                    << "] buffer contents: \""
                    << escaped
                    << "\"");
#endif
        std::runtime_error error("Invalid status message part.");
        status_message_promise.set_exception(
          boost::copy_exception(error));
        headers_promise.set_exception(boost::copy_exception(error));
        source_promise.set_exception(boost::copy_exception(error));
        destination_promise.set_exception(boost::copy_exception(error));
        body_promise.set_exception(boost::copy_exception(error));
      } else {
        partial_parsed.append(
          boost::begin(result_range),
          boost::end(result_range));
        part_begin = part.begin();
        delegate_->read_some(
          boost::asio::mutable_buffers_1(part.c_array(), part.size()),
          callback
          );
      }
      return parsed_ok;
    }

    void parse_headers_real(string_type & headers_part) {
      typename boost::iterator_range<typename string_type::const_iterator>
        input_range = boost::make_iterator_range(headers_part)
        , result_range;
      logic::tribool parsed_ok;
      response_parser_type headers_parser(
        response_parser_type::http_header_line_done);
      typename headers_container<Tag>::type headers;
      std::pair<string_type,string_type> header_pair;
      while (!boost::empty(input_range)) {
        fusion::tie(parsed_ok, result_range) =
          headers_parser.parse_until(
            response_parser_type::http_header_colon,
            input_range);
        if (headers_parser.state()
          != response_parser_type::http_header_colon)
          break;
        header_pair.first = string_type(boost::begin(result_range),
                        boost::end(result_range));
        input_range.advance_begin(boost::distance(result_range));
        fusion::tie(parsed_ok, result_range) =
          headers_parser.parse_until(
            response_parser_type::http_header_line_done,
            input_range);
        header_pair.second = string_type(boost::begin(result_range),
                         boost::end(result_range));
        input_range.advance_begin(boost::distance(result_range));

        trim(header_pair.first);
        if (header_pair.first.size() > 1) {
          header_pair.first.erase(
            header_pair.first.size() - 1
          );
        }
        trim(header_pair.second);
        headers.insert(header_pair);
      }
      // determine if the body parser will need to handle chunked encoding
      typename headers_range<basic_response<Tag> >::type transfer_encoding_range = 
          headers.equal_range("Transfer-Encoding");
      is_chunk_encoding = !empty(transfer_encoding_range) 
          && boost::iequals(boost::begin(transfer_encoding_range)->second, "chunked");
      headers_promise.set_value(headers);
    }

    template <class Delegate, class Callback>
    fusion::tuple<logic::tribool, size_t> parse_headers(Delegate & delegate_,
                              Callback callback,
                              size_t bytes) {
      logic::tribool parsed_ok;
      typename buffer_type::const_iterator part_end = part.begin();
      std::advance(part_end, bytes);
      typename boost::iterator_range<typename buffer_type::const_iterator>
        result_range,
        input_range = boost::make_iterator_range(part_begin, part_end);
      fusion::tie(parsed_ok, result_range) = response_parser_.parse_until(
        response_parser_type::http_headers_done,
        input_range);
      if (parsed_ok == true) {
        string_type headers_string;
        std::swap(headers_string, partial_parsed);
        headers_string.append(boost::begin(result_range),
                    boost::end(result_range));
        part_begin = boost::end(result_range);
        this->parse_headers_real(headers_string);
      } else if (parsed_ok == false) {
        // We want to output the contents of the buffer that caused
        // the error in debug builds.
#ifdef BOOST_NETWORK_DEBUG
        string_type escaped;
        debug_escaper escaper(escaped);
        std::for_each(part_begin, part_end, escaper);
        BOOST_NETWORK_MESSAGE("[parser:"
                    << response_parser_.state()
                    << "] buffer contents: \""
                    << escaped
                    << "\" consumed length: "
                    << boost::distance(result_range));
#endif
        std::runtime_error error("Invalid header part.");
        headers_promise.set_exception(boost::copy_exception(error));
        body_promise.set_exception(boost::copy_exception(error));
        source_promise.set_exception(boost::copy_exception(error));
        destination_promise.set_exception(boost::copy_exception(error));
      } else {
        partial_parsed.append(boost::begin(result_range),
                    boost::end(result_range));
        part_begin = part.begin();
        delegate_->read_some(
          boost::asio::mutable_buffers_1(part.c_array(), part.size()),
          callback
          );
      }
      return fusion::make_tuple(
        parsed_ok,
        std::distance(
          boost::end(result_range)
          , part_end
          )
        );
    }

    template <class Delegate, class Callback>
    void parse_body(Delegate & delegate_, Callback callback, size_t bytes) {
      // TODO: we should really not use a string for the partial body
      // buffer.
      partial_parsed.append(part_begin, bytes);
      part_begin = part.begin();
      delegate_->read_some(
        boost::asio::mutable_buffers_1(part.c_array(), part.size()),
        callback
        );
    }

    typedef response_parser<Tag> response_parser_type;
    // TODO: make 1024 go away and become a configurable value.
    typedef boost::array<typename char_<Tag>::type, 1024> buffer_type;

    response_parser_type response_parser_;
    boost::promise<string_type> version_promise;
    boost::promise<boost::uint16_t> status_promise;
    boost::promise<string_type> status_message_promise;
    boost::promise<typename headers_container<Tag>::type> headers_promise;
    boost::promise<string_type> source_promise;
    boost::promise<string_type> destination_promise;
    boost::promise<string_type> body_promise;
    buffer_type part;
    typename buffer_type::const_iterator part_begin;
    string_type partial_parsed;
    bool is_chunk_encoding;
  };


} /* impl */

} /* http */

} /* network */

} /* boost */

#endif /* BOOST_NETWORK_PROTOCOL_HTTP_IMPL_HTTP_ASYNC_PROTOCOL_HANDLER_HPP_20101015 */
