/* Copyright 1999 American Power Conversion, All Rights Reserved
 * 
 * Description:
 *   Implements the native UPS sevice state machine.   The various states executed
 *  within the state machine are housed in separate files.
 *
 * Revision History:
 *   dsmith  31Mar1999  Created
 *
 */
#include <windows.h>

#include "states.h"
#include "events.h"
#include "driver.h"
#include "running_statmach.h"
#include "upsreg.h"

// Internal function Prototypes
static void enter_state(DWORD aNewState, DWORD anEvent);
static DWORD do_work(DWORD aCurrentState);
static void exit_state(DWORD anOldState, DWORD anEvent);
static DWORD change_state(DWORD aCurrentState, DWORD anEvent);
static DWORD get_new_state(DWORD aCurrentState, DWORD anEvent);


// State Machine Variables
BOOL theIsStateMachineActive = TRUE;


/**
 * RunStateMachine
 *
 * Description:
 * Starts the state machine.  This method will not return until the state machine
 * exits.
 *
 * Parameters:
 *   None
 *
 * Returns:
 *   None
 */
void RunStateMachine(){
	
	// Set the primary state to RUNNING
	DWORD new_state = RUNNING;
	DWORD current_state = new_state;
	DWORD event = NO_EVENT;
	
	enter_state(new_state, event);
	
	// Continue processing state changes until the state becomes the EXIT_NOW state
	while (new_state != EXIT_NOW){
		current_state = new_state;
		
		event = do_work(current_state);
		new_state = change_state(new_state, event);	
	}
}

/**
 * StopStateMachine
 *
 * Description:
 *   Stops the UPS service state machine if the service is not in the 
 * middle of a shutdown sequence.
 *
 * Parameters:
 *   None
 *
 * Returns:
 *   None
 */
void StopStateMachine(){

	theIsStateMachineActive = FALSE;

	// Wake up the main service thread
	UPSCancelWait();
}

/**
 * IsStateMachineActive
 *
 * Description:
 *   Returns the running status of the state machine.  If the state machine 
 * has been commanded to exit, then the status will be FALSE, otherwise, 
 * this method will return true.
 *
 * Parameters:
 *   None
 *
 * Returns:
 *   TRUE if the state machine is active
 *   FALSE if the state machine is not active
 */
BOOL IsStateMachineActive(){
	return theIsStateMachineActive;
}

/**
 * change_state
 *
 * Description:
 *   Determines what the new state should be based upon the input parameters.  
 * The current state is exited and the new state is initialized.
 *
 * Parameters:
 *   anEvent The event that is causing the state transition.
 *   aCurrentState The state before the transition.
 *
 * Returns:
 *   The new state.
 */
DWORD change_state(DWORD aCurrentState, DWORD anEvent){
	DWORD new_state;

	new_state = get_new_state(aCurrentState, anEvent);
	if (new_state != aCurrentState){
		exit_state(aCurrentState, anEvent);
		enter_state(new_state, anEvent);
	}
return new_state;
}

/**
 * get_new_state
 *
 * Description:
 *   Determines what the new state should be based upon the input parameters
 *   and registry entries.  
 *
 * Parameters:
 *   anEvent The event that is causing the state transition.
 *   aCurrentState The state before the transition.
 *
 * Returns:
 *   The new state.
 */
static DWORD get_new_state(DWORD aCurrentState, DWORD anEvent){
	DWORD new_state;

	switch (anEvent){
	case INITIALIZATION_COMPLETE:
		new_state = RUNNING;
		break;

	case LOST_COMM:
	case LOW_BATTERY:
	case ON_BATTERY_TIMER_EXPIRED:
		{
			DWORD shutdown_behavior = UPS_SHUTDOWN_SHUTDOWN;
			
			// Check the registry to determine if we shutdown or hibernate
			InitUPSConfigBlock();
			
			if ((GetUPSConfigCriticalPowerAction(&shutdown_behavior) == ERROR_SUCCESS) 
				&& (shutdown_behavior == UPS_SHUTDOWN_HIBERNATE)) {
				// Hibernate was selected as the CriticalPowerAction
				new_state = HIBERNATE;
				
			}
			else {
				// Shutdown was selected as the CriticalPowerAction
				new_state = WAITING_TO_SHUTDOWN;
			}
			
			// Free the UPS registry config block
			FreeUPSConfigBlock();
		}
		break;

	case SHUTDOWN_ACTIONS_COMPLETED:
		new_state = SHUTTING_DOWN;
		break;
	case SHUTDOWN_COMPLETE:
		new_state = STOPPING;
		break;
	case STOPPED:
		new_state = EXIT_NOW;
		break;
	case RETURN_FROM_HIBERNATION:
		new_state = INITIALIZING;
		break;
	case HIBERNATION_ERROR:
		new_state = SHUTTING_DOWN;		
    break;
	default:
		new_state = aCurrentState;
	}

	// If the state machine has been commanded to exit, then return the 
	// stopping state
	if (IsStateMachineActive() == FALSE){
		
		// Ignore this condition if the transition is into the shutting down state
		// Shutdowns in progress cannot be interrupted.
		if (new_state != SHUTTING_DOWN && new_state != EXIT_NOW){
			new_state = STOPPING;
		}
	}
	return new_state; 
}

/**
* enter_state
*
* Description:
*   Initializes the new state.
*
* Parameters:
*   anEvent The event that is causing the transition to a new state.
*   aNewState  The state to enter.
*
* Returns:
*   None
*/
static void enter_state(DWORD aNewState, DWORD anEvent){
	switch (aNewState){
	case INITIALIZING:
		Initializing_Enter(anEvent);
		break;
	case RUNNING:
		Running_Enter(anEvent); 
		break;
	case WAITING_TO_SHUTDOWN:
		WaitingToShutdown_Enter(anEvent);
		break;
	case SHUTTING_DOWN:
		ShuttingDown_Enter(anEvent);
		break;
	case HIBERNATE:
		Hibernate_Enter(anEvent);
		break;
	case STOPPING:
		Stopping_Enter(anEvent);
		break;
	default:
		break;
	}
}

/**
* do_work
*
* Description:
*   Transfers control to a state.
*
* Parameters:
*   aCurrentState The state to perform the work in.
*
* Returns:
*   The event that caused the transition from one of the states
*/
static DWORD do_work(DWORD aCurrentState){
	DWORD event = NO_EVENT;
	switch (aCurrentState){
	case INITIALIZING:
		event = Initializing_DoWork();
		break;
	case RUNNING:
		event = Running_DoWork();  
		break;
	case WAITING_TO_SHUTDOWN:
		event = WaitingToShutdown_DoWork();
		break;
	case SHUTTING_DOWN:
		event = ShuttingDown_DoWork();
		break;
	case HIBERNATE:
		event = Hibernate_DoWork();
		break;
	case STOPPING:
		event = Stopping_DoWork();
		break;
	default:
		break;
	}
	
	return event;
}

/**
* exit_state
*
* Description:
*   Exits the currently executing state.
*
* Parameters:
*   anEvent The event that is causing the transition from the state.
*   anOldState  The current state.
*
* Returns:
*   None
*/
static void exit_state(DWORD anOldState, DWORD anEvent){
	switch (anOldState){
	case INITIALIZING:
		Initializing_Exit(anEvent);
		break;
	case RUNNING:
		Running_Exit(anEvent);  
		break;
	case WAITING_TO_SHUTDOWN:
		WaitingToShutdown_Exit(anEvent);
		break;
	case SHUTTING_DOWN:
		ShuttingDown_Exit(anEvent);
		break;
 case HIBERNATE:
		Hibernate_Exit(anEvent);
		break;
	case STOPPING:
		Stopping_Exit(anEvent);
		break;
	default:
		break;
	}
}