package omq.common.message.request;

import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import omq.Remote;
import omq.common.message.response.Response;
import omq.common.util.ParameterQueue;
import omq.common.util.Serializer;
import omq.exception.RetryException;
import omq.exception.TimeoutException;
import omq.server.remote.request.RequestListener;


import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;

/**
 * This class represents a Synchronous request
 * 
 * @author Sergi Toda <sergi.toda@estudiants.urv.cat>
 */
public class SyncRequest extends Request {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private long timeout;
	private int retries;
	
	
	public SyncRequest() {
		super();
	}
	
	/**
	 * This is the constructor of a synchronous request
	 * 
	 * @param uid
	 *            This represents the real object uid registered in the server
	 * @param corrId
	 *            This is the correlation id of this method request
	 * @param methodName
	 *            The name of the method to send
	 * @param args
	 *            The arguments of this method
	 * @param timeout
	 *            How long you will wait for a request
	 * @param retries
	 *            How many retries you will do
	 */
	public SyncRequest(String uid, String corrId, String methodName, Vector<Object> args, long timeout, int retries) {
		super(uid, corrId, methodName, args);
		this.timeout = timeout;
		this.retries = retries;
	}

	public void invokeRequest(Remote obj, BasicProperties props) throws Exception {
		Response resp = obj.invokeMethod(this.getMethodName(), this.getArgs());

		// Send the response to the proxy
		Channel channel = RequestListener.getRequestListener().getChannel();

		BasicProperties replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId()).build();

		channel.basicPublish("", props.getReplyTo(), replyProps, Serializer.serialize(resp));

		channel.close();
	}

	@Override
	public Object publishRequest(Map<String, Response> results, Properties env, Channel channel) throws Exception {
		int i = 0;
		while (i < retries) {
			try {
				publishRequest(env, channel);
				return getResponse(env, results);
			} catch (TimeoutException te) {
				System.out.println("Timeout exception catched " + te);
			}
			i++;
		}
		throw new RetryException(retries, timeout);
	}

	private void publishRequest(Properties env, Channel channel) throws Exception {

		// Get the environment properties
		String exchange = env.getProperty(ParameterQueue.RPC_EXCHANGE);
		String replyQueueName = env.getProperty(ParameterQueue.RPC_REPLY_QUEUE);
		String routingkey = env.getProperty(ParameterQueue.RPC_ROUTING_KEY);

		// Add the correlation ID and create a replyTo property
		BasicProperties props = new BasicProperties.Builder().appId(uid).correlationId(corrId).replyTo(replyQueueName).build();

		// Publish the message
		channel.basicPublish(exchange, routingkey, props, Serializer.serialize(this));
	}

	private Object getResponse(Properties env, Map<String, Response> results) throws Exception {
		Response resp = null;

		// Wait for the results.
		long localTimeout = 0;
		long start = System.currentTimeMillis();
		synchronized (results) {
			// Due to we are using notifyAll(), we need to control the real time
			while (!results.containsKey(corrId) && (timeout - localTimeout) >= 0) {
				results.wait(timeout);
				localTimeout = System.currentTimeMillis() - start;
			}
			if ((timeout - localTimeout) <= 0) {
				throw new TimeoutException("Timeout exception time: " + timeout);
			}
			resp = results.get(corrId);
			// Remove and indicate the key exists (a hashmap can contain a null
			// object, using this we'll know whether a response has been
			// received before)
			results.put(corrId, null);
		}

		return resp.getResp(env);
	}

	
	public long getTimeout() {
		return timeout;
	}

	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public int getRetries() {
		return retries;
	}

	public void setRetries(int retries) {
		this.retries = retries;
	}
	
}
