/*
 * EnergyModel.java
 *
 * Created on May 10, 2006, 10:39 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

/* 
 * Implementation details:
 *  At the first sight, it is a cumbersome way of computing the energyLevel. In a nutshell, during simulation time we record the total amount of time a functional entity
 *  (Radio, CPU or sensor) is staying in a given state, and the actual energyLevel is computed upon request by subtracting from the battery capacity the costs associated
 *  with each state. 
 *
 */

package sidnet.models.energy.energyconsumption;

import sidnet.models.energy.batteries.Battery;
import jist.runtime.JistAPI;
import jist.swans.Constants;
/**
 *
 * @author Oliviu Ghica
 */
public class EnergyModelImpl implements EnergyModel, EnergyModelAccessible{
    // CONSTANTS
    public static final boolean OK         = true;
    public static final boolean ERROR      = false;
    
    // Events
    public static final int RADIO_TRANSMIT = 3;
    public static final int RADIO_RECEIVE  = 4;
    
    // States of the functional units in a node. They also act as triggering events
    public static final int RADIO_LISTEN   = 5;
    public static final int RADIO_SLEEP    = 6;
    
    private int cpuDutyCycle = 100;     // percentage; 100% - fully loaded; 0% - idling; default: 100
    
    private static EnergyCostParameters eCostParam;
    
    /* Radio energy */
    public long lastRadioTransmitTime, totalRadioTransmitDuration; // all expressed in [ms]
    public long lastRadioReceiveTime, totalRadioReceiveDuration;
    public long lastRadioListenTime, totalRadioListenDuration;
    public long lastRadioSleepTime, totalRadioSleepDuration;
    
    /* CPU energy */
    private long lastCPUEventTime, totalCPUActiveDuration;  // in [ms]
    
    /* Sensing energy */
    private long lastSensorActiveTime, totalSensorActiveDuration; // in [ms]

    private int RadioState;              // The previous state of the Radio
    private int CPUState;                // The previous state of the CPU
    
    private int id;                     // for debugging purposes only
    
    private Battery battery;
    
    /** Creates a new instance of EnergyModel */
    public EnergyModelImpl(EnergyCostParameters eCostParam, Battery battery) {
       this.eCostParam = eCostParam;   
       RadioState  = RADIO_LISTEN;

       this.id = id;
       this.battery = battery;
    }
    
    public void setID(int id)
    {
        this.id = id;
    }
    
    public int getID()
    {
        return id;
    }
    
    public int getRadioState()
    {
        return RadioState;
    }
    
    public synchronized double getEnergyLevel(){
        double energyLevel = battery.getEnergyLevel();
        long now = JistAPI.getTime()/Constants.MILLI_SECOND;
        
        if (battery.getCapacity() != battery.INF)
        {   
            /* Radio energy toll */
            energyLevel -= totalRadioTransmitDuration * eCostParam.Radio_Transmitting_Cost;
            energyLevel -= totalRadioReceiveDuration * eCostParam.Radio_Receiving_Cost;
            if (RadioState == RADIO_LISTEN)
            {
                totalRadioListenDuration = now - totalRadioTransmitDuration
                                               - totalRadioReceiveDuration
                                               - totalRadioSleepDuration;
                energyLevel -= totalRadioSleepDuration  * eCostParam.Radio_Sleeping_Cost;
                energyLevel -= totalRadioListenDuration * eCostParam.Radio_Listening_Cost;
            }
            else    /* RadioState == RADIO_SLEEP */
            {
                totalRadioSleepDuration = now - totalRadioTransmitDuration
                                              - totalRadioReceiveDuration
                                              - totalRadioListenDuration;
                energyLevel -= totalRadioListenDuration * eCostParam.Radio_Listening_Cost;
                energyLevel -= totalRadioSleepDuration  * eCostParam.Radio_Sleeping_Cost;
            }
            
            /* CPU toll */
            //UpdateCPUEnergy(now);
            energyLevel -= totalCPUActiveDuration * eCostParam.CPU_Processing_Cost;                                                     /* Cost due to processing period */
            energyLevel -= (now - totalCPUActiveDuration) * eCostParam.CPU_Idling_Cost;            /* Cost due to idling period */

            /* Sensor toll */
            energyLevel -= totalSensorActiveDuration * eCostParam.Sensor_Active_Cost;                                                   /* Cost due to active period */
            energyLevel -= (now - totalSensorActiveDuration) * eCostParam.Sensor_Passive_Cost;     /* Cost due to passive period */
            
            if (energyLevel < 0)
                energyLevel = 0;
        }
         
        return energyLevel;
    }
    
    public void simulateSensing(long duration)
    {
        long eventTime = JistAPI.getTime()/Constants.MILLI_SECOND;
        duration = duration / Constants.MILLI_SECOND;
        
        /* Consistency check */
        if (eventTime < lastSensorActiveTime)
        {
            System.out.println("[" + JistAPI.getTime()/Constants.MILLI_SECOND+"][CRITICAL ERROR: energyModel node] - Out of order timestamps in simulateSensing()");
            Exception e = new Exception("[" + JistAPI.getTime()/Constants.MILLI_SECOND+"][CRITICAL ERROR: energyModel node] - Out of order timestamps in simulateSensing()");
            e.printStackTrace();
            System.exit(1);
        }
        
        /* Update durations */
        totalSensorActiveDuration += duration;
        lastSensorActiveTime = eventTime + duration;
    }
    
    public synchronized void setCPUDutyCycle(int cpuDutyCycle)
    {
        UpdateCPUEnergy(JistAPI.getTime());
        this.cpuDutyCycle = cpuDutyCycle;
    }    
    
    public synchronized void simulateCPUActivity(long duration, int cpuDutyCycle) 
    {
        setCPUDutyCycle(cpuDutyCycle); 
        JistAPI.sleepBlock(duration);
        setCPUDutyCycle(0); 
    }
    
    private synchronized void UpdateCPUEnergy(long eventTime) 
    {
        if (eventTime == 0) // the enery map calls this at 0 and fails ... we must prevent this at 0, but no error, obviously, since this is not caused by nodes' activity
            return;
        eventTime = eventTime / Constants.MILLI_SECOND;
        if (eventTime < lastCPUEventTime)
        {
            System.out.println("[ENERGY MODEL #"+id+"] - Out of order time stamps in the CPU energy model (UpdateCPUEnergy)" +
                     "\n: eventTime(" + eventTime + ")[ms] should be bigger than the lastCPUEventTime (" + lastCPUEventTime + ")[ms]");
             Exception e = new Exception("[ENERGY MODEL #"+id+"] - Out of order time stamps in the CPU energy model (UpdateCPUEnergy)" +
                     "\n: eventTime(" + eventTime + ")[ms] should be bigger than the lastCPUEventTime (" + lastCPUEventTime + ")[ms]");
             e.printStackTrace();
             System.exit(1);
        }
        if (eventTime < totalCPUActiveDuration)
        {
            System.out.println("[ENERGY MODEL #"+id+"] - Out of order time stamps in the CPU energy model (UpdateCPUEnergy)" +
                     "\n: eventTime(" + eventTime + ")[ms] should be bigger than the totalCPUActiveDuration (" + totalCPUActiveDuration + ")[ms]");
             Exception e = new Exception("[ENERGY MODEL #"+id+"] - Out of order time stamps in the CPU energy model (UpdateCPUEnergy)" +
                     "\n: eventTime(" + eventTime + ")[ms] should be bigger than the totalCPUActiveDuration (" + totalCPUActiveDuration + ")[ms]");
             e.printStackTrace();
             System.exit(1);
        }
        
        totalCPUActiveDuration += cpuDutyCycle  * (eventTime - lastCPUEventTime) / 100;
        lastCPUEventTime = eventTime;   
        // totalCPUIdleDuration is computed by subtracting totalCPUActiveDuration from the total simulation time 
    }
    
    public void simulatePacketReceive(long eventTime, long duration)
    {          
        if (RadioState == RADIO_SLEEP)
            return;
        eventTime = eventTime / Constants.MILLI_SECOND;
        duration = duration / Constants.MILLI_SECOND;
        
        /* Consistency check */
        if (eventTime < lastRadioReceiveTime ||
            eventTime < lastRadioTransmitTime)
        {
            System.out.println("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulatePacketReceive()");
            Exception e = new Exception("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulatePacketReceive()");
            e.printStackTrace();
            System.exit(1);
        }
        
        /* Update durations */
        totalRadioReceiveDuration += duration;
        lastRadioReceiveTime = eventTime + duration;
    }
    
    public void simulatePacketTransmit(long duration)
    {
        long eventTime = JistAPI.getTime()/Constants.MILLI_SECOND;
        duration = duration / Constants.MILLI_SECOND;
        if (RadioState == RADIO_SLEEP)
            return;
        
        /* Consistency check */
        if (eventTime < lastRadioReceiveTime ||
            eventTime < lastRadioTransmitTime)
        {
            System.out.println("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulatePacketTransmit()");
            return;
        }
        
        /* Update durations */
        totalRadioTransmitDuration += duration;
        lastRadioTransmitTime = eventTime + duration;
    }
    
   
    
    public void simulateRadioGoesToSleep()
    {
        long eventTime = JistAPI.getTime()/Constants.MILLI_SECOND;
        if (RadioState == RADIO_SLEEP)
            return;         /* Node already in the sleep mode. Do nothing */
        
        /* Consistency check */
        if (eventTime < lastRadioSleepTime ||
            eventTime < lastRadioListenTime  ||
            eventTime < lastRadioTransmitTime ||
            eventTime < lastRadioReceiveTime)
        {
            System.out.println("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulateRadioGoesToSleep()");
            Exception e = new Exception("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulateRadioGoesToSleep()");
            System.exit(1);
        }
        
        /* Update durations */
        totalRadioListenDuration = JistAPI.getTime()/Constants.MILLI_SECOND - totalRadioTransmitDuration
                                                                            - totalRadioReceiveDuration
                                                                            - totalRadioSleepDuration;
        lastRadioListenTime = eventTime;
        
        RadioState = RADIO_SLEEP;
    }
    
    public void simulateRadioWakes()
    {
        long eventTime = JistAPI.getTime()/Constants.MILLI_SECOND;
        if (RadioState == RADIO_LISTEN)
            return;         /* Node already in the listening/wake mode. Do nothing */
        
        /* Consistency check */
        if (eventTime < lastRadioSleepTime ||
            eventTime < lastRadioListenTime  ||
            eventTime < lastRadioTransmitTime ||
            eventTime < lastRadioReceiveTime)
        {
            System.out.println("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulateRadioWakes()");
            Exception e = new Exception("[CRITICAL ERROR: energyModel #"+id+"] - Out of order timestamps in simulateRadioWakes()");
            e.printStackTrace();
            System.exit(1);
        }
        
        /* Update durations */
        totalRadioSleepDuration = JistAPI.getTime()/Constants.MILLI_SECOND - totalRadioTransmitDuration
                                                                            - totalRadioReceiveDuration
                                                                            - totalRadioListenDuration;
        lastRadioSleepTime = eventTime;  
        
        RadioState = RADIO_LISTEN;
    }
      
    public void println()
    {
        System.out.println("totalRadioTransmitDuration: " + totalRadioTransmitDuration);
        System.out.println("totalRadioReceiveDuration : " + totalRadioReceiveDuration);
        System.out.println("totalRadioListenDuration  : " + totalRadioListenDuration);
        System.out.println("totalRadioSleepDuration   : " + totalRadioSleepDuration);    
        System.out.println("totalCPUEffectiveActiveDuration" + totalCPUActiveDuration);
    }
}
