#include <stdexcept>
#include <regex>
#include <string>
//#include <typeinfo>

#include "HttpCodec.hh"
#include "loggers.hh"

#include "LibItsHttp_TypesAndValues.hh"

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

  TTCN_EncDec::clear_error();
  TTCN_Buffer encoding_buffer;

  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("HttpCodec::encode: Unbound HttpMessage");
    return -1;
  }

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

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

int HttpCodec::decode (const OCTETSTRING& data, LibItsHttp__TypesAndValues::HttpMessage& msg, Params* params)
{
  loggers::get_instance().log_msg(">>> HttpCodec::decode: data=", data);
  TTCN_EncDec::clear_error();
  TTCN_Buffer decoding_buffer(data);
  loggers::get_instance().log_to_hexa("HttpCodec::decode: decoding_buffer=", decoding_buffer);
  _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("HttpCodec::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("HttpCodec::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+)");
		std::sregex_iterator begin(str.cbegin(), str.cend(), rgx);
		std::smatch m = *begin;
		if (m.size() != 5) {
			loggers::get_instance().error("HttpCodec::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);
    	CHARSTRING body("");
        if (decode_body(decoding_buffer, body) == -1) {
        	response.body().set_to_omit();
        } else if (body.lengthof() == 0) {
        	response.body().set_to_omit();
        } else {
        	// TODO
        }
    	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("HttpCodec::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;
		loggers::get_instance().log_to_hexa("Before decoding Body: ", decoding_buffer);
    	CHARSTRING body("");
        if (decode_body(decoding_buffer, body) == -1) {
        	request.body().set_to_omit();
        } else if (body.lengthof() == 0) {
        	request.body().set_to_omit();
        } else {
        	// TODO
        }
		msg.request() = request;
    }

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

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

  const OPTIONAL<CHARSTRING>& v = p_request.body();
  CHARSTRING body("");
  if (v.ispresent()) {
    body = static_cast<const CHARSTRING&>(*v.get_opt_value());
    loggers::get_instance().log_msg("HttpCodec::encode_request: body: ", body);
  }

  // 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("HttpCodec::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) {
      loggers::get_instance().log("HttpCodec::encode_request: body length: %d", body.lengthof());
      p_encoding_buffer.put_cs(int2str(body.lengthof()));
    } else {
		if (header.header__value().size_of() > 0) {
		  loggers::get_instance().log_msg("HttpCodec::encode_request: Processing value ", header.header__value()[0]);
		  p_encoding_buffer.put_cs(header.header__value()[0]);
		  int j = 1;
		  while (j < header.header__value().size_of()) {
			  p_encoding_buffer.put_cs("; ");
			  loggers::get_instance().log_msg("HttpCodec::encode_request: Processing value ", header.header__value()[j]);
			  p_encoding_buffer.put_cs(header.header__value()[j]);
			  j += 1;
		  } // End of 'while' statement
		}
    }
    p_encoding_buffer.put_cs("\r\n");
  } // End of 'for' statement

  if (body.lengthof() != 0) {
    p_encoding_buffer.put_cs(body);
  }

  p_encoding_buffer.put_cs("\r\n");

  return 0;
}


int HttpCodec::decode_headers(TTCN_Buffer& decoding_buffer, LibItsHttp__TypesAndValues::HeaderLines& headers) {
  loggers::get_instance().log(">>> HttpCodec::decode_headers");
  loggers::get_instance().log_to_hexa("HttpCodec::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("HttpCodec::decode_headers: ", cstr);
		  LibItsHttp__TypesAndValues::HeaderLine header;
		  if (decode_header(cstr, header) == -1) {
			  loggers::get_instance().warning("HttpCodec::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("<<< HttpCodec::decode_headers: ", headers);
		  return 0;
	  case -1:
		  loggers::get_instance().warning("HttpCodec::decode_headers: Failed to decode headers");
		  return -1;

	  } // End of 'switch' statement
  } // End of 'while' statement
}

int HttpCodec::decode_header(CHARSTRING& header_line, LibItsHttp__TypesAndValues::HeaderLine& header) {
  loggers::get_instance().log_msg(">>> HttpCodec::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) {
		return -1;
	}
	header.header__name() = CHARSTRING(m[1].str().c_str());
	for (unsigned int j = 0; j < m.size(); j++) {
		if (m[j + 2].str().length() == 0) {
			break;
		}
		header.header__value()[j] = CHARSTRING(m[j + 2].str().c_str());
	}
	return 0;
  }
  catch(const std::logic_error& e) {
	return -1;
  }
}

int HttpCodec::decode_body(TTCN_Buffer& decoding_buffer, CHARSTRING& body) {
  loggers::get_instance().log(">>> HttpCodec::decode_body");
  loggers::get_instance().log_to_hexa("HttpCodec::decode_body", decoding_buffer);

  loggers::get_instance().log_msg("<<< HttpCodec::decode_body: ", body);
  return -1;
}





int HttpCodec::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 emplty 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
}

