#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>       // for read...
#include <stdlib.h>       // for atoi...

#include "debug.h"
#include "ip.h"

const int BACK_LOG = 5;

int ip_init_server_conn(char *ip, int port) {
  char *ip_addr = NULL;
  int sSocket = 0, on = 0, rc = 0;
  struct sockaddr_in serverName = { 0 };
  char * tmp;

  LOG_ENTER();

  // Handle special case ":<port>"
  // Without it, ip_add will be set to port and port will be null

  if(ip != NULL) {
    if(ip[0] == ':') {
      ip = &ip[1];
    }
    if (strchr(ip, ':') != NULL) {
      ip_addr = strtok(ip, ":");
      tmp = strtok(NULL,":");
      if(tmp != NULL && strlen(tmp) > 0) {
        port = (atoi(tmp));
      }
    } else if(strlen(ip) > 0) {
      port = (atoi(ip));
    }
  }

  LOG(LOG_DEBUG, "Creating server socket");

  sSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (-1 == sSocket) {
    ELOG(LOG_FATAL, "Server socket could not be created");
  } else {

    /*
     * turn off bind address checking, and allow 
     * port numbers to be reused - otherwise
     * the TIME_WAIT phenomenon will prevent 
     * binding to these addresses.
     */

    on = 1;

    rc = setsockopt(sSocket, 
                    SOL_SOCKET, 
                    SO_REUSEADDR,
                    (const char *) &on, 
                    sizeof(on)
                   );
    if (-1 == rc) {
      ELOG(LOG_ERROR, "bind address checking could not be turned off");
    }

    if (ip_addr != NULL && strcmp(ip_addr,"") != 0) { /* changed to strcmp test -04Mar17 gwb */
      LOG(LOG_DEBUG, "Using specified ip address '%s'", ip_addr);
      serverName.sin_addr.s_addr = inet_addr(ip_addr);
    } else {
      serverName.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    serverName.sin_family = AF_INET;

    /* network-order */
    serverName.sin_port = htons(port);

    LOG(LOG_DEBUG, "Binding server socket to port %d", port);
    rc = bind(sSocket,
              (struct sockaddr *) &serverName,
              sizeof(serverName)
             );

    if (-1 == rc) {
      ELOG(LOG_FATAL, "Server socket could not be bound to port");
      sSocket = -1;
    } else {
      LOG(LOG_INFO, "Server socket bound to port");

      rc = listen(sSocket, BACK_LOG);
      LOG(LOG_INFO, "Server socket listening for connections");
      if (-1 == rc) {
        ELOG(LOG_FATAL, "Server socket could not listen on port");
        sSocket = -1;
      }
    }
  }
  LOG_EXIT();
  return sSocket;
}

int ip_connect(char *ip) {
  struct sockaddr_in pin;
  struct in_addr cin_addr;
  struct hostent *hp;
  int sd = 0;
  int port = 23;
  char *address;
  char *tmp;

  LOG_ENTER();
  // TODO Can ip be null? If so, fix.
  address = strtok(ip, ":");
  tmp = strtok(NULL, ":");
  if(tmp != NULL && strlen(tmp) > 0) {
    port = atoi(tmp);
  }

  LOG(LOG_DEBUG, "Calling %s", ip);
  memset(&pin, 0, sizeof(pin));

  /* go find out about the desired host machine */
  if((hp = gethostbyname(address)) == 0) {
    // well, not a DNS entry... Treat as IP...
    if(1 != inet_aton(address, &cin_addr)) {
      ELOG(LOG_ERROR, "Host %s was invalid", ip);
      return -1;
    }
  } else {
    cin_addr = *((struct in_addr *)(hp->h_addr));
  }

  pin.sin_family = AF_INET;
  pin.sin_addr.s_addr = cin_addr.s_addr;
  pin.sin_port = htons(port);

  /* grab an Internet domain socket */
  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    ELOG(LOG_ERROR, "could not create client socket");
    return -1;
  }

  /* connect to PORT on HOST */
  if (connect(sd, (struct sockaddr *)&pin, sizeof(pin)) == -1) {
    ELOG(LOG_ERROR, "could not connect to address");
    return -1;
  }
  LOG(LOG_INFO, "Connection to %s established", ip);
  LOG_EXIT();
  return sd;
}

int ip_accept(int sSocket) {
  struct sockaddr_in clientName = { 0 };
  socklen_t clientLength = sizeof(clientName);
  int cSocket = -1;

  LOG_ENTER();
  (void) memset(&clientName, 0, sizeof(clientName));

  cSocket = accept(sSocket,
                   (struct sockaddr *)&clientName, 
                   &clientLength
                  );
  if (-1 == cSocket) {
    ELOG(LOG_ERROR, "Could not accept incoming connection");
    return -1;
  }

  if(-1 == getpeername(cSocket,
                        (struct sockaddr *)&clientName, 
                        &clientLength
                       )) {
    ELOG(LOG_WARN, "Could not obtain peer name");
  } else {
    LOG(LOG_INFO, 
        "Connection accepted from %s",
        inet_ntoa(clientName.sin_addr)
       );
  }
  LOG_EXIT();
  return cSocket;
}

int ip_disconnect(int fd) {
  if(fd > -1)
    close(fd);
  return 0;
}

int ip_write(int fd, unsigned char *data, int len) {
  log_trace(TRACE_IP_OUT, data, len);
  return write(fd, data, len);
}

int ip_read(int fd, unsigned char *data, int len) {
  int res;

  res = recv(fd, data, len, 0);
  if(0 < res)
  	log_trace(TRACE_IP_IN, data, res);
  return res;
}
