package com.eactive.eai.batch.job.jobModule.Component;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;
import java.util.HashMap;

import com.eactive.eai.batch.job.jobConstant.ErrorKeys;
import com.eactive.eai.batch.job.jobHandle.JobProcessData;
import com.eactive.eai.batch.job.jobItem.TelegramService;

import com.eactive.eai.batch.common.LoggingUtil;
import com.eactive.eai.batch.doc.BatchDoc;
import com.eactive.eai.batch.rule.phaseinfo.PhaseInfoVO;
import com.eactive.eai.batch.rule.telegraminfo.TelegramInfoManager;
import com.eactive.eai.batch.rule.telegraminfo.TelegramInfoVO;
import com.eactive.eai.batch.telegram.TelegramItemVO;
import com.eactive.eai.batch.telegram.TelegramManager;

/**
 * 정형필드 는 버퍼에 모아서 전송하고 비정형 필드는 패킷 사이즈 만큼 반복적으로 전송하는 SEND Flow Component
 */
public class SEND2 extends JobComponent implements JobComponentService
{

    private static int DEFAULT_PACKET_SIZE = 4096;

    /**
     * JobComponentService functions
     */
    public BatchDoc execute() throws Exception
    {
        //logger.info("{} 현 " + this.C_TYPE + " 단계 시작합니다. " , logHeader);

        // UUID, 흐름규칙 코드 구하기
        logger.debug("{} 1. node count [" + batchDoc.getBatchMsg().getBody().getNodeCount() + "]", logHeader);

        // 2. 현 단계 시작 시간 셋팅
        //batchDoc.getEAIBatchMessage().getBody().setPhaseStartTime(CalendarUtil.getCurrentTimeNoDash16());
        // 3. 소켓을 찾는다.
        socket = bs_man.getRunningSocket(strUUID);
        if (socket == null ) {
            logger.error("{} 일괄처리용 소켓을 찾을 수 없습니다.", logHeader);
            // socket 이 없다면...
            doErrorAction(null, ErrorKeys.ERROR_NO_SOCKET, new String[] {strUUID});
            return batchDoc;
        }
//        DataInputStream din = new DataInputStream(socket.getInputStream());
        DataOutputStream dout = new DataOutputStream(socket.getOutputStream());
        logger.debug("{} 2. found socket", logHeader);
        // 4. LifeCycle을 통해 현 단계 룰정보를 읽어온다.
        JobProcessData  jobdata_Manager = JobProcessData.getInstance();
        HashMap<String, Object> currdata_hm = jobdata_Manager.getBatchStageData(strUUID);
        if (currdata_hm == null) {
            logger.error("{} JobProcessData 가 지정된 UUID 로 없습니다.  체크해보세요.. ", logHeader);
            doErrorAction(null, ErrorKeys.ERROR_NO_PROCDATA, new String[] {strUUID});
            return batchDoc;
        }
        @SuppressWarnings("unchecked")
        ArrayList<PhaseInfoVO> curr_poss_stage = (ArrayList<PhaseInfoVO>) currdata_hm.get(JobProcessData.PROCDATA_NEXTSTAGE);
        if (curr_poss_stage == null || curr_poss_stage.size() == 0) {
            logger.error("{} 현재 단계의 다음진행사항 데이터가  지정되어 있지 않습니다. ", logHeader);
            doErrorAction(null, ErrorKeys.ERROR_NO_NEXTSTAGE, new String[] {strRuleCode, strPhaseCode});
            return batchDoc;
        }
        // 5. Telegram 클래스의 이름을 알아낸다. - SEND 일때는 한가지뿐이다.
        //                                    - RECEIVE 일때는 NODEINFO에서 얻어낸다.
        //      SEND단계 에서 무조건 첫번째 PhaseInfoVO를 읽어들이도록 한다.
        //      SEND단계 에서 보낼수 있는 전문의 종류는 오직 한 종류 이어야 한다.
        PhaseInfoVO pvo = curr_poss_stage.get(0);
        batchDoc.getBatchMsg().getPhaseinfo().setPhaseSeq       (pvo.getPhaseSeq()  );  //Stage Logging 용
        batchDoc.getBatchMsg().getPhaseinfo().setPhaseTelegramID(pvo.getTelegramID());  //Stage Logging 용
        String strTelegramID   = pvo.getTelegramID().trim();
        String strTelegramType = pvo.getTelegramType();
        if (strTelegramID.length() == 0) {
            logger.error("{} SEND stage : No TELEGRAM_ID assigned in DB table", logHeader);
            doErrorAction(null, ErrorKeys.ERROR_NO_TELEGRAM_ID, new String[] {strRuleCode, strPhaseCode, strTelegramID});
            return batchDoc;
        } else {
            logger.debug("{} 3. 현단계 텔레그램 전문 ID [" + strTelegramID + "]", logHeader);
        }
        // 6. 텔러그램 클래스를 생성한다.
        //    >> 텔레그램 클래스를 찾자 --> TelegramInfoDAO
        TelegramInfoVO telegraminfoVO   = TelegramInfoManager.getInstance().getTelegramInfo(strTelegramID);
        batchDoc.getBatchMsg().getPhaseinfo().setPhaseClassName(telegraminfoVO.getTelegramClsName());  //Stage Logging 용
        batchDoc.getBatchMsg().getPhaseinfo().setPhaseMsgCode  (telegraminfoVO.getMsgMetaDstcd()   );  //Stage Logging 용
        logger.debug("{} 3.1. 전문 송신 클래스 생성...", logHeader);
        logger.debug("{}   => @@@@@@@@@@@@@@@@@@@@@@@@@@@@ ClassName: ["+ telegraminfoVO.getTelegramClsName() +"] @@@@@@@@@@@@@@@@@@@@@@@@@@@@", logHeader);
        TelegramService telegramInst = null;
        try {
            telegramInst= (TelegramService)Class.forName(telegraminfoVO.getTelegramClsName()).newInstance();
            if (telegramInst == null) {
                // 지정된 클래스가 없다... 장애처리
                logger.error("{} 지정된 이름을 가지는 클래스가 없습니다. telegramInst is null ", logHeader);// 20250910 npe telegramInst del
                doErrorAction(null, ErrorKeys.ERROR_NO_CLASS_DEFINITION, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
                return batchDoc;
            }
            telegramInst.setTelegramType(strTelegramType);
        } catch (Exception e) {
            doErrorAction(e, null, ErrorKeys.ERROR_NO_CLASS_DEFINITION, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
            return batchDoc;
        }
        logger.debug("{} 4. telegram class [" + telegraminfoVO.getTelegramClsName() + "] generated ok", logHeader);
        // 6.1 여기에서 텔레그램클래스에서 필요한 값들을 셋팅해 준다. : 전문종별, 거래구분코드, 응답코드, 업무관리구분
        String strSndValue_TelegramType = pvo.getTelegramType();
        String strSndValue_OpCode       = pvo.getOpCode();
        String strSndValue_TrClass      = pvo.getTrClass();

        // 7. 텔레그램 클래스 인스탄스에 EAIBatchMessageDocument를 지정한다.
        telegramInst.setEAIBatchMessage(batchDoc);
        // 8. 텔레그램 클래스에 메시지메타코드 를 지정해준다.
        telegramInst.setTelegramCode(telegraminfoVO.getMsgMetaDstcd());
        // 9. 메시지메타코드를 이용하여 전문의 각 필드를 생성해준다.
        boolean rcB = telegramInst.doPreExecute();
        if (!rcB) {
            logger.error("{} 텔레그램 인스탄스 doPreExecute  실행  시  장애 발생", logHeader);
            doErrorAction(telegramInst, ErrorKeys.ERROR_RECV_PRE_LENGTH, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
            return batchDoc;
        }
        // 10. RULE DB에서 읽은 조립시 참조값들을 넣어준다.
        telegramInst.setFldTelegramType(strSndValue_TelegramType);
        telegramInst.setFldOpCode(strSndValue_OpCode);
        telegramInst.setFldTrClass(strSndValue_TrClass);
        logger.debug("{} 5. get basic value for compiling telegram", logHeader);
        // 11. 송신전문을 조립한다.
        boolean rcCompile = telegramInst.doExecute();
        if (!rcCompile) {
            logger.error("{} 전문조립중 장애 발생 ", logHeader);
            doErrorAction(telegramInst, ErrorKeys.ERROR_EXECUTION, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
            return batchDoc;
        }
        batchDoc.getBatchMsg().getPhaseinfo().setTelegramTypeValue(telegramInst.getTelegramType());  //Stage Logging 용
        batchDoc.getBatchMsg().getPhaseinfo().setBizCodeValue     (telegramInst.getTrClass()     );  //Stage Logging 용
        batchDoc.getBatchMsg().getPhaseinfo().setControlCodeValue (telegramInst.getOpCode()      );  //Stage Logging 용
        batchDoc.getBatchMsg().getPhaseinfo().setResponseCodeValue(telegramInst.getResCode()     );  //Stage Logging 용
        logger.debug("{} 6. doExecute called : compile ok!", logHeader);
        // 12. 조립된 송신전문 Validation
        rcB = telegramInst.doValidation();
        if (!rcB) {
            logger.error("{} 텔레그램 인스탄스 doValidation  실행  시  장애 발생", logHeader);
            doErrorAction(telegramInst, ErrorKeys.ERROR_SEND_VALIDATION);
            return batchDoc;
        }
        logger.debug("{} 7. doValidation called ", logHeader);
        // 13. 소켓을 WRITE 한다.
        try {
            ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
            int nDataLength = telegramInst.getSendLength(); // 총 송신 대상 길이
            logger.info("{} telegram length to send is " + nDataLength + " :텔레그램 데이터 송신시작 ", logHeader);

            for (int i=0; i<telegramInst.getFieldCount(); i++) {
                String strFldLen = telegramInst.getFieldLength(i);
                if (strFldLen.trim().equalsIgnoreCase(TelegramService.FIELD_NO_LENGTH)) {
                    if (outBuffer.size() > 0) {
                        //비정형 필드 데이터를 송신하기 전에 정형 필드 데이터를 담아놨던 Buffer를 송신한다.
                        // XX가 맨처음인 경우에는 write 하지 않음. 2009.01.12
                        dout.write(outBuffer.toByteArray(), 0, outBuffer.size());
                        logger.info("{} >>> Wrote ACCUMULATED FORMAL field data to socket : " + outBuffer.size() + " bytes", logHeader);
                        //정형 필드 Buffer 초기화
                        outBuffer.reset();  //outBuffer = new ByteArrayOutputStream();
                    }

                    // 비정형 필드의 길이 계산
                    int nFieldLength = telegramInst.getCalculatedLength(i);
                    if (nFieldLength<0) {
                        logger.error("{} not calculated of informal Field ["+ (i+1) +"] data length", logHeader);
                        doErrorAction(telegramInst, ErrorKeys.ERROR_INFORMAL_FIELD_LENGTH, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
                        telegramInst.doPostExecute();
                        return batchDoc;
                    }
                    int nFieldSentBytes = 0; //비정형필드 데이터 Write(또는 Send) 바이트
                    int readLen = 0;
                    int nPacketSize = batchDoc.getBatchMsg().getHeader().getPacketSize();

                    if (nPacketSize <=0 ){
                        logger.info("{} (INFORAML field data read buffer size = "+ nPacketSize +"/ DEFAULT_PACKET_SIZE ["+DEFAULT_PACKET_SIZE+"]로  설정합니다.", logHeader);
                        nPacketSize = DEFAULT_PACKET_SIZE;        //nPacketSize 값이 0보다 크거나 같으면 기본값 4096 설정..
                    }
                    byte[] aBuffer = new byte[nPacketSize];
                    logger.info("{} INFORMAL field write start...", logHeader);
                    logger.info("{} (INFORAML field data read buffer size = "+ nPacketSize +" (= Packet Size))", logHeader);
                    while (nFieldSentBytes < nFieldLength) {
                        // 비정형 필드의 데이터를 읽어온다.
                        readLen = telegramInst.getInformalFieldSendData(i, aBuffer);
                        if (readLen < 0) {
                            logger.error("{} read file content error in Telegram [getInformalFieldSendData]", logHeader);
                            doErrorAction(telegramInst, ErrorKeys.ERROR_READ_FILE, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
                            telegramInst.doPostExecute();
                            return batchDoc;
                        }
                        dout.write(aBuffer, 0, readLen);
                        nFieldSentBytes += readLen;
                    }
                    TelegramItemVO item = TelegramManager.getInstance().getTelegramField(telegraminfoVO.getMsgMetaDstcd(), i+1); //log용
                    logger.info("{} >>> Wrote INFORMAL field data to socket : "+ (i+1) +". ["+ item.getFieldType() +"] ["+ item.getFieldLength() +"(비정형)] ["+ nFieldSentBytes +"(사이즈)]["+ new String(aBuffer) +" ["+ item.getFieldName() +"] => OK", logHeader);

                } else {
                    byte[] aField = telegramInst.getFieldDataByIndex(i);
                    outBuffer.write(aField, 0, aField.length);

                    TelegramItemVO item = TelegramManager.getInstance().getTelegramField(telegraminfoVO.getMsgMetaDstcd(), i+1); //log용
                    logger.info("{}   - Wrote FORMAL field data to buffer : "+ (i+1) +". ["+ item.getFieldType() +"] ["+ item.getFieldLength() +"] ["+ new String(aField) +"] ["+ item.getFieldName() +"] => OK", logHeader);
                }
            }
            if (outBuffer.size() > 0) {
            	byte[] byteArray = outBuffer.toByteArray();
            	if (logger.isInfo()) {
                	logger.info("Send >>>>>> {}", LoggingUtil.asciiDump(byteArray));
            		logger.info("Send >>>>>> {}", LoggingUtil.hexDump(byteArray));
            	}
                dout.write(byteArray, 0, byteArray.length);
                logger.info("{} >>> Wrote ACCUMULATED FORMAL field data to socket : " + outBuffer.size() + " bytes", logHeader);
            }
            logger.debug("{} 8. all Field sent ok : " + telegramInst.getFieldCount(), logHeader);

        } catch (Exception ex) {
            logger.error("{} ★★★★★ socket write error !! ★★★★★", logHeader, ex);
            doErrorAction(ex, telegramInst, ErrorKeys.ERROR_WRITE_ERROR);
            doErrorAction(telegramInst, ErrorKeys.ERROR_WRITE_ERROR);
            telegramInst.doPostExecute();
            return batchDoc;
        }

        // 14. 실행 후 작업 수행
        rcB = telegramInst.doPostExecute();
        if (!rcB) {
            logger.error("{} 텔레그램 인스탄스 doPostValidation  실행  시  장애 발생", logHeader);
            doErrorAction(telegramInst, ErrorKeys.ERROR_POST_EXECUTION, new String[] {strTelegramID, telegraminfoVO.getTelegramClsName()});
            return batchDoc;
        }
        logger.debug("{} 9. doPostExecute called", logHeader);

        // 텔레그램클래스에 넘겨주었던 배치메세지를 다시 받는다.
        this.batchDoc = telegramInst.getEAIBatchMessage();

        // 15. 현 단계 수행 전문을 일단 저장
        //현재 Telegram Component를 JobProcessData (PROCDATA_PRE_TELEGRAM) 에 저장해둔다. (다음 수행할 Telegram Component 가 현재 데이터를 참조할 수 있도록)
        super.setPreviousTelegramComponent(telegramInst);

        // 16. 다음 단계 결정 : 실질적인 다음 단계 결정은 각 텔레그램 클래스 에서 하도록 하자
        telegramInst.setNextStageInfo(curr_poss_stage);
        this.nextPhaseInfoVO = telegramInst.getNextStage();
        // 다음단계를 찾을 수 없으면 장애처리하도록 한다. - 20060406 by khs
        if (this.nextPhaseInfoVO == null) {
            // 정상적인 다음 단계를 찾을 수 없는 경우...
            logger.error("{} not found next stage ... ["+telegramInst.getTelegramType()+"]", logHeader);
            doErrorAction(telegramInst, ErrorKeys.ERROR_NOTFOUND_NEXTSTAGE, new String[] {strRuleCode, strPhaseCode});
            return batchDoc;
        }
        batchDoc.getBatchMsg().getBody().setNextPhaseCode(nextPhaseInfoVO.getNextPhaseCode());
        logger.debug("{} 10. decide next Stage [" + batchDoc.getBatchMsg().getBody().getNextPhaseCode()+"]", logHeader);
        // 17. 현 단계 종료 시간 셋팅
        //batchDoc.getEAIBatchMessage().getBody().setPhaseEndTime(CalendarUtil.getCurrentTimeNoDash16());
        // 현 단계에 대한 로깅 20060410 by khs
        //LogUtil.setLog(batchDoc);
        return batchDoc;
    }
}