/*
 * 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 "chordpeer.h"

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

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

ChordPeer::ChordPeer() {

}


ChordPeer::~ChordPeer() {

}


void ChordPeer::Initialize() {
  m_pRT = new ChordRTable;
  m_pNT = new ChordNTable;
  m_pChordRT = (ChordRTable*)m_pRT;
  m_pChordNT = (ChordNTable*)m_pNT;
  Peer::Initialize();  

}//Initialize


int32 ChordPeer::Insert (char8* pchKey, int32 kLen, char8* pchValue, int32 vLen, void* (*insertcallback)(void*, int32), void* pApp) {

  //If not connected return immediately
  if(ePCONNECTED != m_eState) {
    return 0;
  }

  m_objIL.Insert(pchKey, kLen, pchValue, vLen, insertcallback, pApp);
  return 1;
}//Insert



int32 ChordPeer::RLookup (char8* pchKey, int32 klen, void* (*)(void*, int32), void* pApp, void* pConference ) {

  //If not connected return immediately
  if(ePCONNECTED != m_eState) {
    return 0;
  }

  return 1;
}//RLookup

int32 ChordPeer::PLookup (char8* pchKey, int32 kLen, void* (*lookupcallback)(void*, int32), void* pApp, void* Conference ) {

  //If not connected return immediately
  if(ePCONNECTED != m_eState) {
    return 0;
  }

  m_objIL.PLookup(pchKey, kLen, lookupcallback, pApp, Conference);

  return 1;
}//PLookup


uint32 ChordPeer::getNextHop(uchar* peerid, uint32 len, struct peerinfo& stP) {

  uint32 ret;
  ret = m_pChordRT->getNextHop(peerid, len, stP);

  if(ret){
    ret = m_pChordNT->getNextHop(peerid, len, stP);  
  }
  return !(ret);
  //Have to do ! because return value is used differently in helper class written by salman.
}//ChordPeer


void ChordPeer::OnTimer() {

  map<uint32, Transaction*>::iterator itr;
  int32 ret;
  list<peerinfo> t_list;
  list<uint32> lint;
  list<peerinfo>::iterator litr;
  list<uint32>::iterator ilItr;
  for(itr=m_TMap.begin(); itr!=m_TMap.end(); itr++) {
    ret = itr->second->OnTimer();

    if(0 == ret) {
      TRACE(2, "Deleting after Timeout\n", "");
      OnTransactionTimeout(itr->second);
      //The transaction was allocated so it must be deleted
      delete itr->second;
      lint.push_back(itr->first);
      //m_TMap.erase(itr);
    }//end if
  }//end for  

  for(ilItr = lint.begin(); ilItr != lint.end(); ilItr++) {
    itr = m_TMap.find(*ilItr);
    if(itr != m_TMap.end() ) {
      m_TMap.erase(itr);
    }
  }//end for

  switch(m_eState) {
    
  case ePQUERY:
    ComposeQuery();			//send query to boostrap and change state.
    m_eState = ePQUERYSENT;
    break;

  case ePNONE:
    
    break;

  case ePCONNECTED:
    //Check if keep-alive timer has expired

    if( isKAExpired() ) {

      TRACE(4, "ChordPeer::OnTimer...Keep-alive timer expired\n", "");
      //now send keep-alives to all entries in routing table
      t_list.clear();
      m_pChordRT->DumpTable(t_list);
      m_pChordNT->DumpSuccList(t_list);
      m_pChordNT->DumpPredList(t_list);
      t_list.sort();
      t_list.unique();
      for(litr=t_list.begin(); litr!=t_list.end(); litr++){
        ComposeKeepAlive(*litr);
      }
      setKeepAliveTimer();
//      m_eSubState = eS_PKASENT;

    }//end if

    if( isUExpired() ) {
      TRACE(4, "ChordPeer::OnTimer...Update timer expired\n", "");
      //now send gettables to all entries in routing and neighbor tables
      t_list.clear();
      m_pChordRT->DumpTable(t_list);
      m_pChordNT->DumpSuccList(t_list);
      m_pChordNT->DumpPredList(t_list);
      t_list.sort();
      t_list.unique(); 
      for(litr=t_list.begin(); litr!=t_list.end(); litr++){
        ComposeGetTable(*litr);
      }
      setUpdateTimer();
//      m_eSubState = eS_PTUPDATE;
    }//end if
    break;

  default:
    break;

  }//end switch

  Peer::OnTimer();
  
  TRACE(3, "ChordPeer::OnTimer\n", "");

}//OnTimer

void ChordPeer::OnTransactionTimeout(Transaction* pobjTrans) {

  //remove the failed peer from routing table and neighbor table.(wherever it is) 
  //Maybe not remove immediately?
  TRACE(3, "ChordPeer::OnTransactionTimeout %s\n", preqtype[pobjTrans->m_reqtype]);
  m_pChordRT->RemovePeer(pobjTrans->m_pchIP, pobjTrans->m_uiPort);
  m_pChordNT->RemovePeer(pobjTrans->m_pchIP, pobjTrans->m_uiPort);

}//OnTransactionTimeout

void ChordPeer::OnReceive(uchar* buf, int len, char* pchIP, int32 port, transtype tt) {

  map<uint32, Transaction*>::iterator itr;

  //Initialize the message structure
  m_msg.Initialize();

  //Parse the message
  m_msg.parse(buf, len);

  //Find the associated transaction for response
  if(eRESPONSE == m_msg.stcmn_hdr.msgtype) {

    itr=m_TMap.find(m_msg.stcmn_hdr.transid);
    if(itr == m_TMap.end() ) {
      TRACE(1, "ChordPeer::OnReceive...response received...Transaction Does NOT exist\n", "");
      return;
    } else {
      //Remove the transaction from the map
      TRACE(1, "ChordPeer::OnReceive...response received...Transaction size: %d\n", m_TMap.size() );
      delete itr->second;
      m_TMap.erase(itr);
      TRACE(1, "ChordPeer::OnReceive...response received...Transaction removed size: %d\n", m_TMap.size() );
    }
  }

  TRACE(2, "ChordPeer::OnReceive...state: %d msgtype: %d\n", m_eState, m_msg.stcmn_hdr.msgtype);
  //If request
  if(eREQUEST == m_msg.stcmn_hdr.msgtype) {
    OnRequest(pchIP, port, tt);
  }

  //If response
  if(eRESPONSE == m_msg.stcmn_hdr.msgtype) {
    OnResponse(pchIP, port, tt);
  }

  //Call Peer::OnReceive
  Peer::OnReceive(buf, len, pchIP, port, tt);

}//OnReceive

int32 ChordPeer::OnRequest(char* pchIP, int32 port, transtype tt) {
  int32 ret;
  struct peerinfo stPSelf, stPNext;
  int32 respCode=0;
  list<peerinfo> lstP;
  //int32 ret;
  switch(m_eState) {

  case ePCONNECTED:
    switch(m_msg.stcmn_hdr.reqtype) {

    case eJOIN:
      getSelfPeer(stPSelf);
      TRACE(3, "Received a JOIN request\n", "");
      printf("Received a JOIN request\n");
      if(m_pChordRT->getNextHop(m_msg.stpeerinfo.peerid,m_HASH_SIZE,stPNext) && m_pChordNT->getNextHop(m_msg.stpeerinfo.peerid,m_HASH_SIZE,stPNext)){
        respCode=200;
         if(!isSelfAddrPresent(m_msg.stpeerinfo)){
           m_pChordRT->Insert(m_msg.stpeerinfo);
           m_pChordNT->Insert(m_msg.stpeerinfo);
         }
      } 
      else{
        respCode = 302;
        printf("sending 302 %s %d isaddr: %d\n", stPNext.staddr.addr[0], stPNext.staddr.port[0], stPNext.isPresent);
        m_pChordRT->Insert(m_msg.stpeerinfo);
      }

      ComposeJoinResp(respCode, stPSelf, stPNext, pchIP, port, tt);
      break;

    case eKEEPALIVE:
      TRACE(3, "Received a Keep Alive request\n", "");
      ComposeKeepAliveResp(pchIP, port, tt);
      break;

    case eLEAVE:
      break;

    case eGETTABLE:
      //gettable
      TRACE(3, "Received a GETTABLE request\n", "");
      m_pChordRT->DumpTable(lstP);
      m_pChordNT->DumpSuccList(lstP);
      m_pChordNT->DumpPredList(lstP);
      ComposeGetTableResp(lstP, pchIP, port, tt);
      break;
     
    case eINSERT:
      getSelfPeer(stPSelf);
  
      //can we insert the peerinfo object in our own routing table?
      if(!isSelfAddrPresent(m_msg.stpeerinfo)) {
        m_pChordRT->Insert(m_msg.stpeerinfo);
        m_pChordNT->Insert(m_msg.stpeerinfo);
      }
      //get next hop
      if(m_pChordRT->getNextHop(m_msg.stro.resid, m_HASH_SIZE, stPNext) && m_pChordNT->getNextHop(m_msg.stro.resid, m_HASH_SIZE, stPNext)) {
        respCode=200;

        //Store the resource object
        m_objIL.InsertObject(m_msg, 3600);

      } else {
        respCode = 302;

        printf("INSERT sending respcode %d %s %d isaddr: %d\n", respCode, stPNext.staddr.addr[0], stPNext.staddr.port[0], stPNext.isPresent);
      }

      //Send the response
      ComposeInsertResp(respCode, stPSelf, stPNext, pchIP, port, tt);
      break;
      
    case eLOOKUPPEER:
      getSelfPeer(stPSelf);
      if(!isSelfAddrPresent(m_msg.stpeerinfo)) {
        m_pChordRT->Insert(m_msg.stpeerinfo);
        m_pChordNT->Insert(m_msg.stpeerinfo);
      }

      if(m_pChordRT->getNextHop(m_msg.stplookup.peerid, m_HASH_SIZE, stPNext) && m_pChordNT->getNextHop(m_msg.stplookup.peerid, m_HASH_SIZE, stPNext)) {
        respCode = 200;
        for(int i=0; i<m_HASH_SIZE; i++) {
          if(m_msg.stplookup.peerid[i] != stPSelf.peerid[i]) {
            respCode = 404;
            break;
          }
        }//end for
      } 
      else {
        respCode = 302;
        printf("PLOOKUP sending respcode %d %s %d isaddr: %d\n", respCode, stPNext.staddr.addr[0], stPNext.staddr.port[0], stPNext.isPresent);
      }
      
      ComposePLookupResp(respCode, stPSelf, stPNext, pchIP, port, tt);      
      break;

    default:
      break;
    }//end switch
    break;

  default:
    break;

  }//end switch

  return 1;

}//OnRequest

int32 ChordPeer::OnResponse(char* pchIP, int32 port, transtype tt) {

  list<peerinfo>::iterator litr;
  switch(m_eState) {    

  case ePQUERYSENT:
    if(200 == m_msg.stcmn_hdr.respcode) {
      //copy the IDs everywhere.
      memcpy(m_ID, m_msg.stpeerinfo.peerid, idlen);
      m_pChordRT->setParams(m_ID, idlen, m_msg.stp2poptions.hbase);
      m_pChordNT->setParams(m_ID, idlen, m_msg.stp2poptions.hbase);
      
      if(isSelfAddrPresent(m_msg.stpeerinfo)) {		//check if it is the first node in the overlay.
        m_eState = ePCONNECTED;
        TRACE(3, "First node in P2PP. Connected state\n", "");
        printf("First node in P2PP. Connected state\n");
        setKeepAliveTimer();
        setUpdateTimer();
      } 
      else {
        //the painful join process
        m_eState = ePJOIN;

        printf("===========joining\n");
        m_eState = ePJOIN;
        m_eSubState = eS_PNONE;
        ComposeJoin(m_msg.stpeerinfo);
      }
	
    }//end if
    break;

  case ePJOIN:
    switch(m_eSubState){
   
    case eS_PNONE:
      if(302 == m_msg.stcmn_hdr.respcode) {
        TRACE(3, "ChordPeer::OnResponse...302 for Join...Send Join\n", "");
        if(!isSelfAddrPresent(m_msg.stpeerinfo)){ 
          m_pChordRT->Insert(m_msg.stpeerinfo);
          m_pChordNT->Insert(m_msg.stpeerinfo);
        }
        //if there is a 302 present
        if(m_msg.stlpeerinfo.size() > 0) {
          litr=m_msg.stlpeerinfo.begin();

          ComposeJoin(*litr);
        } else {
          printf("Error in Join...There was no peerinfo object in 302\n");
        }

        printf("302 response %s %d\n", m_msg.stpeerinfo.staddr.addr[0], m_msg.stpeerinfo.staddr.port[0]);
      }
      else if(200 == m_msg.stcmn_hdr.respcode){
        TRACE(2, "ChordPeer::OnResponse...200 for Join\n", "");
        m_eState = ePCONNECTED;
        //Important: add this guy as your predecessor and initially also as successor.
        if(!isSelfAddrPresent(m_msg.stpeerinfo)){
          m_pChordNT->Insert(m_msg.stpeerinfo);
          m_pChordRT->Insert(m_msg.stpeerinfo);   //also add to routing table.
        }
        printf("connected state\n");

        //Set the keep-alive and update timers
        setKeepAliveTimer();
        setUpdateTimer();
      }
      break;

    default:
      break;
    }
  break;
  
  case ePCONNECTED:
    switch(m_msg.stcmn_hdr.reqtype) {
    TRACE(3, "Received a response for GETTABLE\n", "");
    case eGETTABLE:
      for(litr = m_msg.stnt.table.begin(); litr!=m_msg.stnt.table.end(); litr++){
        if(!isSelfAddrPresent(*litr)){
          m_pChordNT->Insert(*litr);
          m_pChordRT->Insert(*litr);
        }
      }

      for(litr = m_msg.strt.table.begin(); litr!=m_msg.strt.table.end(); litr++){
        if(!isSelfAddrPresent(*litr)){
          m_pChordRT->Insert(*litr);
          m_pChordNT->Insert(*litr);
        }
      }
      break;
    
    case eKEEPALIVE:
      TRACE(3, "Received a response for KEEPALIVE\n", "");
      break;

    case eINSERT:
      m_objIL.OnInsertResponse(m_msg);
      //Insert the peer in routing table
      if(!isSelfAddrPresent(m_msg.stpeerinfo)){
        m_pChordRT->Insert(m_msg.stpeerinfo);
        m_pChordNT->Insert(m_msg.stpeerinfo);
      }
      break;

    case eLOOKUPPEER:
      m_objIL.OnPLookupResponse(m_msg);
      //Insert the peer in routing table
      if(!isSelfAddrPresent(m_msg.stpeerinfo)) {
        m_pChordRT->Insert(m_msg.stpeerinfo);
        m_pChordNT->Insert(m_msg.stpeerinfo);
      }
      break;

    default:
      break;
    }

    break;

  default:
    break;
  }//end switch
  return 0;
}//OnResponse

void ChordPeer::setKeepAliveTimer() {
  gettimeofday(&m_tKA, NULL);
  m_tKA = addtimeval(m_tKA, 2*_T0, 0);
}//setKeepAliveTimer


int32 ChordPeer::isKAExpired() {
  struct timeval cur, difft;
  gettimeofday(&cur, NULL);

  //difference between timer and current time
  difft = difftimeval(m_tKA, cur);

  if(0 == difft.tv_sec && 0 == difft.tv_usec) {
    TRACE(3, "ChordPeer::isKAExpired...Keep-alive timer expired...start keep-alives\n", "");
    return 1;
  }

  return 0;
}//isKAExpired


void ChordPeer::setUpdateTimer() {
  gettimeofday(&m_tU, NULL);
  m_tU = addtimeval(m_tU, 4*_T0, 0);
}//setKeepAliveTimer


int32 ChordPeer::isUExpired() {
  struct timeval cur, difft;
  gettimeofday(&cur, NULL);

  //difference between timer and current time
  difft = difftimeval(m_tU, cur);

  if(0 == difft.tv_sec && 0 == difft.tv_usec) {
    return 1;
  }
  return 0;
}//isUExpired

void ChordPeer::ShowRT() {

  struct peerinfo stP;

  getSelfPeer(stP);

  printf("\n");
/*  printf("Node Id: 0x");

  for(int i=0; i<m_HASH_SIZE; i++) {
    printf("%x", m_ID[i]);
  }*/
  BigInt bMyId;
  uchar_to_bigint(m_ID, m_HASH_SIZE, bMyId);
  cout<<bMyId<<" : ";
  printf(" %s %d\n", stP.staddr.addr[0], stP.staddr.port[0]);
  printf("Routing Table\n");
  printf("-------------\n");

  m_pChordRT->Show();

}//ShowRT

void ChordPeer::ShowNT() {

  struct peerinfo stP;

  getSelfPeer(stP);

  printf("\n");
/*  printf("Node Id: 0x");

  for(int i=0; i<m_HASH_SIZE; i++) {
    printf("%x", m_ID[i]);
  }*/
  BigInt bMyId;
  uchar_to_bigint(m_ID, m_HASH_SIZE, bMyId);
  cout<<bMyId<<" : ";
  printf(" %s %d\n", stP.staddr.addr[0], stP.staddr.port[0]);
  printf("Neighbor Table\n");
  printf("-------------\n");
  

  m_pChordNT->Show();
}//ShowNT



//ShowResourceObject
//Call m_objIL ShowRO
void ChordPeer::ShowRO() {

}//ShowRO
