#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <chrono>

using namespace std; // Required for isnan()
#include "Port.hh"

#include "tcp_layer_factory.hh"

#include "loggers.hh"

tcp_layer::tcp_layer(const std::string & p_type, const std::string & param) :  SSL_Socket(), layer(p_type), PORT(p_type.c_str()), _params(), _client_id{-1}, _time_key("tcp_layer::Handle_Fd_Event_Readable"), _reconnect_on_send{false} {
  loggers::get_instance().log(">>> tcp_layer::tcp_layer: %s, %s", to_string().c_str(), param.c_str());
  // Setup parameters
  Params::convert(_params, param);
  _params.log();

  set_socket_debugging(false);
  Params::const_iterator it = _params.find(Params::debug);
  if (it == _params.cend()) {
    _params.insert(std::pair<std::string, std::string>(std::string("debug"), "0"));
  } else if (it->second.compare("1") == 0) {
	set_socket_debugging(true);
  }
  it = _params.find(Params::server);
  if (it == _params.cend()) {
    _params.insert(std::pair<std::string, std::string>(std::string("server"), "127.0.0.1"));
  }
  if (!parameter_set(Params::server.c_str(), _params[Params::server].c_str())) {
    loggers::get_instance().warning("tcp_layer::set_parameter: Unprocessed parameter: %s", Params::server.c_str());
  }
  it = _params.find(Params::port);
  if (it == _params.cend()) {
    _params.insert(std::pair<std::string, std::string>(std::string("port"), "80"));
  }
  if (!parameter_set(Params::port.c_str(), _params[Params::port].c_str())) {
    loggers::get_instance().warning("tcp_layer::set_parameter: Unprocessed parameter: %s", Params::server.c_str());
  }
  set_ssl_use_ssl(false);
  it = _params.find(Params::use_ssl);
  if (it == _params.cend()) {
    _params.insert(std::pair<std::string, std::string>(std::string("use_ssl"), "0"));
  } else if (it->second.compare("1") == 0) {
    set_ssl_use_ssl(true);
  }

  parameter_set(use_connection_ASPs_name(), "yes");
  parameter_set(server_backlog_name(), "1024");
  set_server_mode(false);
  set_ttcn_buffer_usercontrol(true);
  set_handle_half_close(true);

  map_user();

  if (!_reconnect_on_send) {
	  open_client_connection(_params[Params::server].c_str(), _params[Params::port].c_str(), NULL, NULL);
  }
}

tcp_layer::~tcp_layer() {
	  loggers::get_instance().log(">>> tcp_layer::~tcp_layer: %d", _client_id);
  if (_client_id != -1) {
    remove_client(_client_id);
  }

  unmap_user();
}

void tcp_layer::Handle_Fd_Event(int fd, boolean is_readable, boolean is_writable, boolean is_error)
{
  loggers::get_instance().log(">>> tcp_layer::Handle_Fd_Event: %d", fd);
  Handle_Socket_Event(fd, is_readable, is_writable, is_error);
  log_debug("<<< tcp_layer::Handle_Fd_Event");
}

void tcp_layer::Handle_Timeout(double time_since_last_call)
{
  loggers::get_instance().log(">>> tcp_layer::Handle_Timeout: %f", time_since_last_call);
  Handle_Timeout_Event(time_since_last_call);
  loggers::get_instance().log("<<< tcp_layer::Handle_Timeout");
}

void tcp_layer::send_data(OCTETSTRING& data, Params& params) {
  loggers::get_instance().log_msg(">>> tcp_layer::send_data: ", data);

  loggers::get_instance().log("tcp_layer::send_data: SSL mode: %x", get_ssl_use_ssl());

  send_outgoing(static_cast<const unsigned char*>(data), data.lengthof(), _client_id);
}

void tcp_layer::receive_data(OCTETSTRING& data, Params& params) {
  loggers::get_instance().log_msg(">>> tcp_layer::receive_data: ", data);

  receive_to_all_layers(data, params);
}

void tcp_layer::message_incoming(const unsigned char* message_buffer, int length, int client_id) {
  loggers::get_instance().log(">>> tcp_layer::message_incoming");
  loggers::get_instance().log_to_hexa("tcp_layer::message_incoming: ", message_buffer, length);


  float duration;
  loggers::get_instance().set_start_time(_time_key);
  OCTETSTRING data(length, message_buffer);
  Params params;
  this->receive_data(data, params); // TODO Check execution time for decoding operation
  loggers::get_instance().set_stop_time(_time_key, duration);
}

void tcp_layer::client_connection_opened(int p_client_id)
{
  loggers::get_instance().log(">>> tcp_layer::client_connection_opened: %d", p_client_id);
  _client_id = p_client_id;
}

bool tcp_layer::add_user_data(int p_client_id)
{
    loggers::get_instance().log(">>> tcp_layer::add_user_data: %d", p_client_id);
    if (_params[Params::use_ssl].compare("0") == 0) {
    	loggers::get_instance().log("tcp_layer::add_user_data: Non secured mode");
    	return Abstract_Socket::add_user_data(p_client_id);
    }
    loggers::get_instance().log("tcp_layer::add_user_data: SSL mode");
    return SSL_Socket::add_user_data(p_client_id);
}

int tcp_layer::send_message_on_fd(int p_client_id, const unsigned char * message_buffer, int length_of_message)
{
  loggers::get_instance().log(">>> tcp_layer::send_message_on_fd: %d", p_client_id);

    if(get_user_data(p_client_id))
    {
    	loggers::get_instance().log("tcp_layer::send_message_on_fd: SSL mode");
        return SSL_Socket::send_message_on_fd(p_client_id, message_buffer, length_of_message);
    }

    loggers::get_instance().log("tcp_layer::send_message_on_fd: Non secured mode");
    return Abstract_Socket::send_message_on_fd(p_client_id, message_buffer, length_of_message);
}

int tcp_layer::send_message_on_nonblocking_fd(int p_client_id, const unsigned char * message_buffer, int length_of_message)
{
	loggers::get_instance().log(">>> tcp_layer::send_message_on_nonblocking_fd: %d", p_client_id);

    if(get_user_data(p_client_id))
    {
    	loggers::get_instance().log("tcp_layer::send_message_on_nonblocking_fd: SSL mode");
        return SSL_Socket::send_message_on_nonblocking_fd(p_client_id, message_buffer, length_of_message);
    }

    loggers::get_instance().log("tcp_layer::send_message_on_nonblocking_fd: Non secured mode");
    return Abstract_Socket::send_message_on_nonblocking_fd(p_client_id, message_buffer, length_of_message);
}

int tcp_layer::receive_message_on_fd(int p_client_id)
{
	loggers::get_instance().log(">>> tcp_layer::receive_message_on_fd: %d", p_client_id);

    if(get_user_data(p_client_id)) {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
    	loggers::get_instance().log("tcp_layer::receive_message_on_fd: SSL mode");
        return SSL_Socket::receive_message_on_fd(p_client_id);
    }

    loggers::get_instance().log("tcp_layer::receive_message_on_fd: Non secured mode");
    return Abstract_Socket::receive_message_on_fd(p_client_id);
}

void tcp_layer::peer_disconnected(int p_client_id)
{
	loggers::get_instance().log(">>> tcp_layer::peer_disconnected: %d", p_client_id);

    Abstract_Socket::peer_disconnected(p_client_id);
	_client_id = -1;
}

tcp_layer_factory tcp_layer_factory::_f;

