/******************************************************************************
 * Copyright (c) 2000-2024 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Baranyi, Botond
 *   Feher, Csaba
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Bence Janos
 *   Szabo, Janos Zoltan – initial implementation
 *   Tatarka, Gabor
 *
 ******************************************************************************/
#ifndef PORT_HH
#define PORT_HH

#include <sys/types.h>

#include "Types.h"
#include "Event_Handler.hh"
#include <stddef.h> // only for NULL
#include <sys/select.h>
#include "Error.hh"
#include "Logger.hh"
#include "Param_Types.hh"
#include "Basetype.hh"
#include "Textbuf.hh"

class COMPONENT;
class COMPONENT_template;
class Text_Buf;
class OCTETSTRING;
class CHARSTRING;
class FLOAT;
class Index_Redirect;

extern const COMPONENT_template& any_compref;

struct port_connection; // no user serviceable parts inside

/** Wrapper class for parameters of 'map' and 'unmap' operations */
class Map_Params {
  unsigned int nof_params;
  CHARSTRING* params;
  
  void init(unsigned int p_nof_params);
  void clear();
  void copy(const Map_Params& p_other);
  
public:
  Map_Params(unsigned int p_nof_params) { init(p_nof_params); }
  Map_Params(const Map_Params& p_other) { copy(p_other); } // not used
  ~Map_Params() { clear(); }
  Map_Params& operator=(const Map_Params& p_other);
  void reset(unsigned int p_nof_params);
  void set_param(unsigned int p_index, const CHARSTRING& p_param);
  unsigned int get_nof_params() const;
  const CHARSTRING& get_param(unsigned int p_index) const;
};

/** Cached parameters of the last received MAP_ACK or UNMAP_ACK message.
  * It is used by the mapping/unmapping requestor to retrieve the output
  * parameters of the 'map'/'unmap' operation. (Only used in parallel mode) */
extern Map_Params map_params_cache;

/** Base class for all test ports */
class PORT : public Fd_And_Timeout_Event_Handler {
  friend class PORT_LIST;
  friend struct port_connection;

  static PORT *list_head, *list_tail;
  
  // Used during translation port activate. It will
  // contains the provider ports. It is needed because the
  // names of the ports of system component and the mtc
  // can be the same.
  static PORT *system_list_head, *system_list_tail;

  void add_to_list(boolean system = FALSE);
  void remove_from_list(boolean system);
  static PORT *lookup_by_name(const char *par_port_name, boolean system = FALSE);

  struct port_parameter; // no user serviceable parts inside
  static port_parameter *parameter_head, *parameter_tail;

  /** @brief Apply the given port parameter.
  *
  *  Applies the parameter to the appropriate port, or all ports if it's
  *  a parameter for all ports ( "*" )
  *  @param par_ptr pointer to the port parameter
  */
  static void apply_parameter(port_parameter *par_ptr);
  void set_system_parameters(const char *system_port);

public:
  struct msg_queue_item_base {
    struct msg_queue_item_base *next_item;
  };
  msg_queue_item_base *msg_queue_head, *msg_queue_tail;
public:
  /** @brief Store a port parameter
  *
  *  @param component_id    component identifier
  *  @param par_port_name   string, name of the port, NULL if the parameter
  *                         refers to all ports ( "*" )
  *  @param parameter_name  string, name of the parameter
  *  @param parameter_value string, the value
  */
  static void add_parameter(const component_id_t& component_id,
    const char *par_port_name, const char *parameter_name,
    const char *parameter_value);
  /** Deallocates the list of port parameters */
  static void clear_parameters();
  /** @brief Apply port parameters to component

  Iterates through all known port parameters and

  @param component_reference
  @param component_name
  */
  static void set_parameters(component component_reference,
    const char *component_name);

protected:
  const char *port_name;
  unsigned int msg_head_count, msg_tail_count, proc_head_count,
  proc_tail_count;
  boolean is_active, is_started, is_halted;
  
private:
  int n_system_mappings;
  char **system_mappings;
  PORT *list_prev, *list_next;
  port_connection *connection_list_head, *connection_list_tail;

private:
  /// Copy constructor disabled.
  PORT(const PORT& other_port);
  /// Assignment disabled.
  PORT& operator=(const PORT& other_port);

public:
  PORT(const char *par_port_name);
  virtual ~PORT();

  inline const char *get_name() const { return port_name; }
  void set_name(const char * name);

  virtual void log() const;

  void activate_port(boolean system = FALSE);
  void deactivate_port(boolean system);
  static void deactivate_all();

  void clear();
  static void all_clear();
  void start();
  static void all_start();
  void stop();
  static void all_stop();
  void halt();
  static void all_halt();
  
  boolean port_is_started();
  
  // activate and start a system port if it's not already started
  // needed by the init_system_port function
  void safe_start();
  
  virtual void add_port(PORT* p);
  virtual void remove_port(PORT* p);
  
  // Returns the outer message port it is mapped to 
  // when the port works in translation mode. 
  // In the case of dual faced ports it returns the port object
  // it is called on (this).
  // Otherwise returns NULL.
  // Emits errors when the port is mapped to more than one port or
  // has both connections and mappings.
  // This function is overridden only in the class of a port with translation
  // capability and dual faced ports.
  virtual PORT* get_provider_port();
  
  virtual boolean incoming_message_handler(const void* message_ptr, const char* message_type,
    component sender_component, const FLOAT& timestamp);
  
  boolean check_port_state(const CHARSTRING& type) const;
  static boolean any_check_port_state(const CHARSTRING& type);
  static boolean all_check_port_state(const CHARSTRING& type);
  
  // Used by the setstate operation through TTCN_Runtime
  virtual void change_port_state(translation_port_state state);

  virtual alt_status receive(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL,
    Index_Redirect* index_redirect = NULL);
  static alt_status any_receive(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);
  virtual alt_status check_receive(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL, Index_Redirect* index_redirect = NULL);
  static alt_status any_check_receive(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL);

  virtual alt_status trigger(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL,
    Index_Redirect* index_redirect = NULL);
  static alt_status any_trigger(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);

  virtual alt_status getcall(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL,
    Index_Redirect* index_redirect = NULL);
  static alt_status any_getcall(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);
  virtual alt_status check_getcall(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL, Index_Redirect* index_redirect = NULL);
  static alt_status any_check_getcall(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL);

  virtual alt_status getreply(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL,
    Index_Redirect* index_redirect = NULL);
  static alt_status any_getreply(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);
  virtual alt_status check_getreply(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL, Index_Redirect* index_redirect = NULL);
  static alt_status any_check_getreply(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL);

  virtual alt_status get_exception(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL, Index_Redirect* index_redirect = NULL);
  static alt_status any_catch(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);
  virtual alt_status check_catch(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL, Index_Redirect* index_redirect = NULL);
  static alt_status any_check_catch(const COMPONENT_template&
    sender_template = any_compref, COMPONENT *sender_ptr = NULL,
    FLOAT* timestamp_redirect = NULL);

  alt_status check(const COMPONENT_template& sender_template = any_compref,
    COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL,
    Index_Redirect* index_redirect = NULL);
  static alt_status any_check(const COMPONENT_template& sender_template =
    any_compref, COMPONENT *sender_ptr = NULL, FLOAT* timestamp_redirect = NULL);

  /** Set a parameter on the port.
  *  @param parameter_name string
  *  @param parameter_value
  *
  *  The implementation in the PORT base class issues a warning and
  *  does nothing. Derived classes need to override this method.
  */
  virtual void set_parameter(const char *parameter_name,
    const char *parameter_value);

  void append_to_msg_queue(msg_queue_item_base*);
private:
  /** Callback interface for handling events - introduced in TITAN R7E
  * To use the finer granularity interface, this method must not be
  * overridden in the descendant Test Port class.
  * Note: Error event always triggers event handler call and is indicated in
  * the is_error parameter even if not requested.
  */
  virtual void Handle_Fd_Event(int fd,
    boolean is_readable, boolean is_writable, boolean is_error);

  /** Callback interface for handling timeout - introduced in TITAN R7E
  * May not be overridden in the descendant Test Port class if
  * timeout is not used.
  */
  virtual void Handle_Timeout(double time_since_last_call);

  /** Callback interface for handling events - introduced in TITAN R7E
  * These methods in the descendant Test Port class are called only if
  * Handle_Fd_Event is not overridden in the descendant Test Port class.
  * The method handling an event which is not used in the Test Port
  * may not be overridden in the descendant Test Port class.
  * This is true even for the error event handler, although error events
  * always trigger event handler call even if not requested.
  * (There is an empty default implementation for the error event handler.)
  */
  virtual void Handle_Fd_Event_Error(int fd);
  virtual void Handle_Fd_Event_Writable(int fd);
  virtual void Handle_Fd_Event_Readable(int fd);

public:
  /** Callback interface for handling events
  * This method is provided for Test Ports developed for TITAN versions
  * before R7E.
  * (It is called only if event handler has been registered with
  * Install_Handler.)
  */
  virtual void Event_Handler(const fd_set *read_fds, const fd_set *write_fds,
    const fd_set *error_fds, double time_since_last_call);

protected:
  /** Interface for handling events and timeout - introduced in TITAN R7E */
  typedef enum {
    EVENT_RD = FD_EVENT_RD, EVENT_WR = FD_EVENT_WR,
      EVENT_ERR = FD_EVENT_ERR,
      EVENT_ALL = FD_EVENT_RD | FD_EVENT_WR | FD_EVENT_ERR
  } Fd_Event_Type;
  void Handler_Add_Fd(int fd, Fd_Event_Type event_mask = EVENT_ALL);
  void Handler_Add_Fd_Read(int fd);
  void Handler_Add_Fd_Write(int fd);
  void Handler_Remove_Fd(int fd, Fd_Event_Type event_mask = EVENT_ALL);
  void Handler_Remove_Fd_Read(int fd);
  void Handler_Remove_Fd_Write(int fd);
  void Handler_Set_Timer(double call_interval, boolean is_timeout = TRUE,
    boolean call_anyway = TRUE, boolean is_periodic = TRUE);

  /** Interface for handling events and timeout
  * This method is provided for Test Ports developed for TITAN versions
  * before R7E.
  */
  void Install_Handler(const fd_set *read_fds, const fd_set *write_fds,
    const fd_set *error_fds, double call_interval);
  /** Interface for handling events and timeout
  * This method is in use in Test Ports developed for TITAN versions
  * before R7E.
  * It can be used together with the interface introduced in TITAN R7E.
  */
  void Uninstall_Handler();
  
  // legacy map and unmap functions for backward compatibility
  virtual void user_map(const char *system_port);
  virtual void user_unmap(const char *system_port);

  // new map and unmap functions (with parameters)
  virtual void user_map(const char *system_port, Map_Params& params);
  virtual void user_unmap(const char *system_port, Map_Params& params);

  virtual void user_start();
  virtual void user_stop();

  virtual void clear_queue();

  component get_default_destination();

  static void prepare_message(Text_Buf& outgoing_buf,
    const char *message_type);
  static void prepare_call(Text_Buf& outgoing_buf,
    const char *signature_name);
  static void prepare_reply(Text_Buf& outgoing_buf,
    const char *signature_name);
  static void prepare_exception(Text_Buf& outgoing_buf,
    const char *signature_name);
  void send_data(Text_Buf& outgoing_buf,
    const COMPONENT& destination_component);

  void process_data(port_connection *conn_ptr, Text_Buf& incoming_buf);
  virtual boolean process_message(const char *message_type,
    Text_Buf& incoming_buf, component sender_component, OCTETSTRING&);
  virtual boolean process_call(const char *signature_name,
    Text_Buf& incoming_buf, component sender_component);
  virtual boolean process_reply(const char *signature_name,
    Text_Buf& incoming_buf, component sender_component);
  virtual boolean process_exception(const char *signature_name,
    Text_Buf& incoming_buf, component sender_component);
  
  // Resets the port type variables to NULL after unmap
  virtual void reset_port_variables();
  
  // Initializes the port variables after map
  virtual void init_port_variables();
  
private:
  port_connection *add_connection(component remote_component,
    const char *remote_port, transport_type_enum transport_type);
  void remove_connection(port_connection *conn_ptr);
  port_connection *lookup_connection_to_compref(component remote_component,
    boolean *is_unique);
  port_connection *lookup_connection(component remote_component,
    const char *remote_port);
  void add_local_connection(PORT *other_endpoint);
  void remove_local_connection(port_connection *conn_ptr);

  static unsigned int get_connection_hash(component local_component,
    const char *local_port, component remote_component,
    const char *remote_port);
  static void unlink_unix_pathname(int socket_fd);
  void connect_listen_inet_stream(component remote_component,
    const char *remote_port);
  void connect_listen_unix_stream(component remote_component,
    const char *remote_port);
  void connect_local(component remote_component, const char *remote_port);
  void connect_stream(component remote_component, const char *remote_port,
    transport_type_enum transport_type, Text_Buf& text_buf);
  void disconnect_local(port_connection *conn_ptr);
  void disconnect_stream(port_connection *conn_ptr);

  void send_data_local(port_connection *conn_ptr, Text_Buf& outgoing_data);
  boolean send_data_stream(port_connection *conn_ptr, Text_Buf& outgoing_data,
    boolean ignore_peer_disconnect);

  void handle_incoming_connection(port_connection *conn_ptr);
  void handle_incoming_data(port_connection *conn_ptr);
  void process_last_message(port_connection *conn_ptr);

  void map(const char *system_port, Map_Params& params, boolean translation);
  void unmap(const char *system_port, Map_Params& params, boolean translation);

public:
  static void process_connect_listen(const char *local_port,
    component remote_component, const char *remote_port,
    transport_type_enum transport_type);
  static void process_connect(const char *local_port,
    component remote_component, const char *remote_port,
    transport_type_enum transport_type, Text_Buf& text_buf);
  static void process_disconnect(const char *local_port,
    component remote_component, const char *remote_port);
  static void make_local_connection(const char *src_port,
    const char *dest_port);
  static void terminate_local_connection(const char *src_port,
    const char *dest_port);

  static void map_port(const char *component_port, const char *system_port, Map_Params& params, boolean translation);
  static void unmap_port(const char *component_port, const char *system_port, Map_Params& params, boolean translation);

  boolean operator==(const PORT& right_val) const;
  boolean operator!=(const PORT& right_val) const;
  boolean operator==(null_type) const;
  boolean operator!=(null_type) const;
};

boolean operator==(null_type, const PORT&);
boolean operator!=(null_type, const PORT&);

template <typename T>
class PORT_REF
{
  template <typename T2>
  friend boolean operator==(null_type, const PORT_REF<T2>& right_val);
  template <typename T2>
  friend boolean operator!=(null_type, const PORT_REF<T2>& right_val);
  template <typename T2>
  friend boolean operator==(const PORT& left_val, const PORT_REF<T2>& right_val);
  template <typename T2>
  friend boolean operator!=(const PORT& left_val, const PORT_REF<T2>& right_val);
private:
  T* ptr; // NULL if it's a null reference
  boolean bound_flag;
public:
  inline void must_bound(const char* err_msg) const {
    if (!bound_flag) {
      TTCN_error("%s", err_msg);
    }
  }
  
  PORT_REF(): ptr(NULL), bound_flag(FALSE) {}

  PORT_REF(null_type): ptr(NULL), bound_flag(TRUE) {}

  PORT_REF(T& p_port): ptr(&p_port), bound_flag(TRUE) {}

  PORT_REF(const PORT_REF<T>& p_other)
  : ptr(p_other.ptr), bound_flag(p_other.bound_flag) {
    p_other.must_bound("Copying an unbound port reference.");
  }
  ~PORT_REF() {}

  PORT_REF& operator=(null_type) { // assignment operator for null reference
    ptr = NULL;
    bound_flag = TRUE;
    return *this;
  }

  PORT_REF& operator=(T& p_port) { // assignment operator for port object
    ptr = &p_port;
    bound_flag = TRUE;
    return *this;
  }

  PORT_REF& operator=(const PORT_REF<T>& p_other) { // assignment operator for another port reference
    p_other.must_bound("Assignment of an unbound port reference.");
    ptr = p_other.ptr;
    bound_flag = TRUE;
    return *this;
  }

  boolean operator==(null_type) const { // equality operator (with null reference)
    must_bound("Unbound left operand of port reference comparison.");
    return ptr == NULL;
  }

  boolean operator!=(null_type) const { // inequality operator (with null reference)
    must_bound("Unbound left operand of port reference comparison.");
    return ptr != NULL;
  }
  
  boolean operator==(const PORT_REF<T>& p_other) const { // equality operator (with another port reference)
    must_bound("Unbound left operand of port reference comparison.");
    p_other.must_bound("Unbound right operand of port reference comparison.");
    return ptr == p_other.ptr;
  }

  boolean operator!=(const PORT_REF<T>& p_other) const { // inequality operator (with another port reference)
    must_bound("Unbound left operand of port reference comparison.");
    p_other.must_bound("Unbound right operand of port reference comparison.");
    return ptr != p_other.ptr;
  }
  
  boolean operator==(const PORT& p_port) const { // equality operator (with port object)
    must_bound("Unbound left operand of port reference comparison.");
    return ptr == &p_port;
  }

  boolean operator!=(const PORT& p_port) const { // inequality operator (with port object)
    must_bound("Unbound left operand of port reference comparison.");
    return ptr != &p_port;
  }

  T& operator()() const { // all access to the referenced port is done through this operator
    must_bound("Accessing an unbound port reference.");
    if (ptr == NULL) {
      TTCN_error("Accessing a null port reference.");
    }
    return *ptr;
  }
  
  void log() const {
    if (!bound_flag) {
      TTCN_Logger::log_event_unbound();
    }
    else if (ptr == NULL) {
      TTCN_Logger::log_event_str("null");
    }
    else {
      ptr->log();
    }
  }

  void clean_up() {
    ptr = NULL;
    bound_flag = FALSE;
  }

  boolean is_bound() const {
    return bound_flag;
  }

  boolean is_value() const {
    return bound_flag;
  }

  boolean is_present() const {
    return bound_flag && ptr != NULL;
  }
};

template<typename T>
boolean operator==(null_type, const PORT_REF<T>& right_val) { // equality operator (with null reference, inverted)
  right_val.must_bound("Unbound right operand of port reference comparison.");
  return right_val.ptr == NULL;
}

template<typename T>
boolean operator!=(null_type, const PORT_REF<T>& right_val) { // inequality operator (with null reference, inverted)
  right_val.must_bound("Unbound right operand of port reference comparison.");
  return right_val.ptr != NULL;
}

template<typename T>
boolean operator==(const PORT& left_val, const PORT_REF<T>& right_val) { // equality operator (with port object, inverted)
  right_val.must_bound("Unbound right operand of port reference comparison.");
  return right_val.ptr == &left_val;
}

template<typename T>
boolean operator!=(const PORT& left_val, const PORT_REF<T>& right_val) { // inequality operator (with port object, inverted)
  right_val.must_bound("Unbound right operand of port reference comparison.");
  return right_val.ptr != &left_val;
}

template <typename T>
void log_address_ptr(T* p_ptr) {
  if (p_ptr == NULL) {
    TTCN_Logger::log_event_str("null");
  }
  else {
    p_ptr->log();
  }
}

template <typename T>
class ADDRESS_TYPE
{
  template <typename T2>
  friend boolean operator==(null_type, const ADDRESS_TYPE<T2>& right_val);
  template <typename T2>
  friend boolean operator!=(null_type, const ADDRESS_TYPE<T2>& right_val);
  template <typename T2>
  friend boolean operator==(const T2& left_val, const ADDRESS_TYPE<T2>& right_val);
  template <typename T2>
  friend boolean operator!=(const T2& left_val, const ADDRESS_TYPE<T2>& right_val);
private:
  T* ptr; // NULL if it's a null address value
public:
  
  ADDRESS_TYPE(): ptr(new T()) {} // constructor for unbound value

  ADDRESS_TYPE(null_type): ptr(NULL) {} // constructor for null address value

  ADDRESS_TYPE(const T& p_val): ptr(new T(p_val)) {} // constructor for actual value

  ADDRESS_TYPE(const ADDRESS_TYPE<T>& p_other) // copy constructor
  : ptr(p_other.ptr != NULL ? new T(*p_other.ptr) : NULL) {}

  void clean_up() {
    if (ptr != NULL) {
      delete ptr;
      ptr = NULL;
    }
  }

  ~ADDRESS_TYPE() {
    clean_up();
  }

  ADDRESS_TYPE& operator=(null_type) { // assignment operator for null address value
    clean_up();
    return *this;
  }

  ADDRESS_TYPE& operator=(const T& p_val) { // assignment operator for actual value
    clean_up();
    ptr = new T(p_val);
    return *this;
  }

  ADDRESS_TYPE& operator=(const ADDRESS_TYPE<T>& p_other) { // assignment operator for another address value
    clean_up();
    ptr = p_other.ptr != NULL ? new T(*p_other.ptr) : NULL;
    return *this;
  }

  boolean operator==(null_type) const { // equality operator (with null value)
    return ptr == NULL;
  }

  boolean operator!=(null_type) const { // inequality operator (with null value)
    return ptr != NULL;
  }
  
  boolean operator==(const ADDRESS_TYPE<T>& p_other) const { // equality operator (with another address value)
    return ptr != NULL && p_other.ptr != NULL && *ptr == *p_other.ptr;
  }

  boolean operator!=(const ADDRESS_TYPE<T>& p_other) const { // inequality operator (with another address value)
    return ptr == NULL || p_other.ptr == NULL || *ptr != *p_other.ptr;
  }
  
  boolean operator==(const T& p_val) const { // equality operator (with actual value)
    return ptr != NULL && *ptr == p_val;
  }

  boolean operator!=(const T& p_val) const { // inequality operator (with actual value)
    return ptr == NULL || *ptr != p_val;
  }

  T& operator()() const { // all access to the actual address value is done through this operator
    if (ptr == NULL) {
      TTCN_error("Accessing a null address value.");
    }
    return *ptr;
  }

  void set_address(const T* p_ptr) { // setter used by port opertations
    clean_up();
    ptr = p_ptr != NULL ? new T(*p_ptr) : NULL;
  }

  T* get_address() const { // getter used by address templates and port operations
    return ptr;
  }
  
  void log() const {
    log_address_ptr(ptr);
  }

  boolean is_bound() const {
    return ptr != NULL && ptr->is_bound();
  }

  boolean is_value() const {
    return ptr == NULL || ptr->is_value();
  }

  boolean is_present() const {
    return ptr != NULL && ptr->is_present();
  }

  void set_param(Module_Param& param) {
    Module_Param_Ptr mp = &param;
#ifdef TITAN_RUNTIME_2
    if (param.get_type() == Module_Param::MP_Reference) {
      mp = param.get_referenced_param();
    }
#endif
    switch (mp->get_type()) {
    case Module_Param::MP_Ttcn_Null:
      clean_up();
      break;
    default:
      ptr->set_param(param);
      break;
    }
  }

#ifdef TITAN_RUNTIME_2
  Module_Param* get_param(Module_Param_Name& param_name) const
  {
    if (ptr == NULL) {
      return new Module_Param_Ttcn_Null();
    }
    return ptr->get_param(param_name);
  }

  void set_err_descr(Erroneous_descriptor_t* p_err_descr) {
    if (ptr == NULL) {
      TTCN_error("Erroneous attributes cannot be applied to a null address value");
    }
    return ptr->set_err_descr(p_err_descr);
  }
#endif

  void encode_text(Text_Buf& text_buf) const {
    if (ptr != NULL) {
      if (!ptr->is_bound()) {
        TTCN_error("Text encoder: Encoding an unbound address value.");
      }
      text_buf.push_int(1);
      ptr->encode_text(text_buf);
    }
    else {
      text_buf.push_int(0);
    }
  }

  void decode_text(Text_Buf& text_buf) {
    clean_up();
    if (text_buf.pull_int().get_val() != 0) {
      ptr = new T;
      ptr->decode_text(text_buf);
    }
  }
};

template<typename T>
boolean operator==(null_type, const ADDRESS_TYPE<T>& right_val) { // equality operator (with null address value, inverted)
  return right_val.ptr == NULL;
}

template<typename T>
boolean operator!=(null_type, const ADDRESS_TYPE<T>& right_val) { // inequality operator (with null address value, inverted)
  return right_val.ptr != NULL;
}

template<typename T>
boolean operator==(const T& left_val, const ADDRESS_TYPE<T>& right_val) { // equality operator (with actual value, inverted)
  return right_val.ptr != NULL && *right_val.ptr == left_val;
}

template<typename T>
boolean operator!=(const T& left_val, const ADDRESS_TYPE<T>& right_val) { // inequality operator (with actual value, inverted)
  return right_val.ptr == NULL || *right_val.ptr != left_val;
}

template<typename T_template, typename T_value>
class ADDRESS_TEMPLATE
{
private:
  T_template* ptr; // NULL if it's a null address value
  
public:
  
  ADDRESS_TEMPLATE(): ptr(new T_template()) {} // constructor for uninitialized template

  ADDRESS_TEMPLATE(null_type): ptr(NULL) {} // constructor for null address value

  ADDRESS_TEMPLATE(const T_template& p_templ): ptr(new T_template(p_templ)) {} // constructor for actual template

  ADDRESS_TEMPLATE(const ADDRESS_TYPE<T_value> p_addr) { // constructor for address value
    T_value* addr_ptr = p_addr.get_address();
    ptr = addr_ptr != NULL ? new T_template(*addr_ptr) : NULL;
  }

  ADDRESS_TEMPLATE(const ADDRESS_TEMPLATE<T_template, T_value>& p_other) // copy constructor
  : ptr(p_other.ptr != NULL ? new T_template(*p_other.ptr) : NULL) {}

  void clean_up() {
    if (ptr != NULL) {
      delete ptr;
      ptr = NULL;
    }
  }

  ~ADDRESS_TEMPLATE() {
    clean_up();
  }

  ADDRESS_TEMPLATE& operator=(null_type) { // assignment operator for null address value
    clean_up();
    return *this;
  }

  ADDRESS_TEMPLATE& operator=(const T_template& p_templ) { // assignment operator for actual template
    clean_up();
    ptr = new T_template(p_templ);
    return *this;
  }

  ADDRESS_TEMPLATE& operator=(const ADDRESS_TEMPLATE<T_template, T_value>& p_other) { // assignment operator for another address template
    clean_up();
    ptr = p_other.ptr != NULL ? new T_template(*p_other.ptr) : NULL;
    return *this;
  }

  ADDRESS_TEMPLATE& operator=(const ADDRESS_TYPE<T_value>& p_addr) { // assignment operator for address value
    clean_up();
    T_value* addr_ptr = p_addr.get_address();
    ptr = addr_ptr != NULL ? new T_template(*addr_ptr) : NULL;
    return *this;
  }

  T_template& operator()() const { // all access to the actual address template is done through this operator
    if (ptr == NULL) {
      TTCN_error("Accessing a null address template.");
    }
    return *ptr;
  }
  
  void log() const {
    log_address_ptr(ptr);
  }

  boolean is_bound() const {
    return ptr != NULL && ptr->is_bound();
  }

  boolean is_value() const {
    return ptr == NULL || ptr->is_value();
  }

  boolean is_present() const {
    return ptr != NULL && ptr->is_present();
  }

  ADDRESS_TYPE<T_value> valueof() const {
    if (ptr == NULL) {
      return ADDRESS_TYPE<T_value>(NULL_VALUE);
    }
    return ADDRESS_TYPE<T_value>(ptr->valueof());
  }

  boolean match_address(const T_value* p_ptr, boolean legacy = FALSE) const {
    if (p_ptr == NULL && this->ptr == NULL) {
      return TRUE;
    }
    if (p_ptr == NULL || this->ptr == NULL) {
      return FALSE;
    }
    return this->ptr->match(*p_ptr, legacy);
  }

  void log_match_address(const T_value* p_ptr, boolean legacy = FALSE) const {
    if (TTCN_Logger::VERBOSITY_COMPACT == TTCN_Logger::get_matching_verbosity() &&
        TTCN_Logger::get_logmatch_buffer_len() != 0) {
      TTCN_Logger::print_logmatch_buffer();
      TTCN_Logger::log_event_str(" := ");
    }
    log_address_ptr(p_ptr);
    TTCN_Logger::log_event_str(" with ");
    this->log();
    TTCN_Logger::log_event_str(match_address(p_ptr, legacy) ? " matched" : " unmatched");
  }

  void set_param(Module_Param& param) {
    Module_Param_Ptr mp = &param;
#ifdef TITAN_RUNTIME_2
    if (param.get_type() == Module_Param::MP_Reference) {
      mp = param.get_referenced_param();
    }
#endif
    switch (mp->get_type()) {
    case Module_Param::MP_Ttcn_Null:
      clean_up();
      break;
    default:
      ptr->set_param(param);
      break;
    }
  }

#ifdef TITAN_RUNTIME_2
  Module_Param* get_param(Module_Param_Name& param_name) const
  {
    if (ptr == NULL) {
      return new Module_Param_Ttcn_Null();
    }
    return ptr->get_param(param_name);
  }

  void set_err_descr(Erroneous_descriptor_t* p_err_descr) {
    if (ptr == NULL) {
      TTCN_error("Erroneous attributes cannot be applied to a null address template");
    }
    return ptr->set_err_descr(p_err_descr);
  }
#endif

  void encode_text(Text_Buf& text_buf) const {
    if (ptr != NULL) {
      text_buf.push_int(1);
      ptr->encode_text(text_buf);
    }
    else {
      text_buf.push_int(0);
    }
  }

  void decode_text(Text_Buf& text_buf) {
    clean_up();
    if (text_buf.pull_int().get_val() != 0) {
      ptr = new T_template;
      ptr->decode_text(text_buf);
    }
  }
};

#endif
