/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL)
* 
* This file is part of GSN.
* 
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* GSN 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 GSN.  If not, see <http://www.gnu.org/licenses/>.
* 
* File: src/ch/epfl/gsn/utils/protocols/ProtocolManager.java
*
* @author Jerome Rousselot
* @author Ali Salehi
*
*/

package ch.epfl.gsn.utils.protocols;

import java.util.Collection;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.naming.OperationNotSupportedException;

import org.slf4j.LoggerFactory;

import ch.epfl.gsn.wrappers.AbstractWrapper;

import org.slf4j.Logger;


/**
 * This class implements a generic finite state machine
 * for HostControllerInterface Protocols.
 * For simple protocols that never wait for an answer
 * from the controller, simply create a ProtocolManager
 * instance with the appropriate Protocol object and
 * then call the method sendQuery.
 * 
 *  Warning: other methods of this class may be refactored soon,
 *  and more states could be added.
 *  
 *  @see AbstractHCIProtocol
 */
public class ProtocolManager {
	private static final transient Logger logger = LoggerFactory.getLogger( ProtocolManager.class );
	private AbstractHCIProtocol protocol;
	private ProtocolStates currentState;
	private AbstractHCIQuery lastExecutedQuery = null;
	private Vector < Object > lastParams;
	private AbstractWrapper outputWrapper;

	private Timer timer;
	private TimerTask answerTimeout = new TimerTask() {

		public synchronized void run ( ) {
			lastExecutedQuery = null;
			currentState = ProtocolStates.READY;
		} 
	};

	public enum ProtocolStates {
		READY, WAITING
	}

	public ProtocolManager(AbstractHCIProtocol protocol, AbstractWrapper outputWrapper) {
		this.protocol = protocol;
		this.outputWrapper = outputWrapper;
		currentState = ProtocolStates.READY;
	}

	public synchronized ProtocolStates getCurrentState() {
		return currentState;
	}

	/*
	 * This method tries to execute a query named queryName with parameters params
	 * on the wrapper wrapper.
	 * If successful, it returns the raw command that has been sent.
	 */
	public synchronized byte[] sendQuery(String queryName, Vector<Object> params) {
		byte[] answer = null;
		if(currentState == ProtocolStates.READY) {
			AbstractHCIQuery query = protocol.getQuery( queryName );
			
			if(query != null) {
				logger.debug( "Retrieved query " + queryName + ", trying to build raw query.");

				byte[] queryBytes = query.buildRawQuery( params );
				if(queryBytes != null) {
					try {
						logger.debug("Built query, it looks like: " + new String(queryBytes));
						outputWrapper.sendToWrapper(null,null,new Object[] {queryBytes});
						lastExecutedQuery = query;
						lastParams = params;
						answer = queryBytes;
						logger.debug("Query succesfully sent!");
						if(query.needsAnswer( params )) {
							logger.debug("Now entering wait mode for answer.");
							timer = new Timer();
							currentState = ProtocolStates.WAITING;
							timer.schedule( answerTimeout , new Date());
						}
					} catch( OperationNotSupportedException e ) {
						logger.debug("Query could not be sent ! See error message.");
						logger.error( e.getMessage( ) , e );
						currentState = ProtocolStates.READY;
					}
				}
			} else {
				logger.warn("Query " + queryName + " found but no bytes produced to send to device. Implementation may be missing.");
			}

		}
		return answer;
	}

	/*
	 * This tries to match incoming data to the pattern
	 * expected by the query. If the pattern describes
	 * several groups then all the different String
	 * matching these groups are returned.
	 */
	public synchronized Object[] getAnswer(byte[] rawData) {
		Object[] answer = null;
		if(currentState == ProtocolStates.WAITING) {
			answer = lastExecutedQuery.getAnswers(rawData);
		}
		return answer;
	}

	/**
	 * @return
	 */
	public String getProtocolName() {
		if(protocol != null)
			return protocol.getName();
		return null;
	}

	/**
	 * @param string
	 * @return
	 */
	public AbstractHCIQuery getQuery(String string) {
		if(protocol != null)
			return protocol.getQuery(string);
		return null;
	}

	/**
	 * @return
	 */
	public Collection<AbstractHCIQuery> getQueries() {
		if(protocol != null)
			return protocol.getQueries();
		return null;
	}
}