/*
ECE649 Spring 2010
Group 7
Justin Ray/justinr2
(other names would go here)
 */
package simulator.elevatorcontrol;

// simulator.framework gives us access to CNI, PFI and Harness
import jSimPack.SimTime;
import simulator.elevatormodules.EmptyCanPayloadTranslator;
import simulator.framework.*;
// simulator.payloads gives us access to all MessagePayload classes
import simulator.payloads.*;

/************************************
CLASS:
ButtonControl
AUTHOR:
Justin Ray
REVISION DATE:
CONSTRUCTOR PARAMETERS:
int index - the index of the Button
boolean verbose - whether or not to do verbose output
simTime period - the period the controller will run at
DESCRIPTION:
This controller class handles button presses and the light output for a single button.
Source of:
 * mButton (network)
 * Light (framework)
Sink of:
 * mCoinCount (network)
 * mEmpty (network)
 * mVend (network)
 * Button (framework)
 *************************************/
public class ButtonControl extends Controller implements TimeSensitive {

    // define convenience state variables to track what state we are in
    public static enum OuterState {

        IDLE,
        EMPTY,
        VEND
    }

    public static enum InnerState {

        FLASH_ON,
        FLASH_OFF
    }
    // defines (in microseconds) how often this module should send out messages
    // and update its state
    SimTime period;
    // Physical and network interface objecst for ButtonControl
    //physical interface
    ButtonPayload localButton;
    LightPayload localLight;
    //output network
    ButtonCanPayloadTranslator mButton;
    //input network
    EmptyCanPayloadTranslator mEmpty;
    VendCanPayloadTranslator mVend;
    CoinCountCanPayloadTranslator mCoinCount;
    //OuterState variables
    int index;  // Keep track of index button/light we're controlling
    OuterState currentOuterState;  // the current state machine state
    InnerState currentInnerState;  // the current state machine state for the VEND substates
    int flashCounter = 0; //used to track time in the flashing states
    //constants
    private final int FLASH_LIMIT;
    private final static int FLASH_HALF_PERIOD = 500;//ms

    public ButtonControl(int index, SimTime period, boolean verbose) {
        super("ButtonControl[" + index + "]", verbose);

        // set local variables to match constructor parameters
        this.index = index;
        this.period = period;

        //compute constants
        //compute constants
        if (FLASH_HALF_PERIOD % period.getTruncMilliseconds() != 0) {
            throw new RuntimeException("Error:  The Flash half-period of 125ms must be evenly divisible by the controller period.  Current period=" + period.toString(SimTime.SimTimeUnit.MILLISECOND));
        }
        FLASH_LIMIT = (int) (FLASH_HALF_PERIOD / period.getTruncMilliseconds());

        //instantiate physical interface objects
        localButton = new ButtonPayload(index);
        localLight = new LightPayload(index);
        physicalInterface.registerTimeTriggered(localButton);
        physicalInterface.sendTimeTriggered(localLight, period);

        // Register for fault messages
        //myFI.RegisterEventTriggered(MessagePayload.FaultEvent);

        //instantiate message objects
        CanMailbox p;
        /*
         * Note: we don't need to keep a reference to the actual mailbox (since we
         * only interact with them through the translators anyway), so we use
         * a local variable in the constructor for the mailbox.
         */

        p = new CanMailbox(MessageDictionary.BUTTON_BASE_CAN_ID + ReplicationComputer.computeReplicationId(index));
        mButton = new ButtonCanPayloadTranslator(p, index);
        mButton.setButtonState(localButton.pressed);
        canInterface.sendTimeTriggered(p, period);

        p = new CanMailbox(MessageDictionary.EMPTY_BASE_CAN_ID + ReplicationComputer.computeReplicationId(index));
        mEmpty = new EmptyCanPayloadTranslator(p, index);
        canInterface.registerTimeTriggered(p);

        p = new CanMailbox(MessageDictionary.VEND_CAN_ID);
        mVend = new VendCanPayloadTranslator(p);
        canInterface.registerTimeTriggered(p);

        p = new CanMailbox(MessageDictionary.COIN_COUNT_CAN_ID);
        mCoinCount = new CoinCountCanPayloadTranslator(p);
        canInterface.registerTimeTriggered(p);

        // initialize the controller to the OFF state
        currentOuterState = OuterState.IDLE;

        //initialize the outputs to the IDLE state
        doIdle();
        log("Intital State: " + currentOuterState);

        // start a timer to interrupt based on the period
        timer.start(period);
    }

    // Necessary for extending the Networkable class
    public void timerExpired(Object callbackData) {
        log("Executing ButtonControl in " + currentOuterState);
        OuterState newState = currentOuterState; //if no transition occurs, remain in the same state
        switch (currentOuterState) {
            case IDLE:
                //execute state actions
                doIdle();
                //check transitions
//#transition 'T2.1'
                if (localButton.pressed == true &&
                        mEmpty.getValue() == false &&
                        mVend.getVendState() == false &&
                        mCoinCount.getCoinCount() == SodaMachine.SODA_COST) {
                    log("T2.1");
                    //log("button=",localButton.pressed,"  empty=",mEmpty.getValue(),"  mVend=",mVend.getVendState());
                    newState = OuterState.VEND;
//#transition 'T2.6'
                } else if (mEmpty.getValue() == true) {
                    log("T2.6");
                    newState = OuterState.EMPTY;
                }

                break;
            case EMPTY:
                //exectue state actions
                doEmpty();

                //check transitions
//#transition 'T2.7'
                if (mEmpty.getValue() == false) {
                    log("T2.7");
                    newState = OuterState.IDLE;
                }

                break;
            case VEND:
                //exectue state actions
                doVend();

                //execute inner state machine
                log("Executing VEND substates in " + currentInnerState);
                InnerState newVendState = currentInnerState; //if no transition, the next state is the same as the current one.
                switch (currentInnerState) {
                    case FLASH_OFF:
                        //execute state actions
                        doFlashOff();
                        //execute transitions
//#transition 'T2.4'
                        if (flashCounter >= FLASH_LIMIT) {
                            log("T2.4");
                            newVendState = InnerState.FLASH_ON;
                        }
                        break;

                    case FLASH_ON:
                        //execute state actions
                        doFlashOn();
                        //execute transitions
//#transition 'T2.5'
                        if (flashCounter == 0) {
                            log("T2.5");
                            newVendState = InnerState.FLASH_OFF;
                        }
                        break;
                    default:
                        throw new RuntimeException("Unrecognized state " + currentInnerState);
                }
                if (currentInnerState != newVendState) {
                    log("VEND Transition:  " + currentInnerState + "->" + newVendState);
                }

                //advance to the next state we have computed
                currentInnerState = newVendState;




                //check transitions out of VEND state
//#transition 'T2.2'
                if (mVend.getVendState() == true && mEmpty.getValue() == false) {
                    log("T2.2");
                    newState = OuterState.IDLE;
//#transition 'T2.3'
                } else if (mVend.getVendState() == true && mEmpty.getValue() == true) {
                    log("T2.3");
                    newState = OuterState.EMPTY;
                }
                break;
            default:
                throw new RuntimeException("Unrecognized state " + currentOuterState);
        }
        if (currentOuterState != newState) {
            log("Transition:  " + currentOuterState + "->" + newState);
        }

        //advance to the next state we have computed
        currentOuterState = newState;

        // we start the timer to interrupt us again at the next period
        timer.start(period);
    }

    /**
     * actions for the IDLE state.
     */
    private void doIdle() {
        currentInnerState = InnerState.FLASH_OFF; //reset the inner state variable
        localLight.set(true);
        mButton.set(false);
        flashCounter = 0;
    }

    /**
     * actions for the EMPTY state.
     */
    private void doEmpty() {
        currentInnerState = InnerState.FLASH_OFF; //reset the inner state variable
        localLight.set(false);
        mButton.set(false);
        flashCounter = 0;
    }

    /**
     * actions for the VEND state.
     */
    private void doVend() {
        //no actions associated with this state
    }

    /**
     * actions for the FLASH_OFF state.
     */
    private void doFlashOff() {
        localLight.set(false);
        mButton.set(true);
        flashCounter++;
    }

    /**
     * actions for the FLASH_ON state.
     */
    private void doFlashOn() {
        localLight.set(true);
        mButton.set(true);
        flashCounter--;
    }
}
