/*
 * CsmaCA802_15_4.java
 *
 * Created on June 30, 2008, 5:22 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package sidnet.stack.mac.ieee802_15_4;

import jist.runtime.JistAPI; 
import jist.swans.Constants; 

/**
 *
 * @author Oliver
 * Java adaptation after NS-2 C++ implementation
 */
/*
 * Copyright (c) 2003-2004 Samsung Advanced Institute of Technology and
 * The City University of New York. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Joint Lab of Samsung 
 *      Advanced Institute of Technology and The City University of New York.
 * 4. Neither the name of Samsung Advanced Institute of Technology nor of 
 *    The City University of New York may be used to endorse or promote 
 *    products derived from this software without specific prior written 
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE JOINT LAB OF SAMSUNG ADVANCED INSTITUTE
 * OF TECHNOLOGY AND THE CITY UNIVERSITY OF NEW YORK ``AS IS'' AND ANY EXPRESS 
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 
 * NO EVENT SHALL SAMSUNG ADVANCED INSTITUTE OR THE CITY UNIVERSITY OF NEW YORK 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

class CsmaCA802_15_4
{
    
    //timers
    private macBackoffTimer backoffT;
    private macBeaconOtherTimer	bcnOtherT;
    private macDeferCCATimer deferCCAT;

    private Phy802_15_4Impl phy;
    private Mac802_15_4Impl mac;
    private byte NB;
    private byte CW;
    private byte BE;

    private boolean ackReq;
    private boolean beaconEnabled,beaconOther;
    private boolean waitNextBeacon;
    private double bcnTxTime,bcnRxTime;
    private double bPeriod;				//backoff periods
    private int bPeriodsLeft;			//backoff periods left for next superframe (negative value means no backoff)
    private MacMessage_802_15_4 txPkt;
    
    public CsmaCA802_15_4(Phy802_15_4Impl p, Mac802_15_4Impl m)
    {
        phy = p;
	mac = m;
	txPkt = null;
	waitNextBeacon = false;
	backoffT = new macBackoffTimer(this, mac.localAddr.hashCode());
	assert(backoffT != null);
	bcnOtherT = new macBeaconOtherTimer(this);
	assert(bcnOtherT != null);
	deferCCAT = new macDeferCCATimer(this);
	assert(deferCCAT != null);
    }

    protected void reset()
    {
        if (beaconEnabled)
	{
		NB = 0;
		CW = 2;
		BE = mac.mpib.macMinBE;
		if ((mac.mpib.macBattLifeExt)&&(BE > 2))
			BE = 2;
	}
	else
	{
		NB = 0;
		BE = mac.mpib.macMinBE;
	}
    }
    
    protected double adjustTime(double wtime)
    {
        	//find the beginning point of CAP and adjust the scheduled time
	//if it comes before CAP
	double neg;
	double tmpf;

	assert(txPkt != null);
	if (!mac.toParent(txPkt))
	{
		if (mac.mpib.macBeaconOrder != 15)
		{
			/* Linux floating number compatibility
			neg = (JistAPI.getTime() + wtime - bcnTxTime) - mac.beaconPeriods * bPeriod;
			*/
			{
			tmpf = mac.beaconPeriods * bPeriod;
			tmpf = JistAPI.getTime() - tmpf;
			tmpf += wtime;
			neg = tmpf - bcnTxTime;
			}

			if (neg < 0.0)
				wtime -= neg;
			return wtime;
		}
		else
			return wtime;
	}
	else
	{
		if (mac.macBeaconOrder2 != 15)
		{
			/* Linux floating number compatibility
			neg = (JistAPI.getTime() + wtime - bcnRxTime) - mac.beaconPeriods2 * bPeriod;
			*/
			{
			tmpf = mac.beaconPeriods2 * bPeriod;
			tmpf = JistAPI.getTime() - tmpf;
			tmpf += wtime;
			neg = tmpf - bcnRxTime;
			}

			if (neg < 0.0)
				wtime -= neg;
			return wtime;
		}
		else
			return wtime;
	}
    }
    protected boolean canProceed(double wtime, boolean afterCCA /*= false */) // ???
    {
        //check if can proceed within the current superframe
	//(in the case the node acts as both a coordinator and a device, both the superframes from and to this node should be taken into account)
	hdr_cmn ch = txPkt.HDR_CMN();	//for debug
	boolean ok;
	short t_bPeriods,t_CAP;
	double t_fCAP,t_CCATime,t_IFS,t_transacTime,bcnOtherTime,BI2;

	waitNextBeacon = false;
	wtime = mac.locateBoundary(mac.toParent(txPkt),wtime);
	if (!mac.toParent(txPkt))
	{
		if (mac.mpib.macBeaconOrder != 15)
		{
			if (mac.sfSpec.BLE)
				t_CAP = (short)mac.getBattLifeExtSlotNum();
			else
				t_CAP = (short)((mac.sfSpec.FinCAP + 1) * (mac.sfSpec.sd / Const.aUnitBackoffPeriod) - mac.beaconPeriods);	//(mac.sfSpec.sd % aUnitBackoffPeriod) = 0

			/* Linux floating number compatibility
			t_bPeriods = (short)(((JistAPI.getTime() + wtime - bcnTxTime) / bPeriod) - mac.beaconPeriods);
			*/
			{
			double tmpf;
			tmpf = JistAPI.getTime() + wtime;
			tmpf -= bcnTxTime;
			tmpf /= bPeriod;
			t_bPeriods = (short)(tmpf - mac.beaconPeriods);
			}

			/* Linux floating number compatibility
			if (fmod(JistAPI.getTime() + wtime - bcnTxTime, bPeriod) > 0.0)
			*/
			double tmpf;
			tmpf = JistAPI.getTime() + wtime;
			tmpf -= bcnTxTime;
			if (/*fmod(tmpf, bPeriod) > 0.0*/ tmpf % bPeriod > 0)
				t_bPeriods++;
			bPeriodsLeft = t_bPeriods - t_CAP;
		}
		else
			bPeriodsLeft = -1;
	}
	else
	{
		if (mac.macBeaconOrder2 != 15)
		{
			BI2 = mac.sfSpec2.BI / phy.getRate_BitsPerSecond('s');
			
			/* Linux floating number compatibility
			t_CAP = (short)((mac.macBcnRxTime + (mac.sfSpec2.FinCAP + 1) * mac.sfSpec2.sd ) / phy.getRate('s'));
			*/
			{
			double tmpf;
			tmpf = (mac.sfSpec2.FinCAP + 1) * mac.sfSpec2.sd;
			tmpf += mac.macBcnRxTime;
			t_CAP = (short)(tmpf / phy.getRate_BitsPerSecond('s'));
			}

			/* Linux floating number compatibility
			if (t_CAP + aMaxLostBeacons * BI2 < JistAPI.getTime())
			*/
			double tmpf;
			tmpf = (double)(Const.aMaxLostBeacons * BI2);
			if (t_CAP + tmpf < JistAPI.getTime())	
				bPeriodsLeft = -1;
			else
			{
				if (mac.sfSpec2.BLE)
					t_CAP = (short)mac.getBattLifeExtSlotNum();
				else
					t_CAP = (short)((mac.sfSpec2.FinCAP + 1) * (mac.sfSpec2.sd / Const.aUnitBackoffPeriod) - mac.beaconPeriods2);	

				/* Linux floating number compatibility
				t_bPeriods = (short)(((JistAPI.getTime() + wtime - bcnRxTime) / bPeriod) - mac.beaconPeriods2);
				*/
				{
				//double tmpf;
				tmpf = JistAPI.getTime() + wtime;
				tmpf -= bcnRxTime;
				tmpf /= bPeriod;
				t_bPeriods = (short)(tmpf - mac.beaconPeriods2);
				}

				/* Linux floating number compatibility
				if (fmod(JistAPI.getTime() + wtime - bcnRxTime, bPeriod) > 0.0)
				*/
				//double tmpf;
				tmpf = JistAPI.getTime() + wtime;
				tmpf -= bcnRxTime;
				if (/*fmod(tmpf, bPeriod) > 0.0*/ tmpf % bPeriod > 0)
					t_bPeriods++;
				bPeriodsLeft = t_bPeriods - t_CAP;
			}
		}
		else
			bPeriodsLeft = -1;
	}

	ok = true;
	if (bPeriodsLeft > 0)
		ok = false;
	else if (bPeriodsLeft == 0)
	{
		if ((!mac.toParent(txPkt))
		&&  (!mac.sfSpec.BLE))
			ok = false;
		else if ((mac.toParent(txPkt))
		&&  (!mac.sfSpec2.BLE))
			ok = false;
	}
	if (!ok)
	{
                if(Def.DEBUG802_15_4)
                    System.out.println("[" + /* __FILE__ + */"."+ /* __FUNCTION__ + */ "][" + JistAPI.getTime() /* JistAPI.getTime() */ + "](node " + mac.localAddr /* index_ */ + ") cannot proceed: bPeriodsLeft = " + bPeriodsLeft + ", orders = "+ mac.mpib.macBeaconOrder + "/" + mac.macBeaconOrder2 + "/" + mac.macBeaconOrder3 + ", type = " + Trace.wpan_pName(txPkt) + ", src = " + Trace.p802_15_4macSA(txPkt) + ", dst = " + Trace.p802_15_4macDA(txPkt) + ", uid = " + ch.uid() + ", mac_uid = " + txPkt.HDR_LRWPAN().uid + ", size = " + ch.size());

		if (mac.macBeaconOrder2 != 15)
		if (!mac.bcnRxT.busy())
			mac.bcnRxT.start();
		waitNextBeacon = true;
		return false;
	}

	//calculate the time needed to finish the transaction
	t_CCATime = 8 / phy.getRate_BitsPerSecond('s');
	if (txPkt.HDR_CMN().size() <= Const.aMaxSIFSFrameSize)
		t_IFS = Const.aMinSIFSPeriod;
	else
		t_IFS = Const.aMinLIFSPeriod;
	t_IFS /= phy.getRate_BitsPerSecond('s');
	t_transacTime  = mac.locateBoundary(mac.toParent(txPkt),wtime) - wtime;				//boundary location time -- should be 0 here, since we have already located the boundary
	if (!afterCCA)
	{
		t_transacTime += t_CCATime;									//first CCA time
		t_transacTime += mac.locateBoundary(mac.toParent(txPkt),t_transacTime) - (t_transacTime);	//boundary location time for second CCA
		t_transacTime += t_CCATime;									//second CCA time
	}
	t_transacTime += mac.locateBoundary(mac.toParent(txPkt),t_transacTime) - (t_transacTime);		//boundary location time for transmission
	t_transacTime += phy.trxTime(txPkt, false);									//packet transmission time
	if (ackReq)
	{
		t_transacTime += mac.mpib.macAckWaitDuration/phy.getRate_BitsPerSecond('s');				//ack. waiting time (this value does not include round trip propagation delay)
		t_transacTime += 2 * Const.max_pDelay;									//round trip propagation delay (802.15.4 ignores this, but it should be there even though it is very small)
		t_transacTime += t_IFS;										//IFS time -- not only ensure that the sender can finish the transaction, but also the receiver
		t_fCAP = mac.getCAP(true);

		/* Linux floating number compatibility
		if (JistAPI.getTime() + wtime + t_transacTime > t_fCAP)
		*/
		double tmpf;
		tmpf = JistAPI.getTime() + wtime;
		tmpf += t_transacTime;
		if (tmpf > t_fCAP)
			ok = false;
		else
			ok= true;
	}
	else
	{
		//in this case, we need to handle individual CAP 
		ok = true;
		t_fCAP = mac.getCAPbyType(1);

		/* Linux floating number compatibility
		if (JistAPI.getTime() + wtime + t_transacTime > t_fCAP)
		*/
		double tmpf;
		tmpf = JistAPI.getTime() + wtime;
		tmpf += t_transacTime;
		if (tmpf > t_fCAP)
			ok = false;
		if (ok)
		{
			t_fCAP = mac.getCAPbyType(2);
			t_transacTime += Const.max_pDelay;						//one-way trip propagation delay (802.15.4 ignores this, but it should be there even though it is very small)
			t_transacTime += 12/phy.getRate_BitsPerSecond('s');					//transceiver turn-around time (receiver may need to do this to transmit next beacon)
			t_transacTime += t_IFS;							//IFS time -- not only ensure that the sender can finish the transaction, but also the receiver

			/* Linux floating number compatibility
			if (JistAPI.getTime() + wtime + t_transacTime > t_fCAP)
			*/
			//double tmpf;
			tmpf = JistAPI.getTime() + wtime;
			tmpf += t_transacTime;
			if (tmpf > t_fCAP)
				ok = false;
		}
		if (ok)
		{
			t_fCAP = mac.getCAPbyType(3);
			t_transacTime -= t_IFS;							//the third node does not need to handle the transaction

			/* Linux floating number compatibility
			if (JistAPI.getTime() + wtime + t_transacTime > t_fCAP)
			*/
			//double tmpf;
			tmpf = JistAPI.getTime() + wtime;
			tmpf += t_transacTime;
			if (tmpf > t_fCAP)
				ok = false;
		}
	}

	//check if have enough CAP to finish the transaction
	if (!ok)
	{
		bPeriodsLeft = 0;
		if ((mac.mpib.macBeaconOrder == 15)
		&&  (mac.macBeaconOrder2 == 15)
		&&  (mac.macBeaconOrder3 != 15))
		{
			/* Linux floating number compatibility
			bcnOtherTime = (mac.macBcnOtherRxTime + mac.sfSpec3.BI) / phy.getRate('s');
			*/
			{
			double tmpf;
			tmpf = (mac.macBcnOtherRxTime + mac.sfSpec3.BI);
			bcnOtherTime = tmpf / phy.getRate_BitsPerSecond('s');
			}

			while (bcnOtherTime < JistAPI.getTime())
				bcnOtherTime += (mac.sfSpec3.BI / phy.getRate_BitsPerSecond('s'));
			bcnOtherT.startTimer(bcnOtherTime - JistAPI.getTime());
		}
                if(Def.DEBUG802_15_4)
                    System.out.println("[" + /* __FILE__ + */"."+ /* __FUNCTION__ + */ "][" + JistAPI.getTime() /* JistAPI.getTime() */ + "](node " + mac.localAddr /* index_ */ + ") cannot proceed: orders = "+ mac.mpib.macBeaconOrder + "/" + mac.macBeaconOrder2 + "/" + mac.macBeaconOrder3 + ", type = " + Trace.wpan_pName(txPkt) + ", src = " + Trace.p802_15_4macSA(txPkt) + ", dst = " + Trace.p802_15_4macDA(txPkt) + ", uid = " + ch.uid() + ", mac_uid = " + txPkt.HDR_LRWPAN().uid + ", size = " + ch.size());

		if (mac.macBeaconOrder2 != 15)
		if (!mac.bcnRxT.busy())
			mac.bcnRxT.start();
		waitNextBeacon = true;
		return false;
	}
	else
	{
		bPeriodsLeft = -1;
		return true;
	}
    }
    
    protected void newBeacon(char trx)
    {
        //this function will be called by MAC each time a new beacon received or sent within the current PAN
	double rate,wtime;

	if (mac.txAck == null)
		mac.plme_set_trx_state_request(PHYenum.p_RX_ON);	

	if (bcnOtherT.busy())
		bcnOtherT.stopTimerr();

	//update values
	beaconEnabled = ((mac.mpib.macBeaconOrder != 15)||(mac.macBeaconOrder2 != 15));
	beaconOther = (mac.macBeaconOrder3 != 15);
	reset();	
	rate = phy.getRate_BitsPerSecond('s');
	bcnTxTime = mac.macBcnTxTime / rate;
	bcnRxTime = mac.macBcnRxTime / rate;
	bPeriod = Const.aUnitBackoffPeriod / rate;

	if (waitNextBeacon)
	if ((txPkt != null) && (!backoffT.busy()))
	{
		assert(bPeriodsLeft >= 0);
		if (bPeriodsLeft == 0)
		{
			wtime = adjustTime(0.0);
			if (canProceed(wtime, false));
				backoffHandler();	//no need to resume backoff
		}
		else
		{
			wtime = adjustTime(0.0);
			wtime += bPeriodsLeft * bPeriod;
			if (canProceed(wtime, false));
                            backoffT.startTimer(wtime);
		}
	}
	waitNextBeacon = false;
    }
    
    protected void start(boolean firsttime,MacMessage_802_15_4 pkt /*= 0 */,boolean ackreq /*= 0*/) // ???
    {
        boolean backoff;
	double rate,wtime,BI2;

         if (Def.DEBUG802_15_4)
            System.out.println("[CSMACA.start()]");

	if (mac.txAck != null)
	{
		mac.backoffStatus = 0;
		txPkt = null;
		return;
	}

	assert(backoffT.busy() == false);
	if (firsttime)
	{
		beaconEnabled = ((mac.mpib.macBeaconOrder != 15)||(mac.macBeaconOrder2 != 15));
		beaconOther = (mac.macBeaconOrder3 != 15);
		reset();	
		assert(txPkt == null);
		txPkt = pkt;
		ackReq = ackreq;
		rate = phy.getRate_BitsPerSecond('s');
		bPeriod = Const.aUnitBackoffPeriod / rate;
		if (beaconEnabled)
		{
			bcnTxTime = mac.macBcnTxTime / rate;
			bcnRxTime = mac.macBcnRxTime / rate;
			//it's possible we missed some beacons
			BI2 = (mac.sfSpec2.BI / phy.getRate_BitsPerSecond('s'));
			if (mac.macBeaconOrder2 != 15)
			while (bcnRxTime + BI2 < JistAPI.getTime())
				bcnRxTime += BI2;
		}
	}
        wtime = 0;
        while (wtime <= 0)
            wtime = (/*Random.random()*/ Constants.random.nextInt() % (1<<BE)) * bPeriod;
	wtime = adjustTime(wtime);
	backoff = true;
	if (beaconEnabled||beaconOther)
	{
		if (beaconEnabled)
		if (firsttime)
			wtime = mac.locateBoundary(mac.toParent(txPkt),wtime);
		if (!canProceed(wtime, false))		
			backoff = false;
	}
	if (backoff)
             backoffT.startTimer(wtime);

    }
    
    protected void cancel()
    {
        if (bcnOtherT.busy())
		bcnOtherT.stopTimerr();
	else if (backoffT.busy())
		backoffT.stopTimerr();
	else if (deferCCAT.busy())
		deferCCAT.stopTimerr();
	else
		//mac.taskP.taskStatus(TP_CCA_csmaca) = false;
                mac.taskP.setTaskStatus(taskPending.TP_CCA_csmaca, false);
	txPkt = null;
    }
    protected void backoffHandler()
    {
        if (Def.DEBUG802_15_4)
            System.out.println("[CSMACA.backoffHandler()]");
        //mac.taskP.taskStatus(TP_RX_ON_csmaca) = true;
        mac.taskP.setTaskStatus(taskPending.TP_RX_ON_csmaca, true);
	mac.plme_set_trx_state_request(PHYenum.p_RX_ON);
    }
    
    protected void RX_ON_confirm(PHYenum status)
    {
        double now,wtime;

	if (status != PHYenum.p_RX_ON)
	{
		if (status == PHYenum.p_BUSY_TX)
			//mac.taskP.taskStatus(TP_RX_ON_csmaca) = true;
                        mac.taskP.setTaskStatus(taskPending.TP_RX_ON_csmaca, true);
		else
			backoffHandler();
		return;
	}

	//locate backoff boundary if needed
	now = JistAPI.getTime();
	if (beaconEnabled)
		wtime = mac.locateBoundary(mac.toParent(txPkt),0.0);
	else
		wtime = 0.0;

	if (wtime == 0.0)
	{
		//mac.taskP.taskStatus(Def.TP_CCA_csmaca) = true;
                mac.taskP.setTaskStatus(taskPending.TP_CCA_csmaca, true);
		phy.PLME_CCA_request();
	}
	else
		deferCCAT.startTimer(wtime);
    }
    
    protected void bcnOtherHandler()
    {
        newBeacon('R');
    }
    
    protected void deferCCAHandler()
    {
        //mac.taskP.taskStatus(Def.TP_CCA_csmaca) = true;
        mac.taskP.setTaskStatus(taskPending.TP_CCA_csmaca, true);
	phy.PLME_CCA_request();
    }
    protected void CCA_confirm(PHYenum status)
    {
        	//This function should be called when mac receiving CCA_confirm.
	boolean idle;

	idle = (status == PHYenum.p_IDLE) ? true : false;	
	if (idle)
	{
		if ((!beaconEnabled)&&(!beaconOther))
		{
			txPkt = null;
			mac.csmacaCallBack(PHYenum.p_IDLE);
		}
		else
		{
			if (beaconEnabled)
				CW--;
			else
				CW = 0;
			if (CW == 0)
			{
				//timing condition may not still hold -- check again
				if (canProceed(0.0, true))
				{
					txPkt = null;
					mac.csmacaCallBack(PHYenum.p_IDLE);
				}
				else	//postpone until next beacon sent or received
				{
					if (beaconEnabled) CW = 2;
					bPeriodsLeft = 0;
				}
			}
			else	//perform CCA again
				backoffHandler();
		}
	}
	else	//busy
	{
		if (beaconEnabled) CW = 2;
		NB++;
		if (NB > mac.mpib.macMaxCSMABackoffs)
		{
			txPkt = null;
			mac.csmacaCallBack(PHYenum.p_BUSY);
		}
		else	//backoff again
		{
			BE++;
			if (BE > Const.aMaxBE)
				BE = Const.aMaxBE;
			start(false, null, false);
		}
	}
    }
};
/* END *** CSMACA *** */
