#include <stdio.h>


#include "bigint.h"


//#include "chorddist.h"
#include "kaddist.h"

#include "p2pconfig.h"

#include "msgheader.h"
#include "socketi.h"

#include "trace.h"

#include "bootstrap.h"
#include "peer.h"
//#include "chordpeer.h"
#include "kadpeer.h"
#include "client.h"

#include "kaddist.h"
#include "sysutils.h"
#include "threadi.h"

#include "diagnostics.h"
#include "bamboopeer.h"
#include "bamboodist.h"

#include "rmanager.h"
#include "cmdclient.h"

#include <string>
using namespace std;

//a: ip address
//p: port
//u: lookup user
//o: lookup object
//i: insert key
//v: value
//R: get routing table
//O: get object table

enum reqoritype g_ritype;
enum content_type g_conttype;
enum transtype g_tt;
enum diag_type g_diagtype;
char8* g_pchIP;
int32 g_port;
char8* g_pchKey;
char8* g_pchValue;
char8* g_pchConfig = "configchecker.txt";
char8* g_pchLog = "log.txt";
string g_strIP;
int g_rec;
struct nodeval g_stnv;

FILE* _fptrace;
int g_tracelevel;
int idlen=20;
Node* gpNode;


int init (char *filename) {
  _fptrace = fopen(filename, "w+");
  //fprintf(_fptrace, "mytext\n");
  //_fptrace=stderr;
  return 1;
}//init

/*
 *Commands
 * R: get routing table
 * O: get resource table
 * u: user lookup
 * o: string lookup
 * i: insert key value pair
 * S: set specific values
 * U: unset specific values
 * r: recursive routing
 * K: keepalive
 * E: Relay list
 * N: TURN allocations
 */
int processargs(int argc, char** argv) {
  extern char* optarg;
  char c;
  g_tt = eUDP;
  g_rec=0; // default is iterative


  while( (c = getopt(argc, argv, "u:o:i:v:a:p:ktRSUENOr")) != -1) {
    
    switch(c) {

    case 'u':
      g_ritype = eLOOKUPOBJECT;
      g_conttype = eUSERINFO;
      g_pchKey = optarg;
      break;

    case 'o':
      g_ritype = eLOOKUPOBJECT;
      g_conttype = eSTRINGVAL;
      g_pchKey = optarg;
      break;

    case 'i':
      g_ritype = ePUBLISHOBJECT;
      g_conttype = eSTRINGVAL;
      g_pchKey = optarg;
      break;

    case 'v':
      g_pchValue = optarg;
      break;

    case 'R':
      g_ritype = eGETDIAGNOSTICS;
      g_diagtype = eGET_RT;
      break;

    case 'O':
      g_ritype = eGETDIAGNOSTICS;
      g_diagtype = eGET_RO;
      break;

    case 'E':
      g_ritype = eGETDIAGNOSTICS;
      g_diagtype = eGET_RELAYLIST;
      break;

    case 'N':
      g_ritype = eGETDIAGNOSTICS;
      g_diagtype = eGET_TURNALLOC;
      break;

    case 'a':
      g_pchIP = optarg;
      break;

    case 'p':
      g_port = atoi(optarg);
      break;

    case 't':
      g_tt = eTCP;
      break;

    case 'K':
      g_ritype = eKEEPALIVE;
      break;

    case 'r':
      g_rec=1;

    case 'S':
      g_ritype = eSET;
      break;

    case 'b':
      g_stnv.stbw.isPresent=1;
      g_stnv.stbw.rbw=atoi(optarg);
      break;

    case 's':
      g_stnv.stbw.isPresent=1;
      g_stnv.stbw.sbw=atoi(optarg);
      break;

    case 'm':
      g_stnv.stmem.isPresent=1;
      g_stnv.sttmem.mem=atoi(optarg);
      break;

    case 'c':
      g_stnv.stcpuutil.isPresent=1;
      g_stnv.stcpuutil.cpuusage=atoi(optarg);
      break;

    case 'U':
      g_ritype = eRESET;
      break;

    default:
      break;

    }//end switch

  }//end while  
  return 1;
}//processargs


const char presponsecode [][20]= {"200 Ok", "404 Not found", "413 Timeout" };
const char* DecodeResponse(int32 respcode) {
  switch(respcode){
  case 200:
    return presponsecode[0];
    break;
  case 404:
    return presponsecode[1];
    break;
  case 413:
    return presponsecode[2];
    break;
  default:
    break;
  }
  return NULL;
}
////////////////////////////////////////////////////////////////
//              CALL BACK FUNCTIONS for LOOKUP AND INSERT
////////////////////////////////////////////////////////////////

void* OnRLookup(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;
  struct timeval currtime, difftime;
  long double delay;

  printf("OnResourceLookup: %s\n", DecodeResponse(respcode));

  pobjLO = (LObject*)pN;
  if(NULL == pN) {
    return 0;
  }

  for(i1=pobjLO->lstro.begin(); i1!=pobjLO->lstro.end(); i1++) {
    printf("Value: %s\n", (char*) (*i1).strov.value);
  }//end for
  
  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );

  exit(0);

  //return 0;
}//OnRLookup


void* OnULookup(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;
  struct timeval currtime, difftime;
  long double delay;

  printf("OnUserLookup: %s\n", DecodeResponse(respcode));

  if(NULL == pN) {
    printf("Not found\n");
    return 0;
  }

  pobjLO = (LObject*)pN;

  for(i1=pobjLO->lstro.begin(); i1!=pobjLO->lstro.end(); i1++) {
    printf("USER-INFO: %s:%d", (char*) (*i1).stpeerinfo.staddr.addr[0], (*i1).stpeerinfo.staddr.port[0]);
  }//end for


  printf("\n");

  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );

  exit(0);

  //return 0;
}//OnULookup


void* OnSIPLookup(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;
  struct timeval currtime, difftime;
  long double delay;

  printf("OnSIPLookup: %d\n", respcode);

  if(NULL == pN) {
    printf("Not found\n");
    return 0;
  }

  pobjLO = (LObject*)pN;

  for(i1=pobjLO->lstro.begin(); i1!=pobjLO->lstro.end(); i1++) {
    printf("sip:%s@%s:%d\n", (char*)pobjLO->strKey.c_str(), (char*) (*i1).staddr.addr[0], (*i1).staddr.port[0]);
  }//end for

  printf("\n");

  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );

  exit(0);
}//OnSIPLookup


void* OnPublishObject(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  long double delay;
  struct timeval currtime, difftime;

  printf("OnPublish: %s\n", DecodeResponse(respcode));
  pobjLO = (LObject*)pN;

  if(!pobjLO){
    exit(0);
  }
  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Publish time taken: %.3Lf ms\n", delay );


  exit(0);

  //return 0;
}//OnPublishObject


void printRNtable(list<peerinfo>& lstP) {
  
  list<peerinfo>::iterator i1;
  int32 len, count=1;
  BigInt lpeerid;
  string strHost;

  len = idlen;
  
  for(i1=lstP.begin(); i1!=lstP.end(); i1++) {
    
    //create the bigint out of peerid
    uchar_to_bigint( (*i1).stpeerid.id, len, lpeerid);
    
    //Transport::gethostbyaddr((*i1).staddr.addr[0], strHost);

    printf("(%d) ID: 0x%s IP: %s:%d ", count, lpeerid.ToHex().c_str(), (*i1).staddr.addr[0], (*i1).staddr.port[0] );

    if(i1->strtt.isPresent==1) {
      printf("RTT: %ums ",(*i1).strtt.utime);
    }//if

    if( (*i1).stunhashed.isPresent) {
      printf("userid: %s\n", (*i1).stunhashed.unhashed);
    }

    printf("\n");

    strHost = "";
    count++;
	   
  }//end for
  
}//printRNtable



void* OnGetDiagnostics(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;  
  int32 len, count=1;
  BigInt lresid;
  string str, strHost;
  long double delay;
  struct timeval currtime, difftime;

  printf("OnGetDiagnostics: %s\n", DecodeResponse(respcode));

  if(NULL == pN) {
    printf("Not found\n");
    return 0;
  }

  if(respcode != 200) {
    exit(0);
  }

  pobjLO = (LObject*)pN;

  //Print time taken
  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );
  
  //Print bw  
  pobjLO->stbw.isPresent==1? printf("Bandwidth\n-------------\nSendBW: %u B/s RcvBW: %u B/s\n", pobjLO->stbw.sbw, pobjLO->stbw.rbw) : 1;

  //Print CPU util  
  if(1==pobjLO->stcpuutil.isPresent) {
    printf("\nCPU utilization\n---------------\nCPU usage: %u %%", pobjLO->stcpuutil.cpuusage);
    if(1==pobjLO->sttcpuutil.isPresent) {
      printf(" Total: %u %%", pobjLO->sttcpuutil.cpuusage);
    }
    printf("\n\n");
  }//end if

  //Print routing table
  pobjLO->lstrt.size()>0 ? printf("Routing table\n-------------\n") : 1;
  printRNtable(pobjLO->lstrt);

  //Print neighbor table
  pobjLO->lstnt.size()>0 ? printf("Neighbor table\n-------------\n") : 1;
  printRNtable(pobjLO->lstnt);

  //Print resource table
  pobjLO->lstro.size()>0 ? printf("Resource table\n--------------\n") : 1;
  for(i1=pobjLO->lstro.begin(); i1!=pobjLO->lstro.end(); i1++) {
    
    len = idlen;

    //create the bigint out of peerid
    uchar_to_bigint( (*i1).stresid.id, len, lresid);   
    

    //print unhashed id
    if( (*i1).stunhashedid.isPresent) {
      str = (char*)(*i1).stunhashedid.unhashed;
    }

    if( (*i1).conttype == eUSERINFO) {
      
      //Transport::gethostbyaddr((*i1).stpeerinfo.staddr.addr[0], strHost);
      strHost=(*i1).stpeerinfo.staddr.addr[0];
      //printf("USERINFO: %s IP: %s:%d", (char*)str.c_str(), (char8*)strHost.c_str(), (*i1).stpeerinfo.staddr.port[0] );
      //      printf("USERINFO: %s IP: %s port: %d STORED BY IP: %s port: %d Hostname: %s", (char*)str.c_str(), (*i1).stpeerinfo.staddr.addr[0], (*i1).stpeerinfo.staddr.port[0], pobjLO->pchIP, pobjLO->port, (char8*)strHost.c_str() );
      
    } else if ( (*i1).conttype == eSTRINGVAL) {

      //printf("(%d) ID: %s ", count, lresid.ToHex().c_str() );
      //Transport::gethostbyaddr( pobjLO->pchIP, strHost);
      printf("KEY: %s VALUE: %s STORED BY IP: %s:%d Hostname: %s", (char*)str.c_str(), (*i1).strov.value, pobjLO->pchIP, pobjLO->port, (char8*)strHost.c_str() );
      //      printf("STRINGVAL: %s %s STORED BY IP: %s port: %d Hostname: %s", (char*)str.c_str(), (*i1).strov.value, pobjLO->pchIP, pobjLO->port, (char8*)strHost.c_str() );
      printf("\n");
      count++;
    } else if( (*i1).conttype == eSIP) {
      //printf("(%d) ID: %s ", count, lresid.ToHex().c_str() );
      printf("SIP-CONTACT: sip:%s@%s:%d ", (char*)str.c_str(), (*i1).staddr.addr[0], (*i1).staddr.port[0]);      
      printf("\n");
      count++;
    }//end else

    
   
  }//end for

  exit(0);

  //return 0;
}//OnGetDiagnostics



void* OnGetRelayList(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  list<peerinfo>::iterator litr;
  struct timeval currtime, difftime;
  BigInt lpeerid; //b, result, max, bindex, 
  int32 len, i=1;
  long double delay;

  if(NULL == pN) {
    printf("Not found\n");
    exit(1);
  }

  printf("OnGetRelayList: %s\n", DecodeResponse(respcode));

  if(respcode != 200) {
    exit(0);
  }


  pobjLO = (LObject*)pN;

  //Print time taken
  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );

  //Print bw  
  pobjLO->stbw.isPresent==1? printf("Bandwidth\n-------------\nSendBW: %u B/s RcvBW: %u B/s\n", pobjLO->stbw.sbw, pobjLO->stbw.rbw) : 1;

  //Print CPU util  
  if(1==pobjLO->stcpuutil.isPresent) {
    printf("\nCPU utilization\n---------------\nCPU usage: %u %%", pobjLO->stcpuutil.cpuusage);
    if(1==pobjLO->sttcpuutil.isPresent) {
      printf(" Total: %u %%", pobjLO->sttcpuutil.cpuusage);
    }
    printf("\n\n");
  }//end if
  
  len = idlen;

  printf("\n");
  printf("Total Relay server entries: %d \n", pobjLO->lstrl.size() );

  //iterate over CP list
  for(litr=pobjLO->lstrl.begin(); litr!=pobjLO->lstrl.end(); litr++, i++) {
      
    cout<<endl<<"("<<i<<")"<<"   ";

    for(int j=0; j<litr->staddr.num; j++) {
      cout<<litr->staddr.addr[0]<<" "<<litr->staddr.port[0];
    }//end for

    cout<<";";

    //printf("\n");
     
  }//end for

  exit(0);

}//OnGetRelayList



void* OnGetTurnAlloc(void* pN, int32 respcode) {
  struct LObject* pobjLO;
  list<turnalloc>::iterator litta;
  struct timeval currtime, difftime;
  int32 i=1;
  long double delay;

  if(NULL == pN) {
    printf("Not found\n");
    return 0;
  }

  if(respcode != 200) {
    printf("OnGetTurnAlloc: %s\n", DecodeResponse(respcode));
    exit(0);
  }

  printf("OnGetTurnAlloc: %s\n", DecodeResponse(respcode));

  pobjLO = (LObject*)pN;

  //Print time taken
  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Query time taken: %.3Lf ms\n", delay );

  //Print program bw  
  pobjLO->stbw.isPresent==1? printf("Bandwidth\n-------------\nSendBW: %u B/s RcvBW: %u B/s\n", pobjLO->stbw.sbw, pobjLO->stbw.rbw) : 1;

  //Print CPU util  
  if(1==pobjLO->stcpuutil.isPresent) {
    printf("\nCPU utilization\n---------------\nCPU usage: %u %%", pobjLO->stcpuutil.cpuusage);
    if(1==pobjLO->sttcpuutil.isPresent) {
      printf(" Total: %u %%", pobjLO->sttcpuutil.cpuusage);
    }
    printf("\n\n");
  }//end if

  printf("Total TURN allocations: %d \n", pobjLO->lstta.size() );

  for(litta=pobjLO->lstta.begin(); litta!=pobjLO->lstta.end(); litta++, i++) {
    printf("(%d) Alloc for %s:%d at port %d SendBW: %u RcvBW: %u (B/s)\n", 
	   i, litta->staddr.addr[0], litta->staddr.port[0], 
	   litta->relayport, litta->stbw.sbw, litta->stbw.rbw);
  }//end for

  exit(0);

}//OnGetTurnAlloc


void* OnKeepAlive(void* pN, int32 respcode) {

  printf("OnKeepAlive: %s\n", DecodeResponse(respcode));
  exit(0);

}//OnKeepAlive


void* OnSet(void* pN, int32 respcode) {

  struct LObject* pobjLO;
  long double delay;
  struct timeval currtime, difftime;

 
  printf("OnSet: %s\n", DecodeResponse(respcode));
  pobjLO = (LObject*)pN;

  if(!pobjLO){
    exit(0);
  }

  gettimeofday(&currtime, NULL);
  difftime=difftimeval(currtime, pobjLO->senttime);
  delay= ((long double)(difftime.tv_sec*1000000 + difftime.tv_usec )) * 0.001;
  printf("Set/Reset time taken: %.3Lf ms\n", delay );


  exit(0);

  //return 0;
}//OnSet


///////////////////////////////////////////////////////////////////
//            THREAD FUNCTIONS (Listener and Command Line)
//////////////////////////////////////////////////////////////////

#ifdef _LINUX_
inline void* _Listener(void* arg) {
#endif
#ifdef WIN32
DWORD WINAPI _Listener(void* arg) {
#endif
  
  Node* pNode = (Node*)arg;

  //pNode->m_pobjTransport->Initialize(pNode->m_TCP_PORT, pNode->m_UDP_PORT);
  pNode->Initialize();

  return 0;
}//_Listener


#ifdef _LINUX_
inline void* _CmdListener(void* arg) {
#endif
#ifdef WIN32
DWORD WINAPI _CmdListener(void* arg) {
#endif

  Node* pNode = (Node*)arg;
  
  switch(g_ritype) {

  case eLOOKUPOBJECT:
    if(eSTRINGVAL == g_conttype) {
      pNode->LookupObject(g_pchKey, strlen(g_pchKey), g_tt, OnRLookup, NULL, NULL);
    } else if(eUSERINFO == g_conttype) {
      //TEMP commented
      //pNode->LookupUser(g_pchKey, strlen(g_pchKey), g_tt, OnULookup, NULL, NULL);
      pNode->LookupSIP(g_pchKey, strlen(g_pchKey), g_tt, OnSIPLookup, NULL, NULL);
    }
    break;

  case ePUBLISHOBJECT:
    pNode->PublishObject(g_pchKey, strlen(g_pchKey), g_pchValue, strlen(g_pchValue), g_tt, OnPublishObject, NULL);
    break;

  case eGETDIAGNOSTICS:
    if(eGET_RT == g_diagtype) {
      pNode->GetRT((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnGetDiagnostics, NULL);
    } else if(eGET_NT == g_diagtype) {      
      pNode->GetNT((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnGetDiagnostics, NULL);
    } else if(eGET_RO == g_diagtype) {      
      pNode->GetRO((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnGetDiagnostics, NULL);
    } else if(eGET_RELAYLIST == g_diagtype) {      
      pNode->GetRelayList((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnGetRelayList, NULL);
    } else if(eGET_TURNALLOC == g_diagtype) {      
      pNode->GetTurnAlloc((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnGetTurnAlloc, NULL);
    }
    break;

  case eKEEPALIVE:
    pNode->GetRO((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnKeepAlive, NULL);
    break;

  case eSET:
    //value of '1' sets the values in the node
    pNode->Set((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, g_stnv, 1, OnSet, NULL);
    break;

  case eRESET:
    //value of '0' resets these values
    pNode->Set((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, g_stnv, 0, OnSet, NULL);
    break;

  default:    
    break;
  }//end switch


  //sleep for 20 seconds
  sleep(20);
  printf("Exiting because checker is stuck\n");
  exit(1);

  
  return 0;

}//_CmdListener


  


int main(int argc, char** argv) {
  
  P2PConfig cfg;
  //Node* pNode;
  CmdClient* pClient;
  Transport transport;
  int32 ret;

  //process the command line arguments
  processargs(argc, argv);

  //initialize the log file
  init(g_pchLog);

  //Parse the configuration file
  cfg.parseFile(g_pchConfig);

  //get the IPaddress of the host
  ret = Transport::gethostbyname(g_pchIP, g_strIP);
  if(0 == ret) {
    printf("Error: Invalid hostname\n");
    exit(1);
  }
  
  strcpy(cfg.m_BOOT_IP, (char*)g_strIP.c_str());
  cfg.m_BOOT_PORT = g_port;
  cfg.m_REC=g_rec;

  //set idlen
  idlen=cfg.m_HASH_SIZE;

  //Create the client object
  pClient = new CmdClient();

  pClient->setTransport(&transport);
  pClient->setConfigParams(cfg);
  transport.setNode(pClient);

  gpNode = pClient;


  //1) Start the timer thread
  //2) Start the listening sockets and threads
  
  int tid, ctid;

#ifdef _LINUX_
  tid = CreateThreadI(_Listener, (void*)gpNode);

  //Command thread
  ctid = CreateThreadI(_CmdListener, (void*)gpNode);
  
#endif

#ifdef WIN32
  LPDWORD t;
  tid = (int)CreateThread(0, 0, _Listener, (void*)gpNode, 0, (LPDWORD)&t);
  
  //Command thread
  ctid = (int)CreateThread(0, 0, _CmdListener, (void*)gpNode, 0, (LPDWORD)&t);

#endif
  
  WaitForThread(ctid);
  
  WaitForThread(tid);
  
  return 0;
}//main
