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

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

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

#include <math.h>

BambooRTable::BambooRTable() {
  m_iSize =0;
  m_RT.clear();
}//BambooRTable

BambooRTable::~BambooRTable() {

}//~BambooRTable

uint32 BambooRTable::getSize() {
  return m_iSize;
}//getSize

void BambooRTable::setNodeId(uchar* chPeerid, int32 len, int32 base) {
  struct Internalpeerinfo p;
  list<Internalpeerinfo> listP;
  map < BigInt, list<Internalpeerinfo> > mapP;
  BigInt t;

  //set hash size in bytes
  m_HASH_SIZE = len;
  m_iSize = len;
  
  //Base
  m_BASE = base;
  
  //Create max bigint
  for(int i=0; i<(int)(8*m_HASH_SIZE); ++i)
    {
      m_Max.setBitAt(i,1);
    }

  //create the bigint out of peerid
  uchar_to_bigint(chPeerid, len, m_NodeId);
  
  //Initialize the distance function with base, Id and hashlen
  m_BambooDist.setParams(base, len);
  m_BambooDist.setKey(m_NodeId);
  
  //initialize routing table
  listP.clear();
  for(int k=0; k<base; ++k){
    t = k;
    mapP[t] = listP;
  }

  for(int j=0;j<len*8;++j){
    t=j;
    m_RT[t] = mapP;
  }

  m_iSize =0;
}//setNodeId

uint32 BambooRTable::getNextHop(BigInt peer, peerinfo& pinfo) {
  return 1;
}//getNextHop

uint32 BambooRTable::getNextHop(uchar* chPeerid, uint32 len, struct peerinfo& nextP) {
  BigInt max, lpeerid, bpeerid, result, mydistance;
  int isPresent=0;
  
  uchar_to_bigint(chPeerid, len, bpeerid);
  
  //initialize max to a large number
  max = m_Max;

  for( map< BigInt, map < BigInt, list< Internalpeerinfo > > >::iterator mmitr = m_RT.begin(); mmitr!=m_RT.end();  mmitr++) {
    for(map<BigInt, list < Internalpeerinfo > >::iterator mitr= mmitr->second.begin(); mitr!= mmitr->second.end(); mitr++) {
      for(  list < Internalpeerinfo >::iterator litr= mitr->second.begin(); litr!= mitr->second.end(); litr++ ) {
	//if search query==search result then continue
	peerid stPeerid=(*litr).m_stP.stpeerid;
	if( strncmp( (const char*)(stPeerid.id),(const char*)chPeerid, (int)len ) ==0 ) {
	  //distance=0; so return as next hop
	  isPresent = 1 ;
	  nextP = litr->m_stP;
	  return 0;
	}//end of if

	//get peerid of peer in RT
	//uchar_to_bigint(litr->m_stP.stpeerid.id, len, lpeerid);

	//calculate distance
	m_BambooDist.getDistance(litr->ID, bpeerid, result);

	if(result<max) {
	  isPresent =1;
	  
	  //set the peer info structure
	  nextP = litr->m_stP;
	  
	  //update the max
	  max = result;
	}
      }//end of for litr
    }//end of for mitr
   }//end of for mmitr
  
  //Calculate distance of peer from me.
  m_BambooDist.getDistance(bpeerid, m_NodeId, mydistance);

  if(0==isPresent)
    {
      return 0;
    }
  if(mydistance > max){
    //I am not the hop
    return 1;
  }else{
    //I am next hop.
    return 0;
  }
  return 0;
}//getNextHop

uint32 getSize() {
  return 1;
}//getSize

int32 BambooRTable::Insert(struct peerinfo& stP, int updateKA) {
  BigInt temp, bPeerid, bbit, peerid_old;
  BigInt distance1, distance2;
  unsigned int bit;
  Internalpeerinfo pinfo;
  uint32 byte_bit;
  list<Internalpeerinfo>::iterator litr;
  
  uchar_to_bigint(stP.stpeerid.id, m_HASH_SIZE, bPeerid);
  if(bPeerid==m_NodeId)
    //dont add self
    {
      return 1;
    }

  //peerinfo ID
  pinfo.m_stP = stP;
  pinfo.ID = bPeerid;

  //find number of bits that form a digit 
  byte_bit = (uint32) ((float)log((float)m_BASE) / (float)log((float)2));

  //find how many digits of subject id matches nodeid
  int i=0;
  for(i=(m_HASH_SIZE*8)/byte_bit -1 -1; i>=0 ; --i){
    temp = bPeerid ^ m_NodeId;
    for(int j=(i*byte_bit)+1;j<(int)m_HASH_SIZE*8;++j){
      temp.setBitAt(m_HASH_SIZE*8-1-j, 0);
    }
     
    for(int j=0; j<(int)(m_HASH_SIZE*8);++j){
      temp.getBitAt(j, bit);
      if(bit==1) {
	break;
      }
    }
    
    if(bit==1)
      continue;
    else
      break;
  }
  if(bit==1) {
    //no digits are same in nodeid and stP.stpeerid.id
    temp=0;
    bbit=0;
  } else {
    //number of similar digits in nodeid and stP.stpeerid.id
    //is i*byte_bit+1
    bPeerid.getBitAt((m_HASH_SIZE*8-1)-(i*byte_bit+1), bit);
    temp = (i*byte_bit)+1;
    bbit = bit;
  }
  for(litr=m_RT[temp][bbit].begin(); litr!=m_RT[temp][bbit].end();++litr){
    if(strncmp((const char*)litr->m_stP.stpeerid.id, (const char*)stP.stpeerid.id, m_HASH_SIZE)==0){
      //allready added
      return 1;
    }
  }
  list< struct Internalpeerinfo>::iterator ipiter=m_RT[temp][bbit].begin();
  if(ipiter!=m_RT[temp][bbit].end()){
    //uchar_to_bigint(m_RT[temp][bbit].begin()->m_stP.stpeerid.id, m_HASH_SIZE, peerid_old);
    
    m_BambooDist.getDistance(m_RT[temp][bbit].begin()->ID, m_NodeId, distance1);
    m_BambooDist.getDistance(bPeerid, m_NodeId, distance2);
    if(distance1>distance2){
      m_RT[temp][bbit].push_front(pinfo);
    }else {
      m_RT[temp][bbit].push_back(pinfo);
    }
  }else{
    m_RT[temp][bbit].push_back(pinfo);
  }//end of if
  m_iSize++;

  TRACE(2,"%s: equal bits %d bit value %d\n", stP.staddr.addr[0], i+1, bit);
  return 1;
}//Insert

/*
    //this will only work for base 2
    bPeerid.getBitAt((m_HASH_SIZE*8-1)-(i*byte_bit+1), bit);
      temp = (i*byte_bit)+1;
      bbit = bit;
      for(litr=m_RT[temp][bbit].begin(); litr!=m_RT[temp][bbit].end();++litr){
	if(strncmp((const char*)litr->m_stP.stpeerid.id, (const char*)stP.stpeerid.id, m_HASH_SIZE)==0){
	  //allready added
	  return 1;
	}
      }
      list< struct Internalpeerinfo>::iterator ipiter=m_RT[temp][bbit].begin();
      if(ipiter!=m_RT[temp][bbit].end()){
	//uchar_to_bigint(m_RT[temp][bbit].begin()->m_stP.stpeerid.id, m_HASH_SIZE, peerid_old);

	m_BambooDist.getDistance(m_RT[temp][bbit].begin()->ID, m_NodeId, distance1);
	m_BambooDist.getDistance(bPeerid, m_NodeId, distance2);
	if(distance1>distance2){
	  m_RT[temp][bbit].push_front(pinfo);
	}else {
	  m_RT[temp][bbit].push_back(pinfo);
	}
      }else{
        m_RT[temp][bbit].push_back(pinfo);
      }
      TRACE(2,"%s: equal bits %d bit value %d\n", stP.staddr.addr[0], i+1, bit);
      return 1;
  }
  //no prefix match
  bPeerid.getBitAt(m_HASH_SIZE*8 -1, bit);
  temp = 0;
  bbit = 0;
  for(litr=m_RT[temp][bbit].begin(); litr!=m_RT[temp][bbit].end();++litr){
    if(strncmp((const char*)litr->m_stP.stpeerid.id, (const char*)stP.stpeerid.id, m_HASH_SIZE)==0){
      //allready added
      return 1;
    }
  }
  list< struct Internalpeerinfo>::iterator ipiter=m_RT[temp][bbit].begin();
  if(ipiter!=m_RT[temp][bbit].end()){
    //uchar_to_bigint(m_RT[temp][bbit].begin()->m_stP.stpeerid.id, m_HASH_SIZE, peerid_old);
    m_BambooDist.getDistance(m_RT[temp][bbit].begin()->ID, m_NodeId, distance1);
    m_BambooDist.getDistance(bPeerid, m_NodeId, distance2);
    if(distance1>distance2){
      m_RT[temp][bbit].push_front(pinfo);
    }else {
      m_RT[temp][bbit].push_back(pinfo);
    }
  }else{
    m_RT[temp][bbit].push_back(pinfo);
  }

  TRACE(4, "%s: equal bits %d bit value %d\n", stP.staddr.addr[0], 0, 0);
  return 1;
}//Insert
*/

int32 Insert(struct peerinfo& stP, int32 updateKA, uint16 stunPort) {
  //TODO: write code
  return 1;
}

int32 BambooRTable::Insert(list<peerinfo>& lstP, int updateKA) {
  list<peerinfo >::iterator iter;

  //iterate over the list and add it to routing table
  iter=lstP.begin();
  for(;iter!=lstP.end(); iter++){
    Insert(*iter,updateKA);
  }
  return 1;
}//Insert


int32 BambooRTable::getTable(list<peerinfo>& retval) {
  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr;
  list<Internalpeerinfo> ::iterator litr;
  map< BigInt, list<Internalpeerinfo> >::iterator mitr;

  //iterate over the table and add it to the list
  for(mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++){
    for(mitr = mmitr->second.begin();mitr!=mmitr->second.end(); mitr++){
      for(litr=mitr->second.begin(); litr!=mitr->second.end(); litr++){
	
	retval.push_back(litr->m_stP);
	
      }
    }
  }
  return 1;
}//getTable

uint32 BambooRTable::getTableLevel(list<peerinfo>& retval, uchar level) {
  uint32 count=0;
  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr;
  list<Internalpeerinfo> ::iterator litr;
  map< BigInt, list<Internalpeerinfo> >::iterator mitr;

  //return all peers at routing table level 'level'
  retval.clear();
  for(mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++){
    if(count==level)
      {
	mitr = mmitr->second.begin();
	while(mitr!=mmitr->second.end()){
	  for(litr=mitr->second.begin(); litr!=mitr->second.end(); litr++){
	    retval.push_back(litr->m_stP);
	  }
	  mitr++;
	}
	break;
      }
    count++;
  }
  return 1;    
}//getTableLevel

uint32 BambooRTable::getPeerLevelDigit(uint32& level, uint32 digit, peerinfo & stP) {
  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr=m_RT.begin();
  map< BigInt, list<Internalpeerinfo> >::iterator mitr;
  list<Internalpeerinfo> ::iterator litr;
  int count=0, rndm; 
  
  //return routing table entry at a random level
    //l=(uint32)level;
  if(m_iSize<=0)
    return 0;
  rndm=rand()%m_iSize;
  //l=(uint32)level;

  for(level=0,mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++,level++){
    for(mitr = mmitr->second.begin();mitr!=mmitr->second.end(); mitr++){
      for(litr=mitr->second.begin(); litr!=mitr->second.end(); litr++, count++){
	if(rndm==count){
	  stP=litr->m_stP;
	  level=rand()%(m_HASH_SIZE*8);
	  return 1;
	}//end of if
      }//end of for litr
    }//end of for mitr
  }//end of for mmitr

  return 0;
}//getPeerLevelDigit

uint32 BambooRTable::removePeer(char8* pchIP, uint32 port) {
  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr;
  list<Internalpeerinfo> ::iterator litr;
  map< BigInt, list<Internalpeerinfo> >::iterator mitr;

  //iterate and remove
  for(mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++){
    for(mitr = mmitr->second.begin();mitr!=mmitr->second.end(); mitr++){
      for(litr=mitr->second.begin(); litr!=mitr->second.end(); litr++){
	for(int i=0; i<litr->m_stP.staddr.num && litr->m_stP.staddr.isPresent; i++) {
	  if(0 == strcmp(litr->m_stP.staddr.addr[i], pchIP) && port == litr->m_stP.staddr.port[i]) {
	    //remove the peer from routing table
	    TRACE(1, "BambooRTable::RemovePeer...removing peer: %s %d\n", pchIP, port);
	    
	    //remove the peer from the list 
	    mitr->second.erase(litr);
	    
	    //Decrement the number of elements
	    m_iSize--;
	    return 1;                     
	  }//end of if
	}//end address for i
      }//end of for litr
    }//for mitr
  }//for mmitr
  return 1;

}//removePeer

uint32 BambooRTable::OnPLookupRequest(peerinfo strequester, list<peerinfo>& reply) {
  map<BigInt, map<BigInt, list<Internalpeerinfo > > >::iterator mitr;
  BigInt bPeerid, temp, bbit;
  unsigned int bit=0;
  
  uchar_to_bigint(strequester.stpeerid.id, m_HASH_SIZE, bPeerid);

  for(int i=0;i<(int)(m_HASH_SIZE*8/m_BASE);++i)
    {
      temp = bPeerid ^ m_NodeId;
      for(int j=i+1;j<(int)m_HASH_SIZE*8;++j){
	temp.setBitAt((m_HASH_SIZE*8)-j,0);
      }
      if( temp > (BigInt)0) {
	bPeerid.getBitAt(i+1, bit); 
	bbit = bit;
	temp = i;
	reply.push_back( m_RT[temp][bbit].front().m_stP);
	return 1;
      }
    }
  return 0;
}//OnPLookupRequest

void BambooRTable::Show() {
  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr=m_RT.begin();;
  map< BigInt, list<Internalpeerinfo> >::iterator mitr=mmitr->second.begin();
  list<Internalpeerinfo> ::iterator litr=mitr->second.begin();

  BigInt bpeerid;
  int level=0;
  for(mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++, level++){
    for(mitr = mmitr->second.begin();mitr!=mmitr->second.end(); mitr++){
      for(litr=mitr->second.begin(); litr!=mitr->second.end(); litr++){
	
	cout<<"level :" << level <<endl;
	uchar_to_bigint(litr->m_stP.stpeerid.id, m_HASH_SIZE, bpeerid);
	//cout<<"0x"<<bpeerid.ToHex()<<" "<<endl;
	cout<<"0x";
	for(int i=0; i<(int)m_HASH_SIZE; i++) {
	  printf("%x", litr->m_stP.stpeerid.id[i]);
	}
	cout<< "("<< litr->m_stP.staddr.addr[0]<<" "<<litr->m_stP.staddr.port[0]<<")";
	cout<<";" <<endl;
      }
    }
  }

}//Show


int32 BambooRTable::OnTimer() {

  return 1;
}//OnTimer


void BambooRTable::Disconnect() {

  map<BigInt, map<BigInt, list<Internalpeerinfo> > >::iterator mmitr=m_RT.begin();;
  map< BigInt, list<Internalpeerinfo> >::iterator mitr=mmitr->second.begin();
  list<Internalpeerinfo> ::iterator litr=mitr->second.begin();

  for(mmitr=m_RT.begin(); mmitr!=m_RT.end(); mmitr++) {
    for(mitr = mmitr->second.begin();mitr!=mmitr->second.end(); mitr++){
      mitr->second.clear();     
    }    
    mmitr->second.clear();
  }

  m_RT.clear();

}//Disconnect


BambooNTable::BambooNTable(){
}//BambooNTable

BambooNTable::~BambooNTable(){
}//~BambooNTable

void BambooNTable::setNodeId(uchar* chPeerid, int32 len, uint32 base, uint32 k) {
  m_k =k;
  m_HASH_SIZE = len;
  m_BASE = base;
  //create the bigint out of peerid
  uchar_to_bigint(chPeerid, len, m_NodeId);
  
  //Initialize the distance function with base, Id and hashlen
  m_BambooDist.setParams(base, len);
  m_BambooDist.setKey(m_NodeId);

}//setNodeId

uint32 BambooNTable::getSize() {
  return 1;
}//getSize

int32 BambooNTable::Insert(struct peerinfo& peerinfo, int updateKA) {
  BigInt distance, bPeerid, temp;
  Internalpeerinfo pinfo;

  uchar_to_bigint(peerinfo.stpeerid.id, m_HASH_SIZE, bPeerid);
  pinfo.m_stP = peerinfo;
  m_BambooDist.getDistance(m_NodeId, bPeerid,distance);
  if(distance==(BigInt)0)
    return 0;

  temp = m_k;
  if(distance>temp )
    {
      //discard
      return 0;
    } else {
      //insert
      m_NT[distance] = pinfo;
      return 1;
    }
  return 1;
}//Insert

int32 BambooNTable::Insert( list<peerinfo>& lstP, int updateKA) {
  BigInt distance, bPeerid, temp;
  Internalpeerinfo pinfo;
  list< peerinfo >::iterator itr;
  
  itr = lstP.begin();
  while(itr != lstP.end()) {
    uchar_to_bigint(itr->stpeerid.id, m_HASH_SIZE, bPeerid);
    pinfo.m_stP = *itr;
    m_BambooDist.getDistance(m_NodeId, bPeerid, distance);
    temp = m_k;
    if(distance> temp)
      {
	//discard
	return 0;
      } else {

	pinfo.ID = bPeerid;

	//insert in 1 to 2k space
	if(m_NodeId>bPeerid)
	  m_NT[distance*2] = pinfo;
	else
	  m_NT[distance] = pinfo;
	  
	return 1;
      }
    itr++;
  }
  return 1;
}//Insert

int32 BambooNTable::getTable(list<peerinfo >& lstP) {
  map < BigInt, Internalpeerinfo >::iterator miter;
  miter = m_NT.begin();
  while(miter!=m_NT.end()){
    lstP.push_back(miter->second.m_stP);
    miter++;
  }
  return 1;
}//getTable

uint32 BambooNTable::getNextHop(uchar* chPeerid, uint32 len, peerinfo& nextP){
  BigInt distance, bpeerid, temp, min, lpeerid;
  map<BigInt, Internalpeerinfo>::iterator itr;
  uchar_to_bigint(chPeerid, len, bpeerid);
  m_BambooDist.getDistance(bpeerid, m_NodeId, distance);
  temp=m_k;

  return 0;

  if(distance<temp ){
    min = temp+1;
    for(itr=m_NT.begin(); itr!=m_NT.end(); itr++){
      //uchar_to_bigint(itr->second.m_stP.stpeerid.id,len,lpeerid);
      m_BambooDist.getDistance(bpeerid, itr->second.ID, distance);
      if(distance<min){
	min = itr->first;
	nextP = itr->second.m_stP;
      }
    }
    if(min<temp+1)
      return 1;
  }
  return 0;
}//getNextHop

uint32 BambooNTable::getNextHop(BigInt, peerinfo&) {
  return 1;
}//getNextHop


void BambooNTable::Show() {

  list<peerinfo > lstP;
  list< peerinfo>::iterator litr;
  BigInt bpeerid;

  getTable(lstP);
  litr = lstP.begin();
  while(litr!=lstP.end())
    {
      uchar_to_bigint(litr->stpeerid.id, m_HASH_SIZE, bpeerid);
      //cout<<"0x"<<bpeerid.ToHex()<<" ";
      cout<<"0x";
      for(int i=0; i<(int)m_HASH_SIZE; i++) {
	printf("%x", litr->stpeerid.id[i]);
      }
      cout<< "("<< litr->staddr.addr[0]<<" "<<litr->staddr.port[0]<<")";
      cout<<";" <<endl;
      litr++;
    }
}//Show



uint32 BambooRTable::LocalTuning(uchar level, list<peerinfo> table, peerinfo stP){
  map < BigInt, map<BigInt,list<Internalpeerinfo> > >::iterator mmitr;
  map < BigInt, list<Internalpeerinfo> >::iterator mitr;
  list<peerinfo>::iterator litr;
  BigInt distance1, distance2, peerid1, peerid2;
  int count=0,l=0;

  l=(int)level;
  count=0;
  mmitr = m_RT.begin();
  for( ; mmitr!=m_RT.end();++mmitr, ++count){
    if(count==l){
      cout<<"level:"<<l<<" count:"<<count<<" from:" <<stP.staddr.addr[0]<<endl;
      mitr=mmitr->second.begin();
      while(mitr!=mmitr->second.end()){
    
	if(strncmp((char*)mitr->second.begin()->m_stP.stpeerid.id, (char*)stP.stpeerid.id, m_HASH_SIZE)==0){
	  uchar_to_bigint(mitr->second.begin()->m_stP.stpeerid.id, m_HASH_SIZE, peerid1);
	  m_BambooDist.getDistance(m_NodeId, peerid1, distance1);
	  litr=table.begin();
	  while(litr!=table.end()){
	    uchar_to_bigint(litr->stpeerid.id, m_HASH_SIZE, peerid2);
	    if(peerid2==m_NodeId || strcmp(litr->staddr.addr[0],"")==0 || (litr->staddr.port[0]==0)){
	      litr++;
	      continue;
	    }
	    m_BambooDist.getDistance(m_NodeId, peerid2, distance2);
	    Internalpeerinfo stI;
	    stI.m_stP = *litr;
	    if(distance2<distance1){
	      //mitr->second.clear();
	      mitr->second.push_front(stI);
	    } else {
	      //mitr->second.push_back(stI);
	    }
	    litr++;
	  }
	  break;
	}
	mitr++;
      }//while
    }//if(count==level)
  }//for mmitr
  return 1;
}//LocalTuning
    


int32 BambooNTable::OnTimer() {

  return 1;
}//OnTimer


void BambooNTable::Disconnect() {
  //erase the neighbor table
  m_NT.clear();

}//Disconnect

