/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program is distributed in the hope   *
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.process;

import static org.compiere.model.SystemIDs.REFERENCE_WFINSTANCE_STATE;

import java.util.logging.Level;

import org.compiere.model.MRefList;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.Util;

/**
 *	Workflow State Engine.<br/>
 *	Based on OMG Workflow State.
 *	
 *  @author Jorg Janke
 *  @version $Id: StateEngine.java,v 1.3 2006/07/30 00:54:44 jjanke Exp $
 */
public class StateEngine
{
	/**
	 * 	Default Constructor (not started)
	 */
	public StateEngine ()
	{
		this (STATE_NotStarted);
		log = CLogger.getCLogger(getClass());
	}	//	State
	
	/**
	 * 	Initialized Constructor
	 * 	@param startState start state
	 */
	public StateEngine (String startState)
	{
		if (startState != null)
			m_state = startState;
	}	//	State
	
	//	Same as AD_WF_Process.WFSTATE
	
	/** Open - Not Started 	*/
	public static final String	STATE_NotStarted = "ON";
	/** Open - Running		*/
	public static final String	STATE_Running 	= "OR";
	/** Open - Suspended	*/
	public static final String	STATE_Suspended = "OS";
	/** Closed - Completed - normal exit	*/
	public static final String	STATE_Completed = "CC";
	/** Closed - Aborted - Environment/Setup Error	*/
	public static final String	STATE_Aborted = "CA";
	/** Closed - Teminated - Execution Error		*/
	public static final String	STATE_Terminated = "CT";

	/** Suspend			*/
	public static final String	ACTION_Suspend = "Suspend";
	/** Start			*/
	public static final String	ACTION_Start = "Start";
	/** Resume			*/
	public static final String	ACTION_Resume = "Resume";
	/** Complete		*/
	public static final String	ACTION_Complete = "Complete";
	/** Abort			*/
	public static final String	ACTION_Abort = "Abort";
	/** Terminate		*/
	public static final String	ACTION_Terminate = "Terminate";
	
	/**	Internal State				*/
	private String	m_state = STATE_NotStarted;

	/** If true throw exceptions	*/
	private boolean	m_throwException	= false;
	
	/**	Logger			*/
	protected CLogger	log = null;
	
	/**
	 * 	Is throw Exception when there is error
	 *	@return true if exceptions is thrown when there is error
	 */
	public boolean isThrowException()
	{
		return m_throwException;
	}	//	isThrowException
	
	/**
	 * 	Set if Exceptions are Thrown
	 *	@param throwException
	 */
	public void setThrowException(boolean throwException)
	{
		m_throwException = throwException;
	}	//	setThrowException
		
	/**
	 * 	Get State
	 *	@return state
	 */
	public String getState()
	{
		return m_state;
	}	//	getState

	/**
	 * 	Get clear text State Info.
	 *	@return state info
	 */
	public String getStateInfo()
	{
		String state = getState();	//	is overwritten to update
		String stateName = MRefList.getListName(Env.getCtx(), REFERENCE_WFINSTANCE_STATE, state);
		if (! Util.isEmpty(stateName))
			return stateName;

		return state;
	}	//	getStateInfo

	/**
	 * 	State is Open
	 *	@return true if open (running, not started, suspended)
	 */
	public boolean isOpen()
	{
		return STATE_Running.equals(m_state)
			|| STATE_NotStarted.equals(m_state)
			|| STATE_Suspended.equals(m_state);
	}	//	isOpen
	
	/**
	 * 	State is Not Running
	 *	@return true if not running (not started, suspended)
	 */
	public boolean isNotRunning()
	{
		return STATE_NotStarted.equals(m_state)
			|| STATE_Suspended.equals(m_state);
	}	//	isNotRunning
	
	/**
	 * 	State is Closed
	 *	@return true if closed (completed, aborted, terminated)
	 */
	public boolean isClosed()
	{
		return STATE_Completed.equals(m_state)
			|| STATE_Aborted.equals(m_state)
			|| STATE_Terminated.equals(m_state);
	}	//	isClosed
	
	/**
	 * 	State is Not Started
	 *	@return true if Not Started
	 */
	public boolean isNotStarted()
	{
		return STATE_NotStarted.equals(m_state);
	}	//	isNotStarted
	
	/**
	 * 	State is Running
	 *	@return true if Running
	 */
	public boolean isRunning()
	{
		return STATE_Running.equals(m_state);
	}	//	isRunning
	
	/**
	 * 	State is Suspended
	 *	@return true if Suspended
	 */
	public boolean isSuspended()
	{
		return STATE_Suspended.equals(m_state);
	}	//	isSuspended
	
	/**
	 * 	State is Completed
	 *	@return true if Completed
	 */
	public boolean isCompleted()
	{
		return STATE_Completed.equals(m_state);
	}	//	isCompleted
	
	/**
	 * 	State is Aborted (Environment/Setup issue)
	 *	@return true if Aborted
	 */
	public boolean isAborted()
	{
		return STATE_Aborted.equals(m_state);
	}	//	isAborted

	/**
	 * 	State is Terminated (Execution issue)
	 *	@return true if Terminated
	 */
	public boolean isTerminated()
	{
		return STATE_Terminated.equals(m_state);
	}	//	isTerminated

	/**
	 * 	Start: not started -&gt; running
	 *	@return true if set to running
	 */
	public boolean start()
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isNotStarted())
		{
			m_state = STATE_Running;
			if (log.isLoggable(Level.INFO)) log.info("starting ...");
			return true;
		}
		String msg = "start failed: Not Not Started (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	start
	
	/**
	 * 	Resume: suspended -&gt; running
	 *	@return true if set to running
	 */
	public boolean resume()	//	raises CannotResume, NotRunning, NotSuspended
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isSuspended())
		{
			m_state = STATE_Running;
			if (log.isLoggable(Level.INFO)) log.info("resuming ...");
			return true;
		}
		String msg = "resume failed: Not Suspended (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	resume

	/**
	 * 	Suspend: running -&gt; suspended
	 *	@return true if suspended
	 */
	public boolean suspend()	//	raises CannotSuspend, NotRunning, AlreadySuspended
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isRunning())
		{
			m_state = STATE_Suspended;
			if (log.isLoggable(Level.INFO)) log.info("suspending ...");
			return true;
		}
		String msg = "suspend failed: Not Running (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	suspend

	/**
	 * 	Complete: running -&gt; completed
	 *	@return true if set to completed
	 */
	public boolean complete()
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isRunning())
		{
			m_state = STATE_Completed;
			if (log.isLoggable(Level.INFO)) log.info("completing ...");
			return true;
		}
		String msg = "complete failed: Not Running (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	complete
	
	/**
	 * 	Abort: open -&gt; aborted
	 *	@return true if set to aborted
	 */
	public boolean abort()	//	raises CannotStop, NotRunning
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isOpen())
		{
			m_state = STATE_Aborted;
			if (log.isLoggable(Level.INFO)) log.info("aborting ...");
			return true;
		}
		String msg = "abort failed: Not Open (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	abort
	
	/**
	 * 	Terminate: open -&gt; terminated
	 *	@return true if set to terminated
	 */
	public boolean terminate()	//	raises CannotStop, NotRunning
	{
		if (log == null)
			log = CLogger.getCLogger(getClass());
		if (isOpen())
		{
			m_state = STATE_Terminated;
			if (log.isLoggable(Level.INFO)) log.info("terminating ...");
			return true;
		}
		String msg = "terminate failed: Not Open (" + getState() + ")";
		if (m_throwException)
			throw new IllegalStateException (msg);
		log.warning(msg);
		return false;
	}	//	terminate
	
	
	/**
	 * 	Get New State Options based on current State
	 *	@return array of new states
	 */
	public String[] getNewStateOptions()
	{
		if (isNotStarted())
			return new String[] {STATE_Running, STATE_Aborted, STATE_Terminated};
		if (isRunning())
			return new String[] {STATE_Suspended, STATE_Completed, STATE_Aborted, STATE_Terminated};
		if (isSuspended())
			return new String[] {STATE_Running, STATE_Aborted, STATE_Terminated, STATE_Completed};
		//
		return new String[] {};
	}	//	getNewStateOptions

	/**
	 * 	Is the new State valid based on current state
	 *	@param newState new state
	 *	@return true valid new state
	 */
	public boolean isValidNewState (String newState)
	{
		String[] options = getNewStateOptions();
		for (int i = 0; i < options.length; i++)
		{
			if (options[i].equals(newState))
				return true;
		}
	 	return false;
	}	//	isValidNewState
		
	/**
	 * 	Set State to new State
	 *	@param newState new state
	 *	@return true if set to new state
	 */
	public boolean setState (String newState)	//	raises InvalidState, TransitionNotAllowed
	{
		if (STATE_Running.equals(newState))
		{
			if (isNotStarted())
				return start();
			else
				return resume();
		}
		else if (STATE_Suspended.equals(newState))
			return suspend();
		else if (STATE_Completed.equals(newState))
			return complete();
		else if (STATE_Aborted.equals(newState))
			return abort();
		else if (STATE_Terminated.equals(newState))
			return terminate();
		return false;
	}	//	setState

	/**
	 * 	Get Workflow Action Options based on current State
	 *	@return array of actions
	 */
	public String[] getActionOptions()
	{
		if (isNotStarted())
			return new String[] {ACTION_Start, ACTION_Abort, ACTION_Terminate};
		if (isRunning())
			return new String[] {ACTION_Suspend, ACTION_Complete, ACTION_Abort, ACTION_Terminate};
		if (isSuspended())
			return new String[] {ACTION_Resume, ACTION_Abort, ACTION_Terminate};
		//
		return new String[] {};
	}	//	getActionOptions

	/**
	 * 	Is The Action Valid based on current state
	 *	@param action action
	 *	@return true if valid
	 */
	public boolean isValidAction (String action)
	{
		String[] options = getActionOptions();
		for (int i = 0; i < options.length; i++)
		{
			if (options[i].equals(action))
				return true;
		}
		return false;
	}	//	isValidAction
	
	/**
	 * 	Process workflow action
	 *	@param action
	 *	@return true if set to new state
	 */
	public boolean process (String action)	//	raises InvalidState, TransitionNotAllowed
	{
		if (ACTION_Start.equals(action))
			return start();
		else if (ACTION_Complete.equals(action))
			return complete();
		else if (ACTION_Suspend.equals(action))
			return suspend();
		else if (ACTION_Resume.equals(action))
			return resume();
		else if (ACTION_Abort.equals(action))
			return abort();
		else if (ACTION_Terminate.equals(action))
			return terminate();
		return false;
	}	//	process

	/**
	 * 	Get New State If Action performed
	 *	@param action action
	 *	@return potential new state
	 */
	public String getNewStateIfAction (String action)
	{
		if (isValidAction(action))
		{
			if (ACTION_Start.equals(action))
				return STATE_Running;
			else if (ACTION_Complete.equals(action))
				return STATE_Completed;
			else if (ACTION_Suspend.equals(action))
				return STATE_Suspended;
			else if (ACTION_Resume.equals(action))
				return STATE_Running;
			else if (ACTION_Abort.equals(action))
				return STATE_Aborted;
			else if (ACTION_Terminate.equals(action))
				return STATE_Terminated;
		}
		//	Unchanged
		return getState();
	}	//	getNewStateIfAction
	
	/**
	 * 	String Representation
	 *	@return info
	 */
	@Override
	public String toString ()
	{
		return getStateInfo();
	}	//	toString
	
}	//	State
