package com.eactive.eai.adapter.wca;

import com.eactive.eai.adapter.ElinkDefaultAdapter;
import com.eactive.eai.adapter.wca.cfg.SNAAdapterManager;
import com.eactive.eai.adapter.wca.data.WCAHeader;
import com.eactive.eai.adapter.wca.data.WCAQueue;
import com.eactive.eai.adapter.wca.exception.WCAException;
import com.eactive.eai.adapter.wca.global.WCADefine;
import com.eactive.eai.adapter.wca.util.MyUtil;
import com.eactive.eai.common.util.Logger;

import java.util.ArrayList;
import java.util.Properties;


/** 
* 1. 기능 : SNAAdapter를 통신을 위한 client 접근 모듈
* 2. 처리 개요 : 
*      - OUTBOUNT Client 호출 메소드 재정의한 클래스
* 3. 주의사항 
*
* @author  : 
* @version : v 1.0.0
* @see     : SNAProperties
* @since   : 2005. 6. 16. 오후 3:39:43
*/
public class SNAAdapterClient extends ElinkDefaultAdapter implements SNAProperties {    
   
    /**
     * SNAAdapter critical한 정보 로깅을 위한 logger 
     */
    Logger adapter_logger = Logger.getLogger(WCADefine.ADAPTER_LOGGER_NAME);
    
    /**
     * SNAAdapter 관리 instance
     */
    SNAAdapterManager	smanager   = SNAAdapterManager.getInstance();
    
    /**
     * 생성자
     */
    public SNAAdapterClient() {        
    }   
    
   /** 
    * 1. 기능 : SNAAdapter 통신을 위한 client 호출 메소드
    * 2. 처리 개요 : 
    *     - Adapter 그룹에 해당하는 WCA 세션을 할당하여 데이타 송수신한다.
    *       LU 이름이 정의되어 있을 경우, 해당 LU 그룹 세션을 할당한다.
    *     - LU 세션이 사용중일 경우, 다른 LU 세션 그룹 세션을 이용한다.
    * 3. 주의사항 
    *
    * @param        prop        프라퍼티 정보 (프라퍼티 정보는 SNAProperties에 정의)
    * @param        message     송신데이타
    * @exception    Exception  
    **/   
    public Object execute(Properties prop, Object message) throws Exception {
        WCASession  wcaSession  = null;
        String      sKey        = null;
        byte[]      pRetBuff    = null;
        String		sCurNodeName = "";
        String      sCurGroupCode  =  "";
        int         nTranTimeout = -1;
        
        long lStartTime = 0L, lEndTime = 0L;
        
        // 20200724 Start
        // Info 레벨로 변경
        // 시간 측정 
        // if (adapter_logger.isDebug()) { lStartTime = System.currentTimeMillis(); }
        if (adapter_logger.isInfo()) { lStartTime = System.currentTimeMillis(); }        
        // 20200724 End
        
        // 1. Validation
        if (message == null) {
        	throw new WCAException(WCAException.E_SEND_DATA_ERROR, "송신데이타가 NULL 입니다.");
        }
        
        String sAdapterGrpName = prop.getProperty(ADAPTER_GRP_NAME);
        if (sAdapterGrpName == null) {
            throw new WCAException(WCAException.E_SEND_DATA_ERROR, "어댑터 그룹 이름");
        }
        
        String sAdapterName = prop.getProperty(ADAPTER_NAME);
        if (sAdapterName == null) {
            throw new WCAException(WCAException.E_SEND_DATA_ERROR, "어댑터 그룹");
        }
        
        sKey = prop.getProperty(TRAN_KEY);
        if (sKey == null) {
            throw new WCAException(WCAException.E_SEND_DATA_ERROR, "Transaction Key");
        }
        
        String sLUName = prop.getProperty(LU_NAME) == null ? "" : prop.getProperty(LU_NAME);            
        // String sDataType = prop.getProperty(DATA_TYPE)== null ? "" : prop.getProperty(DATA_TYPE); 
        String sTranMode = prop.getProperty(TRAN_MODE)== null ? "" : prop.getProperty(TRAN_MODE); 
        
        byte[] pDataBuff = (byte[])message;
        byte[] pSndBuff = new byte[pDataBuff.length + WCAHeader.WCA_HEADER_LEN];
        byte[] pHeaderBuff = null;
        
        if (adapter_logger.isInfo()) { 
        	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] SEND - Properties = " + prop);        	
        }
        
        if (adapter_logger.isDebug()) {        
        	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Data = " + MyUtil.printBytes(WCADefine.EBCDIC_CODE, pDataBuff.length, pDataBuff));
        }
                
        // 2. Properties에서 Header 정보를 구하여 송신데이타 조립
        WCAHeader header = new WCAHeader();
        header.setKey(sKey);
        header.setLUName(prop.getProperty(LU_NAME));
        header.setMode(prop.getProperty(TRAN_MODE));
        header.setDataType(prop.getProperty(DATA_TYPE));       
        header.toString();
        pHeaderBuff = header.getHeader();
        
        System.arraycopy(pHeaderBuff, 0, pSndBuff, 0, WCAHeader.WCA_HEADER_LEN);
        System.arraycopy(pDataBuff , 0, pSndBuff, WCAHeader.WCA_HEADER_LEN,  pDataBuff.length);
        
        // 요청한 timeout으로 설정, 없을 경우 DB에 있는 값으로 한다.
        String sTranTimeout = prop.getProperty(TRAN_TIMEOUT) == null ? "-1" : prop.getProperty(TRAN_TIMEOUT); 
        if (adapter_logger.isInfo()) {  
        	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] Client Request TRAN TIMEOUT = " + sTranTimeout);
        }
        
        nTranTimeout = Integer.parseInt(sTranTimeout);        
        if (adapter_logger.isDebug()) {  
        	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] Key Str = " + sKey);
        }
        
        // 3. Queue 생성
        WCAQueue queueWSA = new WCAQueue(pSndBuff.length, pSndBuff);
        queueWSA.setKey(sKey);
        queueWSA.setTimeout(nTranTimeout);
        
        try {
        	int nLuGrpCount = 1;
        	
            // 4. LU 이름이 정의되었을 경우 해당 LU 세션 그룹 세션을 구한다.
            if (!sLUName.trim().equals("")) {
                if (adapter_logger.isInfo()) {
                	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Dedicate 호출, LU 이름 = " + sLUName + ", So Set LU Group Count to 1.");
                }
                
                // smanager.getGroupCode(sLUName);            
                ArrayList alInfo = smanager.getNodeAndGroupName(sLUName);
                sCurNodeName = (String)alInfo.get(0);
                sCurGroupCode = (String)alInfo.get(1);
            }       
            else {
                // 어댑터 그룹에 속한 WCA 세션 풀 크기를 구함
                nLuGrpCount = smanager.getLuGroupMapCount(sAdapterGrpName);    
                if (nLuGrpCount < 1) {
                    if (adapter_logger.isInfo()) {
                    	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Invalid LU Group Count, Count = " + nLuGrpCount + ", So Set Count to 1.");
                    }                	
                	nLuGrpCount = 1;
                }
                
                if (adapter_logger.isInfo()) {
                	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] LU Group Count = " + nLuGrpCount);
                }                
            }
            
            int iSendCnt = 0;
            
            // 4. 전송 (LU 가 사용중일 경우 다른 LU 세션 그룹으로 전송한다.)            
            for (iSendCnt = 0; iSendCnt < nLuGrpCount; iSendCnt++) {
            	if (iSendCnt > 0) {
                    if (adapter_logger.isWarn()) {
                    	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] LU 세션 사용중 -> 재송신 ");
                    }
            	}
            	
                // 어댑터 그룹, LU 그룹 노드, LU 그룹에 해당하는 WCA 세션을 얻어옴
                wcaSession = getWCASession(queueWSA, sAdapterGrpName, sCurNodeName, sCurGroupCode);
                
                // 20200825 Start
                if (wcaSession == null) {
                	throw new WCAException(WCAException.E_SOFTLINK_NO_SESSION_SLOT, toString());
                }
                // 20200825 End
                
                // 얻어온 WCA 세션 정보 로깅
                if (adapter_logger.isInfo()) {
                	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Session = " + 
                			wcaSession.toString() + ", Trans Timeout = " + wcaSession.getTranTimeout());
                }
                 
                // 요청한 timeout으로 설정이 없을 경우 DB에 있는 값으로 한다.
                if (nTranTimeout == -1) {
                    nTranTimeout = wcaSession.getTranTimeout() + 20; // DB 설정 값 + 20초
                    queueWSA.setTimeout(nTranTimeout);
                }
                
                // 얻어온 WCA 세션에서 LU 그룹 노드 이름 및 LU 그룹 이름 추출
                sCurNodeName = wcaSession.getNodeName();            
                sCurGroupCode = wcaSession.getGroupCode();     
                
                wcaSession.registerRcvQueue(sKey, queueWSA);
                wcaSession.sendData(pSndBuff.length, pSndBuff);
                
                if (adapter_logger.isDebug()) {  
                	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Send Data OK");
                }
                
                // 호스트 데이터에 대한  수신 대기
                synchronized (queueWSA) {
                    if (adapter_logger.isDebug()) {
                    	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Wait to Receive Response...");
                    }
                 
                    // 데이터를 수신할때까지 대기...
                    if (queueWSA.getCurrQueueDataCnt() == 0) {
                        try {
                            if (adapter_logger.isDebug()) {
                            	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] REMAIN_TRANSACTION_TIMEOUT = " + queueWSA.getRemainedTime());
                            }

                            // 20200724 Start
                            // 로그 레벨 변경
                            // if (adapter_logger.isDebug()) { adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Before Wait"); }
                            if (adapter_logger.isInfo()) { adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Before Wait"); }
                            // 20200724 End
                            
                            queueWSA.wait(queueWSA.getRemainedTime());
                            
                            // 20200724 Start
                            // 로그 레벨 변경
                            // if (adapter_logger.isDebug()) { adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] After Wait"); }
                            if (adapter_logger.isInfo()) { adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] After Wait"); }
                            // 20200724 End
                        }
                        // 데이터를 수신하였거나, Timeout이 발생하면 세션 상태를 READY로 전환
                        catch (InterruptedException ie) {
                            if (adapter_logger.isWarn()) { 
                            	adapter_logger.warn("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Interupt Wait");
                            }
                        }
                    }
                }
                
                // 5. 수신 데이타 확인
                // 수신 데이터 갯수 확인, 0인 경우, 타임 아웃 처리
                if (queueWSA.getCurrQueueDataCnt() == 0) {
                    if (adapter_logger.isInfo()) {
                    	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] RECV - Transaction Timeout");                    
                    }
                    WCAException exception = new WCAException(WCAException.E_WCA_TRANTIMEOUT);
                    exception.setMessage(WCAException.displayError(WCAException.E_WCA_TRANTIMEOUT) + ", KEY = " + sKey );
                    throw exception;
                }
                else {
                    if (adapter_logger.isDebug()) {
                    	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Try to get Data From WCAQueue");
                    }
                    
                    // LU가 사용중인 경우 세션그룹 수 만큼 다른 세션 그룹에 할당한다.
                    //if (queueWSA.getResendFlag()) {
                    // 2010.11.16 갈길수차장, 홍명준팀장 요청 : LU연결 전체가 없을 경우 버그 수정
                    
                    // 20191011 Start
                    // if (queueWSA.getResendFlag() && nLuGrpCount > 1) {
                    if (queueWSA.getResendFlag() && nLuGrpCount > 1 && (iSendCnt < (nLuGrpCount - 1))) {
                    // 20191011 End                    	
                    	queueWSA.initRecvData();
                        continue;
                    }
                    
                    // 정상 수신 데이터 처리
                    byte[] pRecvBuff = queueWSA.getDataFromQueue();                    
                    if (adapter_logger.isDebug()) {
                    	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Host Data is Received...");
                    	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Transaction End...");              
                    }          
                    
                    // 20200724 Start
                    // Debug 모드인 경우에 데이터 로깅 처리로 로그 레벨 변경
                    /*
                    if (adapter_logger.isInfo()) {
                    	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] RECV - " + 
                    			MyUtil.printBytes(WCADefine.EBCDIC_CODE, pRecvBuff.length, pRecvBuff));
                    }
                    */
                    if (adapter_logger.isDebug()) {
                    	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] RECV - " + 
                    			MyUtil.printBytes(WCADefine.EBCDIC_CODE, pRecvBuff.length, pRecvBuff));
                    }                
                    // 20200724 End
                                    
                    // property 설정
                    byte[] pRecvModeBuff = new byte[WCAHeader.LEN_MODE];
                    byte[] pRecvStatusBuff = new byte[WCAHeader.LEN_DATA_TYPE];
                    byte[] pRecvLuNameBuff = new byte[WCAHeader.LEN_LUNAME];
                    pRetBuff = new byte[pRecvBuff.length - WCAHeader.WCA_HEADER_LEN];
                    
                    System.arraycopy(pRecvBuff, WCAHeader.OFFSET_MODE, pRecvModeBuff, 0, WCAHeader.LEN_MODE);
                    System.arraycopy(pRecvBuff, WCAHeader.OFFSET_LUNAME, pRecvLuNameBuff, 0, WCAHeader.LEN_LUNAME);
                    System.arraycopy(pRecvBuff, WCAHeader.OFFSET_DATA_TYPE, pRecvStatusBuff, 0, WCAHeader.LEN_DATA_TYPE);
                    System.arraycopy(pRecvBuff, WCAHeader.WCA_HEADER_LEN, pRetBuff, 0, pRecvBuff.length - WCAHeader.WCA_HEADER_LEN);
                
                    // 20200724 Start
                    /*
                    if (new String(pRecvBuff).indexOf(WCAException.WCA_ERROR_PREFIX) != -1) {
                        prop.setProperty(DATA_TYPE, DATA_TYPE_ERROR);   
                        
                        // 로그 
                        String sLogGroupCode = "";
                        String sLogErrCode = new String(pRetBuff).substring(0, WCAException.SOFTLINK_ERROR_SIZE);
                        if (wcaSession != null) {
                        	sLogGroupCode = wcaSession.getGroupCode();
                        }
                        ClientUtil.getInstance().insertErrorLog(sAdapterGrpName, sLogGroupCode, sLUName, sTranMode, sKey, sLogErrCode);
                    }
                    // 20191011 Start
                    else if (new String(pRecvBuff).indexOf(WCAException.SOFTLINK_ERROR_PREFIX) != -1) {
                        prop.setProperty(DATA_TYPE, DATA_TYPE_ERROR);   
                        
                        // 로그 
                        String sLogGroupCode = "";
                        String sLogErrCode = new String(pRetBuff).substring(0, WCAException.SOFTLINK_ERROR_SIZE);
                        if (wcaSession != null) {
                        	sLogGroupCode = wcaSession.getGroupCode();
                        }
                        ClientUtil.getInstance().insertErrorLog(sAdapterGrpName, sLogGroupCode, sLUName, sTranMode, sKey, sLogErrCode);
                    } 
                    // 20191011 End
                    */ 
                    // public static final String 	SOFTLINK_ERROR_PREFIX          = "RECEAISNA";
                    // public static final String 	WCA_ERROR_PREFIX               = "RECEAIOWA";
                    
                    String strRecvErrorPrefix = "";
                    if (pRetBuff != null && pRetBuff.length >= 12) {
                    	byte[] pRecvErrorPrefix = new byte[6];
                    	System.arraycopy(pRetBuff, 0, pRecvErrorPrefix, 0, 6);
                    	strRecvErrorPrefix = new String(pRecvErrorPrefix);
                    }
                    
                    if (strRecvErrorPrefix.compareTo("RECEAI") == 0) {
                        prop.setProperty(DATA_TYPE, DATA_TYPE_ERROR);   
                        
                        byte[] pRecvErrorMsg = new byte[12];
                        System.arraycopy(pRetBuff, 0, pRecvErrorMsg, 0, WCAException.SOFTLINK_ERROR_SIZE);
                        String strRecvErrorMsg = new String(pRecvErrorMsg);
                        
                        String sLogGroupCode = "";
                        if (wcaSession != null) {
                        	sLogGroupCode = wcaSession.getGroupCode();
                        }
                        
                        ClientUtil.getInstance().insertErrorLog(sAdapterGrpName, sLogGroupCode, sLUName, sTranMode, sKey, strRecvErrorMsg);                    	
                    }
                    // 20200724 End                    
                    else {
                        // DATA Type Propperties 변경   
                        ClientUtil.getInstance().setDataTypeProperty(new String(pRecvStatusBuff), prop);
                        
                        // 20070522 : LU_NAME Trim
                        prop.setProperty(LU_NAME, new String(pRecvLuNameBuff).trim());
                    }
                    
                    break;
                }
            }
            
            // 20200724 Start
            // Info 레벨로 변경
            /*
            // 수신 시간 측정
            if (adapter_logger.isDebug()) {
            	lEndTime = System.currentTimeMillis();
            	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Lapse time = " + (lEndTime - lStartTime) + "ms");
            }
            */
            if (adapter_logger.isInfo()) {
            	lEndTime = System.currentTimeMillis();
            	
            	long		elapseTime = lEndTime - lStartTime;
            	
            	adapter_logger.info("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] Lapse time = " + elapseTime + "ms");
            	
    			if (elapseTime > 5000) {
    				adapter_logger.error("SNAAdapterClient:execute> OVEROVERTIME [" + sAdapterGrpName + "] [" + sKey + "] Lapse time = " + elapseTime + "ms");
    			}        
    			else if (elapseTime > 1000) {
    				adapter_logger.error("SNAAdapterClient:execute> OVERTIME [" + sAdapterGrpName + "] [" + sKey + "] Lapse time = " + elapseTime + "ms");
    			}			
            }
            // 20200724 End            
            
            return pRetBuff;
        }
        catch (WCAException wca) {
            if (sKey != null && wcaSession != null) { 
            	wcaSession.unregisterRcvQueue(sKey);
            }
            
            if (adapter_logger.isError()) {  
            	adapter_logger.error("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] " + wca.toString());
            }
            
            //wca.printStackTrace();           
            
            // 로그 
            String sLogGroupCode = "";
            if (wcaSession != null) { 
            	sLogGroupCode = wcaSession.getGroupCode();
            }
            
            ClientUtil.getInstance().insertErrorLog(sAdapterGrpName, sLogGroupCode, sLUName, sTranMode, sKey, wca.getErrorReturnCode());
        	throw wca;
        }
        catch (Exception e) {
            if (sKey != null && wcaSession != null) { 
            	wcaSession.unregisterRcvQueue(sKey);
            }
            
            if (adapter_logger.isDebug()) { 
            	adapter_logger.debug("SNAAdapterClient:execute> [" + sAdapterGrpName + "] [" + sKey + "] " + e.toString());
            }
            adapter_logger.error("SNAAdapterClient:execute error", e);
            throw new WCAException(e);
        }
    }
    

   /** 
    * 1. 기능 : 타임아웃동 이용가능한 WCA 세션 (softlink와 연결된 TCP 세션)이 있을때까지 대기)
    * 2. 처리 개요 : 
    * 3. 주의사항 
    *
    * @param 	queueWSA        	QUEUE 데이타
    * @param 	sAdapterGrpName 	어댑터 그룹 이름
    * @param 	sNodeNmae		 	LU 그룹 노드 이름
    * @param 	sGroupCode      	LU 그룹 이름
    * 
    * @return	WCASession			WCA Session Object
    **/
    private WCASession getWCASession(WCAQueue queueWSA, String sAdapterGrpName, String sNodeName, String sGroupCode) throws WCAException {
        if (queueWSA.getTimeout() == -1) {   
            queueWSA.setDefaultTimeout(WCADefine.DEFAULT_TRANSACTION_TIMEOUT);
        }
            
        while (true) {
            try {
                return smanager.getWCASession(sAdapterGrpName, sNodeName, sGroupCode);
            }
            catch (WCAException e) {       
                // "WCA세션이 없는 경우"가 아니면
                if (!e.getErrorReturnCode().equals(WCAException.E_SOFTLINK_NO_SESSION_SLOT)) {
                    throw e;
                }
                
                // 타임아웃 발생하였다면
                if (queueWSA.getRemainedTime() <= 0L) {
                    throw e;
                }
                else {
                    try { 
                    	Thread.sleep(100);
                    }
                    catch (InterruptedException interrupted) {}
                    
                    continue;
                }
            }
        }        
    }
}




