/*
 * 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.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#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 "cpuutil.h"


#ifdef _WENGO
#include "p2pp/bigint/bigint.h"

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

#include "p2pp/p2pconfig/p2pconfig.h"

#include "p2pp/msg/msgheader.h"
#include "p2pp/net/socketi.h"

#include "p2pp/common/trace.h"

#include "p2pp/node/bootstrap.h"
#include "p2pp/node/peer.h"
//#include "p2pp/node/chordpeer.h"
#include "p2pp/node/kadpeer.h"
#include "p2pp/node/bamboopeer.h"

#include "p2pp/distance/kaddist.h"
#include "p2pp/distance/bamboodist.h"
#include "p2pp/sys/sysutils.h"
#include "p2pp/sys/threadi.h"

#include "p2pp/node/client.h"
#include "p2pp/node/diagnostics.h"
#endif

extern "C" {
#include "pjlib.h"
}

#include <list>
using namespace std;


//#define ROUTE_TIMER 30
//#define RESOURCE_TIMER 45

struct temp1 {
  char id[20];    
};


FILE* _fptrace;
int g_tracelevel;
int idlen=20;
Node* gpNode;
Transport* gpTransport;
int gtid, gctid;
int gisConnected;

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


//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;
extern CpuUtil *gCpuUtilization;

////////////////////////////////////////////////////////////////
//              CALL BACK FUNCTIONS for LOOKUP AND INSERT
////////////////////////////////////////////////////////////////

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

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;

  printf("OnRLookup: %d\n", 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

  return 0;
}//OnRLookup


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

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;

  printf("OnULookup: %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("IP: %s port: %d\n", (char*) (*i1).stpeerinfo.staddr.addr[0], (*i1).stpeerinfo.staddr.port[0]);
  }//end for

  return 0;
}//OnULookup

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

  struct LObject* pobjLO;
  list<resourceobject>::iterator i1;

  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 IP: %s port: %d\n", (char*) (*i1).staddr.addr[0], (*i1).staddr.port[0]);
  }//end for

  return 0;
}//OnSIPLookup


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

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

  return 0;
}//OnLookup


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 port: %d Hostname: %s", count, lpeerid.ToHex().c_str(), (*i1).staddr.addr[0], (*i1).staddr.port[0], strHost.c_str() );

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

    printf("\n");

    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;

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

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

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

  pobjLO = (LObject*)pN;

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

  //Print CPU util  
  pobjLO->stcpuutil.isPresent==1? printf("CPU utilization\n-------------\nCPU usage: %u %%\n", pobjLO->stcpuutil.cpuusage) : 1 ;

  //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);

  //Prinht 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);
    
    printf("(%d) ID: %s ", count, lresid.ToHex().c_str() );

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

    if( (*i1).conttype == eUSERINFO) {
      
      Transport::gethostbyaddr((*i1).stpeerinfo.staddr.addr[0], strHost);
      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) {

      Transport::gethostbyaddr( pobjLO->pchIP, strHost);
      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() );
    } else if( (*i1).conttype == eSIP) {
      printf("SIP-CONTACT: %s:%d User: %s", (*i1).staddr.addr[0], (*i1).staddr.port[0], (char*)str.c_str());
    }//end else

    printf("\n");

    count++;
  }//end for

  return 0;
}//OnGetDiagnostics


void* OnDisconnect(void* pN, int32 respcode) {
  return 0;
}//OnDisconnect


void* OnAppCallBack(void* pN, int32 respcode) {
  
  struct LObject* pobjLO;

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

  pobjLO = (LObject*)pN;
  
  if(NULL != pobjLO && pobjLO->ritype == eBOOTSTRAP && 400 == respcode) {
    printf("Error: Cannot connect to bootstrap server\n");
  }

  return 0;
}//OnDisconnect



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

void printHelp() {

  printf("\nHELP\n----\n");
  printf("connect    -- connect to the overlay\n");
  printf("disconnect -- disconnect from the overlay\n");
  printf("rlookup   [resource] -- perform lookup for a STRING.\n");
  printf("ulookup   [user]     -- perform p2pp user lookup\n");
  printf("siplookup [sipuser]  -- perform sip user lookup\n");
  printf("changeid [user]      -- change user name to 'user'\n");
  printf("insert   [key] [value]  -- insert key value pair in the overlay.\n");
  printf("getrt [hostname] [port] -- get routing table of hostname port\n");
  printf("getnt [hostname] [port] -- get neighbor table of hostname port\n");
  printf("getro [hostname] [port] -- get resource table of hostname port\n");
  
  printf("\n---Peer Functions---\n");
  printf("showrt -- show routing table\n");
  printf("shownt -- show neighbor table, the successor/leaftsef table of the peer.\n");
  printf("showro -- show resource table, the resource object the peer stores.\n");
  printf("showsys -- show peer bandwidth, cpu for peer and machine\n");
  printf("showturnalloc -- show turn allocations for clients on this peer.\n");

  printf("\n---Client Functions---\n");
  printf("showcp -- show the peer to which client is connected.\n");
  printf("showcplist -- show the backup peers list a client maintains.\n");
  printf("showrelaylist -- show the relay list the client maintains.\n");
  printf("showrelayalloc -- show relay allocations for this client.\n");

}//printHelp


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

  //register listener thread
  pj_thread_register("LISTENER", handler_desc, &listener_thread);

  gisConnected=1;

  pNode->Initialize();

  //exit the thread
  //TODO: fix thread id
  
  //ExitThread(0);
  printf("Exiting listener thread\n");
  
  gisConnected=0;

  return 0;
}//_Listener


#ifdef _LINUX_
inline void* _CmdListener(void* arg) {
#endif
#ifdef WIN32
DWORD WINAPI _CmdListener(void* arg) {
#endif
  
  char line[500];
  char *pchCmd;
  char* pchArg;
  char* pchArg1;

  pj_thread_desc handler_desc;
  pj_thread_t *cmd_listener_thread;
  
  //register thread with pjlib
  pj_thread_register("CMDLISTENER", handler_desc, &cmd_listener_thread);

  Node* pNode;
  string strIP;
  int32 ret;
  uint32 sbw, rbw;



  while(1) {
    pchCmd=NULL;
    pchArg=NULL;
    pchArg1=NULL;    
    printf(">>");
    cin.getline(line, 200);

    //Jugar now
    pNode = gpNode;


    //print command help
    //if(0 == strcmp("?", line)) {
    //  printHelp();
    //}

    if(strlen(line) < 4) {
      continue;
    }    

    printf("%s\n",line);
    //tokenize it
    pchCmd = strtok(line, " ");

    //Exit the program
    if(0 == strcmp("exit", pchCmd)) {
      exit(0);
    }
    
    if(0 == gisConnected && 0 != strcasecmp("connect", pchCmd) ) {
      printf("Not connected. Type connect [Enter]\n");
      continue;
    }

    if(0 == strcasecmp("showrt", pchCmd)) {
      pNode->ShowRT();
      continue;
    } else if(0 == strcasecmp("shownt", pchCmd)) {
      pNode->ShowNT();
      continue;
    } else if(0 == strcasecmp("showro", pchCmd)) {
      pNode->ShowRO();
      continue;
    } else if(0 == strcasecmp("showsys", pchCmd)) {
      pNode->getTransport()->getSendRecvBW(sbw, rbw);
      long tsbw=0, trbw=0;
      getsendrecvbw(&tsbw,&trbw);
      sbw+=tsbw; rbw+=trbw;
      //if bandwidth has been set, report it.
      if(g_stnodeval.stbw.isPresent && 0 == g_stnodeval.stbw.ismach) {
        sbw=g_stnodeval.stbw.sbw;
        rbw=g_stnodeval.stbw.rbw;
      } 
      printf("Bandwidth\n-------------\nSBW: %u B/s RBW: %u B/s set: %d\n", sbw, rbw, g_stnodeval.stbw.isPresent);

      printf("CPU program: %.2Lf machine: %.2Lf\n", gCpuUtilization->GetCpuUsage(), gCpuUtilization->GetTCpuUsage() );
      
      continue;
    } else if(0 == strcasecmp("showcp", pchCmd)) {
      pNode->ShowCP();
      continue;
    } else if(0 == strcasecmp("showcplist", pchCmd)) {
      pNode->ShowCPlist();
    } else if(0 == strcasecmp("showrelaylist", pchCmd)) {
      pNode->ShowRelayServerList();
    } else if(0 == strcasecmp("showturnalloc", pchCmd)) {
      pNode->ShowTURNAlloc();
    } else if(0 == strcasecmp("showrelayalloc", pchCmd)) {
      pNode->ShowRelayAllocations();
    } else if(0 == strcasecmp("disconnect", pchCmd)) {
      pNode->Disconnect(OnDisconnect, NULL);      
      continue;
    } else if(0 == strcasecmp("connect", pchCmd)) {      
      printf("Waiting for thread to exit\n");
      WaitForThread(gtid); 
      printf("Starting new thread\n");

#ifdef _LINUX_
      gtid = CreateThreadI(_Listener, (void*)gpNode);
#endif
      
#ifdef WIN32
      LPDWORD t;
      gtid = (int)CreateThread(0, 0, _Listener, (void*)gpNode, 0, (LPDWORD)&t);
#endif

      continue;
    }

    
    pchArg = strtok(NULL, " ");
    if(pchArg==NULL || strcmp(pchArg,"")==0){
      continue;
    }
    if(0 == strcasecmp("rlookup", pchCmd)) {
      printf("%s %s\n", pchCmd, pchArg);
      pNode->LookupObject(pchArg, strlen(pchArg), eUDP, OnRLookup, NULL, NULL);
      continue;

    } else if(0 == strcasecmp("ulookup", pchCmd)) {
      printf("%s %s\n", pchCmd, pchArg);
      pNode->LookupUser(pchArg, strlen(pchArg), eUDP, OnULookup, NULL, NULL);
      continue;

    } else if(0 == strcasecmp("siplookup", pchCmd)) {
      printf("%s %s\n", pchCmd, pchArg);
      pNode->LookupSIP(pchArg, strlen(pchArg), eUDP, OnSIPLookup, NULL, NULL);
      continue;

    } else if(0 == strcasecmp("changeid", pchCmd)) {

      printf("%s %s\n", pchCmd, pchArg);
      pNode->ChangeUserID(pchArg, strlen(pchArg), NULL, NULL);
      continue;
    }
    
    
    pchArg1 = strtok(NULL, " ");
    if(pchArg1==NULL || strcmp(pchArg1,"")==0){
      continue;
    }
    if(0 == strcasecmp("insert", pchCmd)) {
      printf("%s %s %s\n", pchCmd, pchArg, pchArg1);
      pNode->PublishObject(pchArg, strlen(pchArg), pchArg1, strlen(pchArg1), eUDP, OnPublishObject, NULL);
      continue;

    } else if(0 == strcasecmp("getrt", pchCmd)) {

      ret = Transport::gethostbyname(pchArg, strIP);
      if(0 == ret) {
        printf("Error: Invalid hostname\n");
      } else {
        printf("%s %s %d\n", pchCmd, pchArg, atoi(pchArg1) );
        pNode->GetRT((char*)strIP.c_str(), strIP.length(), atoi(pchArg1), eUDP, OnGetDiagnostics, NULL);
      }
      continue;

    } else if(0 == strcasecmp("getnt", pchCmd)) {

      ret = Transport::gethostbyname(pchArg, strIP);
      if(0 == ret) {
        printf("Error: Invalid hostname\n");
      } else {
        printf("%s %s %d\n", pchCmd, pchArg, atoi(pchArg1) );
        pNode->GetNT((char*)strIP.c_str(), strIP.length(), atoi(pchArg1), eUDP, OnGetDiagnostics, NULL);
      }
      continue;

    } else if(0 == strcasecmp("getro", pchCmd)) {

      ret = Transport::gethostbyname(pchArg, strIP);
      if(0 == ret) {
        printf("Error: Invalid hostname\n");
      } else {
        printf("%s %s %d\n", pchCmd, pchArg, atoi(pchArg1) );
        pNode->GetRO((char*)strIP.c_str(), strIP.length(), atoi(pchArg1), eUDP, OnGetDiagnostics, NULL);
      }
      continue;
    } 

    
  }//end while

  return 0;

}//_CmdListener


////////////////////////////////////////////////////////////////
//      LIST OF COMMANDS
// connect -- connect to the network again
// disconnect -- disconnect and destroy all the objects
// rlookup -- [key] performs a lookup for STRINGVAL
// ulookp  -- [key] performs a lookup for USERINFO
// siplookp  -- [user] performs a lookup for SIP
// changeid -- [id] change user id to 'id'
// insert  -- [key, value] inserts a key/value pair. object type STRING VAL
// getrt   -- [IP, port] obtains the routing table from a IP port
// getnt   -- [IP, port] obtains the neighbor table from a IP port
// getro   -- [IP, port] obtains the resource table from a IP port

// PEER FUNCTIONS
// showrt  -- shows the resource table
// shownt  -- shows the neighbor table
// showro  -- shows the resource table
// showsys
// showturnalloc

//CLIENT FUNCTIONS
// showcp
// showcplist
// showrelaylist
// showrelayalloc


////////////////////////////////////////////////////////////////


////////////////////////////////////
//             MAIN               //
////////////////////////////////////

int main(int argc, char** argv) {

  if(argc != 3) {
    printf("Usage: p2pmain config_file log_file\n");
    exit(-1);
  }

  BigInt obj;
  obj = 3;
  cout << obj << endl;

  init(argv[2]);
    
  char* pchCfg = argv[1];
  //char* pchNode = argv[2];

  P2PConfig cfg;
  //Node* pNode;
  Peer* pPeer=NULL;
  Client* pClient;
  

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

  //set idlen
  idlen=cfg.m_HASH_SIZE;

  //create the transprot object
  gpTransport = new Transport();

  //set associations
  if(eBOOTSTRAP_SERVER == cfg.m_NODE_TYPE) {  

    if(eCHORD == cfg.m_P2P) {
      printf("Algorithm is Chord...Bootstrap server\n");
    } else if(eKADEMLIA == cfg.m_P2P) {
      printf("Algorithm is Kademlia...Bootstrap server\n");
    } else if(eBAMBOO == cfg.m_P2P) {
      printf("Algorithm is Bamboo...Bootstrap server\n");
    }

    gpNode = new Bootstrap();    


    gpNode->setTransport(gpTransport);
    gpNode->setConfigParams(cfg);
    gpTransport->setNode(gpNode);
    
  } else if(ePEER == cfg.m_NODE_TYPE) {

    if(eCHORD == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Chord\n");
      //pPeer = new ChordPeer();
    } else if(eKADEMLIA == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Kademlia\n");
      pPeer = new KadPeer();
    } else if(eBAMBOO == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Bamboo\n");
      pPeer = new BambooPeer();
    }
 
    pPeer->setTransport(gpTransport);
    pPeer->setConfigParams(cfg);
    gpTransport->setNode(pPeer);

    //set node* to peer
    gpNode = pPeer;
  } else if(eCLIENT == cfg.m_NODE_TYPE) {
    printf("Starting Client\n");
    pClient = new Client();

    pClient->setTransport(gpTransport);
    pClient->setConfigParams(cfg);
    gpTransport->setNode(pClient);

    gpNode = pClient;
  } else if(eDIAGNOSTIC_SERVER == cfg.m_NODE_TYPE) {
    printf("Starting Diagnostic server\n");
    gpNode = new Diagnostics();

    gpNode->setTransport(gpTransport);
    gpNode->setConfigParams(cfg);
    ((Diagnostics*)gpNode)->setDiagnosticsFile(cfg.m_Diagnosticsfile);
    gpTransport->setNode(gpNode);
  } else if(eAUTO==cfg.m_NODE_TYPE) {
    printf("Starting NAT detection and Client/Peer mode selection\n");
    printf("Starting Client\n");
    pClient = new Client();

    pClient->setTransport(gpTransport);
    pClient->setConfigParams(cfg);
    gpTransport->setNode(pClient);

    gpNode = pClient;
    
  }


  //set the application callback
  gpNode->setCallBack(OnAppCallBack, NULL);


  //1) Start the timer thread
  //2) Start the listening sockets and threads  

#ifdef _LINUX_
  gtid = CreateThreadI(_Listener, (void*)gpNode);
  //command line
  if(1 == cfg.m_CMDLINE) {
    gctid = CreateThreadI(_CmdListener, (void*)gpNode);
  }
#endif

#ifdef WIN32
  LPDWORD t;

  gtid = (int)CreateThread(0, 0, _Listener, (void*)gpNode, 0, (LPDWORD)&t);

  if(1 == cfg.m_CMDLINE) {
    gctid = (int)CreateThread(0, 0, _CmdListener, (void*)gpNode, 0, (LPDWORD)&t);
  }
#endif

  WaitForThread(gtid); 

  if(eAUTO==cfg.m_NODE_TYPE && STIManager::getNatType() == PJ_STUN_NAT_TYPE_OPEN) {

    printf("Moving to Peer mode \n");
    if(eCHORD == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Chord\n");
      //pPeer = new ChordPeer();
    } else if(eKADEMLIA == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Kademlia\n");
      pPeer = new KadPeer();
    } else if(eBAMBOO == cfg.m_P2P) {
      printf("Starting PEER...Algorithm is Bamboo\n");
      pPeer = new BambooPeer();
    }
 
    pPeer->setTransport(gpTransport);
    pPeer->setConfigParams(cfg);

    //set the mode
    pPeer->setMode("PEER");
    gpTransport->setNode(pPeer);

    //set node* to peer
    gpNode = pPeer;
    
#ifdef _LINUX_
    gtid = CreateThreadI(_Listener, (void*)gpNode);
#endif
    
#ifdef WIN32
    gtid = (int)CreateThread(0, 0, _Listener, (void*)gpNode, 0, (LPDWORD)&t);
#endif
    WaitForThread(gtid); 

  }//if(eAUTO==cfg.m_NODE_TYPE)

  if(1 == cfg.m_CMDLINE) {
    WaitForThread(gctid);
  }

  
  return 0;

}//main
