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

/************************************
CLASS:
VendControl
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 actual vend process once the VC is aligned with the chute
Source of:
 * mVend (network)
 * Vend (framework)
Sink of:
 * mVendPosition[s] (network)
 * mButton[s] (network)
 * mCoinCount (network)
 * mVendMotor (network)
 *************************************/
public class VendControl extends Controller implements TimeSensitive {

    // define convenience state variables to track what state we are in
    public static enum State {
        IDLE,
        VEND
    }
    // 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
    VendActuatorPayload localVend;
    //output network
    VendCanPayloadTranslator mVend;
    //input network
    VendPositionCanPayloadTranslator mVendPosition[];
    ButtonCanPayloadTranslator mButton[];
    CoinCountCanPayloadTranslator mCoinCount;
    VendMotorCanPayloadTranslator mVendMotor;
    //State variables
    State currentState;  // the current state machine state
    int vendCounter=0;
    //Constants
    private final int VEND_LIMIT; //assume the controller runs at a period of 400

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

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

        //compute constants
        if (500 % period.getTruncMilliseconds() != 0) {
            throw new RuntimeException("Error:  The Vend pulse period of 500ms must be evenly divisible by the controller period.  Current period=" + period.toString(SimTime.SimTimeUnit.MILLISECOND));
        }
        VEND_LIMIT = (int)(500/period.getTruncMilliseconds());


        //instantiate physical interface objects
        localVend = new VendActuatorPayload();
        physicalInterface.sendTimeTriggered(localVend, 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.VEND_CAN_ID);
        mVend = new VendCanPayloadTranslator(p);
        mVend.setVendState(false);
        canInterface.sendTimeTriggered(p, period);

        mVendPosition = new VendPositionCanPayloadTranslator[SodaMachine.NUM_CHUTES];
        mButton = new ButtonCanPayloadTranslator[SodaMachine.NUM_CHUTES];
        for (int i=0; i < SodaMachine.NUM_CHUTES; i++) {
            int index = i+1;

            p = new CanMailbox(MessageDictionary.VEND_POSITION_BASE_CAN_ID + ReplicationComputer.computeReplicationId(index));
            mVendPosition[i] = new VendPositionCanPayloadTranslator(p, index);
            canInterface.registerTimeTriggered(p);

            p = new CanMailbox(MessageDictionary.BUTTON_BASE_CAN_ID + ReplicationComputer.computeReplicationId(index));
            mButton[i] = new ButtonCanPayloadTranslator(p, index);
            canInterface.registerTimeTriggered(p);
        }

        p = new CanMailbox(MessageDictionary.VEND_MOTOR_CAN_ID);
        mVendMotor = new VendMotorCanPayloadTranslator(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
        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) {
        
        //do some intermediate calculations

        //search for the currently selected button and position sensor
        int currentButton = SodaMachine.NONE;  //default value means no button pressed
        int currentPosition = SodaMachine.NONE;  //default value means we are not at any chute
        for (int i=0; i < SodaMachine.NUM_CHUTES; i++) {
            int index = i+1;
            if (mButton[i].getButtonState() == true) {
                currentButton = index;
            }
            if (mVendPosition[i].getValue() == true) {
                currentPosition = index;
            }
        }

        //execute state machine
        log ("Executing " + currentState);
        State newState = currentState; //if no transition occurs, remain in the same state
        switch (currentState) {
            case IDLE:
                //execute state actions
                doIdle();
                //check transitions
//#transition 'T4.1'
                if ((currentButton != SodaMachine.NONE && currentButton == currentPosition) &&
                        mCoinCount.getCoinCount()==SodaMachine.SODA_COST &&
                        mVendMotor.getDirection() == Direction.STOP) {
                    log("T4.1");
                    log("currentButton=",currentButton,"; currentPosition=",currentPosition,";coinCount=",mCoinCount.getCoinCount(),"; VendMotor=",mVendMotor.getDirection());
                    newState = State.VEND;
                }

                break;
            case VEND:
                //execute state actions
                doVend();
                //check transitions
//#transition 'T4.2'
                if (vendCounter > VEND_LIMIT) {
                    log("T4.2");
                    newState = State.IDLE;
                }

                break;
            default:
                throw new RuntimeException("Unrecognized state " + currentState);
        }

        //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() {
        localVend.active = false;
        mVend.set(false);
        vendCounter = 0;
    }

    /**
     * actions for the VEND state.
     */
    private void doVend() {
        localVend.active = true;
        mVend.set(true);
        vendCounter++;
    }

}
