package com.eactive.eai.adapter;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;

import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.util.Logger;

public class AdapterGroupVO implements Serializable, Lifecycle {
	private static final long serialVersionUID = -8613631838169562236L;

	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	private String name; // 어뎁터업무그룹명[TSEAIAD01.ADPTRBZWKGROUPNAME]
	private String adptrMsgPtrnCd; // 어댑터메시지유형코드[TSEAIAD01.ADPTRMSGPTRNCD] //[K, N, S] 값을 가짐
	private String eaiBizCode; // EAI업무구분코드[TSEAIAD01.EAIBZWKDSTCD]
	private String type; // 어댑터코드[TSEAIAD01.ADPTRCD]
	private String messageType; // 어댑터메시지구분코드[TSEAIAD01.ADPTRMSGDSTCD]
	private String messageEncode; // 어댑터메시지구분코드[TSEAIAD01.MSGENCODE]
	private String gubun; // 어뎁터입출력구분코드[TSEAIAD01.ADPTRIODSTCD]
	private String refClass; // 참고클래스명[TSEAIAD01.REFCLSNAME]
	private boolean srLogFlag; // 송수신시각로그여부[TSEAIAD01.SNDRCVHMSLOGYN]
	private boolean usedFlag; // 어댑터사용여부[TSEAIAD01.ADPTRUSEYN]
	private boolean spcfcLUUseYn; // 특정LU사용여부[TSEAIAD01.SPCFCLUUSEYN] -> 용도변경 : 로그밸런싱 방식으로 사용 RR/LC
	private String description; // 어댑터업무그룹설명[TSEAIAD01.ADPTRBZWKGROUPDESC]
	private String osidInstiNo; // 대외기관번호[TSEAIAD01.OSIDINSTINO]
	// KESA F/W사용여부[TSEAIAD01.KESAUSEYN]
	// 0 : KESA 미사용, 1: nKESA 사용, 2: uKESA 사용, 3: zKESA 사용
	private String kesaUseYn;

	// 0 : 기본어댑터, 1: TAS어댑터, 2: TAS참조대표어댑터
	private String testMasterYn;
	private String adptrNickName;
	private Map<String, AdapterVO> adapters;

	private Vector<AdapterVO> actives;
	private Vector<AdapterVO> disables;

	private List<String> adapterNames;
	private String companyCodeData; // 업체코드데이터 (업체코드 추출정보 등록)
	private String adptrInstiCode; // 어댑터업체코드
	private String bidInterface; // MCI에서 bid 거래시의 대표 인터페이스
	private AtomicInteger idx = new AtomicInteger(0);
	private boolean started;
	private LifecycleSupport lifecycle = new LifecycleSupport(this);

	private int currentTx;
	private long successTx;
	private long errorTx;
	
	private String linkedAdapterGroup;
	private String sameSessionYn;
	private String pairAdapterYn;
	private String clientId;
	
	private boolean weighted = false;
	
	
	public AdapterGroupVO() {
		this("");
	}

	public AdapterGroupVO(String name) {
		this.name = name;
		this.adapters = new ConcurrentHashMap<>();
		this.actives = new Vector<>();
		this.disables = new Vector<>();
		this.adapterNames = new ArrayList<>();
	}
	
	public boolean isWeighted() {
		return weighted;
	}

	public void setWeighted(boolean weighted) {
		this.weighted = weighted;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

	public void setEaiBizCode(String eaiBizCode) {
		this.eaiBizCode = eaiBizCode;
	}

	public String getEaiBizCode() {
		return this.eaiBizCode;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getType() {
		return this.type;
	}

	public void setGubun(String gubun) {
		this.gubun = gubun;
	}

	public String getGubun() {
		return this.gubun;
	}

	public void setMessageType(String messageType) {
		this.messageType = messageType;
	}

	public String getMessageType() {
		return this.messageType;
	}

	public String getMessageEncode() {
		return messageEncode;
	}

	public void setMessageEncode(String messageEncode) {
		this.messageEncode = messageEncode;
	}

	public void setRefClass(String refClass) {
		this.refClass = refClass;
	}

	public String getRefClass() {
		return this.refClass;
	}

	public void setSrLogFlag(boolean srLogFlag) {
		this.srLogFlag = srLogFlag;
	}

	public boolean isSrLogFlag() {
		return this.srLogFlag;
	}

	public void setUsedFlag(boolean usedFlag) {
		this.usedFlag = usedFlag;
	}

	public boolean isUsedFlag() {
		return this.usedFlag;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getDescription() {
		return this.description;
	}

	public void setSpcfcLUUseYn(boolean spcfcLUUseYn) {
		this.spcfcLUUseYn = spcfcLUUseYn;
	}

	public boolean isSpcfcLUUseYn() {
		return this.spcfcLUUseYn;
	}

	public void setAdptrMsgPtrnCd(String adptrMsgPtrnCd) {
		this.adptrMsgPtrnCd = adptrMsgPtrnCd;
	}

	public String getAdptrMsgPtrnCd() {
		return this.adptrMsgPtrnCd;
	}

	public String getOsidInstiNo() {
		return osidInstiNo;
	}

	public void setOsidInstiNo(String osidInstiNo) {
		this.osidInstiNo = osidInstiNo;
	}

	public String getKesaUseYn() {
		return kesaUseYn;
	}

	public void setKesaUseYn(String kesaUseYn) {
		this.kesaUseYn = kesaUseYn;
	}

	public String getTestMasterYn() {
		return testMasterYn;
	}

	public void setTestMasterYn(String testMasterYn) {
		this.testMasterYn = testMasterYn;
	}

	public String getAdptrNickName() {
		return adptrNickName;
	}

	public void setAdptrNickName(String adptrNickName) {
		this.adptrNickName = adptrNickName;
	}

	public String getCompanyCodeData() {
		return companyCodeData;
	}

	public void setCompanyCodeData(String companyCodeData) {
		this.companyCodeData = companyCodeData;
	}

	public String getAdptrInstiCode() {
		return adptrInstiCode;
	}

	public void setAdptrInstiCode(String adptrInstiCode) {
		this.adptrInstiCode = adptrInstiCode;
	}

	public String getBidInterface() {
		return bidInterface;
	}

	public void setBidInterface(String bidInterface) {
		this.bidInterface = bidInterface;
	}
	
	public String getClientId() { return clientId; }

	public void setClientId(String clientId) {
		this.clientId = clientId;
	}

	public synchronized void addAdapterVO(AdapterVO avo) {
		this.adapters.put(avo.getName(), avo);
		this.adapterNames.add(avo.getName());
	}

	public AdapterVO getAdapterVO(String adapterName) {
		return adapters.get(adapterName);
	}

	public synchronized void increase() {
		++currentTx;
	}

	public synchronized void decrease() {
		--currentTx;
	}

	public int currentTx() {
		return this.currentTx;
	}

	// GW관련 어댑터 카운터 추가-시작
	public synchronized void increaseSuccess() {
		++successTx;
	}

	public synchronized void increaseError() {
		++errorTx;
	}

	public long successTx() {
		return this.successTx;
	}

	public long errorTx() {
		return this.errorTx;
	}

	public synchronized void resetCount() {
		this.successTx = 0;
		this.errorTx = 0;
	}
	// GW관련 어댑터 카운터 추가-종료
	
	public String getLinkedAdapterGroup() {
		return linkedAdapterGroup;
	}

	public void setLinkedAdapterGroup(String linkedAdapterGroup) {
		this.linkedAdapterGroup = linkedAdapterGroup;
	}

	public String getSameSessionYn() {
		return sameSessionYn;
	}

	public void setSameSessionYn(String sameSessionYn) {
		this.sameSessionYn = sameSessionYn;
	}

	public String getPairAdapterYn() {
		return pairAdapterYn;
	}

	public void setPairAdapterYn(String pairAdapterYn) {
		this.pairAdapterYn = pairAdapterYn;
	}

	public void start() throws LifecycleException {
		if (started)
			throw new LifecycleException("RECEAIAAC030");

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(STARTING_EVENT, this);

		if (!this.usedFlag) {
			String code = "RECEAIAAC040";
			String[] msgArgs = new String[1];
			msgArgs[0] = name;
			String errText = ExceptionUtil.make(code, msgArgs);
			if (logger.isError())
				logger.error(errText);
			throw new LifecycleException(errText);
		}
		
		setWeighted(false);
		idx.set(0);
		
		AdapterVO avo = null;
		Iterator<AdapterVO> it = adapters.values().iterator();
		while (it.hasNext()) {
			avo = it.next();
			if (avo instanceof Lifecycle) {
				try {
					LifecycleListener[] listeners = this.lifecycle.findLifecycleListeners();
					if (listeners != null) {
						for (int j = 0; j < listeners.length; j++) {
							avo.addLifecycleListener(listeners[j]);
						}
					}

					// 어댑터 프라퍼티에 active_onstartup = false 일 경우
					// EAI 서버 기동시에 Adapter를 start하지 않는다.
					boolean activeOnstartup = true;
					AdapterPropManager manager = AdapterPropManager.getInstance();
					String activeOnstartupValue = manager.getProperty(avo.getPropGroupName(), "active_onstartup");

					if (activeOnstartupValue != null && "false".equals(activeOnstartupValue.trim())) {
						activeOnstartup = false;
					}
					
					String lbWeight = manager.getProperty(avo.getPropGroupName(), Keys.LB_WEIGHT);
					if(StringUtils.isNoneEmpty(lbWeight)) {
						int weight = 1;
						try {
							weight = Integer.parseInt(lbWeight);
						}
						catch(NumberFormatException ex) {
							weight = 1;
						}
						
						if(weight > 1) setWeighted(true);  
						avo.setWeight(weight);
					}
					
					avo.setAdapterGroupVO(this);
					if (activeOnstartup) {
						avo.start();
					} else {
						disable(avo);
					}
				} catch (Exception e) {
					disable(avo);
					throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIAAC031") + " : " + this.name, e);
				}
			}
		}
		started = true;
		lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
	}

	public void stop() throws LifecycleException {
		if (!started)
			throw new LifecycleException("RECEAIAAC032");

		lifecycle.fireLifecycleEvent(STOPING_EVENT, this);

		AdapterVO avo = null;
		Iterator<AdapterVO> it = adapters.values().iterator();
		while (it.hasNext()) {
			avo = it.next();
			if (!avo.isStarted())
				continue;
			if (avo instanceof Lifecycle) {
				try {
					avo.stop();
				} catch (Exception e) {
					throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIAAC033") + " : " + this.name, e);
				}
			}
		}

		started = false;
		lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
	}

	public boolean isStarted() {
		return this.started;
	}

	public void active(AdapterVO vo) {
		if (!this.actives.contains(vo) && vo.isStatus()) {
			this.actives.add(vo);
			this.disables.remove(vo);
		}
	}

	public void disable(AdapterVO vo) {
		if (!this.disables.contains(vo) && !vo.isStatus()) {
			this.disables.add(vo);
			this.actives.remove(vo);
		}
	}

	public boolean isAlive() {
		return !this.actives.isEmpty();
	}

	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	public LifecycleListener[] findLifecycleListeners() {
		return lifecycle.findLifecycleListeners();
	}

	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	public Iterator<AdapterVO> getAdapters() {
		return this.adapters.values().iterator();
	}

	public String[] getAdapterNames() {
		String[] names = new String[this.adapterNames.size()];
		for (int i = 0; i < names.length; i++) {
			names[i] = this.adapterNames.get(i);
		}
		return names;
	}

	public synchronized AdapterVO nextAdapterVO() {
		if (this.actives.isEmpty()) {
			return null;
		}
		
		if (this.actives.size() == 1) {
			return this.actives.get(0);
		}
			
		if(spcfcLUUseYn) {
			if(logger.isDebug()) logger.debug("nextAdapterVO selectWithLeastConnection");
			return selectWithLeastConnection();
		}
		else {
			if(isWeighted()) {
				if(logger.isDebug()) logger.debug("nextAdapterVO selectWithWeightedRoundRobin");
				return selectWithWeightedRoundRobin();
			}
			else {
				if(logger.isDebug()) logger.debug("nextAdapterVO selectWithRoundRobin");
				return selectWithRoundRobin();
			}
		}
	}
	
	private AdapterVO selectWithRoundRobin() {
		try {
			if (idx.get() >= this.actives.size())
				idx.set(0);
			return this.actives.get(idx.getAndIncrement());
		} catch (Exception e) {
			if (this.actives.isEmpty())
				return null;
			idx.set(0);
			return this.actives.get(idx.getAndIncrement());
		}
	}
	
	private AdapterVO selectWithWeightedRoundRobin() {
		try {
	        int totalWeight = actives.stream().mapToInt(AdapterVO::getWeight).sum();
	        int currentIndex = idx.getAndIncrement() % totalWeight;
	        
	        for (AdapterVO adapter : actives) {
	            currentIndex -= adapter.getWeight();
	            if (currentIndex < 0) {
	                return adapter;
	            }
	        }
	        return this.actives.get(0);
		} catch (Exception e) {
			if (this.actives.isEmpty())
				return null;
			idx.set(0);
			return this.actives.get(idx.getAndIncrement());
		}
	}
	
	private AdapterVO selectWithLeastConnection() {
		try {
			AdapterVO selected = this.actives.get(0);
	        for (AdapterVO adapter : this.actives) {
	            if (adapter.getRequestCount() < selected.getRequestCount()) {
	            	selected = adapter;
	            }
	        }	        
	        return selected;
		} catch (Exception e) {
			if (this.actives.isEmpty())
				return null;
			return this.actives.get(0);
		}
	}
	
	public synchronized void removeAdapterVO(String name) {
		adapters.remove(name);
		this.adapterNames.remove(name);
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("AdapterGroupVO[ name=").append(name);
		sb.append(", adptrMsgPtrnCd=").append(adptrMsgPtrnCd);
		sb.append(", eaiBizCode=").append(eaiBizCode);
		sb.append(", type=").append(type);
		sb.append(", messageType=").append(messageType);
		sb.append(", messageEncode=").append(messageEncode);
		sb.append(", gubun=").append(gubun);
		sb.append(", refClass=").append(refClass);
		sb.append(", srLogFlag=").append(srLogFlag);
		sb.append(", usedFlag=").append(usedFlag);
		sb.append(", spcfcLUUseYn=").append(spcfcLUUseYn);
		sb.append(", description=").append(description);
		sb.append(", osidInstiNo=").append(osidInstiNo);
		sb.append(", kesaUseYn=").append(kesaUseYn);
		sb.append(", testMasterYn=").append(testMasterYn);
		sb.append(", adptrNickName=").append(adptrNickName);
		sb.append(", companyCodeData=").append(companyCodeData); 
		sb.append(", adptrInstiCode=").append(adptrInstiCode);
		sb.append(", bidInterface=").append(bidInterface);
		sb.append(", actives=").append(this.actives.size());
		sb.append(", disables=").append(this.disables.size());
		sb.append(", AdapterList=").append(adapters);
		sb.append(", clientId=").append(clientId);
		sb.append(" ]");

		return sb.toString();
	}
}
