#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;


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


int processargs(int argc, char** argv) {
  extern char* optarg;
  char c;
  g_tt = eUDP;

  while( (c = getopt(argc, argv, "u:o:i:v:a:p:ktRO")) != -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 'a':
      g_pchIP = optarg;
      break;

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

    case 't':
      g_tt = eTCP;
      break;

    case 'k':
      g_ritype = eKEEPALIVE;
      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;

  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


  exit(0);

  //return 0;
}//OnRLookup


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

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

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

  printf("\n");

  exit(0);

  //return 0;
}//OnULookup


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

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

  exit(0);

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

    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;

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

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

  if(respcode != 200) {
    exit(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 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", (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) {

      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() );
      //      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() );
    }//end else

    printf("\n");

    count++;
  }//end for

  exit(0);

  //return 0;
}//OnGetDiagnostics

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

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


  exit(0);

}//OnKeepAlive



///////////////////////////////////////////////////////////////////
//            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) {
      pNode->LookupUser(g_pchKey, strlen(g_pchKey), g_tt, OnULookup, 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);
    }
    break;

  case eKEEPALIVE:
    pNode->GetRO((char*)g_strIP.c_str(), g_strIP.length(), g_port, g_tt, OnKeepAlive, 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;


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