#include <string>

#include "avp.hh"

#include "diameter_uar_codec.hh"

#include "loggers.hh"
#include "converter.hh"

template <typename T_type> class OPTIONAL;
class TTCN_EncDec;

int diameter_uar_codec::encode(const LibDiameter__TypesAndValues::UAR__MSG& p_uar_msg, OCTETSTRING& p_data) {
  loggers::get_instance().log_msg(">>> diameter_uar_codec::encode", (const Base_Type &)p_uar_msg);

  TTCN_EncDec::clear_error();
  TTCN_Buffer encoding_buffer;

  if (_codec.encode_header(p_uar_msg.header(), encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode: Failed to encode UAR header");
    return -1;
  }

  if (encode_avps(p_uar_msg, encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode: Failed to encode UAR AVPs");
    return -1;
  }

  p_data = OCTETSTRING(encoding_buffer.get_len(), encoding_buffer.get_data());	
  // Update length at offset #1 on 3 bytes

  return 0;
}

int diameter_uar_codec::encode_avps(const LibDiameter__TypesAndValues::UAR__MSG& p_uar_msg, TTCN_Buffer p_encoding_buffer) {
  loggers::get_instance().log_msg(">>> diameter_uar_codec::encode_avps: ", OCTETSTRING(p_encoding_buffer.get_len(), p_encoding_buffer.get_data()));

  // SessionId
  if (_codec.encode_session_id(p_uar_msg.session__Id(), p_encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode_avps: Failed to encode session__Id__AVP");
    return -1;
  }

  // Mandatory AVPs
  if (_codec.encode_origin_host(p_uar_msg.uAR__Body().origin__Host(), p_encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode_avps: Failed to encode  Origin__Host__AVP");
    return -1;
  }
  if (_codec.encode_origin_realm(p_uar_msg.uAR__Body().origin__Realm(), p_encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode_avps: Failed to encode  Origin__Realm__AVP");
    return -1;
  }



  // Optional AVPs
  if (_codec.encode_auth_session_state(p_uar_msg.uAR__Body().auth__Session__State(), p_encoding_buffer) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::encode_avps: AVP is missing");
    return -1;
  }
  // TODO Continue

  loggers::get_instance().log_msg("<<< diameter_uar_codec::encode_avps: ", OCTETSTRING(p_encoding_buffer.get_len(), p_encoding_buffer.get_data()));
  return 0;
}

int diameter_uar_codec::decode(const OCTETSTRING& p_data, LibDiameter__TypesAndValues::UAR__MSG& p_uar_msg, params* p_params) {
  loggers::get_instance().log_msg(">>> diameter_uar_codec::decode: ", (const Base_Type &)p_data);

  TTCN_EncDec::set_error_behavior(TTCN_EncDec::ET_ALL, TTCN_EncDec::EB_DEFAULT);
  TTCN_EncDec::clear_error();
  TTCN_Buffer decoding_buffer(p_data);

  params _params;
  _params.insert(std::make_pair<std::string, std::string>("Request", "1"));

  if (_codec.decode_header(decoding_buffer, p_uar_msg.header(), static_cast<const params&>(_params)) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Failed to decode UAR message header");
    return -1;
  }

  if (_codec.decode_avps(decoding_buffer, p_uar_msg.header(), static_cast<const params&>(_params)) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Failed to decode UAR AVPs");
    return -1;
  }

  // Mandatory AVPs
  if (decode_auth_session_state(p_uar_msg.uAR__Body().auth__Session__State()) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Missing Auth__Session__State__AVP");
    return -1;
  }
  if (decode_origin_host(p_uar_msg.uAR__Body().origin__Host()) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Missing Origin__Host__AVP");
    return -1;
  }
  if (decode_origin_realm(p_uar_msg.uAR__Body().origin__Realm()) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Missing Origin__Realm__AVP");
    return -1;
  }
  if (decode_destination_realm(p_uar_msg.uAR__Body().destination__Realm()) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Missing Destination__Realm__AVP");
    return -1;
  }
  if (decode_session_id(p_uar_msg.session__Id()) == -1) {
    loggers::get_instance().warning("diameter_uar_codec::decode: Missing Destination__Realm__AVP");
    return -1;
  }

  // Optinal AVPs
  if (decode_destination_host(p_uar_msg.uAR__Body().destination__Host()) == -1) {
    p_uar_msg.uAR__Body().destination__Host().set_to_omit();
  }

  p_uar_msg.uAR__Body().vendor__Specific__Application__Id().set_to_omit();
  p_uar_msg.uAR__Body().auth__Application__Id().set_to_omit();
  p_uar_msg.uAR__Body().sIP__AOR().set_to_omit();
  p_uar_msg.uAR__Body().user__Name().set_to_omit();
  p_uar_msg.uAR__Body().sIP__Visited__Network__Id().set_to_omit();
  p_uar_msg.uAR__Body().sIP__User__Authorization__Type().set_to_omit();
  p_uar_msg.uAR__Body().supported__Features().set_to_omit();
  p_uar_msg.uAR__Body().public__Identity().set_to_omit();
  p_uar_msg.uAR__Body().visited__Network__Identifier().set_to_omit();
  p_uar_msg.uAR__Body().user__Authorization__Type().set_to_omit();
  p_uar_msg.uAR__Body().uAR__Flags().set_to_omit();
  p_uar_msg.uAR__Body().proxy__Info().set_to_omit();
  p_uar_msg.uAR__Body().route__Record().set_to_omit();
  p_uar_msg.uAR__Body().aVP__Type().set_to_omit();

  return 0;
}

int diameter_uar_codec::decode_session_id(LibDiameter__Types__Base__AVPs::Session__Id__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_session_id: looking for '%d", _codec.AVP_Session_Id_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Session_Id_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_session_id: AVP is missing");
    return -1;
  }
  avp_ptr->decode_session_id(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_session_id: ", p_avp);

  return 0;
}

int diameter_uar_codec::decode_auth_session_state(LibDiameter__Types__Base__AVPs::Auth__Session__State__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_auth_session_state: looking for '%d", _codec.AVP_Auth_Session_State_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Auth_Session_State_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_auth_session_state: AVP is missing");
    return -1;
  }
  avp_ptr->decode_auth_session_state(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_auth_session_state: ", p_avp);

  return 0;
}

int diameter_uar_codec::decode_origin_host(LibDiameter__Types__Base__AVPs::Origin__Host__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_origin_host: looking for '%d", _codec.AVP_Origin_Host_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Origin_Host_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_origin_host: AVP is missing");
    return -1;
  }
  avp_ptr->decode_origin_host(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_origin_host: ", p_avp);

  return 0;
}

int diameter_uar_codec::decode_destination_host(LibDiameter__Types__Base__AVPs::Destination__Host__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_destination_host: looking for '%d", _codec.AVP_Destination_Host_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Destination_Host_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_destination_host: AVP is missing");
    return -1;
  }
  avp_ptr->decode_destination_host(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_destination_host: ", p_avp);

  return 0;
}

int diameter_uar_codec::decode_origin_realm(LibDiameter__Types__Base__AVPs::Origin__Realm__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_origin_realm: looking for '%d", _codec.AVP_Origin_Realm_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Origin_Realm_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_origin_realm: AVP is missing");
    return -1;
  }
  avp_ptr->decode_origin_realm(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_origin_realm: ", p_avp);

  return 0;
}

int diameter_uar_codec::decode_destination_realm(LibDiameter__Types__Base__AVPs::Destination__Realm__AVP& p_avp) {
  loggers::get_instance().log("diameter_uar_codec::decode_destination_realm: looking for '%d", _codec.AVP_Destination_Realm_AVP);
  const std::unique_ptr<avp>& avp_ptr = _codec.get_avp(_codec.AVP_Destination_Realm_AVP);
  if (avp_ptr.get() == nullptr) {
    loggers::get_instance().warning("diameter_uar_codec::decode_destination_realm: AVP is missing");
    return -1;
  }
  avp_ptr->decode_destination_realm(p_avp);
  loggers::get_instance().log_msg("diameter_uar_codec::decode_destination_realm: ", p_avp);

  return 0;
}
