package com.eactive.eai.flowcontrol.jms;

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

import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

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.property.PropManager;
import com.eactive.eai.common.routing.ElinkESBProcessProxy;
import com.eactive.eai.common.routing.RouteKeys;
import com.eactive.eai.common.routing.RoutingManager;
import com.eactive.eai.common.routing.RoutingVO;
import com.eactive.eai.common.server.EAIServerManager;
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 FCQueueReceiver {
	private static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
	private static Logger esbLogger = Logger.getLogger(Logger.LOGGER_ESBFW);
	// ------------------------------------
	// 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 final boolean isSingleApplication = false;
	public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";

	// ------------------------------------
	// Objects needed for JMS clients
	// ------------------------------------
	private QueueConnectionFactory factory;
	private QueueConnection[] connection;
	private QueueSession[] session;
	private ReceiverObject[] receiverObj = null;
	private boolean isTx = false;

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

	private static String instid = "11";
	static {
		instid = EAIServerManager.getInstance().getInstId();
	}

	// For Local Test with Single Application
	private static InitialContext getInitialContext(String url) throws NamingException {
		Hashtable<String, String> env = new Hashtable<>();
		env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
		env.put(Context.PROVIDER_URL, url);
		return new InitialContext(env);
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[ FCQueueReceiver 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 FCQueueReceiver(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 QueueConnection[receiverCount];
		session = new QueueSession[receiverCount];
		receiverObj = new ReceiverObject[receiverCount];
		receiverThread = new Thread[receiverCount];

		Queue[] queue = new Queue[receiverCount];

		InitialContext ctx = null;

		String clientId = receiverName;

		String queueConFactory = "";
		try {
			if (isSingleApplication) {
				if (logger.isDebug())
					logger.debug("Local Application Test");
				ctx = getInitialContext("t3://localhost:7001");
				queueConFactory = "TestConnectionFactory";
			} else {
				ctx = new InitialContext();
				PropManager propManager = PropManager.getInstance();
				queueConFactory = propManager.getProperty(RouteKeys.GROUP_NAME,
						RouteKeys.ROUTING_QUEUE_CONNECTION_FACTORY);

			}
		} catch (Exception e) {
			if (logger.isError())
				logger.error("FCQueueReceiver: get InitialContext Error", e);
			throw e;
		}

		if (logger.isDebug())
			logger.debug(queueConFactory);

		try {
			// Create the connections
			for (int i = 0; i < receiverCount; i++) {
				factory = (QueueConnectionFactory) ctx.lookup(queueConFactory);
				// create each connection
				connection[i] = factory.createQueueConnection();

				// Auto Reconnection with WebLogic Function
				// ((weblogic.jms.extensions.WLConnection)
				// connection[i]).setReconnectPolicy("all");

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

				// connection[i].setClientID(clientId);

				session[i] = connection[i].createQueueSession(isTx, ackMode);
				queue[i] = (Queue) ctx.lookup(queueName);
				receiverObj[i] = new ReceiverObject(i, session[i], queue[i], this.messageSelector, this.errorQueueName,
						this.errorDelayTimeMs, this.retryCount, queueConFactory);
				receiverObj[i].setName(clientId);
				if (logger.isDebug())
					logger.debug("[" + new java.util.Date() + "] Receiver Thread : [" + clientId + "] " + queue[i]);
				receiverThread[i] = new Thread(receiverObj[i]);
				receiverThread[i].setDaemon(true);// avoids thread leaks ifz
			}
//		} catch(weblogic.jms.common.InvalidClientIDException icid) {
//			if(logger.isError()) logger.error("Duplicate ClientID Exception while in constructor : ");
//			icid.printStackTrace();
//			close();
//			throw icid;
		} 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 {
			for (int i = 0; i < connection.length; i++) {
				connection[i].start();
			}

			for (int i = 0; i < receiverThread.length; i++) {
				receiverThread[i].start();
			}
			allThreadDowned = false;
		} catch (JMSException jmse) {
			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 exits JVM
	 */
	public void close() {
		if (isTx) {
			for (int k = 0; k < session.length; k++) {
				try {
					if (session[k] != null) {
						session[k].rollback();
					}
				} catch (JMSException jmse) {
					logger.warn("Exception rolling back session " + k, jmse);
				}
			}
		}

		// Close all the connections before exiting the application
		for (int i = 0; i < connection.length; i++) {
			try {
				if (connection[i] != null)
					connection[i].close();
			} catch (JMSException jmse) {
				logger.error("In close(): Caught JMSException trying to close connection(s)", jmse);
			}
		}
	}

	// ////////////////////////////////////////////////////////////////////////
	/**
	 * Inner class: ReceiverObject is an ASYNCHRONOUS receiver.
	 */
	// ////////////////////////////////////////////////////////////////////////
	private class ReceiverObject extends Thread {

		// Session
		private QueueSession qsession = null; // session to use for receiving

		private QueueReceiver qreceiver = null; // reciever object to use
												// for receiving

		private Queue queue = null; // queue object to receive from

		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(int id, QueueSession sess, Queue q, String messageSelector, String errorQueueName,
				int errorDelayMs, int retryCnt, String qConFactory) throws Exception {
			init(id, sess, q, messageSelector, errorQueueName, errorDelayMs, retryCnt, qConFactory);
		}

		public void init(int sid, QueueSession sess, Queue q, String selector, String errQueueName, int deplayMs,
				int retryCnt, String qConFactory) throws Exception {
			id = sid;
			qsession = sess;
			queue = q;
			messageSelector = selector;
			errorQueueName = errQueueName;
			errorDelayTimeMs = deplayMs;
			retryCount = retryCnt;
			queueConFactory = qConFactory;

			try {

				if (messageSelector == null || messageSelector.trim().length() == 0) {
					qreceiver = qsession.createReceiver(queue);
				} else {
					qreceiver = qsession.createReceiver(queue, messageSelector);
				}
			} catch (JMSException jmse) {
				if (logger.isWarn())
					logger.warn(this.getName()
							+ "] In ReceiverObject constructor: JMSException while setting up receiverObj[" + id + "]",
							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.isWarn())
					logger.warn(this.getName() + "] Clearing id: [" + id + "] clearing: " + msgCounter);
				// reset counter
				msgCounter = 0;
				msgSuccessCounter = 0;
			}
		}

		/**
		 * Stop the receiver
		 */
		public void stopReceiver() {
			// stop Receiver from sending which causes thread to exit run() method
			// and thus finish.
			keepRunning = false;
		}

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

		@Override
		public void run() {
			if (Thread.currentThread().getName().startsWith("Thread-")) {
				Thread.currentThread().setName(queueName + "-" + Thread.currentThread().getName());
			}
			keepRunning = true;
			threadDowned[id] = false;
			javax.jms.Message msg = null;
			ObjectMessage omsg = null;
			EAIMessage eaiMessage = null;

			while (keepRunning) {
				try {
					receiverStatus = 0;
					eaiMessage = null;

					msg = qreceiver.receive(1000);
					if (msg == null) {
						continue;
					}

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

					if (msg instanceof ObjectMessage) {
						omsg = (ObjectMessage) msg;
					} else {
						throw new RuntimeException(this.getName() + "] 지원하지않는 메시지 - " + msg);
					}

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

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

					if (esbLogger.isInfo()) {
						esbLogger.info(guidLogPrefix + this.getName() + "] receiverObj[" + id + "] Executed - "
								+ eaiMessage.getEAISvcCd());
					}

					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 (JMSException 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) {
											logger.warn("sleep error", e);
										}
									}
								} else {
									if (logger.isWarn()) {
										logger.warn(guidLogPrefix + this.getName() + "] FlowControl Call Error("
												+ resEAIMessage.getRspErrCd() + ") No Retry");
									}
								}
							}
							retry++;

						} // while(retry <= this.retryCount)

					} catch (Exception e) {
						if (logger.isError()) {
							logger.error(guidLogPrefix + this.getName() + "] FlowControl Call Error", e);
						}
					}

					if (msg != null) {
						if (isTx) {
							try {
								qsession.commit();
							} catch (JMSException jmse) {
								throw jmse;
							}
						}
						// increment message counter
						synchronized (lockCounter) {
							msgSuccessCounter++;
						}
						msg = null;
					}

					if (resEAIMessage != null) {
						// EAI 거래처리 Error 면
						// errorQueueName 이 있으면 redirect
						// errorDelayTimeMs > 0 면 sleep
						if (!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) {
							logger.warn("sleep error", e);
						}
					}
				} catch (Exception jmse) {
					if (msg != null) {
						msg = null;
					}
					if (logger.isError()) {
						logger.error(
								this.getName() + "] JMSException for receiverObj[" + id + "] - " + jmse.getMessage(),
								jmse);
					}

					if (eaiMessage != null) {
						// --------------------------------------------
						// Set Error
						String[] msgArgs = new String[1];
						msgArgs[0] = eaiMessage.getEAISvcCd();
						String rspErrorCode = "RECEAIFQR001"; // [EAI 서비스코드:{1}] 큐수신자가 플로우컨트롤 호출 중 오류 발생
						String rspErrorMsg = ExceptionUtil.make(jmse, rspErrorCode, msgArgs);
//						eaiMessage.setRspErrCd(rspErrorCode);
//						eaiMessage.setRspErrMsg(rspErrorMsg);
						eaiMessage.setRspErr(rspErrorCode,rspErrorMsg);
						// --------------------------------------------
//						String telgmDmndDstcd = eaiMessage.getKbMsg().getKBHeader().getTelgmDmndDstcd();
//						String telgmDmndDstcd = eaiMessage.getExtMsg().getSendRecv();
						String telgmDmndDstcd = eaiMessage.getMapper()
								.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
						if (STDMessageKeys.SEND_RECV_CD_RECV.equals(telgmDmndDstcd)) {
							// 응답인 경우
							eaiMessage.setLogPssSno(400);
						} else {
							// 요청 인경우
							eaiMessage.setLogPssSno(200);
						}

						try {
							EAILogSender.send(eaiMessage, 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) {
						logger.warn("sleep error", e);
					}
				} catch (Throwable rte) {
					if (logger.isError()) {
						logger.error(this.getName() + "] runtime exception for receiverObj[" + id + "] - "
								+ rte.getMessage(), rte);
					}

					if (eaiMessage != null) {
						// --------------------------------------------
						// Set Error
						String[] msgArgs = new String[1];
						msgArgs[0] = eaiMessage.getEAISvcCd();
						String rspErrorCode = "RECEAIFQR001"; // [EAI 서비스코드:{1}] 플로우컨트롤 호출 중 오류가 발생
						String rspErrorMsg = ExceptionUtil.make(rte, rspErrorCode, msgArgs);
//						eaiMessage.setRspErrCd(rspErrorCode);
//						eaiMessage.setRspErrMsg(rspErrorMsg);
						eaiMessage.setRspErr(rspErrorCode,rspErrorMsg);
						// --------------------------------------------
//						String telgmDmndDstcd = eaiMessage.getKbMsg().getKBHeader().getTelgmDmndDstcd();
//						String telgmDmndDstcd = eaiMessage.getExtMsg().getSendRecv();
						String telgmDmndDstcd = eaiMessage.getMapper()
								.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
						if (STDMessageKeys.SEND_RECV_CD_RECV.equals(telgmDmndDstcd)) {
							// 응답인 경우
							eaiMessage.setLogPssSno(400);
						} else {
							// 요청 인경우
							eaiMessage.setLogPssSno(200);
						}

						try {
							EAILogSender.send(eaiMessage, null);

						} catch (Exception e) {
							if (logger.isError())
								logger.error(this.getName() + "] ErrorLogging :" + e.getMessage());
						}
					}
				}
			} // 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

	}// end of inner class ReceiverObject
}
