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

#include "held_codec_factory.hh"

#include "loggers.hh"

#include "LibHttp_XmlMessageBodyTypes.hh"

#include "urn_ietf_params_xml_ns_geopriv_held.hh"
#include "urn_ietf_params_xml_ns_pidf_geopriv10.hh"

// TODO Unify held_codec for bothHttp & Sip: instead of LibHttp__XmlMessageBodyTypes::XmlBody, use a generic XmlBody common to LibItsHttp & LibSip
int held_codec::encode (const LibHttp__XmlMessageBodyTypes::XmlBody& msg, OCTETSTRING& data)
{
  loggers::get_instance().log_msg(">>> held_codec::encode: ", (const Base_Type&)msg);

  TTCN_EncDec::clear_error();
  TTCN_EncDec::set_error_behavior(TTCN_EncDec::ET_ALL, TTCN_EncDec::EB_DEFAULT);
  TTCN_Buffer encoding_buffer;
  
  CHARSTRING h("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n");
  encoding_buffer.put_s(h.lengthof(), (const unsigned char*)static_cast<const char*>(h));
  if (msg.ischosen(LibHttp__XmlMessageBodyTypes::XmlBody::ALT_locationRequest)) {
    const urn__ietf__params__xml__ns__geopriv__held::LocationRequestType& location_request = msg.locationRequest();
    loggers::get_instance().log_msg("held_codec::encode: Process LocationRequestType", (const Base_Type&)location_request);
    location_request.encode(urn__ietf__params__xml__ns__geopriv__held::LocationRequest_descr_, encoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
  } else if  (msg.ischosen(LibHttp__XmlMessageBodyTypes::XmlBody::ALT_locationResponse)) {
    const urn__ietf__params__xml__ns__geopriv__held::LocationResponseType& location_response = msg.locationResponse();
    loggers::get_instance().log_msg("held_codec::encode: Process LocationResponseType", (const Base_Type&)location_response);
    location_response.encode(urn__ietf__params__xml__ns__geopriv__held::LocationResponse_descr_, encoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
  } else {
    loggers::get_instance().warning("held_codec::encode: Unsupported variant");
    return -1;
  }
  data = OCTETSTRING(encoding_buffer.get_len(), encoding_buffer.get_data());
  { // TITAN issue: variant "name as 'device'"; does not work, need to replace "deviceIdenty" by "device"
    std::string s(static_cast<const unsigned char*>(data), data.lengthof() + static_cast<const unsigned char*>(data));
    size_t idx = 0;
    std::string f("deviceIdentity");
    std::string t("device");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement

    // NG112-2021: Apply draft-ietf-geopriv-held-identity-extensions-06: Use of Device Identity in HTTP-Enabled Location Delivery (HELD) draft-ietf-geopriv-held-identity-extensions-06
    // FIXME: Add parameter to enable/disable draft-ietf-geopriv-held-identity-extensions-06 support
    f.assign("xmlns:id");
    t.assign("xmlns");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement
    f.assign("id:device");
    t.assign("device");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement
    f.assign("device:id");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement
    f.assign("xmlns:held");
    t.assign("xmlns");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement
    f.assign("held:location");
    t.assign("location");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement
    f.assign("location:held");
    while ((idx = s.find(f)) != std::string::npos) {
      s.replace(idx, f.length(), t);
      idx += t.length();
    } // End of 'while' statement

    data = OCTETSTRING(s.length(), (const unsigned char*)s.c_str());
  }
  loggers::get_instance().log_msg("held_codec::encode: After encoding: ", data);
  
  loggers::get_instance().log("<<< held_codec::encode");
  return 0;
}

int held_codec::decode (const OCTETSTRING& p_data, LibHttp__XmlMessageBodyTypes::XmlBody& msg, params* p_params)
{
  loggers::get_instance().log_msg(">>> held_codec::decode: p_data=", p_data);

  // Sanity checks
  params::const_iterator it;
  if (p_params == nullptr) {
    loggers::get_instance().warning("held_codec::decode: Failed to access p_params (null pointer)");
    return -1;
  } else {
    it = p_params->find("decode_str");
    if (it == p_params->cend()) {
      loggers::get_instance().warning("held_codec::decode: Failed to access p_params item (decode_str)");
      return -1;
    }
  }
  
  TTCN_EncDec::set_error_behavior(TTCN_EncDec::ET_ALL, TTCN_EncDec::EB_DEFAULT);
  TTCN_EncDec::clear_error();
  TTCN_Buffer decoding_buffer(OCTETSTRING(it->second.length(), (const unsigned char*)it->second.c_str()));

  if ((it->second.find("<locationRequest") != std::string::npos) || (it->second.find(":locationRequest") != std::string::npos)) {
    urn__ietf__params__xml__ns__geopriv__held::LocationRequestType location_request;
    location_request.decode(urn__ietf__params__xml__ns__geopriv__held::LocationRequest_descr_, decoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
    msg.locationRequest() = location_request;
  } else if ((it->second.find("<locationResponse") != std::string::npos) || (it->second.find(":locationResponse") != std::string::npos)) {
    urn__ietf__params__xml__ns__geopriv__held::LocationResponseType location_response;
    location_response.decode(urn__ietf__params__xml__ns__geopriv__held::LocationResponse_descr_, decoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
    msg.locationResponse() = location_response;
  } else if ((it->second.find("<presence") != std::string::npos) || (it->second.find(":presence") != std::string::npos)) {
    urn__ietf__params__xml__ns__pidf::Presence presence;
    presence.decode(urn__ietf__params__xml__ns__pidf::Presence_descr_, decoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
    msg.presence() = presence;
  } else if ((it->second.find("<error") != std::string::npos) || (it->second.find(":error") != std::string::npos)) {
    urn__ietf__params__xml__ns__geopriv__held::Error error;
    error.decode(urn__ietf__params__xml__ns__geopriv__held::Error_descr_, decoding_buffer, TTCN_EncDec::CT_XER, XER_EXTENDED);
    msg.errorType() = error;
  } else {
    loggers::get_instance().warning("held_codec::decode: Unsupported variant");
    return -1;
  }
  
  loggers::get_instance().log_msg("<<< held_codec::decode: ", (const Base_Type&)msg);
  return 0;
}

held_codec_factory held_codec_factory::_f;
