package com.eactive.eai.adapter.amq;

import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Properties;
import java.util.UUID;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.jms.pool.PooledConnectionFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.SessionCallback;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.destination.DestinationResolver;

import com.eactive.eai.adapter.ElinkDefaultAdapter;
import com.eactive.eai.adapter.Keys;
import com.eactive.eai.common.message.EAIMessageKeys;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.env.ElinkConfig;

public class AMQSenderAdapter extends ElinkDefaultAdapter {
	public static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

	public static final String ADAPTER_NAME = "ADAPTER_NAME";

	private static final class ProducerConsumer implements SessionCallback<Message> {
		private final String msg;
		private final DestinationResolver destinationResolver;
		private final String queue;
		private final String receiveQueue;
		private final Long receiveTimeoutSeconds;
		private final Long timeToLiveSeconds;
		private final String messageType;
		private final String relayPropertyGroup;

		public ProducerConsumer(final String msg, String queue, String receiveQueue,
				final DestinationResolver destinationResolver, Long receiveTimeoutSeconds, Long timeToLiveSeconds,
				String messageType, String relayPropertyGroup) {
			this.msg = msg;
			this.queue = queue;
			this.receiveQueue = receiveQueue;
			this.destinationResolver = destinationResolver;
			this.receiveTimeoutSeconds = receiveTimeoutSeconds;
			this.timeToLiveSeconds = timeToLiveSeconds;
			this.messageType = messageType;
			this.relayPropertyGroup = relayPropertyGroup;
		}

		public Message doInJms(final Session session) throws JMSException {
			MessageConsumer consumer = null;
			MessageProducer producer = null;
			try {
				final String correlationId = UUID.randomUUID().toString();
				if (logger.isDebug()) {
					logger.debug("correlationId=====" + correlationId);
				}
				final Destination requestQueue = destinationResolver.resolveDestinationName(session, queue, false);
				final Destination replyQueue = destinationResolver.resolveDestinationName(session, receiveQueue, false);

				// Create the consumer first!
				consumer = session.createConsumer(replyQueue, "JMSCorrelationID = '" + correlationId + "'");

				JSONObject propObject = new JSONObject();
				String strMsg = AMQSenderAdapter.extractRequestProperties(propObject, msg, messageType,
						relayPropertyGroup);
				final Message textMessage = session.createTextMessage(strMsg);
				textMessage.setJMSCorrelationID(correlationId);
				textMessage.setJMSReplyTo(replyQueue);
				AMQSenderAdapter.assignRequestProperties(textMessage, propObject);

				// Send the request second!
				producer = session.createProducer(requestQueue);
				if (timeToLiveSeconds != null) {
					producer.setTimeToLive(timeToLiveSeconds.longValue() * 1000);
				}
				producer.send(requestQueue, textMessage);

				// Block on receiving the response with a timeout
				if (receiveTimeoutSeconds != null && receiveTimeoutSeconds.longValue() > 0) {
					return consumer.receive(receiveTimeoutSeconds.longValue() * 1000);
				} else {
					return consumer.receive(AMQAdapterConstants.DEFAULT_MESSAGE_RECEIVE_TIMEOUT * 1000);
				}
			} catch (JMSException jmse) {
				throw jmse;
			} catch (Exception e) {
				throw new JMSException(e.getMessage());
			} finally {
				JmsUtils.closeMessageConsumer(consumer);
				JmsUtils.closeMessageProducer(producer);
			}
		}
	}

	@Override
	public Object execute(Properties prop, Object message, Properties tempProp) throws Exception {
		String syncAsync = tempProp.getProperty(AMQAdapterConstants.TEMP_INTERFACE_RESPONSE_TYPE);
		if (StringUtils.equals(syncAsync, EAIMessageKeys.ASYNC_SVC)) {
			return executeAsync(prop, message, tempProp);
		}

		String sendQueue = prop.getProperty(AMQAdapterConstants.PROP_REQUEST_QUEUE_NAME);
		if (StringUtils.isBlank(sendQueue)) {
			throw new Exception("No have send Queue name");
		}

		String replyQueue = prop.getProperty(AMQAdapterConstants.PROP_REPLY_QUEUE_NAME);
		if (StringUtils.isBlank(replyQueue)) {
			throw new Exception("No have reply Queue name");
		}

		String timeToLive = prop.getProperty(AMQAdapterConstants.PROP_MESSAGE_TIME_TO_LIVE);
		Long timeToLiveSeconds = null;
		if (NumberUtils.isParsable(timeToLive)) {
			timeToLiveSeconds = new Long(timeToLive);
		}

		String receiveTimeout = prop.getProperty(AMQAdapterConstants.PROP_MESSAGE_RECEIVE_TIMEOUT);
		Long receiveTimeoutSeconds = AMQAdapterConstants.DEFAULT_MESSAGE_RECEIVE_TIMEOUT;
		if (NumberUtils.isParsable(receiveTimeout)) {
			receiveTimeoutSeconds = new Long(receiveTimeout);
		}

		// 인터페이스 정보에 설정된 타임아웃이 우선
		String interfaceTimeout = tempProp.getProperty(com.eactive.eai.common.routing.Process.INTERFACE_TIME_OUT);
		if (NumberUtils.isParsable(interfaceTimeout)) {
			long miliseconds = NumberUtils.toLong(interfaceTimeout);
			if (miliseconds >= 1000) {
				receiveTimeoutSeconds = new Long(miliseconds / 1000);
			}
		}

		String encode = tempProp.getProperty(Keys.CHARSET_KEY_OUT, Charset.defaultCharset().name());
		String sendMsg = "";
		if (message instanceof String) {
			sendMsg = (String) message;
		} else if (message instanceof byte[]) {
			sendMsg = new String((byte[]) message, encode);
		}

		String relayPropertyGroup = prop.getProperty(AMQAdapterConstants.PROP_RELAY_PROPERTY_GROUP);
		String relayResponsePropertyKeys = prop.getProperty(AMQAdapterConstants.PROP_RELAY_RESPONSE_PROPERTY_KEYS);
		String messageType = tempProp.getProperty(AMQAdapterConstants.TEMP_MESSAGE_TYPE);

		String adapterName = tempProp.getProperty(ADAPTER_NAME);

		try {
			PooledConnectionFactory connectionFactory = AMQAdapterManager.getInstance()
					.getOutboundPooledConnectionFactory(adapterName);

			JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
			jmsTemplate.setExplicitQosEnabled(true);
			jmsTemplate.setDeliveryPersistent(false);
			jmsTemplate.setReceiveTimeout(receiveTimeoutSeconds.longValue() * 1000);
			if (timeToLiveSeconds != null) {
				jmsTemplate.setTimeToLive(timeToLiveSeconds.longValue() * 1000);
			}

			Message receiveMessage = jmsTemplate
					.execute(new ProducerConsumer(sendMsg, sendQueue, replyQueue, jmsTemplate.getDestinationResolver(),
							receiveTimeoutSeconds, timeToLiveSeconds, messageType, relayPropertyGroup), true);

			String returnMsg = "";
			if (receiveMessage != null) {
				returnMsg = assignResponseProperties(receiveMessage, messageType, relayPropertyGroup,
						relayResponsePropertyKeys);

				if (logger.isDebugEnabled()) {
					logger.debug("AMQSenderAdapter] Received message: {}", returnMsg);
				}
			} else {
				throw new JMSException(
						"Received NULL(The timeout expires or this message consumer is concurrently closed)");
			}

			return returnMsg;
		} catch (Exception e) {
			logger.error("AMQSenderAdapter] Exception (" + adapterName + ") : " + e.getMessage(), e);
			throw e;
		} catch (Throwable t) {
			logger.error("AMQSenderAdapter] Throwable (" + adapterName + ") : " + t.getMessage(), t);
			throw t;
		}
	}

	@SuppressWarnings({ "unchecked", "deprecation" })
	private String assignResponseProperties(Message receiveMessage, String messageType, String relayPropertyGroup,
			String relayResponsePropertyKeys) throws Exception {
		if (!(receiveMessage instanceof TextMessage)) {
			return receiveMessage.toString();
		}

		String returnMsg = ((TextMessage) receiveMessage).getText();

		// Queue property to JSON
		try {
			if (MessageType.JSON.equals(messageType) && StringUtils.isNotBlank(relayPropertyGroup)
					&& StringUtils.isNotBlank(relayResponsePropertyKeys)) {
				String[] relayKeyArr = org.springframework.util.StringUtils
						.tokenizeToStringArray(relayResponsePropertyKeys, ",");
				JSONObject propertyJson = new JSONObject();
				if (StringUtils.equalsIgnoreCase(relayResponsePropertyKeys, "ALL")) {
					Enumeration<String> propertyNames = receiveMessage.getPropertyNames();
					while (propertyNames.hasMoreElements()) {
						String propertyName = propertyNames.nextElement();
						propertyJson.put(propertyName, receiveMessage.getStringProperty(propertyName));
					}
				} else {
					for (String key : relayKeyArr) {
						String value = receiveMessage.getStringProperty(key);
						if (value != null) {
							propertyJson.put(key, value);
						}
					}
				}

				if (propertyJson.size() <= 0) {
					return returnMsg;
				}

				JSONObject bodyJson = null;
				if (StringUtils.isBlank(returnMsg)) {
					bodyJson = new JSONObject();
				} else {
					bodyJson = (JSONObject) JSONValue.parse(returnMsg);
				}

				// malformed json message
				if (bodyJson == null) {
					return returnMsg;
				}

				bodyJson.put(relayPropertyGroup, propertyJson);
				return bodyJson.toJSONString();
			}
		} catch (Exception e) {
			if (logger.isWarn()) {
				logger.warn(e.getMessage());
			}
		}

		return returnMsg;
	}

	private Object executeAsync(Properties prop, Object message, Properties tempProp) throws Exception {
		String sendQueue = prop.getProperty(AMQAdapterConstants.PROP_REQUEST_QUEUE_NAME);
		if (StringUtils.isBlank(sendQueue)) {
			throw new Exception("No have send Queue name");
		}

		String relayPropertyGroup = prop.getProperty(AMQAdapterConstants.PROP_RELAY_PROPERTY_GROUP);
		String messageType = tempProp.getProperty(AMQAdapterConstants.TEMP_MESSAGE_TYPE);
		String timeToLive = prop.getProperty(AMQAdapterConstants.PROP_MESSAGE_TIME_TO_LIVE);

		String encode = tempProp.getProperty(Keys.CHARSET_KEY_OUT, Charset.defaultCharset().name());
		final String sendMsg;
		if (message instanceof String) {
			sendMsg = (String) message;
		} else if (message instanceof byte[]) {
			sendMsg = new String((byte[]) message, encode);
		} else {
			sendMsg = "";
		}

		String adapterName = tempProp.getProperty(ADAPTER_NAME);

		try {
			PooledConnectionFactory connectionFactory = AMQAdapterManager.getInstance()
					.getOutboundPooledConnectionFactory(adapterName);

			JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
			jmsTemplate.setExplicitQosEnabled(true);
			jmsTemplate.setDeliveryPersistent(false);
			if (NumberUtils.isParsable(timeToLive)) {
				jmsTemplate.setTimeToLive(NumberUtils.toLong(timeToLive) * 1000);
			}

			jmsTemplate.send(sendQueue, new MessageCreator() {
				public Message createMessage(Session session) throws JMSException {
					JSONObject propObject = new JSONObject();
					String strMsg = AMQSenderAdapter.extractRequestProperties(propObject, sendMsg, messageType,
							relayPropertyGroup);
					Message ms = session.createTextMessage(strMsg);
					AMQSenderAdapter.assignRequestProperties(ms, propObject);

					return ms;
				}
			});

			return ElinkConfig.getAsyncDummyData(null);
		} catch (Exception e) {
			logger.error("AMQSenderAdapter Async] Exception (" + adapterName + ") : " + e.getMessage(), e);
			throw e;
		} catch (Throwable t) {
			logger.error("AMQSenderAdapter Async] Throwable (" + adapterName + ") : " + t.getMessage(), t);
			throw t;
		}
	}

	@SuppressWarnings({ "deprecation", "unchecked" })
	protected static String extractRequestProperties(JSONObject propObject, String sendMsg, String messageType,
			String relayPropertyGroup) {
		String strMsg = sendMsg;
		if (MessageType.JSON.equals(messageType) && StringUtils.isNotBlank(relayPropertyGroup)
				&& StringUtils.contains(strMsg, relayPropertyGroup)) {
			try {
				JSONObject dataObject = (JSONObject) JSONValue.parse(strMsg);
				JSONObject tmpPropObject = (JSONObject) dataObject.get(relayPropertyGroup);
				if (tmpPropObject != null) {
					propObject.putAll(tmpPropObject);
					dataObject.remove(relayPropertyGroup);
					strMsg = dataObject.toJSONString();
				}
			} catch (Exception e) {
				if (logger.isWarn()) {
					logger.warn(e.getMessage());
				}
			}
		}

		return strMsg;
	}

	protected static void assignRequestProperties(Message ms, JSONObject propObject) {
		if (propObject == null || propObject.size() == 0) {
			return;
		}

		for (Object key : propObject.keySet()) {
			Object obj = propObject.get(key);
			if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
				continue;
			}

			try {
				ms.setStringProperty((String) key, (String) obj);
			} catch (Exception e) {
				if (logger.isWarn()) {
					logger.warn(e.getMessage());
				}
			}
		}
	}
}
