/**
 * ll-wz.cc
 */

#include "ll-wz.h"
#include "random.h"

#include <address.h>
#include <mac.h>

#include <tcp.h>

//del const int LLWz::mc_iRedefine3DupAck = 5;

static class LLWzClass : public TclClass {
public:
    LLWzClass() : TclClass("LL/LLWz") {}
    TclObject* create(int, const char*const*)
    {
        return (new LLWz());
    }
} llwz_class;

LLWz::LLWz() : LL(), m_dLastRTT(0.0), enableAckControl_(0)
{
	bind("enableAckControl_", &enableAckControl_);
    
	bind("srtt_", &m_dSrtt);
	bind("rttvar_", &m_dRttvar);
	bind("g_", &m_dG);
    bind("ackqueuesize_", &ackqueuesize_);
    bind("Redefine3DupAck_", &m_iRedefine3DupAck);
    bind("WirelessDownTime_", &m_dWirelessDownTime);
    bind("WirelessDownDuration_", &m_dWirelessDownDuration);
    
	//print parameter values
	printf("%x LLWz created --------------------------\n", this);

    m_pktSampleWirelessAck = NULL;
    m_piNextWiredDataToSend = NULL;
    m_iLastWirelessAck = -1;
    m_iLastAckSent = -1;
	m_acTimer = new LLWzACTimerHandler(this);
	m_wirelessUpTimer = new LLWzWirelessUpTimerHandler(this);
    m_iLastWirelessRcvTime = 0.0;
    m_iWirelessAckRcvAvg = 0.0;
    m_fWiredDataRate = 0.0;
    m_actmPending_ = NULL;
    m_wutmPending_ = NULL;

    m_bLastAckSent3Dup = false;

    //delete!  not used.
    m_dLastAckSentTime = 0.0;
}

LLWz::~LLWz()
{
    Packet::free(m_pktSampleWirelessAck);
    delete m_acTimer;
    delete m_wirelessUpTimer;
}

void LLWz::recv(Packet *p, Handler *h)
{
    printf("%f [%s@%x]: LLWz::recv(): type(%i), seqno(%9i)\n", Scheduler::instance().clock(), name(),
        this, hdr_cmn::access(p)->ptype(), hdr_tcp::access(p)->seqno());

	Tcl &tcl = Tcl::instance();

    packet_t ptType = hdr_cmn::access(p)->ptype();
	hdr_cmn *ch = HDR_CMN(p);
    hdr_ip *iph = hdr_ip::access(p);
    hdr_tcp * tcph = hdr_tcp::access(p);

//    int i = hdr_cmn::access(p)->size();
//    printf("packet size = %i\n", i);
    
	if(ch->direction() == hdr_cmn::UP)
	{   //packet received from wireless link.
        if (ptType == PT_ACK && enableAckControl_ != 0)
        {
            //del printf("%f[%s]<-: LLWz::recv() : ack  (%i)\n", Scheduler::instance().clock(), name(), tcph->seqno());
            //keep a copy until we get the first ack for data packet (i.e. to skip conneciton pkt)
            if(m_pktSampleWirelessAck == NULL || hdr_tcp::access(m_pktSampleWirelessAck)->seqno() == 0)
            {   //make a copy of the sample ack.
                m_pktSampleWirelessAck = p->copy();
            }
            receiveWirelessAck(p);
            return;
        }
        else if (ptType == PT_TCP)
        {   //wan TODO:
            printf("%f[%s]<-: LLWz::recv() : seqno(%i)\n", Scheduler::instance().clock(), name(), tcph->seqno());
            abort();
        }
	}
    else
    {   //packet received from wired link.        
        if (ptType == PT_ACK)
        {   //wan TODO:
            printf("%f[%s]->: LLWz::recv() : ack  (%i)\n", Scheduler::instance().clock(), name(), tcph->seqno());
            abort();
        }
        else if (ptType == PT_TCP)
        {
            if(enableAckControl_ != 0)
            {
                //del printf("%f[%s]->: LLWz::recv() : seqno(%i)", Scheduler::instance().clock(), name(), tcph->seqno());
                //only keep seqno. on BS.
                recordPacket(tcph->seqno());
                //del printf("%s dataqueue(%i), ACEnabled(%i)\n", name(), m_lstWiredDataPkt.size(), enableAckControl_);
            }

            if(handleWirelessLinkDown(p, h) == true)
                return;
        }
    }

    LL::recv(p, h);
}

/**
* @return:  true packet is queued because of wireless link down, otherwise, packet should be sent right away.
*/
bool LLWz::handleWirelessLinkDown(Packet *p, Handler *h)
{
        printf("wan testing 15\n");

    double dNow = Scheduler::instance().clock();
    if(dNow > m_dWirelessDownTime && dNow < (m_dWirelessDownTime + m_dWirelessDownDuration))
    {
        printf("wan testing1\n");
        //queue data packet.
        m_lstDelayedWiredDataPkt.insert(m_lstDelayedWiredDataPkt.begin(), QueuedPacketInfo(p, h));
        printf("wan testing 2\n");

        //shcedule timer to send these data packet later.
        Scheduler &s = Scheduler::instance();
        printf("wan testing 3\n");
        if (m_wutmPending_) {
            s.cancel(m_wutmPending_);
            m_wutmPending_ = NULL;
            printf("wan testing 4\n");
        }
        //wan ???
        m_wutmPending_ = (Event *) p;  //m_pktSampleWirelessAck;
        printf("wan testing 5\n");
        s.schedule(m_wirelessUpTimer, m_wutmPending_, m_dWirelessDownTime + m_dWirelessDownDuration - dNow);

        //print queued data
        printf("%s euquedData: ", name());
        list <QueuedPacketInfo>::iterator iter = m_lstDelayedWiredDataPkt.begin();
        while (iter != m_lstDelayedWiredDataPkt.end())
        {
            hdr_tcp * tcph = hdr_tcp::access(iter->m_pPacket);
            printf("%i ", tcph->seqno());
            iter++;
        }
        printf("\n");
                                
        return true;
    }

    return false;
}

/**
* This function is called when a data packet is received on BS from FH.  The seqno and the time when this data was
*   received will be store in a list.
*/
void LLWz::recordPacket(int iSeqno)
{
    list <PacketInfo>::iterator iter = m_lstWiredDataPkt.begin();
    while (iter != m_lstWiredDataPkt.end())
    {
        if(iter->m_iSeqno == iSeqno)
        {
            iter->m_iNumOfRtm++;
            return;
        }
        else if(iter->m_iSeqno < iSeqno)
        {
            break;
        }
      
        iter++;
    }
	//this data packet has been acked, don't store it anymore.  
	//wan: this only works when there is no packet lost in wired link.  The problem could arise when ack send from this
    //  router to FH is lost on wired link, then, we will have to wait for receiver to resend this ack again to trigger
    //  this ack to be reset from router.	
	if(iSeqno <= m_iLastAckSent)
		return; 

    double dNow = Scheduler::instance().clock();
    m_lstWiredDataPkt.insert(iter, PacketInfo(iSeqno, dNow));
    //data queue    ackqueuesize_ = m_lstWiredDataPkt.size();

    //print current data pkt queue.
    printf("%s euque: ", name());
    iter = m_lstWiredDataPkt.begin();
    while (iter != m_lstWiredDataPkt.end())
    {
        printf("%i ", iter->m_iSeqno);
        if (iter->m_iNumOfRtm > 0)
            printf("DdupD");
            
        iter++;
    }
    //data queue    printf(" Total(%i)\n", (int)ackqueuesize_);

//del    updateWiredDataRate(iSeqno, dNow);

}

/**
*wan: this proc is not used.
* updates the data rate received from wired sender.
* @param iSeqno: the largest seqno received so far.
* @param dNow: the time when the iseno received.
*/
void LLWz::updateWiredDataRate(int iSeqno, double dRecvTime)
{
    //Note: this function will not work when data is recv out of order.  but in my simulation so far, all data are recv in order.
    abort();
    
    //we assumes that we started to receive data at the beginning of sim.  This needs to be changed.
    m_fWiredDataRate = iSeqno / dRecvTime;
    printf("%s WiredDataRate %f %f %i\n", name(), dRecvTime, m_fWiredDataRate, iSeqno);
}

/**
* @return: 0 - duplicate/old ACK, if it's the case, packet p IS freed in this function.
*/
int LLWz::receiveWirelessAck(Packet* p)
{
    hdr_tcp * tcph = hdr_tcp::access(p);
    int iSeqno = tcph->seqno();
    if(iSeqno < m_iLastWirelessAck)
    {   //old ack.  this also reduce the chance of trigger 3-dup acks at sender side
        Packet::free(p);
        return 0;
    }

    bool bDupAck = false;
    if(iSeqno == m_iLastWirelessAck)
    {
        //duplicate acks.  this should still be sent to forwarded, otherwise, TCP is changed!!
        bDupAck = true;
    }
    else
    {
        m_iLastWirelessAck = iSeqno; //wehn iSeqno > m_iLastWirelessAck
        ackqueuesize_ = m_iLastWirelessAck - m_iLastAckSent;
    }

    PacketInfo* pktInfo = getAckedPktSendTime(m_iLastWirelessAck);
    Scheduler &s = Scheduler::instance();
    if(pktInfo == NULL && bDupAck == true) //this is the last ack sent, and ack queue is empty.  re-send this ack.
    {
        Packet::free(p);
        //wan TODO: Note this is not right.  probably should be set to the sending time of data packet which triggers this dup-ack.
        double dNow = Scheduler::instance().clock();
        PacketInfo pkInfo(iSeqno, dNow);
        if(m_bLastAckSent3Dup)
        {
            pkInfo.m_iNumOfDupAcks = m_iRedefine3DupAck;
            printf("%s wan dup-ack %i addition, m_iLastWirelessAck=%i, Refefined3DupAck=%i\n", name(), m_iLastWirelessAck,
                m_iLastWirelessAck, m_iRedefine3DupAck);
        }
        else
        {
            pkInfo.m_iNumOfDupAcks = 1;
            printf("%s wan dup-ack %i second dup, m_iLastWirelessAck=%i\n", name(), m_iLastWirelessAck, m_iLastWirelessAck);
        }
        m_lstWiredDataPkt.push_back(pkInfo);
/*        if (m_actmPending_) {
            s.cancel(m_actmPending_);
            m_actmPending_ = NULL;
        }
*/      pktInfo = getAckedPktSendTime(m_iLastWirelessAck);
/*        if (updateNextWiredDataToSend(pktInfo) == true)
        {
            m_actmPending_ = (Event *) m_pktSampleWirelessAck;
            s.schedule(m_acTimer, m_actmPending_, 0);
        }

        // No calcRTT() for similar reason as in TCP for duplicate acks.
        
        return 0;
*/    }
    
    //good ACK.
    //wan TODO: this needs to mofidy too according to last TODO 5 lines up.
    if(pktInfo->m_iSeqno != m_iLastWirelessAck)
    {
        //this should never happen, because we retrieve pktInfo using m_iLastWirelessAck.
        fprintf(stderr, "iack = %i, pktinfo.iseqno = %i\n", m_iLastWirelessAck, pktInfo->m_iSeqno);
        abort();
    }
    if(bDupAck == true)
    {
        pktInfo->m_iNumOfDupAcks++;
        //here since we only send one ack packet, we will need to send 2 more such ack packet to trigger 3-dup at sender side.
        //  Therefore, in total, we need 5-dup acks to trigger the normal 3-dup in TCP.
        if(pktInfo->m_iNumOfDupAcks >= m_iRedefine3DupAck)
        {
            printf("%s sending first dup-ack %i, # of dup = %i\n", name(), m_iLastWirelessAck, pktInfo->m_iNumOfDupAcks);
            Packet::free(p);
            if (m_actmPending_) {
                s.cancel(m_actmPending_);
                m_actmPending_ = NULL;
            }
            if (updateNextWiredDataToSend(pktInfo) == true)
            {
                m_actmPending_ = (Event *) m_pktSampleWirelessAck;
                s.schedule(m_acTimer, m_actmPending_, 0);
            }

            // No calcRTT() for similar reason as in TCP for duplicate acks.

            return 0;
        }
    }
    else //not duplicate ack.
    {
        pktInfo->m_dAckTime = Scheduler::instance().clock();

//wan testing
        double dOldLastWirelessRcvTime = m_iLastWirelessRcvTime;
        if(m_iLastWirelessRcvTime == 0.0)
        {
            m_iLastWirelessRcvTime = Scheduler::instance().clock();
            m_iWirelessAckRcvAvg = 0.0;
        }
        else
        {
            double dOldLastWirelessRcvTime = m_iLastWirelessRcvTime;
            m_iLastWirelessRcvTime = Scheduler::instance().clock();
            m_iWirelessAckRcvAvg = (1 - m_dG) * m_iWirelessAckRcvAvg + m_dG * (m_iLastWirelessRcvTime - dOldLastWirelessRcvTime);
        }
//del        printf("m_iLastWirelessRcvTime(%f), recv interval(%f)\n", m_iWirelessAckRcvAvg,
//            (m_iLastWirelessRcvTime - dOldLastWirelessRcvTime));
//wan testing end

        //No calcRTT() for similar reason as in TCP for duplicate acks.
        calcRtt(pktInfo->m_dSendTime);

        //based on the new calculated srtt to re-schedule next ack to be transmitted.
        Scheduler &s = Scheduler::instance();
        Packet::free(p);
        if (m_actmPending_) {
            s.cancel(m_actmPending_);
            m_actmPending_ = NULL;
        }
        if (updateNextWiredDataToSend() == true)
        {
            m_actmPending_ = (Event *) m_pktSampleWirelessAck;
            double dSendTime = nextAckTime();
            s.schedule(m_acTimer, m_actmPending_, dSendTime);
            printf("scheduled to send packet %i, in %f\n", m_piNextWiredDataToSend->m_iSeqno, dSendTime);
        }
    }

    return 1;
}
/**
* @return: the packinfo of the packet with largest seqno, which has been acked by iAck. Returns NULL if there's no packet
*   in the data queue, or iAck didn't ack any packet.
*/
PacketInfo* LLWz::getAckedPktSendTime(int iAck)
{
    list <PacketInfo>::iterator iter = m_lstWiredDataPkt.begin();
    if(iter == m_lstWiredDataPkt.end())
    {
        return NULL; //there is no data pkt in the queue, OR all data seqno > iAck.
    }

    while (iter != m_lstWiredDataPkt.end())
    {
        if (iter->m_iSeqno <= iAck)
            return &(*iter);
            
        iter++;
    }

    return NULL;
}
/**
* @return: the latest send time of the packet this ACK frees, i.e. the packet with largest seqno. which are freed.
*Note:  the return value is not correctly implimented!!!
*/
double LLWz::clearDataBuf(int iAck)
{
	double dSndTime = -1;

	if (m_lstWiredDataPkt.empty())
		return dSndTime;

    int seq = -1;
    while ( ! m_lstWiredDataPkt.empty())
    {
		if (m_lstWiredDataPkt.back().m_iSeqno <= iAck) //this is NOT "<" instead since ack is NOT next expected seqno in ns!
        {
			dSndTime = m_lstWiredDataPkt.front().m_dSendTime;
            m_lstWiredDataPkt.pop_back();
            //data queue    ackqueuesize_ = m_lstWiredDataPkt.size();
		}
        else //if (seq > ack)
			break;
	}

	return dSndTime;
}

/**
* Update m_piNextWiredDataToSend, which indicates the next ack to send when timer expires.
* @param pNextAck:  if provided, it indicates the next packet to send, otherwise, the next packet to send is the last ack pack in
*   the queue, which should also be the next ack of the last ack sent.
*/
bool LLWz::updateNextWiredDataToSend(PacketInfo* pNextAck)
{
    if(pNextAck != NULL)
    {
        m_piNextWiredDataToSend = pNextAck;
        return true;        
    }
    
    m_piNextWiredDataToSend = NULL;
    if(m_lstWiredDataPkt.empty() == false)
    {
        list <PacketInfo>::iterator iterNextUnSent = m_lstWiredDataPkt.end();
        //iterNextUnSent->m_iSeqno == (m_iLastAckSent + 1) //make sure ack we send is in order.
        while (iterNextUnSent != m_lstWiredDataPkt.begin() && iterNextUnSent->m_iSeqno != (m_iLastAckSent + 1))
            iterNextUnSent--;
            
        if(iterNextUnSent != m_lstWiredDataPkt.begin()
            && iterNextUnSent->m_iSeqno <= m_iLastWirelessAck) //make sure what to send is also acked by receiver!
        {
            m_piNextWiredDataToSend = &(*iterNextUnSent);
        }
/*        && m_lstWiredDataPkt.back().m_iSeqno == (m_iLastAckSent + 1) //make sure ack we send is in order.
        && m_lstWiredDataPkt.back().m_iSeqno <= m_iLastWirelessAck) //make sure what to send is also acked by receiver!
    {
        list <PacketInfo>::iterator iter = m_lstWiredDataPkt.end();
        iter--;
        m_piNextWiredDataToSend = &(*iter);
    }
*/
    }

/*    if(m_piNextWiredDataToSend == NULL)
        printf("m_piNextWiredDataToSend is NULL\n");
    else
        printf("nextseqnotosend(%i), end--(%i), numofDataInQueue(%i)\n", m_piNextWiredDataToSend->m_iSeqno,
            (m_lstWiredDataPkt.end()--)->m_iSeqno, m_lstWiredDataPkt.size());
*/
    return (m_piNextWiredDataToSend != NULL);
}

/**
* This calculates the RTT from the time data packet was received at BS to time when ack packet was received at BS, which includes
*   data packet queue time, transmission time from BS to MH, then process time of MH, ack transmission time from MH to BS.
*
* Note: this works only when every ack is acked!
*/
void LLWz::calcRtt(double dSendTime)
{
    //del printf("%f: Srtt(%f), rttdev(%f), g(%f)", Scheduler::instance().clock(), m_dSrtt, m_dRttvar, m_dG);
	double rtt = Scheduler::instance().clock() - dSendTime;
//    if (rtt > 0
//        && (rtt < m_dSrtt || ackqueuesize_ <= 2))
    if (rtt > 0)
    {
        if(m_dSrtt != 0.0)
            m_dSrtt = (1 - m_dG) * m_dSrtt + m_dG * rtt;
        else
            m_dSrtt = rtt;   //this is for delay [20, 80], rtt;

//wan working: change this to smaller number to see how it works.  check ACK queue.
//m_dSrtt = 0.060;

        double delta = rtt - m_dSrtt;
		if (delta < 0)
			delta = -delta;
		if (m_dRttvar != 0)
			m_dRttvar = (1 - m_dG) * m_dRttvar + m_dG * delta;
		else
			m_dRttvar = delta;
        printf("wan Srtt(%f), rtt(%f), rttdev(%f), delta(%f), g_(%f)\n", m_dSrtt, rtt, m_dRttvar, delta, m_dG);
	}
}

void LLWz::sendNextAck()
{
    printf("wan 8-2 lastsentack(%i), lastrecvack(%i), queued(%i), nowtosend(%i)\n", m_iLastAckSent, m_iLastWirelessAck,
        m_iLastWirelessAck - m_iLastAckSent, m_piNextWiredDataToSend->m_iSeqno);

    if(m_piNextWiredDataToSend == NULL)
    {
        //this should never happen.
        printf("there is no next ack to send\n");
        return;
    }
        
	Scheduler &s = Scheduler::instance();

    if(m_iLastAckSent < m_piNextWiredDataToSend->m_iSeqno)
    {
        m_iLastAckSent = m_piNextWiredDataToSend->m_iSeqno;
        ackqueuesize_ = m_iLastWirelessAck - m_iLastAckSent;
    }
        
    m_bLastAckSent3Dup = (m_piNextWiredDataToSend->m_iNumOfDupAcks >= m_iRedefine3DupAck);
    
    //construct and transmit ACK packet.
    Packet* p = m_pktSampleWirelessAck->copy();
    hdr_tcp::access(p)->seqno() = m_iLastAckSent;
    LL::recv(p, NULL); //del  h);
    double dNow = s.clock();
    printf("sending %i at %f %f, # of dup = %i (%i)\n", m_iLastAckSent, dNow, dNow - m_dLastAckSentTime, m_bLastAckSent3Dup, m_piNextWiredDataToSend->m_iNumOfDupAcks);
    m_dLastAckSentTime = dNow; //del s.clock();
    clearDataBuf(m_iLastAckSent); //clear wired data packet in the queue, which are acked by this ack packet sent.
    updateNextWiredDataToSend();
 
    //Reset timeout for later time if there's more ACK in the queue.
    if(m_piNextWiredDataToSend != NULL)
    {
        if (m_actmPending_)
            s.cancel(m_actmPending_);
        m_actmPending_ = (Event *) m_pktSampleWirelessAck;
        double dSendTime = nextAckTime();
        s.schedule(m_acTimer, m_actmPending_, dSendTime);
        printf("scheduled to send packet %i, at %f\n", m_piNextWiredDataToSend->m_iSeqno, dSendTime);        
    }
}

double LLWz::nextAckTime()
{
//wan testing
//del return m_iWirelessAckRcvAvg;

	//send connection bits right away.
    if (m_iLastAckSent < 1) 
        return 0.0;

//del return m_iWirelessAckRcvAvg/2.0;
        
/*if(m_iLastAckSent <= 10 //pass connection bits. in fact, 1 would be ok here.
    || (m_iLastWirelessAck-m_iLastAckSent) > 1
    || m_dRttvar <= 0.00001)
    return 0.0;
else
    return 10;
  */      
/*    if(m_dRttvar < 0.0001)
        return 0.0;
    else
        return m_iWirelessAckRcvAvg;
  */      
    double dNow = Scheduler::instance().clock();
    double dRet = 0.0; //passed supposed schedule time, transmit right away.
/*    if(dNow < m_dLastAckSentTime + m_dSrtt)
        dRet = m_dLastAckSentTime + m_dSrtt - dNow;
*/
    if(dNow < m_piNextWiredDataToSend->m_dSendTime + m_dSrtt)
        dRet = m_piNextWiredDataToSend->m_dSendTime + m_dSrtt - dNow;

//del    printf("scheduledat %f %f lastsent(%f), srtt(%f)\n", dNow, dRet, m_dLastAckSentTime, m_dSrtt);
    return dRet;
}

void LLWz::sendQueuedDataToWirelessLink()
{
    printf("wan testing 7\n");

    if(m_lstDelayedWiredDataPkt.empty() == false)
    {   //send all the queued data packet while wireless link is down.
        list <QueuedPacketInfo>::iterator iter = m_lstDelayedWiredDataPkt.begin();
        while (iter != m_lstDelayedWiredDataPkt.end())
        {
            LL::recv(iter->m_pPacket, iter->m_pHandler);
            iter++;
        }
    }
    else
    {
        printf("ERROR: scheduled to sent data to wireless link, but there is no data in queue.\n");
    }
    m_lstDelayedWiredDataPkt.clear();

    //print queued data
    printf("%s euquedData: ", name());
    list <QueuedPacketInfo>::iterator iter = m_lstDelayedWiredDataPkt.begin();
    while (iter != m_lstDelayedWiredDataPkt.end())
    {
        hdr_tcp * tcph = hdr_tcp::access(iter->m_pPacket);
        printf("%i ", tcph->seqno());
        iter++;
    }
    printf("\n");
}

/***************  class LLWzACTimerHandler ***************
*/
void
LLWzACTimerHandler::handle(Event *)
{
    m_llwz->m_actmPending_ = NULL; //indicates there is no timer.
    m_llwz->sendNextAck();
        
/*del    Packet *p = snoop_->pkts_[snoop_->buftail_];
	snoop_->toutPending_ = 0;
	if (p == 0)
		return;
	hdr_snoop *sh = hdr_snoop::access(p);
	if (sh->seqno() != snoop_->lastAck_ + 1)
		return;
	if ((snoop_->bufhead_ != snoop_->buftail_) ||
	    (snoop_->fstate_ & SNOOP_FULL))
     {
		//		printf("%f Snoop timeout\n", Scheduler::instance().clock());
		if (snoop_->snoop_rxmit(p) == SNOOP_SUPPRESS)
			snoop_->expNextAck_ = snoop_->next(snoop_->buftail_);
	}*/
}

/***************  class LLWzWirelessUpTimerHandler ***************
*/
void
LLWzWirelessUpTimerHandler::handle(Event *)
{
    printf("wan testing 6\n");
    m_llwz->m_wutmPending_ = NULL; //indicates there is no timer.
    m_llwz->sendQueuedDataToWirelessLink();
}
