/*
 * Copyright (C) 2008 The Trustees of Columbia University in the City of New York. 
 * All rights reserved.
 *
 * See the file LICENSE included in this distribution for details.
 */

#include <stdlib.h>


#include "transport.h"
#include "socketi.h"

#ifndef _WENGO
#include "trace.h"
#include "msgheader.h"
#endif

#ifdef _WENGO
#include "p2pp/common/trace.h"
#include "p2pp/msg/msgheader.h"
#endif

#include <map>
using namespace std;

#ifdef _LINUX_
#include <sys/socket.h> //socket
#include <sys/types.h>  //socket
#include <sys/socket.h>

#include <netinet/in.h> //hton, ntoh
#include <arpa/inet.h>  //inet_addr

#include <linux/sockios.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include <netdb.h>

#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>

#include <linux/sockios.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>

#include <fcntl.h>


#endif

#ifdef WIN32
#include <winsock.h>
//#include <ws2tcpip.h>
#endif

//this structure contains values for program
//and machine bw, cpuutil, and memory. if the
//appropriate values are set, then actual bandwidth/cpu/mem
//is not calculated. rather the set bw/cpuutil/mem is used.
extern struct nodeval g_stnodeval;

Transport::Transport() {
  //we are in connected state
  isConnected=0;
  m_Listen=0;

#ifdef WIN32
  WORD wVersionRequested;
  WSADATA wsaData;
  int err;

  wVersionRequested = MAKEWORD( 2, 2 ); 
  err = WSAStartup( wVersionRequested, &wsaData );
  if ( err != 0 ) {

    TRACE(-1, "Cannot start Windows sockets\n", "");
  }
#endif

}//Transport


Transport::~Transport() {
//FIXME
	//close WSA for windows
}//~Transport


void Transport::setNode(Node* pobjNode) {
  m_pobjNode = pobjNode;
}//setNode

void Transport::build_select_list() {

  map<string, int32>::iterator itr;

  FD_ZERO(&sockfds);
  FD_SET(m_iTcpSock, &sockfds);
  FD_SET(m_iUdpSock, &sockfds);

  sockmax = m_iUdpSock;

  if(m_iTcpSock > m_iUdpSock) {
    sockmax = m_iTcpSock;
  }

  //The socket map
  for(itr=m_SL.begin(); itr!=m_SL.end(); itr++) {
    //update the max sock structure
    if(itr->second > sockmax) {
      sockmax = itr->second;
    }
    
    FD_SET(itr->second, &sockfds);
  }//end for
  

  //The sockets that were created
  for(itr=m_nSL.begin(); itr!=m_nSL.end(); itr++) {
    //update the max sock structure
    if(itr->second > sockmax) {
      sockmax = itr->second;
    }
    
    FD_SET(itr->second, &sockfds);

    //update the main socket list
    m_SL[itr->first] = itr->second;

  }//end for

  //empty the nSL list
  m_nSL.clear();

  sockmax +=1;
  
  //TRACE(5, "Transport::build_select_list...building select list complete\n", "");

}//build_select_list



void Transport::handle_new_connection() {
  int connection;
  char arr[30];
  char temp[10];
  string str;
  struct sockaddr_in staddr;
  int32 len;

  connection = accept(m_iTcpSock, NULL, NULL);

  //If cannot accept
  if(connection < 0) {
    TRACE(-1, "accept\n", "");
    return;
  }
  
  //set nonblocking
  setBlocking(connection, 1);

  len=sizeof(staddr);
  getpeername(connection, (struct sockaddr*) &staddr, (socklen_t*)&len);

  //
  strncpy(arr, inet_ntoa(staddr.sin_addr), 30);
  sprintf(temp, "%d", htons(staddr.sin_port));
  str = arr;
  str += ":";
  str += temp;
  
  TRACE(3, "Transport::handle_new_connection...%s\n", (char*)str.c_str());

  m_nSL[str] = connection;

}//handle_new_connection


void Transport::read_socks() {

  map<string, int32>::iterator itr, itr1;
  int ret;
  int pkt_len=65000;

  int index;
  string str1, str2, str;
  
  struct sockaddr_in staddr;
  int addrlen;

  unsigned int msglen, qlen;

  //TRACE(5, "Transport::read_socks...Begins\n", "");

  //Check if there is a new connection
  if(FD_ISSET(m_iTcpSock, &sockfds)) {
    TRACE(5, "Transport::read_socks...a new TCP connection\n", "");
    handle_new_connection();
  }


  //Check if there is data on UDP socket
  //TRACE(4, "Transport::read_socks...FD_ISSET(UDP)\n", "");

  if(FD_ISSET(m_iUdpSock, &sockfds)) {

    //len
    addrlen=sizeof(staddr);

    //receive the data
    ret = recvfrom(m_iUdpSock, (char*)m_buf, 1500,  0, 
                   (struct sockaddr*) &staddr, (socklen_t*)&addrlen);


    //bandwidth calculation
    if(ret != -1) {
      m_curR += ret;
    }

    str=inet_ntoa(staddr.sin_addr);
    int32 udpport = htons(staddr.sin_port);


    TRACE(2, "Transport::read_socks...calling OnReceive\n", "");
    if(m_pobjNode->OnReceive(m_buf, ret, (char*)str.c_str(), udpport, eUDP)==0) {
      m_Listen=0;
      return;
    }
  }


  
  //Check all TCP sockets
  //TRACE(4, "Transport::read_socks...FD_ISSET(TCP): %d\n", m_SL.size() );

  for(itr=m_SL.begin(); itr!=m_SL.end(); itr++) {

    TRACE(2, "Transport::read_socks...calling OnReceive for TCP\n", "");
    if(FD_ISSET(itr->second, &sockfds)) {

      //read the number of bytes in the queue without copying data
      ret = recv(itr->second, (char*)m_buf, 16, MSG_PEEK);

      //if the connection was closed mark this connection in the
      //remove connection list      
      if(0 == ret || -1 == ret) {
        m_rSL[itr->first] = itr->second;
	TRACE(2, "Transport::read_socks...OnReceive...closing connection\n", "");
        close(itr->second);
        continue;
      }

      //new code
      if(ret < 16) {
	continue;
      }
      msglen = ntohl( *((uint32*)((char*)m_buf+12)) );
      getsockopt_tcp(itr->second, S_RQ, &qlen, NULL);
      if (qlen < msglen) {
	//printf("waiting for message to arrive TCP msglen: %u rcv len: %d\n", msglen, ret);
	continue;
      }

      //seperate ip and port
      index=itr->first.find(':');
      str1=itr->first.substr(0, index);
      str2=itr->first.substr(index+1, itr->first.size());
      
      //now actually receive the data
      pkt_len=65000;
      ret = recv(itr->second, (char*)m_buf, pkt_len, 0);

      //call the onreceive function
      if( m_pobjNode->OnReceive(m_buf, ret, (char*)str1.c_str(), atoi((char*)str2.c_str()), eTCP )==0) {
	m_Listen=0;
	return;
      }

      //TRACE(2, "Transport::read_socks AFTER TCP OnReceive\n", "");
    }
  }
  
  
  
  //now remove the closed connections from the map
  for(itr=m_rSL.begin(); itr!=m_rSL.end(); itr++) {
    itr1 = m_SL.find(itr->first);

    if(itr1 != m_SL.end() ) {
      printf("destroyed tcp connection\n");
      m_SL.erase(itr1);
    }
  }

}//read_sock



void Transport::Initialize(int32 iTcpPort, int32 iUdpPort) {

  //fd_set writefd, errorfd;
  int ret;
  struct sockaddr_in sttaddr, stuaddr;
  int len;

  if(!isConnected){

  //we are in connected state
  isConnected=1;

  //create listener sockets
  m_iTcpSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  m_iUdpSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

  
  //bind them to ports
  if(-1 == bindi(m_iTcpSock, iTcpPort) ) {
    TRACE(-1, "-1: Transport::Initialize...Error in TCP bind\n", "");
    iTcpPort=0;
    bindi(m_iTcpSock, iTcpPort);
  }
  if(-1 == bindi(m_iUdpSock, iUdpPort) ) {
    TRACE(-1, "-1: Transport::Initialize...Error in UDP bind\n", "");
    iUdpPort=0;
    bindi(m_iUdpSock, iUdpPort);
  }
  

  len=sizeof(sttaddr);
  getsockname(m_iTcpSock, (struct sockaddr*) &sttaddr, (socklen_t*)&len);
  len=sizeof(sttaddr);
  getsockname(m_iUdpSock, (struct sockaddr*) &stuaddr, (socklen_t*)&len);


  //set the ports as member variables
  m_uiTcpPort = ntohs(sttaddr.sin_port);
  m_uiUdpPort = ntohs(stuaddr.sin_port);

  
  TRACE(3, "3: TCP port: %d UDP port: %d\n", ntohs(sttaddr.sin_port), ntohs(stuaddr.sin_port));

  //TCP port listen
  ret = listen(m_iTcpSock, 10);

  
  //timer for calculating send and receive bandwidth
  gettimeofday(&m_timeSR, NULL);
  m_timeSR = addtimeval(m_timeSR, 5, 0);
  
  m_curS = m_prevS = 0;
  m_curR = m_prevR = 0;

  }//if(!isConnected)

  ListenLoop();
}//Initialize

void Transport::ListenLoop() {

  int readsocks=0;
  struct timeval timeout;
  struct timeval cur_time, diff_time;
  //timeout value
  timeout.tv_sec=timeout.tv_usec=0;

  m_Listen=1;
  //The while loop
  while(m_Listen) {

    if(0 == timeout.tv_sec && 0 == timeout.tv_usec) {
      TRACE(5, "Transport::Initialize...Setting timer \n", "");
      timeout.tv_sec=0;
      timeout.tv_usec=TIMEOUT;
    }

    //build the select list
    build_select_list();

    //TRACE(5, "Transport::Initialize...before select \n", "");
    readsocks = select(sockmax, &sockfds, (fd_set*)0, (fd_set*)0, &timeout);
    //TRACE(5, "Transport::Initialize...after select \n", "");
    //readsocks = select(NULL, NULL, NULL, NULL, &timeout);

#ifdef WIN32
    if(0 == readsocks) {
      timeout.tv_sec = timeout.tv_usec = 0;
    }
#endif    

    if(0 == timeout.tv_sec && 0 == timeout.tv_usec) {
      TRACE(5, "Transport::Initialize...before...rOnTimer\n", "");
      m_pobjNode->OnTimer();
      TRACE(5, "Transport::Initialize...after...OnTimer \n", "");
    }     

    
    //check state and break if disconnected
    if(0 == isConnected) {
      TRACE(1, "Transport::Initialize...Disconnecting\n", "");
      break;
    }


    //the bandwidth 
    gettimeofday(&cur_time, NULL);
    diff_time = difftimeval(m_timeSR, cur_time);

    //divide by 5 because we are averaging over 5 seconds
    if(0 == diff_time.tv_sec && 0 == diff_time.tv_usec) {
      m_prevS = (uint32) ((0.8) * (m_curS/5.0)) + (uint32) ((0.2) * m_prevS);
      m_prevR = (uint32) ((0.8) * (m_curR/5.0)) + (uint32) ((0.2) * m_prevR);

      //Initialize current
      m_curS = m_curR = 0;

      //update the bandwidth time
      m_timeSR = addtimeval(cur_time, 5, 0);
    }
    
    read_socks();
    //printf("sec: %ld OPlsec: %ld\n", timeout.tv_sec, timeout.tv_usec);
  }
  
}//ListenLoop



void Transport::Disconnect() {
  map<string, int32>::iterator itr;

  //Disconnect immediately
  isConnected = 0;
  
  //Close the UDP and TCP sockets
  close(m_iUdpSock);
  close(m_iTcpSock);

  
  //Check all TCP sockets
  for(itr=m_SL.begin(); itr!=m_SL.end(); itr++) {
    close(itr->second);
  }
  
}//Disconnect



int32 Transport::sockcon(char* pchIP, int32 port) {
  int sockfd;
  struct sockaddr_in dest_addr;
  string str;
  char temp[10];
  int itport=0;

  //create socket
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  
  //bind it to any port
  bindi(sockfd, itport);
  
  dest_addr.sin_family = AF_INET;          // host byte order
  dest_addr.sin_port = htons(port);   // short, network byte order
  dest_addr.sin_addr.s_addr = inet_addr(pchIP);
  memset(&(dest_addr.sin_zero), '\0', 8);  // zero the rest of the struct

  if(0 != connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) ) {
    TRACE(-1, "Transport::handle_sockcon...ERROR...ip: %s port: %d\n", pchIP, port);
    return -1;
  }

  //set the socket to non-blocking
  setBlocking(sockfd, 1);

  //update in the local map
  sprintf(temp, "%d", port);
  str=pchIP;
  str+=":";
  str+=temp;  
  
  m_nSL[str]=sockfd;
  
  return sockfd;
}//sockcon


int32 Transport::Send(uchar* buf, uint32 len, char* ip, int32 port, transtype eType) {

  char temp[10];
  string str;
  map<string, int32>::iterator itr;
  int32 sockfd;
  int nbytes=0;


  //create the index for the map
  sprintf(temp, "%d", port);
  str=ip;
  str+=":";
  str+=temp;

  
  switch(eType) {

  case eUDP:
    TRACE(3,"Transport::Send IP: %s PORT: %d UDP\n", ip, port);
    nbytes = sendtoi(m_iUdpSock, (char*)buf, len, ip, port);
    break;

  case eTCP:
    TRACE(3,"Transport::Send IP: %s PORT: %d TCP\n", ip, port);
    itr=m_SL.find(str);

    //use existing connection
    if(itr!=m_SL.end()) {
      nbytes = send(itr->second, (const char*)buf, len, 0);
      
    } else {
      //we have to open a new connection
      sockfd=sockcon(ip, port);

      //printf("opening a connection\n");
      if(-1 == sockfd) {
        return -1;
      }
      nbytes = send(sockfd, (const char*)buf, len, 0);
    }
    
    break;

  default:
    break;
  }
  
  //bandwidth calculation
  if(nbytes != -1) {
    m_curS += nbytes;    
  }
  
  return 0;
}//Transport::Send


int32 Transport::getAddress(int32& num, char8 addr[][IPADDRLEN], uint16 port[], uchar ht[], uchar tt[]) {

  char name[255];
  struct hostent* hostinfo;
  char* ip;
  int i=0;
  
  if( gethostname ( name, sizeof(name)) == 0) {
    //printf("Host name: %s\n", name);
    
    if((hostinfo = ::gethostbyname(name)) != NULL) {
      int nCount = 0;

      while(hostinfo->h_addr_list[nCount]) {

        ip = inet_ntoa(*(struct in_addr *)hostinfo->h_addr_list[nCount]);
        //printf("IP #%d: %s\n", nCount, ip);

        if(m_uiTcpPort == m_uiUdpPort) {
          strncpy( addr[i], ip, 16);          
          port[i] = (uint16)m_uiTcpPort;
          ht[i]=eHOST;
          tt[i]=eUDP|eTCP;

          i+=1;

        } else {
          strncpy( addr[i], ip, 16);
          port[i] = (uint16)m_uiTcpPort;
          ht[i]=eHOST;
          tt[i]=eTCP;
          
          //printf("address is: %s i: %d\n", ip, i);
          
          strncpy( addr[i+1], ip, 16);
          port[i+1] = (uint16)m_uiUdpPort;
          ht[i+1]=eHOST;
          tt[i+1]=eUDP;

          i+=2;          
        }

        nCount++;

      }//end while
    }
  }

  //the total number of IP addresses
  num=i;

  return num;

}//getAddress



int32 Transport::getAddrPort(map<string, port_trans>& addrmap) {

  char* ip;
  string str;
  struct port_trans pt;


#ifndef _LINUX_
  char name[255];
  struct hostent* hostinfo;
  
  if( gethostname ( name, sizeof(name)) == 0) {
    
    if((hostinfo = ::gethostbyname(name)) != NULL) {
      int nCount = 0;

      while(hostinfo->h_addr_list[nCount]) {

        ip = inet_ntoa(*(struct in_addr *)hostinfo->h_addr_list[nCount]);        
        str=ip;
        
        if(m_uiTcpPort == m_uiUdpPort) {          
          pt.port = m_uiTcpPort;
          pt.tt = eTCP;
          pt.ht = eHOST;
          
          addrmap[str] = pt;

        } else {
          pt.port = m_uiTcpPort;
          pt.tt = eTCP;
          pt.ht = eHOST;
          addrmap[str] = pt;
          
          pt.port = m_uiUdpPort;
          pt.tt = eUDP;
          pt.ht = eHOST;
          
          addrmap[str] = pt;          
        }

        nCount++;

      }//end while
    }
  }

#endif

#ifdef _LINUX_
  char          buf[1024];
  struct ifconf ifc;
  struct ifreq *ifr;
  int           sck;
  int           nInterfaces;
  int           i;

	
  /* Get a socket handle. */
  sck = socket(AF_INET, SOCK_DGRAM, 0);
  if(sck < 0) {
    perror("socket");
    return 1;
  }

  /* Query available interfaces. */
  ifc.ifc_len = sizeof(buf);
  ifc.ifc_buf = buf;
  if(ioctl(sck, SIOCGIFCONF, &ifc) < 0) {
    perror("ioctl(SIOCGIFCONF)");
    return 1;
  }

  /* Iterate through the list of interfaces. */
  ifr         = ifc.ifc_req;
  nInterfaces = ifc.ifc_len / sizeof(struct ifreq);


  for(i = 0; i < nInterfaces; i++) {
    struct ifreq *item = &ifr[i];

    ip = inet_ntoa(((struct sockaddr_in *)&item->ifr_addr)->sin_addr);
    str = ip;

    if(0 != strcmp("127.0.0.1", ip)) {

      if(m_uiTcpPort == m_uiUdpPort) {
	pt.port = m_uiTcpPort;
	pt.tt = eTCP;
	pt.ht = eHOST;
	
	addrmap[str] = pt;
	
      } else {
	pt.port = m_uiTcpPort;
	pt.tt = eTCP;
	pt.ht = eHOST;
	addrmap[str] = pt;
          
	pt.port = m_uiUdpPort;
	pt.tt = eUDP;
	pt.ht = eHOST;
	
	addrmap[str] = pt;          
      }      

      /* Show the device name and IP address */
      //printf("%s: IP %s", item->ifr_name, ip);
      break;
    }
  }

  close(sck);

#endif


  return 1;
}//getAddrPort


void Transport::getSendRecvBW(uint32& sbw, uint32& rbw) {
  sbw = m_prevS;
  rbw = m_prevR;
}//getSendRecvBW


int32 Transport::gethostbyname(char* name, string& strIP) {

  struct hostent* hostinfo;
  
  if((hostinfo = ::gethostbyname(name)) != NULL) {
    int nCount = 0;

    if(hostinfo->h_addr_list[nCount]) {
      strIP = inet_ntoa(*(struct in_addr *)hostinfo->h_addr_list[nCount]);
      return 1;
    } else {
      return 0;
    }  
  }

  return 0;
}//gethostbyname


int32 Transport::gethostbyaddr(char* ipstr, string& strHostName) {

  struct hostent* hostinfo;
  struct in_addr ip;

  if (!inet_aton(ipstr, &ip)) 
    return 0;

#ifdef _LINUX_
  if((hostinfo = ::gethostbyaddr( (const void*)&ip, sizeof(ip), AF_INET)) !=NULL) {
    strHostName = hostinfo->h_name;
    return 1;
  }
#endif


#ifdef WIN32
    if((hostinfo = ::gethostbyaddr( (const char*)ipstr, sizeof(ipstr), AF_INET)) !=NULL) {
      strHostName=hostinfo->h_name;
      return 1;
    }
    
    //code copied from MSDN example
    // Declare and initialize variables
/*    int iResult;
    DWORD dwRetval;

    struct sockaddr_in saGNI;
    char hostname[NI_MAXHOST];
    char servInfo[NI_MAXSERV];
    u_short port = 0;

    //-----------------------------------------
    // Set up sockaddr_in structure which is passed
    // to the getnameinfo function
    saGNI.sin_family = AF_INET;
    saGNI.sin_addr.s_addr = inet_addr(argv[1]);
    saGNI.sin_port = htons(port);

    //-----------------------------------------
    // Call getnameinfo
    dwRetval = getnameinfo((struct sockaddr *) &saGNI,
                           sizeof (struct sockaddr),
                           hostname,
                           NI_MAXHOST, servInfo, 
                           NI_MAXSERV, NI_NUMERICSERV);

    //FAILED
    if (dwRetval != 0) {
      //printf("getnameinfo failed with error # %ld\n", WSAGetLastError());
      return 0;
    } else {
      //printf("getnameinfo returned hostname = %s\n", hostname);
      strHostName=hostname;
      return 1;
    }
    */
#endif  

  return 0;
  
}//gethostbyaddr



int Transport::getsockopt_tcp(int sockid, enum SOCK_OPTIONS options, void* optval, int* optlen) {

  int ret=0;
  struct _tcp_info* ptcp;

#ifdef _LINUX_
  struct tcp_info stcp;
  socklen_t len = sizeof(stcp);
#endif

  switch(options) {

  case S_TCP_NODELAY:
#ifdef _LINUX_
    ret = ::getsockopt(sockid, IPPROTO_TCP, TCP_NODELAY, optval, (socklen_t*)optlen);
#endif

#ifdef WIN32
    ret = ::getsockopt(sockid, IPPROTO_TCP, TCP_NODELAY, (char*)optval, (socklen_t*)optlen);
#else
    *(int*)(optval)=1;
#endif
    break;

  case S_TCP_QUICKACK:
#ifdef _LINUX_
    ret = ::getsockopt(sockid, IPPROTO_TCP, TCP_QUICKACK, optval, (socklen_t*)optlen);
#else
    *(int*)(optval)=1;
#endif
    break;

  case S_TCP_INFO:
#ifdef _LINUX_
    ret = ::getsockopt(sockid, IPPROTO_TCP, TCP_INFO, &stcp, &len);
    if(0 == ret) {
      ptcp = (struct _tcp_info*)optval;
      ptcp->tcpi_snd_cwnd = stcp.tcpi_snd_cwnd;
      ptcp->tcpi_snd_ssthresh = stcp.tcpi_snd_ssthresh;
      ptcp->tcpi_ca_state = stcp.tcpi_ca_state;
      ptcp->tcpi_snd_mss = stcp.tcpi_snd_mss;
      ptcp->tcpi_rcv_mss = stcp.tcpi_rcv_mss;
      ptcp->tcpi_lost = stcp.tcpi_lost;
      ptcp->tcpi_total_retrans = stcp.tcpi_retrans;
      ptcp->tcpi_rtt = stcp.tcpi_rtt;
      //ptcp->tcpi_rcv_rtt = stcp.tcpi_rcv_rtt;
    }
#endif
    break;


  case S_RCVBUF:
#ifdef _LINUX_
    ret=::getsockopt(sockid, SOL_SOCKET,SO_RCVBUF, optval, (socklen_t*)optlen);
#else
    ret=::getsockopt(sockid, SOL_SOCKET,SO_RCVBUF, (char*)optval, (socklen_t*)optlen);
#endif
    break;

  case S_WQ:
    //get the write buffer queue size
#ifdef _LINUX_
    ioctl(sockid, SIOCOUTQ, optval);
#else
    //ioctlsocket(sockid, FIONWRITE, (u_long*)optval);
    *(int*)(optval)=1;
#endif
    break;

  case S_RQ:

#ifdef _LINUX_
    //get the read buffer queue size
    ioctl(sockid, SIOCINQ, optval);
#else
    ioctlsocket(sockid, FIONREAD, (u_long*)optval);
#endif
    break;

  default:
    break;

  }
  return ret;
}//getsockopt

