package com.eactive.eai.flowcontrol.jms;

import java.util.Enumeration;
import java.util.Properties;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.ObjectMessage;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;

import com.eactive.eai.common.activemq.AmqPooledConnectionFactory;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.logger.EAILogSender;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.routing.ElinkESBProcessProxy;
import com.eactive.eai.common.routing.RoutingManager;
import com.eactive.eai.common.routing.RoutingVO;
import com.eactive.eai.common.util.JMSSender;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.ext.eai.common.stdmessage.STDMessageKeys;

public class FCActiveMqReceiver {
	private static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	// ------------------------------------
	// Rule Var
	// ------------------------------------
	private String receiverName;
	private String queueName;
	private String errorQueueName;
	private int receiverCount;
	private int errorDelayTimeMs;
	private int retryCount;
	private String messageSelector = "";
	private String description;

	// ------------------------------------
	// Used for Test
	// ------------------------------------
	public static boolean isSingleApplication = false;

	// ------------------------------------
	// Objects needed for RabbitMQ clients
	// ------------------------------------
	private ActiveMQConnectionFactory factory;
	private Connection[] connection;
	private ReceiverObject[] receiverObj = null;

	// ------------------------------------
	// Thread Runtime Info
	// ------------------------------------
	private Thread[] receiverThread = null;
	private boolean[] threadDowned = null;
	public boolean allThreadDowned = true;
	// ------------------------------------

//	private static String instid = "11";
//	static {

//		String serverName = System.getProperty(Keys.WLI_SERVER_KEY);
//		if(serverName != null && serverName.length() > 2) {
//			instid = serverName.substring(serverName.length()-2, serverName.length());
//		}
//	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[ FCActiveMqReceiver INFO ]");
		sb.append("\nReveciver Name = " + this.receiverName);
		sb.append(", Queue Name = " + this.queueName);
		sb.append(", Error Queue Name = " + this.errorQueueName);
		sb.append(", Recever Count = " + this.receiverCount);
		sb.append(", Delay Time(ms) = " + this.errorDelayTimeMs);
		sb.append(", Retry Count = " + this.retryCount);
		sb.append(", Selector = " + this.messageSelector);
		sb.append(", Description = " + this.description);

		return sb.toString();
	}

	public FCActiveMqReceiver(String rcvName, String qname, String errorqname, int rcvCount, int delayMs, int retryCnt,
			boolean isTx, int ackMode, String msgSelector, String descr) throws Exception {

		queueName = qname;
		errorQueueName = errorqname;
		receiverName = rcvName;
		receiverCount = rcvCount;
		errorDelayTimeMs = delayMs;
		retryCount = retryCnt;
		messageSelector = msgSelector;
		description = descr;

		// trim space
		if (queueName != null)
			queueName = queueName.trim();
		if (errorQueueName != null)
			errorQueueName = errorQueueName.trim();
		if (receiverName != null)
			receiverName = receiverName.trim();
		if (messageSelector != null)
			messageSelector = messageSelector.trim();

//		this.isTx = isTx;
		threadDowned = new boolean[receiverCount];
		connection = new Connection[receiverCount];
		receiverObj = new ReceiverObject[receiverCount];
		receiverThread = new Thread[receiverCount];

		String clientId = receiverName;

		String connectionUrl = AmqPooledConnectionFactory.CONNECTION_URI; 

		if (logger.isInfo())
			logger.info("FCActiveMqReceiver Connection : " + connectionUrl);

		try {
			factory = new ActiveMQConnectionFactory(connectionUrl);
			factory.setTrustAllPackages(true);

			// Create the connections
			for (int i = 0; i < receiverCount; i++) {
				// create each connection
				connection[i] = factory.createConnection();

				// clientId = receiverName + instid + "-" + i;
//				clientId = receiverName + "-" + i;

				receiverObj[i] = new ReceiverObject(rcvName, i, connection[i], queueName, this.messageSelector,
						this.errorQueueName, this.errorDelayTimeMs, this.retryCount, connectionUrl);
				receiverObj[i].setName(clientId);
				if (logger.isDebug())
					logger.debug("[" + new java.util.Date() + "] Receiver Thread : [" + clientId + "] " + queueName);
				receiverThread[i] = new Thread(receiverObj[i]);
				receiverThread[i].setDaemon(true);// avoids thread leaks if
				receiverThread[i].start();
			}
		} catch (Exception e) {
			if (logger.isError())
				logger.error(" Exception while in constructor : ", e);
			close();
			throw e;
		}

	}

	/**
	 * Start receiving for all receivers
	 */
	public synchronized void startReceiving() throws Exception {

		if (!allThreadDowned) {
			throw new Exception("Thread Started Already !");
		}

		try {

			allThreadDowned = false;
		} catch (Exception jmse) {
			if (logger.isError()) {
				logger.error("FCQueueReceiver: JMSException while in startReceiving() method : ", jmse);
			}
			close();
			throw jmse;
		}

	}

	/**
	 * Clear any messages received after the connection was started but before the
	 * user starts the test.
	 */
	public synchronized void clearReceived() {
		// Clear Counter
		for (int i = 0; i < receiverObj.length; i++) {
			receiverObj[i].clearCount();
		}
	}

	/**
	 * Stop sending for all Receiver threads
	 */
	public synchronized void stopReceiving() {
		// Cleanup each ReceiverObj (stops sending and causes thread to exit)
		for (int i = 0; i < receiverObj.length; i++) {
			// stops each ReceiverObject
			if (receiverObj[i] != null)
				receiverObj[i].stopReceiver();
			receiverObj[i].stopThread();
		}
	}

	public String[][] getStatus() {
		String[][] receiveStatus = new String[receiverObj.length][4];
		for (int i = 0; i < receiverObj.length; i++) {
			if (receiverObj[i] != null) {
				receiveStatus[i][0] = receiverObj[i].getName();
				receiveStatus[i][1] = "" + receiverObj[i].msgCounter;
				receiveStatus[i][2] = "" + receiverObj[i].msgSuccessCounter;
				receiveStatus[i][3] = "" + receiverObj[i].receiverStatus;
			}
		}
		return receiveStatus;
	}

	public String[] getMessageCount() {
		String[] receiveStatus = new String[2];
		int receivedTotal = 0;
		int successTotal = 0;

		for (int i = 0; i < receiverObj.length; i++) {
			if (receiverObj[i] != null) {
				receivedTotal += receiverObj[i].msgCounter;
				successTotal += receiverObj[i].msgSuccessCounter;
			}
		}
		receiveStatus[0] = "" + receivedTotal;
		receiveStatus[1] = "" + successTotal;

		return receiveStatus;
	}

	/**
	 * Closes all connections which in turn stops the receivers and close
	 */
	public void close() {
		// Close all the connections
		for (int i = 0; i < connection.length; i++) {
			try {
				if (connection[i] != null)
					connection[i].close();
			} catch (Exception ex) {
				if (logger.isError()) {
					logger.error("In close(): Caught Exception trying to close connection(s)", ex);
				}
			}
		}
	}

	// ////////////////////////////////////////////////////////////////////////
	/**
	 * Inner class: ReceiverObject is an ASYNCHRONOUS receiver.
	 */
	// ////////////////////////////////////////////////////////////////////////
	private class ReceiverObject extends Thread implements ExceptionListener {
		private String recvName = null;

		private String queueName = null;
		// Channel
		private Connection connection = null;
		Session session = null;
		MessageConsumer consumer = null;

		private int id; // identifies the ReceiverObject

		private int msgCounter = 0; // keeps track of messages

		private int msgSuccessCounter = 0;

		private int receiverStatus = 0; // 0 - wait, 1 - running, 2 - retry

		private Object lockCounter = new Object();// used to synchronize
													// updates of the message
													// counter

		private boolean keepRunning = false; // used to exit the sending loop
		// in the run method

		private String messageSelector = "";

		private String errorQueueName = "";

		private int errorDelayTimeMs = 0;

		private int retryCount = 0;

		private String queueConFactory = "";

		/**
		 * Construct a ReceiverObject param id - the id number should be unique for each
		 * ReceiverObject param sess - the session object under which the receiver is
		 * created param q - the Queue object to receiver from
		 */
		public ReceiverObject(String recvName, int id, Connection connection, String queueName, String messageSelector,
				String errorQueueName, int errorDelayMs, int retryCnt, String connectionUri) throws Exception {
			init(recvName, id, connection, queueName, messageSelector, errorQueueName, errorDelayMs, retryCnt,
					connectionUri);
		}

		public void init(String recvName, int sid, Connection connection, String queueName, String selector,
				String errQueueName, int deplayMs, int retryCnt, String qConFactory) throws Exception {
			this.recvName = recvName;
			this.id = sid;
			this.connection = connection;
			this.queueName = queueName;
			this.messageSelector = selector;
			this.errorQueueName = errQueueName;
			this.errorDelayTimeMs = deplayMs;
			this.retryCount = retryCnt;
			this.queueConFactory = qConFactory;

			logger.info("recvName=" + this.recvName + ",messageSelector=" + this.messageSelector);
			try {
				this.connection.start();
				this.connection.setExceptionListener(this);
				session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
				Destination destination = session.createQueue(queueName);
				consumer = session.createConsumer(destination);
			} catch (Exception jmse) {
				if (logger.isError())
					logger.error(this.getName()
							+ "] In ReceiverObject constructor: JMSException while setting up receiverObj[" + id + "]"
							+ recvName, jmse);
				close();
				throw jmse;
			}
		}

		/**
		 * Get number of message received since last get
		 * 
		 * return totaSinceLastGet total number of messages received since the last get
		 */
		public int getMsgCount() {
			synchronized (lockCounter) {
				return msgCounter;
			}
		}

		public void setMsgCount(int msgCounter) {
			synchronized (lockCounter) {
				this.msgCounter = msgCounter;
			}
		}

		/**
		 * Clear any messages received
		 */
		public void clearCount() {
			synchronized (lockCounter) {
				if (logger.isError()) {
					logger.error(this.getName() + "] Clearing id: [" + id + "] clearing: " + msgCounter);
				}
				// reset counter
				msgCounter = 0;
				msgSuccessCounter = 0;
			}
		}

		/**
		 * Stop the receiver
		 */
		public void stopReceiver() {
			keepRunning = false;
			// stop Receiver from sending which causes thread to exit run() method
			// and thus finish.
			if (consumer != null)
				try {
					consumer.close();
				} catch (JMSException e) {
				}
			if (session != null)
				try {
					session.close();
				} catch (JMSException e) {
				}
			if (connection != null)
				try {
					connection.close();
				} catch (JMSException e) {
				}
		}

		public void stopThread() {
			// super.stop();
		}

		public void run() {
			if (Thread.currentThread().getName().startsWith("Thread-")) {
				Thread.currentThread().setName(queueName + "-" + Thread.currentThread().getName());
			}
			keepRunning = true;
			threadDowned[id] = false;
			EAIMessage eaiMessage = null;

			while (keepRunning) {
				try {
					receiverStatus = 0;

					Message message = consumer.receive(2000);

					if (message == null) {
//						Thread.sleep(500);
						continue;
					}

					synchronized (lockCounter) {
						receiverStatus = 1;
						msgCounter++;
					}

					ObjectMessage omsg = null;

					if (message == null) {
//						 logger.warn(name + " -> EMPTY MESSAGE...");
						continue;
					}

					if (message instanceof ObjectMessage) {
						omsg = (ObjectMessage) message;
					} else {
						throw new RuntimeException("Invlid JMS Message Type. - " + message);
					}

					try {
						eaiMessage = (EAIMessage) omsg.getObject();
					} catch (Exception e) {
						throw new RuntimeException(this.getName() + "] EAI메시지가 아닙니다. - " + e.getMessage());
					}

//					String guidLogPrefix = "ActiveMQ Receiver] GUID["+eaiMessage.getKbMsg().getKBHeader().getGuIdNo()+"] UUID["+eaiMessage.getSvcOgNo()+"] ";
					String guidLogPrefix = "ActiveMQ Receiver] GUID["
							+ eaiMessage.getMapper().getGuid(eaiMessage.getStandardMessage()) + "] UUID["
							+ eaiMessage.getSvcOgNo() + "] ";

					if (logger.isDebug()) {
						logger.debug(guidLogPrefix + this.getName() + "] receiverObj[" + id + "] Executed - "
								+ eaiMessage.getEAISvcCd() + ":" + eaiMessage.getSvcOgNo());
					}

					RoutingVO routingVO = RoutingManager.getInstance().getRoutingVO(eaiMessage.getFlwCntlRtnNm());
					String serviceURI = routingVO.getSyncRoutingPath();
					boolean isLocal = true;

					Properties prop = new Properties();
					try {
						Enumeration en = omsg.getPropertyNames();
						while (en.hasMoreElements()) {
							String propName = (String) en.nextElement();
							Object objProp = omsg.getObjectProperty(propName);
							prop.put(propName, objProp);// HttpAdapterServiceRest 의 INBOUND_HEADER 항목 고려해서 변경
						}
					} catch (Exception e) {
						throw new RuntimeException(guidLogPrefix + this.getName() + "] 프라퍼티 설정에러 - " + e.getMessage());
					}

					EAIMessage resEAIMessage = null;

					try {
						// EAI 거래처리 Error 이고 retry count가 있으면 회수만큼 재시도
						// errorDelayTimeMs > 0 면 sleep
						int retry = 1;
						// retry count 만큼 반복한다.
						// -1 : 무한대 retry
						// 0 : 처리되지 않는다.

						if (this.retryCount == 0) {
							if (logger.isWarn()) {
								logger.warn(guidLogPrefix + this.getName() + "] SKIP Execute EAISvcCd ("
										+ eaiMessage.getEAISvcCd() + ") Retry Count - " + this.retryCount);
							}
							continue; // 처리를 중단한다.
						} else if (this.retryCount > 1) {
							receiverStatus = 2;
						}

						while (((retry <= this.retryCount) || (this.retryCount == -1)) && keepRunning) {
							resEAIMessage = ElinkESBProcessProxy.callElinkESBProcess(serviceURI, eaiMessage, isLocal,
									prop);

							if (resEAIMessage == null) {
								if (logger.isWarn()) {
									logger.warn(guidLogPrefix + this.getName()
											+ "] response EAIMessage is NULL. SKIP Retry Count - " + this.retryCount);
								}
								break;
							}

							if (MessageUtil.checkRspErrCd(resEAIMessage.getRspErrCd())) {
								break;
							} else {
								if ((this.retryCount > 0) && (retry <= this.retryCount)) {
									if (logger.isWarn()) {
										logger.warn(guidLogPrefix + this.getName() + "] Call Error("
												+ resEAIMessage.getRspErrCd() + ") Retry - " + retry + "/"
												+ this.retryCount);
									}
									// 다음 처리전에 delay를 준다.
									if (this.errorDelayTimeMs > 0) {
										if (logger.isWarn()) {
											logger.warn(guidLogPrefix + this.getName() + "] Delay Before Retry - "
													+ this.errorDelayTimeMs);
										}
										try {
											Thread.sleep(this.errorDelayTimeMs);
										} catch (Exception e) {
										
										}
									}
								} else {
									if (logger.isWarn()) {
										logger.warn(guidLogPrefix + this.getName() + "] FlowControl Call Error("
												+ resEAIMessage.getRspErrCd() + ") No Retry");
									}
								}
							}
							retry++;

						}

					} catch (Exception e) {
						if (logger.isError()) {
							if (resEAIMessage == null) {
								logger.error(
										guidLogPrefix + this.getName() + "] FlowControl Call Error " + e.getMessage(),
										e);
							} else {
								logger.error(guidLogPrefix + this.getName() + "] FlowControl Call Error("
										+ resEAIMessage.getRspErrCd() + ") " + e.getMessage(), e);
							}
						}
					}

					// increment message counter
					synchronized (lockCounter) {
						msgSuccessCounter++;
					}

					// EAI 거래처리 Error 면
					// errorQueueName 이 있으면 redirect
					// errorDelayTimeMs > 0 면 sleep
					if (resEAIMessage != null && (!MessageUtil.checkRspErrCd(resEAIMessage.getRspErrCd()))) {
						if (this.errorQueueName != null && this.errorQueueName.length() > 0) {
							if (logger.isWarn()) {
								logger.warn(guidLogPrefix + this.getName() + "] Redirect Messsage to - "
										+ this.errorQueueName);
							}
							JMSSender.sendToQueue(eaiMessage, queueConFactory, this.errorQueueName, prop);
						}
					}
					// 다음 처리 전에 delay를 준다.
					if (this.errorDelayTimeMs > 0) {
						if (logger.isWarn()) {
							logger.warn(guidLogPrefix + this.getName() + "] Delay Before Receive Next Message - "
									+ this.errorDelayTimeMs);
						}
						try {
							Thread.sleep(this.errorDelayTimeMs);
						} catch (Exception e) {

						}
					}

				} catch (Exception ex) {
					if (logger.isError()) {
						logger.error(this.getName() + "] JMSException for receiverObj[" + id + "] - " + ex.getMessage(),
								ex);
					}
					try {
						// --------------------------------------------
						// Set Error
						if (eaiMessage != null) {
							String[] msgArgs = new String[1];
							msgArgs[0] = eaiMessage.getEAISvcCd();
							String rspErrorCode = "RECEAIFQR001"; // [EAI 서비스코드:{1}] 큐수신자가 플로우컨트롤 호출 중 오류 발생
							String rspErrorMsg = ExceptionUtil.make(ex, rspErrorCode, msgArgs);
//							eaiMessage.setRspErrCd(rspErrorCode);
//							eaiMessage.setRspErrMsg(rspErrorMsg);
							eaiMessage.setRspErr(rspErrorCode,rspErrorMsg);
							// --------------------------------------------
//							String mesgDmanDvcd = eaiMessage.getKbMsg().getKBHeader().getTelgmDmndDstcd();
//							String mesgDmanDvcd = eaiMessage.getExtMsg().getSendRecv();
							String mesgDmanDvcd = eaiMessage.getMapper()
									.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
							if (STDMessageKeys.SEND_RECV_CD_RECV.equals(mesgDmanDvcd)) {
								// 응답인 경우
								eaiMessage.setLogPssSno(400);
							} else {
								// 요청 인경우
								eaiMessage.setLogPssSno(200);
							}
							EAILogSender.send(eaiMessage, null);
						} else {
							throw new Exception("EAIMessage is NULL");
						}
					} catch (Exception e) {
						if (logger.isError())
							logger.error(this.getName() + "] ErrorLogging :" + e.getMessage());
					}

					// auto reconnect so wait 10 seconds
					try {
						Thread.sleep(10000);
					} catch (Exception e) {
						// Do nothing
					}
				}
			} // end of while

			// if Stop Event Occurred
			if (!keepRunning) {
				threadDowned[id] = true;
				synchronized (this) {
					boolean tmpDowned = false;
					for (int j = 0; j < threadDowned.length; j++) {
						if (threadDowned[j] == true) {
							tmpDowned = true;
						} else {
							tmpDowned = false;
							break;
						}
					}

					if (tmpDowned) {
						allThreadDowned = true;
					}
				}
			}

		} // end of run

		@Override
		public void onException(JMSException arg0) {
			logger.error("JMS Exception occured.  Shutting down client.");
			stopReceiver();
		}

	}// end of inner class ReceiverObject
}
