/******************************************************************
*
*	CyberUPnP for Java
*
*	Copyright (C) Satoshi Konno 2002-2004
*
*	File: Action.java
*
*	Revision;
*
*	12/05/02
*		- first revision.
*	08/30/03
*		- Gordano Sassaroli <sassarol@cefriel.it>
*		- Problem    : When invoking an action that has at least one out parameter, an error message is returned
*		- Error      : The action post method gets the entire list of arguments instead of only the in arguments
*	01/04/04
*		- Added UPnP status methods.
*		- Changed about new ActionListener interface.
*	01/05/04
*		- Added clearOutputAgumentValues() to initialize the output values before calling performActionListener().
*	07/09/04
*		- Thanks for Dimas <cyberrate@users.sourceforge.net> and Stefano Lenzi <kismet-sl@users.sourceforge.net>
*		- Changed postControlAction() to set the status code to the UPnPStatus.
*	04/12/06
*		- Added setUserData() and getUserData() to set a user original data object.
*
******************************************************************/

package org.cybergarage.upnp;
import java.util.Iterator;

import org.cybergarage.upnp.control.ActionListener;
import org.cybergarage.upnp.control.ActionRequest;
import org.cybergarage.upnp.control.ActionResponse;
import org.cybergarage.upnp.control.ControlResponse;
import org.cybergarage.upnp.xml.ActionData;
import org.cybergarage.util.Debug;
import org.cybergarage.util.Mutex;
import org.cybergarage.xml.Node;

public class Action
{
	////////////////////////////////////////////////
	//	Constants
	////////////////////////////////////////////////
	
	public final static String ELEM_NAME = "action";

	////////////////////////////////////////////////
	//	Member
	////////////////////////////////////////////////

	private Node serviceNode;
	private final Node actionNode;

	private Node getServiceNode()
	{
		return serviceNode;
	}

	public Service getService()
	{
		return new Service(getServiceNode());
	}
	
	void setService(Service s){
		serviceNode=s.getServiceNode();
		/*To ensure integrity of the XML structure*/
		Iterator<Argument> i = getArgumentList().iterator();
		while (i.hasNext()) {
			Argument arg = i.next();
			arg.setService(s);
		}		
	}
	
	public Node getActionNode()
	{
		return actionNode;
	}
	
	////////////////////////////////////////////////
	//	Constructor
	////////////////////////////////////////////////
	public Action(Node serviceNode){
		//TODO Test
		this.serviceNode = serviceNode;
		this.actionNode = new Node(Action.ELEM_NAME);		
	}

	public Action(Node serviceNode, Node actionNode)
	{
		this.serviceNode = serviceNode;
		this.actionNode = actionNode;
	}

	public Action(Action action)
	{
		this.serviceNode = action.getServiceNode();
		this.actionNode = action.getActionNode();
	}

	////////////////////////////////////////////////
	// Mutex
	////////////////////////////////////////////////
	
	private Mutex mutex = new Mutex();
	
	public void lock()
	{
		mutex.lock();
	}
	
	public void unlock()
	{
		mutex.unlock();
	}
	
	////////////////////////////////////////////////
	//	isActionNode
	////////////////////////////////////////////////

	public static boolean isActionNode(Node node)
	{
		return Action.ELEM_NAME.equals(node.getName());
	}

	////////////////////////////////////////////////
	//	name
	////////////////////////////////////////////////

	private final static String NAME = "name";
	
	public void setName(String value)
	{
		getActionNode().setNode(NAME, value);
	}

	public String getName()
	{
		return getActionNode().getNodeValue(NAME);
	}

	////////////////////////////////////////////////
	//	argumentList
	////////////////////////////////////////////////

	public ArgumentList getArgumentList()
	{
		ArgumentList argumentList = new ArgumentList();
		Node argumentListNode = getActionNode().getNode(ArgumentList.ELEM_NAME);
		if (argumentListNode == null)
			return argumentList;
		int nodeCnt = argumentListNode.getNNodes();
		for (int n=0; n<nodeCnt; n++) {
			Node node = argumentListNode.getNode(n);
			if (Argument.isArgumentNode(node) == false)
				continue;
			Argument argument = new Argument(getServiceNode(), node);
			argumentList.add(argument);
		} 
		return argumentList;
	}	
	
	public void setArgumentList(ArgumentList al){
		Node argumentListNode = getActionNode().getNode(ArgumentList.ELEM_NAME);
		if (argumentListNode == null){
			argumentListNode = new Node(ArgumentList.ELEM_NAME);
			getActionNode().addNode(argumentListNode);
		}else{
			argumentListNode.removeAllNodes();
		}
		Iterator<Argument> i = al.iterator();
		while (i.hasNext()) {
			Argument a = i.next();
			a.setService(getService());
			argumentListNode.addNode(a.getArgumentNode());
		}
		
	}

	public ArgumentList getInputArgumentList()
	{
		ArgumentList allArgList = getArgumentList();
		int allArgCnt = allArgList.size();
		ArgumentList argList = new ArgumentList();
		for (int n=0; n<allArgCnt; n++) {
			Argument arg = allArgList.getArgument(n);
			if (arg.isInDirection() == false)
				continue;
			argList.add(arg);
		}
		return argList;
	}

	public ArgumentList getOutputArgumentList()
	{
		ArgumentList allArgList = getArgumentList();
		int allArgCnt = allArgList.size();
		ArgumentList argList = new ArgumentList();
		for (int n=0; n<allArgCnt; n++) {
			Argument arg = allArgList.getArgument(n);
			if (arg.isOutDirection() == false)
				continue;
			argList.add(arg);
		}
		return argList;
	}
	
	public Argument getArgument(String name)
	{
		ArgumentList argList = getArgumentList();
		int nArgs = argList.size();
		for (int n=0; n<nArgs; n++) {
			Argument arg = argList.getArgument(n);
			String argName = arg.getName();
			if (argName == null)
				continue;
			if (name.equals(argName) == true)
				return arg;
		}
		return null;
	}

	/**
	 * @deprecated You should use one of the following methods instead:<br>
	 *  - {@link #setInArgumentValues(ArgumentList)} <br>
	 *  - {@link #setOutArgumentValues(ArgumentList)} 
	 */
	@Deprecated
	public void setArgumentValues(ArgumentList argList)
	{
		getArgumentList().set(argList);
	}

	/**
	 * 
	 * @param argList
	 * @since 1.8.0
	 */
	public void setInArgumentValues(ArgumentList argList)
	{
		getArgumentList().setReqArgs(argList); 
	}
	
	/**
	 * 
	 * @param argList
	 * @since 1.8.0
	 */
	public void setOutArgumentValues(ArgumentList argList)
	{
		getArgumentList().setResArgs(argList);
	}
	
	public void setArgumentValue(String name, String value)
	{
		Argument arg = getArgument(name);
		if (arg == null)
			return;
		arg.setValue(value);
	}

	public void setArgumentValue(String name, int value)
	{
		setArgumentValue(name, Integer.toString(value));
	}

	private void clearOutputAgumentValues()
	{
		ArgumentList allArgList = getArgumentList();
		int allArgCnt = allArgList.size();
		for (int n=0; n<allArgCnt; n++) {
			Argument arg = allArgList.getArgument(n);
			if (arg.isOutDirection() == false)
				continue;
			arg.setValue("");
		}
	}
	
	public String getArgumentValue(String name)
	{
		Argument arg = getArgument(name);
		if (arg == null)
			return "";
		return arg.getValue();
	}

	public int getArgumentIntegerValue(String name)
	{
		Argument arg = getArgument(name);
		if (arg == null)
			return 0;
		return arg.getIntegerValue();
	}
	
	////////////////////////////////////////////////
	//	UserData
	////////////////////////////////////////////////

	private ActionData getActionData()
	{
		Node node = getActionNode();
		ActionData userData = (ActionData)node.getUserData();
		if (userData == null) {
			userData = new ActionData();
			node.setUserData(userData);
			userData.setNode(node);
		}
		return userData;
	}
	
	////////////////////////////////////////////////
	//	controlAction
	////////////////////////////////////////////////

	public ActionListener getActionListener() 
	{
		return getActionData().getActionListener();
	}

	public void setActionListener(ActionListener listener) 
	{
		getActionData().setActionListener(listener);
	}
	
	public boolean performActionListener(ActionRequest actionReq)
	{
		ActionListener listener = getActionListener();
		if (listener == null)
			return false;
		ActionResponse actionRes = new ActionResponse();
		setStatus(UPnPStatus.INVALID_ACTION);
		clearOutputAgumentValues();
		if (listener.actionControlReceived(this) == true) {
			actionRes.setResponse(this);
		}
		else {
			UPnPStatus upnpStatus = getStatus();
			actionRes.setFaultResponse(upnpStatus.getCode(), upnpStatus.getDescription());
		}
		if (Debug.isOn() == true)
			actionRes.print();
		actionReq.post(actionRes);
		return true;
	}

	////////////////////////////////////////////////
	//	ActionControl
	////////////////////////////////////////////////

	private ControlResponse getControlResponse() 
	{
		return getActionData().getControlResponse();
	}

	private void setControlResponse(ControlResponse res) 
	{
		getActionData().setControlResponse(res);
	}
	
	public UPnPStatus getControlStatus()
	{
		return getControlResponse().getUPnPError();
	}

	////////////////////////////////////////////////
	//	postControlAction
	////////////////////////////////////////////////

	public boolean postControlAction()
	{
		return postControlAction(null);
	}

	/**
	 *  I2P - bind HTTP socket to specified local host address
	 *
	 *  @param fromHost null to not bind to a particlar local address
	 *  @since 0.9.50
	 */
	public boolean postControlAction(String fromHost)
	{
		// Thanks for Giordano Sassaroli <sassarol@cefriel.it> (08/30/03)
		ArgumentList actionArgList = getArgumentList();
		ArgumentList actionInputArgList = getInputArgumentList();		
		ActionRequest ctrlReq = new ActionRequest();
		ctrlReq.setRequest(this, actionInputArgList);
		if (fromHost != null)
			ctrlReq.setBindHost(fromHost);
		if (Debug.isOn() == true)
			ctrlReq.print();
		ActionResponse ctrlRes = ctrlReq.post();
		if (Debug.isOn() == true)
			ctrlRes.print();
		setControlResponse(ctrlRes);
		// Thanks for Dimas <cyberrate@users.sourceforge.net> and Stefano Lenzi <kismet-sl@users.sourceforge.net> (07/09/04)
		int statCode = ctrlRes.getStatusCode();
		setStatus(statCode);
		if (ctrlRes.isSuccessful() == false)
			return false;
		ArgumentList outArgList = ctrlRes.getResponse();
        try {
            actionArgList.setResArgs(outArgList);
        } catch (IllegalArgumentException ex){
            setStatus(UPnPStatus.INVALID_ARGS,"Action succesfully delivered but invalid arguments returned.");
            return false;
        }
		return true;
	}

	////////////////////////////////////////////////
	//	Debug
	////////////////////////////////////////////////

	public void print()
	{
		Debug.message("Action : " + getName());
		ArgumentList argList = getArgumentList();
		int nArgs = argList.size();
		for (int n=0; n<nArgs; n++) {
			Argument arg = argList.getArgument(n);
			String name = arg.getName();
			String value = arg.getValue();
			String dir = arg.getDirection();
			Debug.message(" [" + n + "] = " + dir + ", " + name + ", " + value);
		}
	}
	
	////////////////////////////////////////////////
	//	UPnPStatus
	////////////////////////////////////////////////

	private UPnPStatus upnpStatus = new UPnPStatus();
	
	public void setStatus(int code, String descr)
	{
		upnpStatus.setCode(code);
		upnpStatus.setDescription(descr);
	}
	
	public void setStatus(int code)
	{
		setStatus(code, UPnPStatus.code2String(code));
	}
		
	public UPnPStatus getStatus()
	{
		return upnpStatus;
	}
	
	////////////////////////////////////////////////
	//	userData
	////////////////////////////////////////////////

	private Object userData = null; 
	
	public void setUserData(Object data) 
	{
		userData = data;
	}

	public Object getUserData() 
	{
		return userData;
	}
}
