/*
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.CoinReturnCanPayloadTranslator;
import simulator.framework.*;
// simulator.payloads gives us access to all MessagePayload classes
import simulator.payloads.*;

/************************************
CLASS:
CoinControl
AUTHOR:
Justin Ray
REVISION DATE:
CONSTRUCTOR PARAMETERS:
boolean verbose - whether or not to do verbose output
simTime period - the period the controller will run at
DESCRIPTION:
This controller class handles the coins put into and returned by the soda machine.
Source of:
 * mCoinCount (network)
 * CoinOut (framework)
Sink of:
 * mVend (network)
 * mVendMotor (network)
 * mCoinReturn (network)
 * CoinIn (framework)
 *************************************/
public class CoinControl extends Controller implements TimeSensitive {

    // define convenience state variables to track what state we are in
    public static enum State {

        IDLE,
        COIN_IN_1,
        COIN_IN_2,
        OVERPAY,
        OVERPAY_STRETCH,
        VEND,
        RETURN_1,
        RETURN_2,
        RETURN_STRETCH
    }
    // 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
    CoinInPayload localCoinIn;
    CoinOutPayload localCoinOut;
    //output network
    CoinCountCanPayloadTranslator mCoinCount;
    //input network
    VendMotorCanPayloadTranslator mVendMotor;
    VendCanPayloadTranslator mVend;
    CoinReturnCanPayloadTranslator mCoinReturn;
    //State variables
    State currentState;  // the current state machine state
    int coinCount = 0; //used to track time in the flashing states

    public CoinControl(SimTime period, boolean verbose) {
        super("CoinControl", verbose);

        // set local variables to match constructor parameters
        this.period = period;

        //instantiate physical interface objects
        localCoinIn = new CoinInPayload();
        localCoinOut = new CoinOutPayload();
        physicalInterface.registerTimeTriggered(localCoinIn);
        //physicalInterface.sendTimeTriggered(localCoinOut, new SimTime(10, SimTime.SimTimeUnit.MILLISECOND));
        physicalInterface.sendTimeTriggered(localCoinOut, 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.COIN_COUNT_CAN_ID);
        mCoinCount = new CoinCountCanPayloadTranslator(p);
        mCoinCount.setCoinCount(coinCount);
        canInterface.sendTimeTriggered(p, period);

        p = new CanMailbox(MessageDictionary.VEND_MOTOR_CAN_ID);
        mVendMotor = new VendMotorCanPayloadTranslator(p);
        canInterface.registerTimeTriggered(p);

        p = new CanMailbox(MessageDictionary.VEND_CAN_ID);
        mVend = new VendCanPayloadTranslator(p);
        canInterface.registerTimeTriggered(p);

        p = new CanMailbox(MessageDictionary.COIN_RETURN_CAN_ID);
        mCoinReturn = new CoinReturnCanPayloadTranslator(p);
        canInterface.registerTimeTriggered(p);

        // initialize the controller to the OFF state
        currentState = State.IDLE;

        //initialize the outputs to the IDLE state
        doIdle();

        // 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 state " + currentState);
        State newState = currentState; //if no transition occurs, remain in the same state
        switch (currentState) {
            case IDLE:
                doIdle();
                //check transitions
//#transition 'T3.1'
                if (localCoinIn.value == true) {
                    log("T3.1");
                    newState = State.COIN_IN_1;
//#transition 'T3.4'
                } else if (coinCount > SodaMachine.SODA_COST) {
                    log("T3.4");
                    newState = State.OVERPAY;
//#transition 'T3.6'
                } else if (mCoinReturn.getValue() == true && coinCount > 0) {
                    log("T3.6");
                    newState = State.RETURN_1;
//#transition 'T3.10'
                } else if (mVend.getVendState() == true) {
                    log("T3.10");
                    newState = State.VEND;
                }

                break;
            case COIN_IN_1:
                doCoinIn1();
                //check transitions
//#transition 'T3.2'
                log("T3.2");
                newState = State.COIN_IN_2; //unconditional transition

                break;
            case COIN_IN_2:
                doCoinIn2();
                //check transitions
//#transition 'T3.3'
                if (localCoinIn.value == false) {
                    log("T3.3");
                    newState = State.IDLE;
                }

                break;
            case OVERPAY:
                doOverpay();
                //check transitions
//#transition 'T3.12'
                log("T3.12");
                newState = State.OVERPAY_STRETCH; //unconditional transition

                break;
            case OVERPAY_STRETCH:
                doOverpayStretch();
                //check transitions

//#transition 'T3.5'
                log("T3.5");
                newState = State.IDLE; //unconditional transition

                break;
            case VEND:
                doVend();
                //check transitions
//#transition 'T3.11'
                if (mVend.getVendState() == false) {
                    log("T3.11");
                    newState = State.IDLE;
                }

                break;
            case RETURN_1:
                doReturn1();
                //check transitions
//#transition 'T3.7'
                log("T3.7");
                newState = State.RETURN_STRETCH; //unconditional transition

                break;
            case RETURN_STRETCH:
                doReturnStretch();
                //check transitions
//#transition 'T3.13'
                log("T3.13");
                newState = State.RETURN_2; //unconditional transition

                break;
            case RETURN_2:
                doReturn2();
                //check transitions
//#transition 'T3.8'
                if (coinCount == 0) {
                    log("T3.8");
                    newState = State.IDLE;
//#transition 'T3.9'
                } else if (coinCount > 0) {
                    log("T3.9");
                    newState = State.RETURN_1;
                }

                break;
            default:
                throw new RuntimeException("Unrecognized state " + currentState);
        }
        //alwasy set mCoinCount to CoinCount
        mCoinCount.setCoinCount(coinCount);
        log("CoinCount=" + coinCount);
        log("CoinOut=" + localCoinOut.value);

        //advance to the next state we have computed
        if (currentState != newState) {
            log ("Transition from " + currentState + " --> " + newState);
        }
        currentState = newState;

        // we start the timer to interrupt us again at the next period
        timer.start(period);
    }

    /**
     * actions for the IDLE state.
     */
    private void doIdle() {
        localCoinOut.set(false);
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the COIN_IN_1 state.
     */
    private void doCoinIn1() {
        localCoinOut.set(false);
        coinCount++;
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the COIN_IN_2 state.
     */
    private void doCoinIn2() {
        localCoinOut.set(false);
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the OVERPAY state.
     */
    private void doOverpay() {
        localCoinOut.set(true);
        coinCount--;
        //mCoinCount is set in timerExpired
    }

    private void doOverpayStretch() {
        localCoinOut.set(true);
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the VEND state.
     */
    private void doVend() {
        localCoinOut.set(false);
        coinCount = 0;
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the RETURN_1 state.
     */
    private void doReturn1() {
        localCoinOut.set(true);
        coinCount--;
        //mCoinCount is set in timerExpired
    }

    private void doReturnStretch() {
        localCoinOut.set(true);
        //mCoinCount is set in timerExpired
    }

    /**
     * actions for the RETURN_2 state.
     */
    private void doReturn2() {
        localCoinOut.set(false);
        //mCoinCount is set in timerExpired
    }
}
