/*
 * 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 "client.h"
#include "trace.h"
#endif


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


Client::Client() {

}//Client



Client::~Client() {
  
}//~Client


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

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

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

  m_eState = ePBOOTSTRAP;

  Peer::Initialize();

}//Initialize



void Client::OnTimer() {

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


  //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).ritype == eGETDIAGNOSTICS ) {
      m_objMH.GetDiagnostics( (char*)(*i1).pchIP, (*i1).port, (*i1).tt, (*i1).diagtype, (*i1).callback, (*i1).pApp );
    } else if ( (*i1).ritype == eLEAVE ) {
      if( 1 == (*i1).disconnect) {
	m_eState = ePDISCONNECTED;

	//create a copy of call back function
	objLO.callback = (*i1).callback;
	objLO.pApp = (*i1).pApp;
	break;
      }
    }

    
  }//end for

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

  ////////////////////////////////////////////////////////////////////  
  //  Process transaction maps starts
  ////////////////////////////////////////////////////////////////////
  
  if(ePDISCONNECTED != m_eState ) {

    //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, "Client::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

  } else {

    TRACE(2, "Client::OnTimer...deleting transaction state: %d (DISCONNECTED) map size: %d\n", m_eState, m_TMap.size() );
    
    for(itr=m_TMap.begin(); itr!=m_TMap.end(); itr++) {	
      //The transaction was allocated so it must be deleted
      delete itr->second;
    }//end for  

    //remove all transactions from map
    m_TMap.clear();
  }


  ////////////////////////////////////////////////////////////////////  
  //  Process transaction maps ENDS
  ////////////////////////////////////////////////////////////////////




  switch(m_eState) {

  case ePBOOTSTRAP:
    
    ComposeBootstrap();
    m_eState = ePBOOTSTRAPSENT;
    break;

  case ePCONNECTED:
    m_objRM.OnTimer();
    break;


  case ePDISCONNECTED:
    TRACE(1, "Client::OnTimer...Transport disconnecting\n", "");
    m_pobjTransport->Disconnect();
    
    m_objMH.Disconnect(); //Maintenance helper disconnect
    m_objRM.Disconnect(); //Resource manager disconnect

    objLO.callback( (void*)&objLO, 200);
    break;

  default:
    break;

  }//end switch


  

}//OnTimer



void Client::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, "Client::OnReceive...received from IP address: %s port: %d\n", pchIP, port);

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

  
  TRACE(2, "Client::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) {
    OnResponse(pchIP, port, tt);

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



int32 Client::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, "Client::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, "Client::OnACK...ACK received for REQUEST\n", "" );
      itr->second->OnACK(m_msg);
    } else {
      TRACE(1, "Client::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, "Client::OnReceive....ACK received for RESPONSE, RESPONSEACK or INDICATION...Transaction size: %d\n", m_TMap.size() );
      delete itr->second;
      m_TMap.erase(itr);
      TRACE(2, "Client::OnReceive.....Transaction removed size: %d\n", m_TMap.size() );
    } else {
      TRACE(2, "Client::OnACK...expecting ACK for RESP, RESPACK, INDIC but received otherwise\n", "" );
    }
    break;

  default:
    break;
  }//end switch
  

  return 1;
}//OnACK



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

  return 1;
  
}//OnRequest



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

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

  //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, "Client::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, "Client::OnResponse.....Transaction terminated. size: %d\n", m_TMap.size() );

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

  switch(m_eState) {

  case ePBOOTSTRAPSENT:
    if(eBOOTSTRAP==m_msg.stcmn_hdr.reqoritype) {

      //Update global idlen
      idlen = m_msg.stp2poptions.halglen;

      getSelfID();

      TRACE(2, "Client::OnResponse...Bootstrap...num peers: %d\n", m_msg.stlpeerinfo.size() );

      //Create bootstrap node list
      CreateBNList(m_BSP);

      //Check if another node was returned. If we are the only node,
      //then stlpeerinfo should be one.
      if(m_msg.stlpeerinfo.size() <= 1) {
        printf("Error: no peer\n");
        exit(1);
      }

      //the painful join process
      m_eState = ePJOIN;
      m_eSubState = eS_PNONE;
      
      TRACE(2, "Client::OnResponse...Bootstrap...num peers: %d\n", m_msg.stlpeerinfo.size() );        
      ComposeJoin( *(m_BSP.begin()) );
      printf("===========joining===========\n");    
      
    }//end if
    break;

  
  case ePJOIN:
    
    switch(m_eSubState) {
      
    case eS_PNONE:
      
      if(302 == m_msg.stcmn_hdr.respcode) {
        TRACE(2, "Client::OnResponse...302 for Join...Send Join\n", "");

        m_BSP.pop_front();

	printf("302 response %s %d\n", m_msg.stpeerinfo.staddr.addr[0], m_msg.stpeerinfo.staddr.port[0]);

        //if there is a peerinfo present
        //TODO: insert in the BSP list
        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");
        }
        

      } else if(200 == m_msg.stcmn_hdr.respcode) {
        TRACE(2, "Client::OnResponse...200 for Join\n", "");
        m_eState = ePCONNECTED;

        printf("JOIN SUCCESSFUL === Peer-Id: 0x");

        for(int i=0; i<idlen; i++) {
          printf("%02x", m_ID[i]);
        }
        printf("\n");

        //Set the keep-alive and update timers
        //setKATTimer();

        //Insert itself as a resource-object in its own
        //resource-object
	//FIXME: UDP
        m_objRM.PublishUserInfo( (char*)m_USERID, strlen((char*)m_USERID), eUDP );
	//inform the application
	objLO.ritype = eBOOTSTRAP;
	objLO.pApp = m_pApp;
	if(appCallBack){
	  appCallBack( (void*)&objLO, 200);
	}
	
      }
      break;

    default:
      break;

    }//end switch    

    break; //end join

  case ePCONNECTED:
    {
      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 eKEEPALIVE:
        //do nothing
        break;

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

  default:
    break;

  }//end switch
  

  return 1;

}//OnResponse



uint32 Client::getNextHop(uchar* peerid, uint32 len, struct peerinfo& stP) {
  
  if(m_BSP.size() > 0) {
    stP = *(m_BSP.begin());
    return 1;
  } 

  return 0;

}//getNextHop


void Client::ShowCP() {
  
  struct peerinfo stP;

  if(m_BSP.size() > 0) {
    stP = *(m_BSP.begin());

    printf("------Client\n");
    printf("Connected Peer IP: %s port: %d\n", stP.staddr.addr[0], stP.staddr.port[0]);
  } 

}//getNextHop



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


void Client::OnTransactionTimeout(Transaction* pobjTrans) {
  
  struct peerinfo stpeer;
  struct LObject objLO;

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

  case ePJOIN:
    
    TRACE(2, "Client::OnTransactionTimeout...size: %d\n", m_BSP.size() );
    m_BSP.pop_front();

    if(m_BSP.size() > 0) {
      
      stpeer = *m_BSP.begin();
      TRACE(2, "Client::OnTransactionTimeout...Sending Join...BSP...size: %d\n", m_BSP.size() );
      ComposeJoin(*m_BSP.begin());

    } else {
      m_eState = ePFAILURE;
      printf("TODO: inform application that join has failed\n");
      objLO.ritype = eBOOTSTRAP;
      objLO.pApp = m_pApp;
      
      appCallBack( (void*)&objLO, 400);
    }

    break;

  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 Client::ShowRT() {
  
  
}//ShowRT



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



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


/*
int32 Client::PublishObject (char8* pchKey, int32 kLen, char8* pchValue, int32 vLen, void* (*insertcallback)(void*, int32), void* pApp ) {
  
  struct LObject objLO;

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

  objLO.strKey = pchKey;
  objLO.conttype = eSTRINGVAL;
  objLO.strVal = pchValue;
  objLO.ritype = ePUBLISHOBJECT;
  objLO.callback = insertcallback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//PublishObject



int32 Client::LookupObject (char8* pchKey, int32 kLen, void* (*lookupcallback)(void*, int32), void* pApp, void *pConference ) {
  
  struct LObject objLO;

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

  objLO.strKey = pchKey;
  objLO.conttype = eSTRINGVAL;
  objLO.ritype = eLOOKUPOBJECT;
  objLO.callback = lookupcallback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);
  return 1;
}//LookupObject


int32 Client::LookupUser (char8* pchKey, int32 kLen, void* (*lookupcallback)(void*, int32), void* pApp, void *pConference) {

  struct LObject objLO;

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

  objLO.strKey = pchKey;
  objLO.conttype = eUSERINFO;
  objLO.ritype = eLOOKUPOBJECT;
  objLO.callback = lookupcallback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//LookupUser



int32 Client::GetRT(char8* pchIP, int32 ilen, uint16 port, void* (*callback)(void*, int32 respcode), void* pApp) {

  struct LObject objLO;

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

  objLO.diagtype = eGET_RT;
  objLO.ritype = eGETDIAGNOSTICS;
  strcpy(objLO.pchIP, pchIP);
  objLO.port = port;

  objLO.callback = callback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//GetRT


int32 Client::GetNT(char8* pchIP, int32 ilen, uint16 port, void* (*callback)(void*, int32 respcode), void* pApp) {

  struct LObject objLO;

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

  objLO.diagtype = eGET_NT;
  objLO.ritype = eGETDIAGNOSTICS;
  strcpy(objLO.pchIP, pchIP);
  objLO.port = port;

  objLO.callback = callback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//GetNT



int32 Client::GetRO(char8* pchIP, int32 ilen, uint16 port, void* (*callback)(void*, int32 respcode), void* pApp) {

  struct LObject objLO;

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

  objLO.diagtype = eGET_RO;
  objLO.ritype = eGETDIAGNOSTICS;
  strcpy(objLO.pchIP, pchIP);
  objLO.port = port;

  objLO.callback = callback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//GetRO



int32 Client::ChangeUserID(char8* pchID, int32 ilen, void* (*callback)(void*, int32 respcode), void* pApp) {

  struct LObject objLO;

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

  objLO.strKey = pchID;
  objLO.ritype = ePUBLISHOBJECT;
  objLO.changeID = 1;

  objLO.callback = callback;
  objLO.pApp = pApp;

  m_LO.push_back(objLO);

  return 1;
  
}//ChangeUserID
*/
