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

#include <sys/ioctl.h>
 
#include "Port.hh"

#include "raw_socket_layer_factory.hh"

#include "loggers.hh"

raw_socket_layer::raw_socket_layer(const std::string& p_type, const std::string& param) : layer(p_type), PORT(p_type.c_str()), _params(), _socket(-1), _time_key("raw_socket_layer::Handle_Fd_Event_Readable"), _if_interface{0}, _if_mac_addr{0}, _mac_src(), _mac_bc(), _eth_type() {
  loggers::get_instance().log(">>> raw_socket_layer::raw_socket_layer: %s, %s", to_string().c_str(), param.c_str());
  // Setup parameters
  Params::convert(_params, param);
  //_params.log();
  // Prepare capture processing
  if ((_socket = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) < 0) {
    loggers::get_instance().error("raw_socket_layer::raw_socket_layer: Failed to create socket: %d", errno);
  }

  Params::const_iterator it = _params.find(Params::nic);
  if ((it != _params.end()) && !it->second.empty()) {
    // Get NIC idx from its name
    strcpy((char *)&_if_interface.ifr_name, it->second.c_str()); // FIXME check size IFNAMESIS
    if (ioctl(_socket, SIOCGIFINDEX, &_if_interface) < 0) {
      loggers::get_instance().error("raw_socket_layer::raw_socket_layer: Failed to fill if interface: %d", errno);
    }
    // Allow the socket to be reused
    int32_t sockopt;
    if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) < 0) {
      loggers::get_instance().error("raw_socket_layer::raw_socket_layer: Failed to set SO_REUSEADDR: %d", errno);
    }
    // Bind to the device to receive packet
    if (setsockopt(_socket, SOL_SOCKET, SO_BINDTODEVICE, (void *)&_if_interface, sizeof(struct ifreq)) < 0) {
      loggers::get_instance().error("raw_socket_layer::raw_socket_layer: Failed to bind socket: %d", errno);
    }
  } else {
    loggers::get_instance().error("raw_socket_layer::raw_socket_layer: Failed to open device");
  }
  
  // Setup filter
  std::string filter = "";
  it = _params.find(Params::mac_src);
  if (it == _params.end()) { // Not found
    loggers::get_instance().error("raw_socket_layer::raw_socket_layer: mac_src parameter not found, cannot continue");
  } else {
    std::transform(
                   _params[Params::mac_src].cbegin(),
                   _params[Params::mac_src].cend(),
                   std::back_inserter(_mac_src),
                   [](unsigned char c) -> unsigned char { return std::string("0123456789abcdef").find(std::tolower(c)); }
                   );
    loggers::get_instance().log_to_hexa("raw_socket_layer::raw_socket_layer: ", _mac_src.data(), _mac_src.size());
  }
  it = _params.find(Params::mac_bc);
  if (it == _params.end()) { // Not found
    loggers::get_instance().error("raw_socket_layer::raw_socket_layer: mac_bc parameter not found, cannot continue");
  } else {
    std::transform(
                   _params[Params::mac_src].cbegin(),
                   _params[Params::mac_src].cend(),
                   std::back_inserter(_mac_bc),
                   [](unsigned char c) -> unsigned char { return std::string("0123456789abcdef").find(std::tolower(c)); }
                   );
    loggers::get_instance().log_to_hexa("raw_socket_layer::raw_socket_layer: ", _mac_bc.data(), _mac_bc.size());
  }
  it = _params.find(Params::eth_type);
  if (it == _params.end()) { // Not found
    loggers::get_instance().error("raw_socket_layer::raw_socket_layer: eth_type parameter not found, cannot continue");
  } else {
    std::transform(
                   _params[Params::mac_src].cbegin(),
                   _params[Params::mac_src].cend(),
                   std::back_inserter(_eth_type),
                   [](unsigned char c) -> unsigned char { return std::string("0123456789abcdef").find(std::tolower(c)); }
                   );
    loggers::get_instance().log_to_hexa("raw_socket_layer::raw_socket_layer: ", _eth_type.data(), _eth_type.size());
  }
  // Pass the device file handler to the polling procedure
  Handler_Add_Fd_Read(_socket); // Live capture
} // End of ctor

raw_socket_layer::~raw_socket_layer() {
  loggers::get_instance().log(">>> raw_socket_layer::~raw_socket_layer");
  
  if (_socket != -1) {
    close(_socket);
  }
} // End of dtor

void raw_socket_layer::send_data(OCTETSTRING& data, Params& params) {
  loggers::get_instance().log_msg(">>> raw_socket_layer::send_data: ", data);
  
  // Setup 'from' parameter
  struct sockaddr_ll sa = { 0 };
  sa.sll_family = AF_INET;
  sa.sll_ifindex = _if_interface.ifr_ifindex;
  sa.sll_halen = ETH_ALEN;
  struct ether_header *eh = (struct ether_header *)(static_cast<const unsigned char *>(data));
  // Setup the destination MAC address
  sa.sll_addr[0] = eh->ether_dhost[0];
  sa.sll_addr[1] = eh->ether_dhost[1];
  sa.sll_addr[2] = eh->ether_dhost[2];
  sa.sll_addr[3] = eh->ether_dhost[3];
  sa.sll_addr[4] = eh->ether_dhost[4];
  sa.sll_addr[5] = eh->ether_dhost[5];
  // Do not owerwrite the source mac address
  
  // Send the data
  int result;
  do {
    result = sendto(_socket, static_cast<const unsigned char *>(data), data.lengthof(), 0, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll));
  } while ((result < 0) && (errno == EINTR));
  if (result < 0) {
    loggers::get_instance().error("raw_socket_layer::send_data: Failed to send: %d", errno);
  }

  return;
}

void raw_socket_layer::receive_data(OCTETSTRING& data, Params& params) {
  loggers::get_instance().log(">>> raw_socket_layer::receive_data: Received %d bytes", data.lengthof());
  loggers::get_instance().log_to_hexa("Packet dump", data);
  
  // Pass the packet to the upper layers
  receive_to_all_layers(data, params);
}

void raw_socket_layer::Handle_Fd_Event_Readable(int fd) {
  //loggers::get_instance().log(">>> raw_socket_layer::Handle_Fd_Event_Readable: %d", fd);

  int result;
  std::vector<unsigned char> buffer(8182);
  do {
    result = ::recvfrom(_socket, static_cast<void *>(buffer.data()), buffer.size(), 0, NULL, NULL);
  } while ((result < 0) && (errno == EINTR));
  if (result < 0) {
    loggers::get_instance().error("raw_socket_layer::Handle_Fd_Event_Readable: Failed to receive: %d", errno);
    return;
  }
  buffer.resize(result);
  // Set receive time
  Params params;
  unsigned long long ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
  // Apply filtering
  if (result < 14) {
    // Skip it
    return;
  } else {
    // Check source
    if (std::equal(buffer.cbegin() + 6, buffer.cbegin() + 12, _mac_src.data())) { // Source is me, skip it
      // Skip it
      return;
    } else {
      // Check destination
      if (
          !std::equal(buffer.cbegin(), buffer.cbegin() + 5, _mac_src.data()) &&
          !std::equal(buffer.cbegin(), buffer.cbegin() + 5, _mac_bc.data())
          ) {
        // Skip it
        return;
      } else {
        // Check Ether type
        if (!std::equal(buffer.cbegin() + 12, buffer.cbegin() + 13, _eth_type.data())) {
          // Skip it
          return;
        } else { 
          // Process the packet at this layer
          params.insert(std::pair<std::string, std::string>(std::string("timestamp"), std::to_string(ms)));
          OCTETSTRING os(buffer.size(), buffer.data());
          loggers::get_instance().log_to_hexa("raw_socket_layer::Handle_Fd_Event_Readable: ", os);
          float duration;
          loggers::get_instance().set_start_time(_time_key);
          this->receive_data(os, params); // TODO Check execution time for decoding operation
          loggers::get_instance().set_stop_time(_time_key, duration);
        }
      }
    }
  }
}

raw_socket_layer_factory raw_socket_layer_factory::_f;
