#ifndef BOOST_NETWORK_PROTOCOL_HTTP_ALGORITHMS_LINEARIZE_HPP_20101028
#define BOOST_NETWORK_PROTOCOL_HTTP_ALGORITHMS_LINEARIZE_HPP_20101028

// Copyright 2010 Dean Michael Berris.
// Copyright 2014 Jussi Lyytinen
// 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 <algorithm>
#include <bitset>
#include <boost/network/traits/string.hpp>
#include <boost/network/protocol/http/message/header/name.hpp>
#include <boost/network/protocol/http/message/header/value.hpp>
#include <boost/network/protocol/http/message/header_concept.hpp>
#include <boost/network/protocol/http/request_concept.hpp>
#include <boost/network/constants.hpp>
#include <boost/concept/requires.hpp>
#include <boost/optional.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/algorithm/string/compare.hpp>
#include <boost/version.hpp>

namespace boost {
namespace network {
namespace http {

template <class Tag>
struct linearize_header {
  typedef typename string<Tag>::type string_type;

  template <class Arguments>
  struct result;

  template <class This, class Arg>
  struct result<This(Arg)> {
    typedef string_type type;
  };

  template <class ValueType>
  BOOST_CONCEPT_REQUIRES(((Header<typename boost::remove_cv<ValueType>::type>)),
                         (string_type))
  operator()(ValueType& header) {
    typedef typename ostringstream<Tag>::type output_stream;
    typedef constants<Tag> consts;
    output_stream header_line;
    header_line << name(header) << consts::colon() << consts::space()
                << value(header) << consts::crlf();
    return header_line.str();
  }
};

template <class Request, class OutputIterator>
BOOST_CONCEPT_REQUIRES(((ClientRequest<Request>)), (OutputIterator))
    linearize(Request const& request,
              typename Request::string_type const& method,
              unsigned version_major, unsigned version_minor,
              OutputIterator oi) {
  typedef typename Request::tag Tag;
  typedef constants<Tag> consts;
  typedef typename string<Tag>::type string_type;
  static string_type http_slash = consts::http_slash(),
                     accept = consts::accept(),
                     accept_mime = consts::default_accept_mime(),
                     accept_encoding = consts::accept_encoding(),
                     default_accept_encoding =
                         consts::default_accept_encoding(),
                     crlf = consts::crlf(), host = consts::host(),
                     connection = consts::connection(), close = consts::close();
  boost::copy(method, oi);
  *oi = consts::space_char();
  if (request.path().empty() || request.path()[0] != consts::slash_char())
    *oi = consts::slash_char();
  boost::copy(request.path(), oi);
  if (!request.query().empty()) {
    *oi = consts::question_mark_char();
    boost::copy(request.query(), oi);
  }
  if (!request.anchor().empty()) {
    *oi = consts::hash_char();
    boost::copy(request.anchor(), oi);
  }
  *oi = consts::space_char();
  boost::copy(http_slash, oi);
  string_type version_major_str =
                  boost::lexical_cast<string_type>(version_major),
              version_minor_str =
                  boost::lexical_cast<string_type>(version_minor);
  boost::copy(version_major_str, oi);
  *oi = consts::dot_char();
  boost::copy(version_minor_str, oi);
  boost::copy(crlf, oi);

  // We need to determine whether we've seen any of the following headers
  // before setting the defaults. We use a bitset to keep track of the
  // defaulted headers.
  enum {
    ACCEPT,
    ACCEPT_ENCODING,
    HOST,
    CONNECTION,
    MAX
  };
  std::bitset<MAX> found_headers;
  static char const* defaulted_headers[][2] = {
      {consts::accept(), consts::accept() + std::strlen(consts::accept())},
      {consts::accept_encoding(),
       consts::accept_encoding() + std::strlen(consts::accept_encoding())},
      {consts::host(), consts::host() + std::strlen(consts::host())},
      {consts::connection(),
       consts::connection() + std::strlen(consts::connection())}};

  typedef typename headers_range<Request>::type headers_range;
  typedef typename range_value<headers_range>::type headers_value;
  BOOST_FOREACH(const headers_value & header, headers(request)) {
    string_type header_name = name(header), header_value = value(header);
    // Here we check that we have not seen an override to the defaulted
    // headers.
    for (int header_index = 0; header_index < MAX; ++header_index)
      if (std::distance(header_name.begin(), header_name.end()) ==
              std::distance(defaulted_headers[header_index][0],
                            defaulted_headers[header_index][1]) &&
          std::equal(header_name.begin(), header_name.end(),
                     defaulted_headers[header_index][0],
                     algorithm::is_iequal()))
        found_headers.set(header_index, true);

    // We ignore empty headers.
    if (header_value.empty()) continue;
    boost::copy(header_name, oi);
    *oi = consts::colon_char();
    *oi = consts::space_char();
    boost::copy(header_value, oi);
    boost::copy(crlf, oi);
  }

  if (!found_headers[HOST]) {
    boost::copy(host, oi);
    *oi = consts::colon_char();
    *oi = consts::space_char();
    boost::copy(request.host(), oi);
    boost::optional<boost::uint16_t> port_ =
#if (_MSC_VER >= 1600 && BOOST_VERSION > 105500)
      port(request).as_optional();
#else
      port(request);
#endif
    if (port_) {
      string_type port_str = boost::lexical_cast<string_type>(*port_);
      *oi = consts::colon_char();
      boost::copy(port_str, oi);
    }
    boost::copy(crlf, oi);
  }

  if (!found_headers[ACCEPT]) {
    boost::copy(accept, oi);
    *oi = consts::colon_char();
    *oi = consts::space_char();
    boost::copy(accept_mime, oi);
    boost::copy(crlf, oi);
  }

  if (version_major == 1u && version_minor == 1u &&
      !found_headers[ACCEPT_ENCODING]) {
    boost::copy(accept_encoding, oi);
    *oi = consts::colon_char();
    *oi = consts::space_char();
    boost::copy(default_accept_encoding, oi);
    boost::copy(crlf, oi);
  }

  if (!connection_keepalive<Tag>::value && !found_headers[CONNECTION]) {
    boost::copy(connection, oi);
    *oi = consts::colon_char();
    *oi = consts::space_char();
    boost::copy(close, oi);
    boost::copy(crlf, oi);
  }

  boost::copy(crlf, oi);
  typename body_range<Request>::type body_data = body(request).range();
  return boost::copy(body_data, oi);
}

} /* http */

} /* net */

} /* boost */

#endif /* BOOST_NETWORK_PROTOCOL_HTTP_ALGORITHMS_LINEARIZE_HPP_20101028 */
