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

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


#ifdef _WENGO
#include "p2pp/node/cmdclient.h"
#include "p2pp/common/trace.h"
#include "p2pp/utils/genhash.h"
#endif


CmdClient::CmdClient() {

}//CmdClient



CmdClient::~CmdClient() {
  
}//~CmdClient


void CmdClient::Initialize() {
  m_uiTransId = 0;

  //m_objIL.setParams((Peer*)this);

  for(int i=0; i<m_HASH_SIZE; i++) {
    m_ID[i]=0;
  } 

  //change state to connected
  m_eState = ePCONNECTED;  

  idlen = m_HASH_SIZE;

  Peer::Initialize();

}//Initialize



void CmdClient::OnTimer() {

  map<string, Transaction*>::iterator itr;
  list<string>::iterator litr;
  list<string> lint;
  list<LObject>::iterator i1;
  int32 ret;

  //CODE for generating the random transaction-Id
  //set the seed
  struct timeval curr_time;
  gettimeofday(&curr_time, NULL);
  srand(curr_time.tv_sec+curr_time.tv_usec);
  
  m_uiTransId = rand();
  //CODE for generating the random transaction-Id ENDS
  
  
  //CODE FOR interacting with command line
  for(i1=m_LO.begin(); i1 != m_LO.end(); i1++) {    
    
    if( (*i1).ritype == ePUBLISHOBJECT) {
      
      if( (*i1).changeID == 0) {
        m_objRM.PublishObject( (char*)(*i1).strKey.c_str(), (*i1).strKey.size(), (char*)(*i1).strVal.c_str(), (*i1).strVal.size(), (*i1).tt, (*i1).callback, (*i1).pApp);
      } else {
        m_objRM.ChangeUserID( (char*)(*i1).strKey.c_str(), strlen( (char*)(*i1).strKey.c_str()) );
      }      
      
    } else if((*i1).ritype == eLOOKUPOBJECT) {
      
      if( (*i1).conttype == eSTRINGVAL) {
        m_objRM.LookupObject((char*)(*i1).strKey.c_str(), (*i1).strKey.size(), (*i1).tt, (*i1).callback, (*i1).pApp);
      } else if( (*i1).conttype == eUSERINFO ) {
        m_objRM.LookupUser((char*)(*i1).strKey.c_str(), (*i1).strKey.size(), (*i1).tt, (*i1).callback, (*i1).pApp);
      } else if( (*i1).conttype == eSIP ) {
        m_objRM.LookupSIP((char*)(*i1).strKey.c_str(), (*i1).strKey.size(), (*i1).tt, (*i1).callback, (*i1).pApp);
      }
    } else if( (*i1).ritype == eGETDIAGNOSTICS ) {      
      m_objMH.GetDiagnostics( (char*)(*i1).pchIP, (*i1).port, (*i1).tt, (*i1).diagtype, (*i1).callback, (*i1).pApp );
    } else if( (*i1).ritype == eSET ) {      
      m_objMH.Set((char*)(*i1).pchIP, (*i1).port, (*i1).tt, (*i1).stnv, (*i1).callback, (*i1).pApp);
    } else if( (*i1).ritype == eRESET ) {
      m_objMH.Reset((char*)(*i1).pchIP, (*i1).port, (*i1).tt, (*i1).stnv, (*i1).callback, (*i1).pApp);
    }
  }//end for

  m_LO.clear();
  //CODE FOR interacting with command line ENDS


  //Transaction management code BEGINS
  //Check if transaction has timedout
  for(itr=m_TMap.begin(); itr!=m_TMap.end(); itr++) {

    ret = itr->second->OnTimer();
    
    //the transaction has expired
    if(0 == ret) {
      OnTransactionTimeout(itr->second);
      
      TRACE(2, "CmdClient::OnTimer...deleting transaction state: %d\n", m_eState);
      
      //The transaction was allocated so it must be deleted
      delete itr->second;
      lint.push_back(itr->first);
    }
    
  }//end for

  
  for(litr=lint.begin(); litr!=lint.end(); litr++) {
    itr = m_TMap.find(*litr);
    if(itr != m_TMap.end() ) {
      m_TMap.erase(itr);
    }
  }//end for
  //Transaction management code ENDS


  switch(m_eState) {

  case ePBOOTSTRAP:
    break;

  case ePCONNECTED:
    m_objRM.OnTimer();
    break;

  default:
    break;

  }//end switch

}//OnTimer



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

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

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

  
  TRACE(2, "CmdClient::OnReceive...received from IP address: %s port: %d packet len: %d\n", pchIP, port, len);

  //Parse the message
  m_msg.parse(buf, len);
  
  TRACE(2, "CmdClient::OnReceive...received transid: %s\n", (char*)m_msg.getTransId().c_str() );

  
  TRACE(2, "CmdClient::OnReceive...state: %d msgtype: %s\n", m_eState, pmsgtype[m_msg.stcmn_hdr.msgtype]);
  

  //At this point ACKs and retransmissions have been matched to the
  //existing transactions. So we have only new requests, response,
  //responseACK, and indications at this point. Now process them.  

  //if ACK
  if(1 == m_msg.stcmn_hdr.ack ) {
    OnACK(pchIP, port, tt);
    
  } else if(eREQUEST == m_msg.stcmn_hdr.msgtype) {    
    OnRequest(pchIP, port, tt);

  } else if(eRESPONSE == m_msg.stcmn_hdr.msgtype || eRESPONSEACK == m_msg.stcmn_hdr.msgtype) {
    return OnResponse(pchIP, port, tt);

  } else if(eINDICATION == m_msg.stcmn_hdr.msgtype) {
    OnIndication(pchIP, port, tt);
  }
  
  //Call Peer::OnReceive
  return Peer::OnReceive(buf, len, pchIP, port, tt);
  
}//OnReceive



int32 CmdClient::OnACK(char* pchIP, int32 port, transtype tt) {
  map<string, Transaction*>::iterator itr;

  //Find the transaction
  itr=m_TMap.find(m_msg.getTransId());

  //If transaction does not exist, discard the ACK
  if(itr==m_TMap.end() ) {
    TRACE(2, "CmdClient::OnACK...ACK received...transaction does not exist %d\n", m_msg.stcmn_hdr.transid);
    return 1;
  }
     
  switch(m_msg.stcmn_hdr.msgtype) {
      
  case eREQUEST:
    if(eREQUEST == itr->second->m_msgtype) {
      TRACE(1, "CmdClient::OnACK...ACK received for REQUEST\n", "" );
      itr->second->OnACK(m_msg);
    } else {
      TRACE(1, "CmdClient::OnACK...expecting ACK for REQUEST but received otherwise\n", "" );
    }
    break;

  case eRESPONSE:
  case eRESPONSEACK:
  case eINDICATION:

    if(eRESPONSE == itr->second->m_msgtype || eRESPONSEACK == itr->second->m_msgtype || 
       eINDICATION == itr->second->m_msgtype) {

      TRACE(1, "CmdClient::OnReceive....ACK received for RESPONSE, RESPONSEACK or INDICATION...Transaction size: %d\n", m_TMap.size() );
      delete itr->second;
      m_TMap.erase(itr);
      TRACE(2, "CmdClient::OnReceive.....Transaction removed size: %d\n", m_TMap.size() );
    } else {
      TRACE(2, "CmdClient::OnACK...expecting ACK for RESP, RESPACK, INDIC but received otherwise\n", "" );
    }
    break;

  default:
    break;
  }//end switch
  

  return 1;
}//OnACK



int32 CmdClient::OnRequest(char* pchIP, int32 port, transtype tt) {

  return 1;
  
}//OnRequest



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

  struct peerinfo stP;
  list<peerinfo>::iterator litr;
  map<string, Transaction*>::iterator itr;

  //If received over unreliable transport send the ACK immediately
  if(tt == eUDP || tt == eDTLS) {   
    ComposeACK(pchIP, port);
  }

  //Find the transaction
  itr=m_TMap.find(m_msg.getTransId());

  TRACE(1, "CmdClient::OnResponse...Transaction map size: %d state: %d\n", m_TMap.size(), m_eState ); 

  //response or responseACK received. check for matched requests
  if(itr != m_TMap.end() ) {
   
    delete itr->second;
    m_TMap.erase(itr);
    
    TRACE(1, "CmdClient::OnResponse.....Transaction terminated. size: %d\n", m_TMap.size() );

  } else {
    TRACE(1, "CmdClient::OnResponse.....Transaction does not exist %d\n", m_TMap.size() );
    return 1;
  }
  

  switch(m_eState) {

  case ePCONNECTED:
    {
      //if response code is 302
      if(302 == m_msg.stcmn_hdr.respcode) {
        printf("302 response: Next hop: %s:%d\n", (*(m_msg.stlpeerinfo.begin())).staddr.addr[0], (*(m_msg.stlpeerinfo.begin())).staddr.port[0] );
      }

      switch(m_msg.stcmn_hdr.reqoritype) {

      case ePUBLISHOBJECT:
        m_objRM.OnPublishObjectResponse(m_msg);
        break;

      case eLOOKUPOBJECT:
        m_objRM.OnLookupObjectResponse(m_msg);
        break;

      case eGETDIAGNOSTICS:
        m_objMH.OnGetDiagnosticsResponse(m_msg);
        break;

      case eSET:
      case eRESET:
      case eKEEPALIVE:
        //do nothing
        m_objMH.OnResponse(m_msg);
        break;

      }//end switch        
      
    }//case ePCONNECTED
    
    break;

  default:
    break;

  }//end switch
  

  return 1;

}//OnResponse



uint32 CmdClient::getNextHop(uchar* peerid, uint32 len, struct peerinfo& stP) {
  
  strcpy(stP.staddr.addr[0], m_BOOT_IP);
  stP.staddr.port[0] = m_BOOT_PORT;

  return 1;

}//getNextHop



int32 CmdClient::OnIndication(char* pchIP, int32 port, transtype tt) {
  
  return 1;
  
}//OnIndication


void CmdClient::OnTransactionTimeout(Transaction* pobjTrans) {
  
  struct peerinfo stpeer;


  TRACE(2, "CmdClient::OnTransactionTimeout...state %d size: %d\n", m_eState, m_BSP.size() );
  
  switch(m_eState) {

  case ePCONNECTED:
    m_objRM.OnTransactionTimeout(pobjTrans->m_msgtype, pobjTrans->m_reqoritype, pobjTrans->getTransId(), pobjTrans->m_pchIP, pobjTrans->m_uiPort);

    //Maintenance helper transaction timeout
    m_objMH.OnTransactionTimeout(pobjTrans->m_msgtype, pobjTrans->m_reqoritype, pobjTrans->getTransId(), pobjTrans->m_pchIP, pobjTrans->m_uiPort);
    break;

  default:
    break;
  }//end switch
  
}//OnTransactionTimeout



void CmdClient::ShowRT() {
  
  
}//ShowRT



void CmdClient::ShowNT() {
  
  
}//ShowNT



void CmdClient::ShowRO() {
  
  
}//ShowRO


