#include <stdexcept>
#include <regex>
#include <string>

#include "codec_stack_builder.hh"

#include "http_codec.hh"

#include "loggers.hh"

#include "LibItsHttp_TypesAndValues.hh"
#include "LibItsHttp_MessageBodyTypes.hh"
#include "LibItsHttp_XmlMessageBodyTypes.hh"

int http_codec::encode (const LibItsHttp__TypesAndValues::HttpMessage& msg, OCTETSTRING& data)
{
  loggers::get_instance().log_msg(">>> http_codec::encode: ", (const Base_Type&)msg);

  TTCN_EncDec::clear_error();
  TTCN_Buffer encoding_buffer;

  _ec.reset();

  int result;
  if (msg.ischosen(LibItsHttp__TypesAndValues::HttpMessage::ALT_request)) {
    result = encode_request(msg.request(), encoding_buffer);
  } else if (msg.ischosen(LibItsHttp__TypesAndValues::HttpMessage::ALT_response)) {
    result = encode_response(msg.response(), encoding_buffer);
  } else {
    loggers::get_instance().warning("http_codec::encode: Unbound HttpMessage");
    return -1;
  }

  data = OCTETSTRING(encoding_buffer.get_len(), encoding_buffer.get_data());

  loggers::get_instance().log_msg("<<< http_codec::encode: data=", data);
  return result;
}

int http_codec::decode (const OCTETSTRING& data, LibItsHttp__TypesAndValues::HttpMessage& msg, params* params)
{
  loggers::get_instance().log_msg(">>> http_codec::decode: data=", data);

  TTCN_EncDec::clear_error();
  TTCN_Buffer decoding_buffer(data);
  loggers::get_instance().log_to_hexa("http_codec::decode: decoding_buffer=", decoding_buffer);

  _dc.reset();

  _params = params;

  // Get the first line (e.g. HTTP/1.1 302 Found or POST / HTTP/1.1)
  CHARSTRING message_id;
  if (get_line(decoding_buffer, message_id) == -1) {
    return -1;
  }
  loggers::get_instance().log_msg("http_codec::decode: message_id: ", message_id);
  // Extract parameters
  try {
    std::string str(static_cast<const char*>(message_id));
    std::regex rgx ("\\s*(\\w+)/");
    std::sregex_iterator begin(str.cbegin(), str.cend(), rgx);
    std::smatch m = *begin;
    loggers::get_instance().log("http_codec::decode: %d - %s", m.size(), m[0].str().c_str());
    if (m[0].str().compare("HTTP/") == 0) { // HTTP response
      LibItsHttp__TypesAndValues::Response response;
      std::regex rgx ("\\s*HTTP/(\\d+)\\.(\\d+)\\s+(\\d+)\\s+([\\w\\s\\t\\v\\f]+)*");
      std::sregex_iterator begin(str.cbegin(), str.cend(), rgx);
      std::smatch m = *begin;
      loggers::get_instance().log("http_codec::decode: Process response: %d", m.size());
      if (m.size() != 5) {
        loggers::get_instance().error("http_codec::decode: Unsupported tag");
        return -1;
      }
      response.version__major() = std::stoi(m[1].str().c_str());
      response.version__minor() = std::stoi(m[2].str().c_str());
      response.statuscode() = std::stoi(m[3].str().c_str());
      response.statustext() = CHARSTRING(m[4].str().c_str());
      LibItsHttp__TypesAndValues::HeaderLines headers;
      decode_headers(decoding_buffer, headers);
      response.header() = headers;
      loggers::get_instance().log_to_hexa("Before decoding Body: ", decoding_buffer);
      LibItsHttp__MessageBodyTypes::HttpMessageBody body;
      if (decode_body(decoding_buffer, body) == -1) {
        response.body().set_to_omit();
      } else {
        response.body() = OPTIONAL<LibItsHttp__MessageBodyTypes::HttpMessageBody>(body);
      }
       msg.response() = response;
    } else { // HTTP request
      LibItsHttp__TypesAndValues::Request request;
      std::regex rgx ("\\s*(\\w+)\\s+(.+)\\s+HTTP/(\\d)\\.(\\d)");
      std::sregex_iterator begin(str.cbegin(), str.cend(), rgx);
      std::smatch m = *begin;
      if (m.size() != 5) {
        loggers::get_instance().error("http_codec::decode: Unsupported tag");
        return -1;
      }
      request.method() = CHARSTRING(m[1].str().c_str());
      request.uri() = CHARSTRING(m[2].str().c_str());
      request.version__major() = std::stoi(m[3].str().c_str());
      request.version__minor() = std::stoi(m[4].str().c_str());
      LibItsHttp__TypesAndValues::HeaderLines headers;
      decode_headers(decoding_buffer, headers);
      request.header() = headers;
      OPTIONAL<LibItsHttp__MessageBodyTypes::HttpMessageBody> body;
      body.set_to_omit();
      if (decode_body(decoding_buffer, body) == -1) {
        request.body().set_to_omit();
      } else {
        request.body() = body;
      }
      msg.request() = request;
    }

    loggers::get_instance().log_msg("<<< http_codec::decode: ", (const Base_Type&)msg);
    return 0;
  }
  catch(const std::logic_error& e) {
    return -1;
  }
}

int http_codec::encode_request(const LibItsHttp__TypesAndValues::Request& p_request, TTCN_Buffer& p_encoding_buffer)
{
  loggers::get_instance().log_msg(">>> http_codec::encode_request: ", (const Base_Type&)p_request);

  const OPTIONAL<LibItsHttp__MessageBodyTypes::HttpMessageBody>& v = p_request.body();
  OCTETSTRING os;
  if (v.ispresent()) {
    const LibItsHttp__MessageBodyTypes::HttpMessageBody& body = static_cast<const LibItsHttp__MessageBodyTypes::HttpMessageBody&>(*v.get_opt_value());
    loggers::get_instance().log_msg("http_codec::encode_request: body: ", body);
    if (encode_body(body, os) == -1) {
      _ec.length = 0;
    }
  }

  // Encode generic part
  p_encoding_buffer.put_cs(p_request.method());
  p_encoding_buffer.put_c(' ');
  p_encoding_buffer.put_cs(p_request.uri());
  p_encoding_buffer.put_cs(" HTTP/");
  p_encoding_buffer.put_cs(int2str(p_request.version__major()));
  p_encoding_buffer.put_c('.');
  p_encoding_buffer.put_cs(int2str(p_request.version__minor()));
  p_encoding_buffer.put_cs("\r\n");
  // Encode headers
  const LibItsHttp__TypesAndValues::HeaderLines& headers = p_request.header();
  for (int i = 0; i < headers.size_of(); i++) {
    const LibItsHttp__TypesAndValues::HeaderLine& header = headers[i];
    loggers::get_instance().log_msg("http_codec::encode_request: Processing header ", header.header__name());
    p_encoding_buffer.put_cs(header.header__name());
    p_encoding_buffer.put_cs(": ");
    if (std::string(static_cast<const char*>(header.header__name())).compare("Content-Length") == 0) {
      if (_ec.length != 0) {
        p_encoding_buffer.put_cs(int2str(_ec.length + 2/*Stand for the last CRLF*/));
      } else {
        p_encoding_buffer.put_cs("0");
      }
      _ec.is_content_length_present = 0x01;
    } else {
      const OPTIONAL<LibItsHttp__TypesAndValues::charstring__list>& o = header.header__value();
      if (o.ispresent()) {
        const LibItsHttp__TypesAndValues::charstring__list& v = dynamic_cast<const OPTIONAL<LibItsHttp__TypesAndValues::charstring__list> &>(o);
        if (v.size_of() > 0) {
          loggers::get_instance().log_msg("http_codec::encode_request: Processing value ", v[0]);
          p_encoding_buffer.put_cs(v[0]);
          int j = 1;
          while (j < v.size_of()) {
                p_encoding_buffer.put_cs(", ");
                loggers::get_instance().log_msg("http_codec::encode_request: Processing value ", v[j]);
                p_encoding_buffer.put_cs(v[j]);
                j += 1;
          } // End of 'while' statement
        }
      } // else, do not include it
    }
    p_encoding_buffer.put_cs("\r\n");
  } // End of 'for' statement

  p_encoding_buffer.put_cs("\r\n");
  if (_ec.length != 0) {
    p_encoding_buffer.put_os(os);
    p_encoding_buffer.put_cs("\r\n");
  }

  return 0;
}

int http_codec::encode_response (const LibItsHttp__TypesAndValues::Response& p_response, TTCN_Buffer& p_encoding_buffer) {
  loggers::get_instance().log_msg(">>> http_codec::encode_response: ", (const Base_Type&)p_response);

  const OPTIONAL<LibItsHttp__MessageBodyTypes::HttpMessageBody>& v = p_response.body();
  OCTETSTRING os;
  if (v.ispresent()) {
    const LibItsHttp__MessageBodyTypes::HttpMessageBody& body = static_cast<const LibItsHttp__MessageBodyTypes::HttpMessageBody&>(*v.get_opt_value());
    loggers::get_instance().log_msg("http_codec::encode_response: body: ", body);
    if (encode_body(body, os) == -1) {
      _ec.length = 0;
    }
  }

  // Encode generic part
  p_encoding_buffer.put_cs("HTTP/");
  p_encoding_buffer.put_cs(int2str(p_response.version__major()));
  p_encoding_buffer.put_c('.');
  p_encoding_buffer.put_cs(int2str(p_response.version__minor()));
  p_encoding_buffer.put_cs(" ");
  p_encoding_buffer.put_cs(int2str(p_response.statuscode()));
  p_encoding_buffer.put_cs(" ");
  if (p_response.statustext().lengthof() != 0) {
    p_encoding_buffer.put_cs(p_response.statustext());
  }
  p_encoding_buffer.put_cs("\r\n");
  // Encode headers
  const LibItsHttp__TypesAndValues::HeaderLines& headers = p_response.header();
  for (int i = 0; i < headers.size_of(); i++) {
    const LibItsHttp__TypesAndValues::HeaderLine& header = headers[i];
    loggers::get_instance().log_msg("http_codec::encode_response: Processing header ", header.header__name());
    p_encoding_buffer.put_cs(header.header__name());
    p_encoding_buffer.put_cs(": ");
    if (std::string(static_cast<const char*>(header.header__name())).compare("Content-Length") == 0) {
      if (_ec.length != 0) {
        p_encoding_buffer.put_cs(int2str(_ec.length + 2/*Stand for the last CRLF*/));
      } else {
        p_encoding_buffer.put_cs("0");
      }
      _ec.is_content_length_present = 0x01;
    } else {
      const OPTIONAL<LibItsHttp__TypesAndValues::charstring__list>& o = header.header__value();
      if (o.ispresent()) {
        const LibItsHttp__TypesAndValues::charstring__list& v = dynamic_cast<const OPTIONAL<LibItsHttp__TypesAndValues::charstring__list> &>(o);
        if (v.size_of() > 0) {
          loggers::get_instance().log_msg("http_codec::encode_response: Processing value ", v[0]);
          p_encoding_buffer.put_cs(v[0]);
          int j = 1;
          while (j < v.size_of()) {
                p_encoding_buffer.put_cs(", ");
                loggers::get_instance().log_msg("http_codec::encode_response: Processing value ", v[j]);
                p_encoding_buffer.put_cs(v[j]);
                j += 1;
          } // End of 'while' statement
        }
      } // else, do not include it
    }
    p_encoding_buffer.put_cs("\r\n");
  } // End of 'for' statement

  p_encoding_buffer.put_cs("\r\n");
  if (_ec.length != 0) {
    p_encoding_buffer.put_os(os);
    p_encoding_buffer.put_cs("\r\n");
  }

  return 0;
}

int http_codec::decode_headers(TTCN_Buffer& decoding_buffer, LibItsHttp__TypesAndValues::HeaderLines& headers) {
  loggers::get_instance().log(">>> http_codec::decode_headers");
  loggers::get_instance().log_to_hexa("http_codec::decode_headers", decoding_buffer);

  CHARSTRING cstr;
  int i = 0;
  while (true) {
    switch(get_line(decoding_buffer, cstr, true)) {
      case 0: {
          loggers::get_instance().log_msg("http_codec::decode_headers: ", cstr);
          LibItsHttp__TypesAndValues::HeaderLine header;
          if (decode_header(cstr, header) == -1) {
            loggers::get_instance().warning("http_codec::decode_headers: Failed to decode header %s", static_cast<const char*>(cstr));
            return -1;
          }
          headers[i++] = header;
        }
        break;
      case 1:
        loggers::get_instance().log_msg("<<< http_codec::decode_headers: ", headers);
        return 0;
      case -1:
        loggers::get_instance().warning("http_codec::decode_headers: Failed to decode headers");
        return -1;
    } // End of 'switch' statement
  } // End of 'while' statement
}

int http_codec::decode_header(CHARSTRING& header_line, LibItsHttp__TypesAndValues::HeaderLine& header) {
  loggers::get_instance().log_msg(">>> http_codec::decode_header", header_line);

  try {
    std::string str(static_cast<const char*>(header_line));
    std::regex rgx ("([0-9a-zA-Z-]+)\\:\\s+(.+)(,(.+))*");
    std::sregex_iterator begin(str.cbegin(), str.cend(), rgx);
    std::smatch m = *begin;
    if (m.size() < 5) {
      loggers::get_instance().warning("http_codec::decode_header: Failed to decode header %s", str.c_str());
      return -1;
    }
    loggers::get_instance().log("http_codec::decode_header: %d", m.size());
    header.header__name() = CHARSTRING(m[1].str().c_str());
    LibItsHttp__TypesAndValues::charstring__list v;
    for (unsigned int j = 0; j < m.size(); j++) {
      if (m[j + 2].str().length() == 0) {
        break;
      }
      v[j] = CHARSTRING(m[j + 2].str().c_str());
    } // End of 'for' statement
    header.header__value() = OPTIONAL<LibItsHttp__TypesAndValues::charstring__list>(v);

    if (m[1].str().compare("Content-Length") == 0) {
      // Save the the body length
      loggers::get_instance().log("http_codec::decode_header: decoded Content-Length %s", m[2].str().c_str());
      _dc.length = std::stoi(m[2].str());
    }
    return 0;
  }
  catch(const std::logic_error& e) {
      return -1;
  }
}

int http_codec::encode_body(const LibItsHttp__MessageBodyTypes::HttpMessageBody& p_message_body, OCTETSTRING& p_encoding_buffer) {
  loggers::get_instance().log_msg(">>> http_codec::encode_body: ", (const Base_Type&)p_message_body);
  loggers::get_instance().log("http_codec::encode_body: # of codecs=%d", _codecs.size());

  if (p_message_body.ischosen(LibItsHttp__MessageBodyTypes::HttpMessageBody::ALT_binary__body)) {
    p_encoding_buffer = p_message_body.binary__body().raw();
  } else if (p_message_body.ischosen(LibItsHttp__MessageBodyTypes::HttpMessageBody::ALT_html__body)) {
    p_encoding_buffer = OCTETSTRING(p_message_body.html__body().lengthof(), (unsigned char*)static_cast<const char*>(p_message_body.html__body()));
  } else if (p_message_body.ischosen(LibItsHttp__MessageBodyTypes::HttpMessageBody::ALT_text__body)) {
    p_encoding_buffer = OCTETSTRING(p_message_body.text__body().lengthof(), (unsigned char*)static_cast<const char*>(p_message_body.text__body()));
  } else if (p_message_body.ischosen(LibItsHttp__MessageBodyTypes::HttpMessageBody::ALT_xml__body)) {
    const LibItsHttp__XmlMessageBodyTypes::XmlBody& xml_body = p_message_body.xml__body();
    if (xml_body.ischosen(LibItsHttp__XmlMessageBodyTypes::XmlBody::ALT_raw)) {
      p_encoding_buffer = OCTETSTRING(xml_body.raw().lengthof(), (unsigned char*)static_cast<const char*>(xml_body.raw()));
    } else if (xml_body.ischosen(LibItsHttp__XmlMessageBodyTypes::XmlBody::ALT_findServiceRequest)) {
      std::map<std::string, std::unique_ptr<codec<Record_Type, Record_Type> > >::const_iterator it = _codecs.find("lost");
      if (it != _codecs.cend()) {
        loggers::get_instance().log("http_codec::encode_body: Call 'lost_codec'");
        _codecs["lost"]->encode((Record_Type&)xml_body, p_encoding_buffer);
      } else {
        loggers::get_instance().warning("http_codec::encode_body: Unsupported HTTP codec, use raw field as default");
        p_encoding_buffer = OCTETSTRING(xml_body.raw().lengthof(), (unsigned char*)static_cast<const char*>(xml_body.raw()));
        // TODO Add new HTTP message codec here
      }
    } else {
      std::map<std::string, std::unique_ptr<codec<Record_Type, Record_Type> > >::const_iterator it = _codecs.find("held");
      if (it != _codecs.cend()) {
        loggers::get_instance().log("http_codec::encode_body: Call 'held_codec'");
        _codecs["held"]->encode((Record_Type&)xml_body, p_encoding_buffer);
      } else {
        loggers::get_instance().warning("http_codec::encode_body: Unsupported HTTP codec, use raw field as default");
        p_encoding_buffer = OCTETSTRING(xml_body.raw().lengthof(), (unsigned char*)static_cast<const char*>(xml_body.raw()));
        // TODO Add new HTTP message codec here
      }
    }
  } else {
    loggers::get_instance().warning("http_codec::encode_body: Failed to encode HTTP message body");
    return -1;
  }
  loggers::get_instance().log_msg("http_codec::encode_body: HTTP message ", p_encoding_buffer);
  _ec.length = p_encoding_buffer.lengthof();
  loggers::get_instance().log("http_codec::encode_body: HTTP message length: %d", _ec.length);

  return 0;
}

int http_codec::decode_body(TTCN_Buffer& decoding_buffer, LibItsHttp__MessageBodyTypes::HttpMessageBody& message_body) {
  loggers::get_instance().log(">>> http_codec::decode_body");
  loggers::get_instance().log_to_hexa("http_codec::decode_body", decoding_buffer);
  loggers::get_instance().log("http_codec::decode_body: # of codecs=%d", _codecs.size());

  // Sanity check
  if (decoding_buffer.get_len() - decoding_buffer.get_pos() <= 0) {
    return -1;
  }

  OCTETSTRING s(decoding_buffer.get_len() - decoding_buffer.get_pos(), decoding_buffer.get_data() + decoding_buffer.get_pos());
  loggers::get_instance().log_msg("http_codec::decode_body: raw body=", s);
  // Align the payload length with the specified Content-lenght value
  loggers::get_instance().log("http_codec::decode_body: _dc.length=%d - body length=%d", _dc.length, s.lengthof());
  OCTETSTRING body;
  if (_dc.length != 0) {
    const unsigned char* p = static_cast<const unsigned char *>(s);
    if ((unsigned int)s.lengthof() <= _dc.length) {
      body = OCTETSTRING(s.lengthof(), p);
    } else {
      body = OCTETSTRING(_dc.length, p);
    }
  } else {
    loggers::get_instance().warning("http_codec::decode_body: No Conten-Length header, process all remaining bytes");
    body = s;
  }
  loggers::get_instance().log_msg("http_codec::decode_body: Aligned body=", body);
  // Remove CRLF if any
  int counter = 0;
  if ((body[body.lengthof() - 1].get_octet() == 0x0d) || (body[body.lengthof() - 1].get_octet() == 0x0a)) {
    counter += 1;
    if ((body[body.lengthof() - 2].get_octet() == 0x0d) || (body[body.lengthof() - 2].get_octet() == 0x0a)) {
      counter += 1;
    }
  }
  loggers::get_instance().log("http_codec::decode_body: Counter=%d", counter);
  body = OCTETSTRING(body.lengthof() - counter, static_cast<const unsigned char*>(body));
  loggers::get_instance().log_msg("http_codec::decode_body: Finalised body=", body);
  // Check if HTTP message body contains binary characters
  for (int i = 0; i < body.lengthof(); i++) {
    unsigned char c = body[i].get_octet();
    if (!std::isprint(c) && !std::isspace(c) && !std::ispunct(c)) {
      loggers::get_instance().log("http_codec::decode_body: Byte #%d is not printable: 0x%02x", i, body[i].get_octet());
      _dc.is_binary = 0x01;
      break;
    }
  } // End of 'for' statement
  loggers::get_instance().log("http_codec::decode_body: Binary mode: %x", _dc.is_binary);
  LibItsHttp__MessageBodyTypes::HttpMessageBody v;
  if (_dc.is_binary == 0x01) {
    LibItsHttp__BinaryMessageBodyTypes::BinaryBody bb;
    bb.raw() = body;
    message_body.binary__body() = bb;
  } else {
    // Convert into string
    params p;
    p["decode_str"] = std::string(static_cast<const unsigned char*>(body), body.lengthof() + static_cast<const unsigned char*>(body));
    // Try to identify xml
    if (p["decode_str"].find("<?xml version=") != std::string::npos) {
       loggers::get_instance().log("http_codec::decode_body: Find xml message");
       LibItsHttp__XmlMessageBodyTypes::XmlBody xml_body;
       // TODO To be refined adding a string identifier to check which codec to use. E.g. held_code.id() returns "xmlns=\"urn:ietf:params:xml:ns:geopriv:held\">"
       if (p["decode_str"].find("xmlns=\"urn:ietf:params:xml:ns:geopriv:held\">") != std::string::npos) {
         loggers::get_instance().log("http_codec::decode_body: Find 'urn:ietf:params:xml:ns:geopriv:held'");
         if (_codecs["held"].get() != nullptr) {
           loggers::get_instance().log("http_codec::decode_body: Call 'held_codec'");
           if (_codecs["held"]->decode(body, (Record_Type&)xml_body, &p) == -1) {
             loggers::get_instance().warning("http_codec::decode_body: Failed to decode HELD message");
             xml_body.raw() = CHARSTRING(body.lengthof(), (char*)static_cast<const unsigned char*>(body));
           } else {
             loggers::get_instance().log_msg("http_codec::decode_body: Decoded message:", xml_body);
             message_body.xml__body() = xml_body;
           }
         } else {
           loggers::get_instance().warning("http_codec::decode_body: No codec for HELD");
           xml_body.raw() = CHARSTRING(body.lengthof(), (char*)static_cast<const unsigned char*>(body));
         }
         message_body.xml__body() = xml_body;
       } else if (p["decode_str"].find("xmlns=\"urn:ietf:params:xml:ns:lost1\">") != std::string::npos) {
         loggers::get_instance().log("http_codec::decode_body: Find 'urn:ietf:params:xml:ns:loat1'");
         if (_codecs["lost"].get() != nullptr) {
           loggers::get_instance().log("http_codec::decode_body: Call 'lost_codec'");
           if (_codecs["lost"]->decode(body, (Record_Type&)xml_body, &p) == -1) {
             loggers::get_instance().warning("http_codec::decode_body: Failed to decode LOST message");
             xml_body.raw() = CHARSTRING(body.lengthof(), (char*)static_cast<const unsigned char*>(body));
           } else {
             loggers::get_instance().log_msg("http_codec::decode_body: Decoded message:", xml_body);
             message_body.xml__body() = xml_body;
           }
         } else {
           loggers::get_instance().warning("http_codec::decode_body: No codec for LOST");
           xml_body.raw() = CHARSTRING(body.lengthof(), (char*)static_cast<const unsigned char*>(body));
         }
         message_body.xml__body() = xml_body;
      }
    } else if (p["decode_str"].find("<html>") != std::string::npos) { // Try to identify HTML
      loggers::get_instance().log("http_codec::decode_body: Find html message");
      LibItsHttp__MessageBodyTypes::TextBody txt_body;
      loggers::get_instance().error("http_codec::decode_body: Not supported");
    } else {
      loggers::get_instance().log("http_codec::decode_body: Use textBdy as default");
      LibItsHttp__MessageBodyTypes::TextBody text_body;
      message_body.text__body() = CHARSTRING(body.lengthof(), (char*)static_cast<const unsigned char*>(body));
    }
  }
  
  return 0;
}

int http_codec::get_line(TTCN_Buffer& buffer, CHARSTRING& to, const bool concatenate_header_lines) {
    unsigned int i = 0;
    const unsigned char *cc_to = buffer.get_read_data();

    // Sanity checks
    if(buffer.get_read_len() == 0) {
        return -1;
    }

    while (true) {
      // Skip spaces, and empty lines
        for( ; i < buffer.get_read_len() && cc_to[i] != '\0' && cc_to[i] != '\r' && cc_to[i] != '\n'; i++);
        if(i >= buffer.get_read_len()) { // No more characters to process
            to = CHARSTRING("");
            return -1;
        } else if(cc_to[i] == '\n') { // New line found, we don't care is '\r' is missing
        if ((i > 0) && ((i + 1) < buffer.get_read_len()) && concatenate_header_lines && ((cc_to[i + 1] == ' ') || (cc_to[i + 1] == '\t'))) {
          i += 1; // Skip it
        } else {
            to = CHARSTRING(i, (const char*)cc_to);
            buffer.set_pos(buffer.get_pos() + i + 1);
            return i == 0 ? 1 : 0;
        }
      } else {
        if ((i + 1) < buffer.get_read_len() && cc_to[i + 1] != '\n') {
             return -1;
        } else if(i > 0 && (i + 2) < buffer.get_read_len() && concatenate_header_lines && (cc_to[i+2] == ' ' || cc_to[i+2] == '\t')) {
             i += 2;
        } else {
            to = CHARSTRING(i, (const char*)cc_to);
            buffer.set_pos(buffer.get_pos() + i + 2);
            return i == 0 ? 1 : 0;
        }
      }
    } // End of 'while' statement
}

void http_codec::set_payload_codecs(const std::string& p_codecs) {
  loggers::get_instance().log(">>> http_codec::set_payload_codecs: %s", p_codecs.c_str());
  
  // Sanity check
  if (p_codecs.length() == 0) {
    return;
  }
  
  // Extract codecs
  try {
    std::regex rgx("(\\w+):(\\w+)(;(\\w+):(\\w+))*");
    std::sregex_iterator begin(p_codecs.cbegin(), p_codecs.cend(), rgx);
    std::sregex_iterator end = std::sregex_iterator();
    // E.g. 9 - xml - :held_codec - held_codec - ;html:html_codec - html:html_codec - html - :html_codec - html_codec
    for (std::sregex_iterator it = begin; it != end; ++it) {
      std::smatch m = *it;
      loggers::get_instance().log("http_codec::set_payload_codecs: %d - %s - %s - %s - %s - %s - %s - %s - %s", m.size(), m[1].str().c_str(), m[2].str().c_str(), m[3].str().c_str(), m[4].str().c_str(), m[5].str().c_str(), m[6].str().c_str(), m[7].str().c_str(), m[8].str().c_str());
      for (unsigned int j = 1; j < m.size() - 1; j += 3) { // Exclude m[0]
        loggers::get_instance().log("http_codec::set_payload_codecs: insert (%s, %s), j = %d", m[j].str().c_str(), m[j + 1].str().c_str(), j);
        std::string key(m[j].str());
        _codecs.insert(std::make_pair(key, std::unique_ptr<codec<Record_Type, Record_Type> >(codec_stack_builder::get_instance()->get_codec(m[j + 1].str().c_str()))));
      } // End of 'for' statement
    } // End of 'for' statement
    //loggers::get_instance().log("http_codec::set_payload_codecs: _codecs length=%d", _codecs.size());
  }
  catch(const std::logic_error& e){
    loggers::get_instance().warning("http_codec::set_payload_codecs: std::logic_error: %s", e.what());
    _codecs.clear();
  }
}
