package com.eactive.eai.adapter.socket.service;

import java.io.IOException;
import java.net.Socket;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;

import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.socket.common.CommonLib;
import com.eactive.eai.adapter.socket.common.DiagLogger;
import com.eactive.eai.adapter.socket.config.ConfigurationContext;
import com.eactive.eai.adapter.socket.config.SocketAdapterManager;
import com.eactive.eai.batch.common.BatchDirUtil;
import com.eactive.eai.batch.common.CalendarUtil;
import com.eactive.eai.batch.common.CommonKeys;
import com.eactive.eai.batch.common.LogUtil;
import com.eactive.eai.batch.doc.BatchDoc;
import com.eactive.eai.batch.flowController.BatchTargetVO;
import com.eactive.eai.batch.flowController.FlowControllerManager;
import com.eactive.eai.batch.message.EAIBatchMsgManager;
import com.eactive.eai.batch.osd.OutsideManager;
import com.eactive.eai.batch.osd.OutsideVO;
import com.eactive.eai.batch.rule.ruleinfo.RuleInfoManager;
import com.eactive.eai.batch.rule.ruleinfo.RuleInfoVO;
import com.eactive.eai.batch.running.BatchRunningJobManager;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.inbound.ResponseHandler;

public class InboundServer extends Thread implements SocketService {

    private	ConfigurationContext    context;
    private SocketChannel           socketChannel;
    private SocketServer            proxy;
    private Selector                selector;
    private boolean					active;

    // 상태정보
    private int					 	sendCount;
    private int					 	recvCount;
    private long				 	firstActivity;
    private long				 	lastActivity;

    private int                      currentState;

    // pending message for checking operation
    private ByteBuffer              pendingMessage;
    // Logger
    private Logger                  logger;

    private String                  remoteIPAddress;
	private String                  remotePort;

    //----------------------------------------------------------------
    //배치 응답송수신 전용, 전체 단계 작업에 대한 BatchMsg 객체 (Socket Coneection 내내 동일함)
    //(전제조건) 1. 배치 전체 단계 전문 Flow 에 대해서 동일한 Socket Connection이 사용되어야 함
    //           2. 전체 단계 Flow 종료시 해당 Socket Connection 이 종료되고 Inbound Server 가 종료되어야 함
    private BatchDoc batchMsgDoc;
    //----------------------------------------------------------------


    public InboundServer(ConfigurationContext context, SocketChannel socketChannel, SocketServer proxy) {
        String name = this.getName();
        this.setName( context.getAdapterName() + "-CONNECTION" + name.substring( name.lastIndexOf("-")) );

        this.context 		= context;
        this.socketChannel 	= socketChannel;
        this.proxy 			= proxy;

        sendCount			= 0;
        recvCount			= 0;
        firstActivity		= System.currentTimeMillis();
        lastActivity		= System.currentTimeMillis();

        pendingMessage      = ByteBuffer.allocate( 4096 );
        pendingMessage.clear();

        this.currentState   = isAckProtocol() ? CommonLib.NEGOTIATING_PROTOCOL : CommonLib.PROCESSING_PROTOCOL;

        logger              = SocketLogManager.getInstance().getLogger( context.getAdapterGroupName() );

        active              = true;
        remoteIPAddress     = this.socketChannel.socket().getInetAddress().getHostAddress();
		remotePort          = this.socketChannel.socket().getPort() +"";

        //----------------------------------------------------------------
		//BatchMsgDoc 생성
		batchMsgDoc = EAIBatchMsgManager.createBatchMsgWithUUID(true);  //true:초기화함

		//BatchMsgDoc Layer, SubLayer, StartTime 설정
		EAIBatchMsgManager.updateBatchMsgWithStageTime(batchMsgDoc, CommonKeys.LAYER_SOCKET_SERVER, CommonKeys.SUB_LAYER_SOCKET_SERVER);

		try {

			//*** 어댑터에 대해 하나의 대외기관 연결정보(BJ05)만이 매핑되어 있어야 하는 것이 보장되어야 한다.
			logger.info("[InboundServer] Group ["+context.getAdapterGroupName()+"] Adapter["+context.getAdapterName()+"]");
			AdapterVO adapter = AdapterManager.getInstance().getAdapterVO(context.getAdapterGroupName(), context.getAdapterName());
            if (adapter.getRqstRspnsDstcd().equalsIgnoreCase("A")) {
                // 응답수신 셋팅
                batchMsgDoc.getBatchMsg().getHeader().setProcessType(CommonKeys.PROCESS_RESPONSE_RECEIVE);
            } else {
                // 응답송신 셋팅
                batchMsgDoc.getBatchMsg().getHeader().setProcessType(CommonKeys.PROCESS_RESPONSE_SEND);
            }
			logger.info("[InboundServer]  ProcessCode >> " + adapter.getBatchProcessCode());
			ArrayList<OutsideVO> osd_array = OutsideManager.getInstance().getOutsideInfo(adapter.getBatchProcessCode());
			int i=0;
			for (i=0; i<osd_array.size(); i++) {
				OutsideVO tmpVO = osd_array.get(i);
                /*
				logger.info("OSD CODE > [" + tmpVO.getOsdCode() + "]");
                logger.info("BLK SIZE > [" + tmpVO.getBlkSize() + "]");
                logger.info("SEQ SIZE > [" + tmpVO.getSequenceSize() + "]");
                */
				if (tmpVO.getOsdCode().equalsIgnoreCase(adapter.getBatchInstitutionCode())) {
					break;
				}
			}
			OutsideVO   outside = null;
			if (i==osd_array.size()) {
				String errMsg = "[InboundServer] 대외기관 정보를 구할 수  없습니다.  배치업무코드 [" + adapter.getBatchProcessCode()+"] 대외기관["+adapter.getBatchInstitutionCode()+"]";
				batchMsgDoc.getBatchMsg().getBody().setErrorCode(99);
				batchMsgDoc.getBatchMsg().getBody().setErrorMsg(errMsg);
				throw new Exception(errMsg);
			} else {
                logger.info("OUSIDE INFO index [" + i + "]");
				outside = (OutsideVO) osd_array.get(i);
			}

			// BatchMsg 에 기본값 지정..
			batchMsgDoc.getBatchMsg().getHeader().setProcessCode(adapter.getBatchProcessCode());
			batchMsgDoc.getBatchMsg().getHeader().setProcessName(adapter.getBatchProcessName());
			batchMsgDoc.getBatchMsg().getHeader().setInstitutionCode(adapter.getBatchInstitutionCode());
			batchMsgDoc.getBatchMsg().getHeader().setInstitutionName(adapter.getBatchInstitutionName());
//			batchMsgDoc.getBatchMsg().getHeader().setInstituaionType(adapter.getBatchInstitutionType());
			batchMsgDoc.getBatchMsg().getBody().setPortNo(adapter.getBatchServerPortNo());
			batchMsgDoc.getBatchMsg().getBody().setTimeoutInterval(Integer.toString(adapter.getBatchInterMsgTimeout()));
			batchMsgDoc.getBatchMsg().getHeader().setFlowCode(adapter.getBatchRcvFlowRuleCode());
			batchMsgDoc.getBatchMsg().getHeader().setSystemConnCode(adapter.getBatchSystemConnCode());
			// 새로 추가되는 것들
			batchMsgDoc.getBatchMsg().getHeader().setRuleCode(adapter.getBatchRcvFlowRuleCode());
			RuleInfoVO vo = RuleInfoManager.getInstance().getRuleInfo(adapter.getBatchRcvFlowRuleCode());
			batchMsgDoc.getBatchMsg().getHeader().setRuleDesc((vo!=null)? vo.getDesc() : "Rule Code not found in the TSEAIBR02");

//			// 응답송신기능의 추가를 위해 일부 기능 수정 함.
//			if (adapter.getRqstRspnsDstcd().equalsIgnoreCase("C") ) {
//				// 응답송신 이면
//				String strPathName = BatchDirUtil.getResSendRealDir();
//				if ( strPathName.charAt(strPathName.length()-1) != '/' ) {
//					strPathName = strPathName + '/';
//				}
//				BatchTargetVO btVO = FlowControllerManager.getInstance().getBatchTargetInfo(outside.getBatchCode(), outside.getOsdCode());
//				StringBuffer sb = new StringBuffer();
//
//				sb.append(btVO.getProcessCode());
//				sb.append("/");
//				sb.append(btVO.getOrganCode());
//				strPathName = strPathName + sb.toString();
//				logger.debug("[응답송신] PATH NAME = [" + strPathName + "]");
//
//				File ff = new File(strPathName);
//				if (!ff.exists()) {
//					logger.warn("응답송신용 디렉토리 [" + strPathName + "] 이 없습니다.");
//					throw new Exception(ExceptionUtil.getErrorCode("BWCEAIASI003"));
//				}
//				File[] fileList = ff.listFiles();
//				String strFileName = "";
//				long lnFileSize = -1;
//				if (fileList.length> 0) {
//					for (int i1=0; i1<fileList.length; i1++) {
//						if ( fileList[i1].isFile()) {
//							strFileName = fileList[i1].getName();
//							lnFileSize = fileList[i1].length();
//							break;
//						}
//					}
//				}
//
//				batchMsgDoc.getBatchMsg().getHeader().setFlowCode(outside.getSendRuleCode());
//				batchMsgDoc.getBatchMsg().getHeader().setRuleCode(outside.getSendRuleCode());
//				batchMsgDoc.getBatchMsg().getHeader().setRuleDesc(RuleInfoManager.getInstance().getRuleInfo(outside.getSendRuleCode()).getDesc());
//
//				batchMsgDoc.getBatchMsg().getHeader().setFilePath(strPathName);
//				batchMsgDoc.getBatchMsg().getHeader().setFileName(strFileName);
//				batchMsgDoc.getBatchMsg().getHeader().setRenamedFileName(strFileName);
//				batchMsgDoc.getBatchMsg().getHeader().setFileSize(lnFileSize);
//			}
//			else {
//				//응답 수신 디렉토리 설정
//				String strPathName = BatchDirUtil.getResponseRealDir();
//				if ( strPathName.charAt(strPathName.length()-1) != '/' ) {
//					strPathName = strPathName + '/';
//				}
//				BatchTargetVO btVO = FlowControllerManager.getInstance().getBatchTargetInfo(outside.getBatchCode(), outside.getOsdCode());
//				StringBuffer sb = new StringBuffer();
//
//				sb.append(btVO.getProcessCode());
//				sb.append("/");
//				sb.append(btVO.getOrganCode());
//				strPathName = strPathName + sb.toString();
//				logger.debug("[응답수신] PATH NAME = [" + strPathName + "]");
//				batchMsgDoc.getBatchMsg().getHeader().setFilePath(strPathName);
//				// <<< 응답수신 디렉토리 설정
//			}

			//응답 수신 디렉토리 설정
			String strPathName = BatchDirUtil.getResponseRealDir();
			if ( strPathName.charAt(strPathName.length()-1) != '/' ) {
				strPathName = strPathName + '/';
			}
			BatchTargetVO btVO = FlowControllerManager.getInstance().getBatchTargetInfo(outside.getBatchCode(), outside.getOsdCode());
			StringBuffer sb = new StringBuffer();

			sb.append(btVO.getProcessCode());
			sb.append("/");
			sb.append(btVO.getOrganCode());
			strPathName = strPathName + sb.toString();
			logger.debug("[응답수신] PATH NAME = [" + strPathName + "]");
			batchMsgDoc.getBatchMsg().getHeader().setFilePath(strPathName);
			// <<< 응답수신 디렉토리 설정


//            batchMsgDoc.getBatchMsg().getHeader().setProtocol("TCP/IP");
            batchMsgDoc.getBatchMsg().getHeader().setUserID(adapter.getLnkID());
            batchMsgDoc.getBatchMsg().getHeader().setUserPassword(adapter.getLnkPwd());
            batchMsgDoc.getBatchMsg().getHeader().setBlockSize(outside.getBlkSize()); //TSEAIBJ06에 필드추가
            batchMsgDoc.getBatchMsg().getHeader().setSequenceSize(outside.getSequenceSize()); //TSEAIBJ06에 필드추가
            batchMsgDoc.getBatchMsg().getHeader().setPacketSize(outside.getPacketSize()); // 20060411 추가
            batchMsgDoc.getBatchMsg().getHeader().setRemoteIP(remoteIPAddress);
            batchMsgDoc.getBatchMsg().getHeader().setPort(remotePort);
            //----------------------------------------------------------------
            logger.info("**** USER ID > [" + batchMsgDoc.getBatchMsg().getHeader().getUserID());
            logger.info("**** PASS WD > [" + batchMsgDoc.getBatchMsg().getHeader().getUserPassword());
            logger.info("**** BLOCK   > [" + batchMsgDoc.getBatchMsg().getHeader().getBlockSize());
            logger.info("**** SEQ SIZ > [" + batchMsgDoc.getBatchMsg().getHeader().getSequenceSize());
            logger.info("**** PACKET  > [" + batchMsgDoc.getBatchMsg().getHeader().getPacketSize());


			//단계 종료시간 설정
			batchMsgDoc.getBatchMsg().getBody().setPhaseEndTime(CalendarUtil.getCurrentTimeNoDash());

			//응답수신에 대한 최초 DB로그. StartLog 호출.
			LogUtil.setStartLog(batchMsgDoc);


		} catch (Exception ex) {
			try {
				// 파일로그 처리
				String errMsg = ExceptionUtil.getErrorCode(ex, "BECEAIASI010", new String[] {batchMsgDoc.getBatchMsg().getHeader().getUUID()});
				logger.error(errMsg, ex);   //응답수신 Socket Server 배치메시지 생성 및 정보 설정시 오류가 발생하였습니다. [UUID: {1}]

				// DB로그 처리
				batchMsgDoc.getBatchMsg().getBody().setPhaseEndTime(CalendarUtil.getCurrentTimeNoDash());
				LogUtil.setErrorLog(batchMsgDoc, errMsg);

			} catch (Exception e) {
				logger.error("[Socket Server] ★★★★★ 응답수신 Socket Server 예외 처리 중 에러 !! ★★★★★", e);
			}
		}
    }

	public long getFirstActivity() {
		return this.firstActivity;
	}

	public long getLastActivity() {
		return this.lastActivity;
	}

    public String getRemotePort() {
        return this.remotePort;
    }

    public String getRemoteIPAddress() {
        return this.remoteIPAddress;
    }

    public boolean idle(long timeout) {
        long idleTime   = System.currentTimeMillis() - this.lastActivity;
        return ( idleTime > timeout );
    }

    public boolean isActive() {
        return active;
    }

    public synchronized void shutdown() {
        active          = false;
        
        notify();

        try {
            Socket socket = this.socketChannel.socket();
            if (socket == null) {
                logger.info("InboundServer : socket 이 null 입니다.");
                return;
            }
            if (socket.isClosed()) {
                logger.info("InboundServer : socket 이 이미 close 되었습니다.");
                return;
            }
            
            socket.setSoLinger(true, 100);
            if (socketChannel.isConnectionPending()) {
                logger.info("InboundServer : socket channle isConnectionPending OK!!!");
                socketChannel.finishConnect();
            }
            socket.shutdownInput();
            socket.shutdownOutput();
            socket.close();
            socketChannel.close();

            logger.info("InboundServer : Inbound Socket Closed !!");
            logger.info("inbound socket                    : "+ socket                   );
            logger.info("inbound socket.isBound()          : "+ socket.isBound()         );
            logger.info("inbound socket.isClosed()         : "+ socket.isClosed()        );
            logger.info("inbound socket.isConnected()      : "+ socket.isConnected()     );
            logger.info("inbound socket.isInputShutdown()  : "+ socket.isInputShutdown() );
            logger.info("inbound socket.isOutputShutdown() : "+ socket.isOutputShutdown());

        } catch (Exception e) {
			logger.error( e.getMessage(), e);
        }
    }

    public Socket getCurrentSocket() {
    	return socketChannel.socket();
    }

    public boolean connect() {
    	return true;
    }

    public void disconnect() {
    	shutdown();
        proxy.quiesceShutdown(this);
        interrupt();
    }

    public void setControl(Object control) {
    }

    public void notifyMessage() {
    }

    public int getCurrentState() {
        return this.currentState;
    }

    // Writing전에 확인...
    public boolean isConnected() throws IOException {
    	boolean	flag = true;

    	if ( socketChannel.isConnected() && socketChannel.socket().isBound() ) {
    		ByteBuffer tmpBuffer = ByteBuffer.allocate( 1 );
            tmpBuffer.clear();
    		int i = socketChannel.read( tmpBuffer );
            if ( i > 0 ) {
                try {
                    pendingMessage.put( tmpBuffer.array() );
                } catch ( BufferOverflowException e ) {
                    throw new IOException("Socket 어플리케이션 프로토콜에러 발생 : " + e.getMessage());
                }
            }
    		if ( i == -1 ) {
    			flag = false;
    		}
    	} else {
    		flag = false;
    	}

    	return flag;
    }

    public void setLastActivity() {
        lastActivity = System.currentTimeMillis();
    }

    public void checkActivity() {
        long currentTime = System.currentTimeMillis();
        if ( !CommonLib.getDate( this.lastActivity ).equals( CommonLib.getDate( currentTime )) ) {  //날자비교
            this.resetCount();
        }
    }



    private void resetCount() {
        this.recvCount = 0;
        this.sendCount = 0;
    }

    private boolean isAckProtocol() {
    	return this.context.isAckProtocol();
    }

    private long getTimeout() {
    	return	( this.context.getTimeout() * 1000L );
    }

    private int getTraceLevel() {
    	return context.getTraceLevel();
    }

    public ConfigurationContext getContext() {
        return context;
    }

    /**
     * InboundServer 는 SocketServer 에서 생성되는 Thread 이다.
     *  외부에서 연결요청한 사항을 처리하기 위해 사용되며, 실제 배치처리에서는 실질적인 업무 플로우는
     *  JPD에서 처리하므로 여기에서는 소켓자체를 배치용 매니저에 등록해주고, 사용하기 쉽게 해주고,
     *  모든 context를 JPD 로 넘겨준다.
     */
    public void run() {
        SocketAdapterManager.getInstance().addConnection( this );
        String  socketInfo = this.getSocketInfo(this.socketChannel);

        if ( getTraceLevel() >= DiagLogger.INFO ) {
            logger.info( CommonLib.getMessage("BICEAIASI002", new String[]{ context.getAdapterName(), socketInfo , Thread.currentThread().getName() }) );
        }
        
        String strUUID = batchMsgDoc.getBatchMsg().getHeader().getUUID();
        
        // BatchRunningJobManager 에 Running Job 등록
        logger.info("InboundServer : BatchRunningJobManager 에 응답송수신 작업 등록......");
        BatchRunningJobManager.getInstance().addRunningJobInfo(strUUID, this, batchMsgDoc);

        
        try {
            // 기관별 로그
            Logger.addInstLog(batchMsgDoc);
        	
            //===================================================
            logger.info("InboundServer : FlowController JPD 호출 시작......");
            ResponseHandler handler = new ResponseHandler();
            this.batchMsgDoc = handler.execute(this.batchMsgDoc);
            logger.info("InboundServer : FlowController JPD 호출 완료 !!");
            //===================================================
            
        } finally {
            // BatchRunningJobManager 에서 Running Job 제거
            logger.info("InboundServer : BatchRunningJobManager 에서 응답송수신 작업 제거......");
            BatchRunningJobManager.getInstance().removeRunningJobInfo(strUUID);
    
            shutdown();
            proxy.quiesceShutdown(this);
    
            // 인바운드 소켓연결이 없어질 때, 처리
            SocketAdapterManager.getInstance().removeConnection( this );
            if ( getTraceLevel() >= DiagLogger.INFO ) {
                logger.info( CommonLib.getMessage("BICEAIASI003", new String[]{ context.getAdapterName(), socketInfo, Thread.currentThread().getName() }) );
            }
        }
    }

    private String  getSocketInfo(SocketChannel channel) {
        StringBuffer connInfo = new StringBuffer();

        connInfo.append("Local Address=");
        connInfo.append( channel.socket().getLocalAddress().getHostAddress()).append(":").append(channel.socket().getLocalPort()).append(", ");
        connInfo.append("Remote Address=");
        connInfo.append( channel.socket().getInetAddress().getHostAddress()).append(":").append(channel.socket().getPort());

        return connInfo.toString();
    }


    public int write(ByteBuffer src) throws IOException {

    	if (this.selector == null) {// 20250910 uwf
    	    this.selector = Selector.open();
    	}
    	
		this.socketChannel.register( this.selector, SelectionKey.OP_WRITE  );

        try {
        	selector.select( getTimeout() );
        } catch(Exception e) {
        	throw new IOException("Write Select Error");
        }

        Iterator<SelectionKey> it = selector.selectedKeys().iterator();
        int		 		writtenBytes = 0;
        SelectionKey 	key = null;

        while ( it.hasNext() ) {
        	key	= it.next();
        	it.remove();
        	if ( key.isWritable() ) {
        		writtenBytes = this.socketChannel.write( src );
        	}
        }
        return  writtenBytes;
    }

    /**
	 * @return Returns the socketchannel.
	 */
	public SocketChannel getSocketchannel() {
		return socketChannel;
	}
	/**
	 * @param socketchannel The socketchannel to set.
	 */
	public void setSocketchannel(SocketChannel socketchannel) {
		this.socketChannel = socketchannel;
	}

    // 1. Connection 리스트정보 - AdapterGroupName,AdapterName,업무코드,INBOUND, CLIENT, LocalIP:Port, RemoteIP:Port, 송신건수, 수신건수, 상태, 최초연결시각, 최종사용시각
    public String toString() {
        boolean status = true;
        StringBuffer strBuff = new StringBuffer();
        strBuff.append(context.getAdapterGroupName()).append(",");
        strBuff.append(context.getAdapterName()).append(",");
        strBuff.append(context.getBwkClsCd()).append(",").append(context.getBoundUsage()).append(",");
        strBuff.append(context.getSocketType()).append(",");
        strBuff.append(context.getResponseType()).append(",");
        try {
            strBuff.append( getCurrentSocket().getLocalAddress().getHostAddress()).append(":").append(getCurrentSocket().getLocalPort()).append(",");
        } catch( Throwable e ) {
            status = false;
            strBuff.append("?:?,");
        }

        try {
            strBuff.append( getCurrentSocket().getInetAddress().getHostAddress()).append(":").append(getCurrentSocket().getPort()).append(",");
        } catch( Throwable e ) {
            status = false;
            strBuff.append("?:?,");
        }
        strBuff.append(this.sendCount).append(",");
        strBuff.append(this.recvCount).append(",");
        strBuff.append( status ).append(",");
        strBuff.append( CommonLib.getCurrentTime( this.firstActivity )).append(",");
        strBuff.append(CommonLib.getCurrentTime( this.lastActivity ));

        return strBuff.toString();
    }


//    static Object arrayGrow(Object a) {
//        Class cl = a.getClass();
//        if ( !cl.isArray() ) return null;
//
//        Class compType = a.getClass().getComponentType();
//        int length =  Array.getLength(a);
//        int newLength =  length * 11 / 10 + 10;
//        Object newArray = Array.newInstance(compType, newLength);
//        System.arraycopy(a, 0, newArray, 0, length);
//        return newArray;
//    }
}
