package com.eactive.eai.batch.job.jobItem;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;

import com.eactive.eai.batch.job.jobConstant.TelegramKeys;
import com.eactive.eai.batch.job.jobHandle.JobProcessData;

import com.eactive.eai.batch.common.StringUtil;
import com.eactive.eai.batch.doc.BatchDoc;
import com.eactive.eai.batch.rule.dirInfo.BatchJobInfoVO;
import com.eactive.eai.batch.rule.dirInfo.DirInfoManager;
import com.eactive.eai.batch.rule.phaseinfo.PhaseInfoVO;
import com.eactive.eai.batch.telegram.TelegramManager;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;

public class DefaultTelegram implements TelegramService
{

    //Telegram 전문구조를 나타내는 MsgMetaDstcd
    protected String TELEGRAM_META_CODE = "";

    protected BatchDoc batchDoc;

    // FileLoger
    protected Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

    public String       logHeader;
    public String       logNodeCount;

    public String   ErrorMsg = "";

    public int      nFileHeadTailLogSize = 500;

    /**
     * Stage 변경 참조용 ArrayList : 실제 FlowComponent에서 조회해서 텔레그램쪽으로 지정해 줘야 한다.
     */
    public ArrayList<PhaseInfoVO> al_NextStage = new ArrayList<PhaseInfoVO>();
    /**
     * 필드를 저장한 Array List : 각각은 Byte Array로 이루어진다.
     */
    public ArrayList<byte []>     al_Field     = new ArrayList<byte []>();
    /**
     * 필드의  Property를 String배열로 가진다.  각 필드 처리시 alignment결정을 위한 것이다.
     * TSEAIBM02의 각 필드에 대한 필드 특성치 (TSEAIBM01참조) 에 대한 배열이다.
     * EX> AN : 좌측정렬, NN : 우측정렬
     */
    public String[]         al_FieldProps ;
    /**
     * 문자열로 각 필드의 실질적인 길이를 나타낸다.
     * 길이값으로 'XX'가 들어 있는 경우가 비정형 필드가 된다.
     */
    public String[]         al_FieldLength;
    public String[]         al_FieldIDs;
    public String[]         al_FieldNames;

    /**
     * Telegram Class의 상태에 따른 KEY WORD 저
     */
    public String           conditionTxt = "";
    /**
     * 모든 Send/Recv 에 사용될 공통 필드 선언 --> SEND TelegramClass 에서 전문 조립시 사용
     * DB에서 읽어들인 값...
     */
    // 전문 종별 : 룰 DB (TSEAIBR02) 의 SEND 단계에 들어 있는 해당 전문 송신에 사용될 전문종별 값이다.
    public String            fldTelegramType = "";
    // 업무관리 코드 : 룰 DB (TSEAIBR02) 의 SEND 단계에 들어 있는 해당 전문 송신에 사용될 업무관리코드 값이다.
    public String            fldOpCode = "";
    // 거래구분코드  : 룰 DB (TSEAIBR02) 의 SEND 단계에 들어 있는 해당 전문 송신에 사용될 거래구분코드 값이다.
    public String            fldTrClass = "";

    //이전 단계 RECV 텔레그램 전문 해석 후 응답코드 값 저장.
    //기본값은 TelegramKeys.RPS_HDB_RET_CODE_SUCCESS (000) 값을 가짐.
    public String            resolveResponseCode = TelegramKeys.RPS_HDB_RET_CODE_SUCCESS;

    public String            telegramType = "";

    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public String getFldTelegramType()
    {
        return this.fldTelegramType;
    }
    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public void   setFldTelegramType(String strTelegramType)
    {
        this.fldTelegramType = strTelegramType;
    }
    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public String getFldOpCode()
    {
        return this.fldOpCode;
    }
    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public void   setFldOpCode(String strOpCode)
    {
        this.fldOpCode = strOpCode;
    }
    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public String getFldTrClass()
    {
        return this.fldTrClass;
    }
    /**
     * SEND 단계에서 사용될 함수 : 룰에 있던 값
     */
    public void   setFldTrClass(String strTrClass)
    {
        this.fldTrClass = strTrClass;
    }

    /**
     * BatchDoc 인스탄스 지정
     */
    public void setEAIBatchMessage(BatchDoc pBatchDoc)
    {
        batchDoc = pBatchDoc;
        logger = batchDoc.getLogger();
    }

    public BatchDoc getEAIBatchMessage()
    {
        return batchDoc;
    }

    /**
     *  Telegram 전문별 특별한 수행기능 처리
     */

    /**
     * boolean doPreExecution
     *      지정된 전문의 구성 필드 배열 선언
     */
    public boolean doPreExecute()
    {
        try {
            /* 1. 단계 기본 정보 추출*/
            TelegramManager telegramManager = TelegramManager.getInstance();
            /* 2. 전문 입력값 생성
             *   - [0]에서 [7]까지는 공통부 (CMS은행 기준)
             *   - 나머지는 개별부
             *   - 주의사항: 전문의 길이를 정의하는 필드는 모든 값을 입력한 다음에 정의되어야 한다.
             *     예, CMS은행의 경우 제일 첫 필드가 전문의 길이를 저장하는 필드이이다. objs[0] 참조
            */
            // 전문필드의 갯수만큼 임시변수 생성
            this.al_FieldLength = telegramManager.getTelegramFieldLengthsArray(this.TELEGRAM_META_CODE);
            this.al_FieldProps  = telegramManager.getTelegramFieldTypesArray  (this.TELEGRAM_META_CODE);
            this.al_FieldIDs    = telegramManager.getTelegramFieldIDsArray    (this.TELEGRAM_META_CODE);
            this.al_FieldNames  = telegramManager.getTelegramFieldNamesArray  (this.TELEGRAM_META_CODE);

            logger.info("{"+logHeader+"} 전문 [" + TELEGRAM_META_CODE + "] 의 필드 수 = " + this.al_FieldLength.length );

            this.al_Field = new ArrayList<byte []>();
            for (int i=0; i<this.al_FieldLength.length ; i++)
            {
                String strLength = this.al_FieldLength[i];
                int    nLength ;
                if (!strLength.equalsIgnoreCase(DefaultTelegram.FIELD_NO_LENGTH)) {

                    try {
                        nLength = Integer.parseInt(strLength);
                    } catch(Exception e) {
                        ErrorMsg = "수신용 길이결정 전문에 길이가 지정되어 있지 않습니다.";
                        logger.info("{"+logHeader+"} 전문[" + TELEGRAM_META_CODE + "] 는 길이결정용 전문구조체이며, 여기에 지정된 필드에 길이가 지정되지 않은  것이 있습니다.");
                        return false;
                    }
                } else {
                    nLength = 1;
                }
                byte[] field = new byte[nLength];
                this.al_Field.add(field);
            }
            return true;

        } catch (Exception e) {
            ErrorMsg = "수신용 길이 결정 중 장애 발생  ";
            logger.error("{} 전문[" + TELEGRAM_META_CODE + "] 용 byte array 를 ArrayList 에 할당 중 장애 발생  >> ", logHeader, e);
        }
        return false;
    }

    public boolean doPreExecuteAfterCopy()
    {
        return true;
    }

    /**
     * 전문 조립하기 / 헤체하기
     */
    public boolean doExecute()
    {
        return true;
    }

    /**
     * 전문 Validation : 송신할 전문, 수시한 전문 Validation
     */
    public boolean doValidation()
    {
        return true;
    }

    /**
     * SEND 단계 : doPreExecute -> doExecute -> dePostExecute 순서로 호출
     * RECV 단계 : doPreExecute -> doExecute -> doPostExecute 순서로 호출
     */
    public boolean doPostExecute()
    {
        return true;
    }

    /**
     * 해당 전문의 총 길이값을 리턴해주는 함수 : 비정형 필드에 대한 고려를 하지 못했다.
     * 사용안함.
     */
    public int getTelegramLength()
    {
        int nTotalLength =0;
        for (int i=0; i< al_Field.size(); i++) {
            byte[] t_field = (byte[]) al_Field.get(i);
            nTotalLength = nTotalLength + t_field.length;
        }

        return nTotalLength;
    }

    /**
     * byte 배열로 받은 데이터를 한꺼번에 필드에 지정해 줄때 사용하는 함수.  비정형필드에 대한 고려가 없다.
     * RECV 방식이 바뀌면서 사용안함.
     * 단순 byte 배열보다는 각 필드별로 데이터를 사용함.
     */
    public void setReceiveData(byte[] recvByteArray)
    {
        ByteArrayInputStream bin = new ByteArrayInputStream(recvByteArray);

        for (int i=0; i<al_Field.size(); i++) {
            byte[] aField = (byte[]) al_Field.get(i);

            bin.read(aField, 0, aField.length);
        }

        // 이것은 업무별로 구현하는 수 밖에는 없다...

        byte[] lenField = (byte[]) al_Field.get(batchDoc.getBatchMsg().getPhaseinfo().getLengthFieldIndex());

        String strRemainLength = new String(lenField);
        int    nTotalLength = 0;
        try {
            nTotalLength = Integer.parseInt(strRemainLength);
        } catch (Exception e) {
            logger.debug("{"+logHeader+"} LENGTH FIELD["+strRemainLength+"] conversion failed : "+e.getMessage() );
            return ;
        }

        int nReceivedLength = 0;
        for (int i=2; i<al_Field.size(); i++) {
            byte[] aField = (byte[]) al_Field.get(i);
            nReceivedLength = nReceivedLength + aField.length;
        }
        logger.info("{"+logHeader+"} 총 수신 전문 길이 : " + nTotalLength);
        logger.info("{"+logHeader+"} 기 수신 전문 길이 : " + nReceivedLength);
    }

    /**
     * byte[] getFieldDataByIndex(int idx)
     * 해당 필드(idx)의 byte 배열을 리턴해 주는 함수.
     * 인덱스가 해당 전문의 필드 수를 넘어가는 경우는 null을 리턴해 준다.
     */
    public byte[] getFieldDataByIndex(int idx)
    {
        if (this.al_Field.size() > idx) {
            byte[] aField = (byte[]) al_Field.get(idx);
            return aField;
        }
        return null;
    }

    /**
     * byte[] getSendData()
     * 각 필드 byte배열을 한꺼번에 조립하여 하나의 byte 배열로 리턴해주는 함수. 비정형 필드에 대해 고려하지 못했다.
     * 사용안함.
     */
    public byte[] getSendData()
    {
        ByteArrayOutputStream ba_out = new ByteArrayOutputStream();
        try {
            for (int i=0; i < al_Field.size(); i++)
            {
                byte[] aField = (byte[]) al_Field.get(i);
                ba_out.write(aField);
            }
            return ba_out.toByteArray();
        } catch (Exception ex) {
            logger.debug("{"+logHeader+"} getSendData : Exception >> " + ex.getMessage());
            return null;
        }
    }

    /**
     * Field Array ( al_Field )에서  지정된 인덱스의 값을 교체해 준다.
     *
     * 지정된 필드의 값을 변경해 준다.
     */
    public void   setFieldDataByIndex(int idx, byte[] buf)
    {
        if (this.al_Field.size() > idx) {
            byte[] aField = (byte[]) al_Field.get(idx);
            int len = aField.length;
            if ( ( len == 1 ) && buf.length > 1) {
                // XX인 경우
                len = (buf.length > 100) ? 100: buf.length;
                aField = new byte[len];
            } else {
                len = (len < buf.length)? len : buf.length;
            }
            for (int i=0; i<len; i++)
                aField[i] = buf[i];
            al_Field.set(idx, aField);
        }
    }

    /**
     * 해당 텔레그램 클래스에 실질적인 전문구조를 나타내는 Meta코드를 지정해 준다.
     * 파라미터 strMsgMetaDstcd는 TSEAIBM02에 들어있는 각 전문구조를 표시하는 MsgMetaDstcd 필드 값이다.
     */
    public void setTelegramCode(String strMsgMetaDstcd)
    {
        this.TELEGRAM_META_CODE = strMsgMetaDstcd;

        //batchMsgDoc 이 먼저 설정되어 있어야함.
        logHeader = "["+ batchDoc.getBatchMsg().getHeader().getUUID() +" / "+
                         batchDoc.getBatchMsg().getHeader().getProcessCode() +" / "+
                         batchDoc.getBatchMsg().getHeader().getInstitutionName() +" / "+
                         batchDoc.getBatchMsg().getHeader().getProcessType() +" / "+
                         batchDoc.getBatchMsg().getHeader().getFileName() +" / "+
                         batchDoc.getBatchMsg().getHeader().getRuleCode() +" / "+
                         batchDoc.getBatchMsg().getPhaseinfo().getPhaseCode() +" / "+
                         batchDoc.getBatchMsg().getPhaseinfo().getPhaseType() +"] ";
        logNodeCount = "[NodeCnt-"+ batchDoc.getBatchMsg().getBody().getNodeCount() +" / "+
                                    this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".")+1) +" / "+
                                    strMsgMetaDstcd +"] ";

    }

    /**
     * SEND/RECV단계 처리 종료 후, 다음 단계 결정을 하기전에 다음단계(Phase)결정의 기본 자료로
     * 현 단계(Phase)에서 가능한 다음 단계 리스트를 지정해 준다. (TSEAIBR02테이블에서 읽어들인 LIST 값)
     */
    public void setNextStageInfo(ArrayList<PhaseInfoVO> stageList)
    {
        this.al_NextStage = stageList;
    }

    /**
     * SEND/RECV 단계에서 사용될 getNextStage 함수구현 ( Child Telegram Class 에서 공통으로 사용 )
     *
     * 다섯개의 조건으로 다음 단계를 결정한다.
     * 1. 전문종별 조건 : RECV단계에서 수신한 전문종별 조건값
     * 2. 업무관리코드 조건 : RECV단계에서 수신한 전문의 업무관리 코드 조건값
     * 3. 거래구분코드 조건 : RECV단계에서 수신한 전문의 거래구분코드 조건값
     * 4. 응답코드 조건 : RECV단계에서 수신한 전문의 응답코드 조건 값
     * 5. KeyWord 조건 : SEND/RECV 단계의 ConditionTxt로 지정되어 있는 KEY 조건 문장
     *
     * 단계(Phase)변환 룰(TSEAIBR02)의 각 레코드에 위의 조건 필드값이 지정되어 있지 않는 경우, 해당 조건은 당연 TRUE 처리한다.
     * KEYWORD 조건 값 중, 'TIMEOUT' 과 'ERROR'는 SEND/RECV의 단계 처리 중, 이미 처리된 부분이므로 여기에서는 고려대상이 안된다.
     * 따라서 이 두 (TIMEOUT, ERROR)에 대한 부분은 무조건 false를 리턴할 것이다.
     *
     * 주의>> 조건이 지정되는 않은 레코드가 PhaseSeq상 앞에 있다면, 항상 해당 레코드의 다음 단계(Phase)가 다음 단계로 결정될 것이다.
     *
     * SEND 단계 :
     *          SEND 단계의 룰정보는 조건전문종별값,조건업무관리코드값,조건거래구분코드값,조건응답코드값 이 지정되어 있으면 안된다.
     *          SEND단계에서 상대방에게 통보하는 응답코드에 따른 룰분기를 할 경우, 상태변환 룰을 적용하고, 해당 텔레그래에서 getResCode를 구현하도록 한다.
     *          KeyWord 조건 값 사용 :
     * RECV 단계 :
     *          위의 다섯 가지 조건이 다 지정될 수도 있다.
     */
    public PhaseInfoVO getNextStage() {
        String strRecvTelegramType  = this.getTelegramType();
        String strRecvOpCode        = this.getOpCode();
        String strRecvTrClass       = this.getTrClass();
        String strRecvResCode       = this.getResCode();

        boolean bCondition_TelegramType = false; //전문종류
        boolean bCondition_OpCode       = false; //업무관리코드
        boolean bCondition_TrClass      = false; //거래구분코드
        boolean bCondition_ResCode      = false; //응답코드
        boolean bCondition_ConditionText= false; //배치 일괄처리 KEY WORD 처리용

        logger.debug("{"+logHeader+"}  getNextStage - strRecvTelegramType : "+strRecvTelegramType);
        logger.debug("{"+logHeader+"}  getNextStage - strRecvOpCode : "+strRecvOpCode);
        logger.debug("{"+logHeader+"}  getNextStage - strRecvTrClass : "+strRecvTrClass);
        logger.debug("{"+logHeader+"}  getNextStage - strRecvResCode : "+strRecvResCode);
        logger.debug("{"+logHeader+"}  getNextStage - ConditionTxt : " + this.getConditionTxt());
        logger.debug("{"+logHeader+"}  this.al_NextStage.size() : " + this.al_NextStage.size());

        //일단 상태변경 규칙 DB Table에 값이 없으면 해당 조건은 true로 처리해 준다.
        int i;
        for (i=0; i < this.al_NextStage.size(); i++) {
            PhaseInfoVO     phase_vo = this.al_NextStage.get(i);
            // 1 전문종별 비교
            if (phase_vo.getCTelegramType().trim().length() == 0) {
                bCondition_TelegramType = true;
            } else {
                if (phase_vo.getCTelegramType().trim().equalsIgnoreCase(strRecvTelegramType.trim()))
                    bCondition_TelegramType = true;
                else
                    bCondition_TelegramType = false;
            }
            // 2 업무관리코드 비교
            if (phase_vo.getCOpCode().trim().length() == 0) {
                bCondition_OpCode = true;
            } else {
                if (phase_vo.getCOpCode().trim().equalsIgnoreCase(strRecvOpCode))
                    bCondition_OpCode = true;
                else
                    bCondition_OpCode = false;
            }
            // 3 거래구분코드 비교
            if (phase_vo.getCTrClass().trim().length() == 0) {
                bCondition_TrClass = true;
            } else {
                if (phase_vo.getCTrClass().trim().equalsIgnoreCase(strRecvTrClass))
                    bCondition_TrClass = true;
                else
                    bCondition_TrClass = false;
            }
            // 4 응답코드 비교
            if (phase_vo.getCResCode().trim().length() == 0) {
                bCondition_ResCode = true;
            } else {
                if (phase_vo.getCResCode().trim().equalsIgnoreCase(strRecvResCode))
                    bCondition_ResCode = true;
                else
                    bCondition_ResCode = false;
            }
            // 5 CondtionTxt (KeyWord) 조건 비교
            if (phase_vo.getConditionTxt().trim().compareToIgnoreCase(this.getConditionTxt().trim()) == 0)
                bCondition_ConditionText = true;
            else
                bCondition_ConditionText = false;

            // 네가지 모든 조건이 일치하면 .. 다시 Condition Text를 체크해 보아야 한다.
            if (bCondition_TelegramType && bCondition_OpCode && bCondition_TrClass && bCondition_ResCode
                && bCondition_ConditionText )
            {
                break;
            }
        }

        if (i < this.al_NextStage.size()) {
            logger.info("{"+logHeader+"}  다음 단계를 찾았습니다.-->" + (PhaseInfoVO)this.al_NextStage.get(i));
            return (PhaseInfoVO)this.al_NextStage.get(i);
        } else {
            logger.debug("{"+logHeader+"} 다음 단계를 찾을 수 없습니다.-->["+this.al_NextStage.size()+"]");
            for (i=0; i < this.al_NextStage.size(); i++) {
                PhaseInfoVO phase_vo = (PhaseInfoVO) this.al_NextStage.get(i);
                String strRuleCTelegramType = phase_vo.getCTelegramType().trim();
                String strRuleCOpCode = phase_vo.getCOpCode().trim();
                String strRuleCTrClass = phase_vo.getCTrClass().trim();
                String strRuleCResCode = phase_vo.getCResCode().trim();
                String strRuleConditionTxt = phase_vo.getConditionTxt().trim();
                logger.debug("{"+logHeader+"} index =["+ ( i + 1 ) +"]["+ phase_vo.getPhaseSeq()+"]strRuleCTelegramType-->["
                        + strRuleCTelegramType+ "]strRuleCOpCode-->["
                        +strRuleCOpCode + "]strRuleCTrClass-->["+strRuleCTrClass+"]strRuleCResCode-->["
                        +strRuleCResCode+ "]strRuleConditionTxt-->["+strRuleConditionTxt+ "]");
            }
            return null;
        }
    }

    /**
     *  RULE의 TelegramType만 가지고, 다음 텔레그램의 ID를 결정한다.
     */
    public String getRealRecvTelegram() {

        //전문헤더의 {전문종별코드} 값
        String strRecvTelegramType = this.getTelegramType();

        //일단 상태변경 규칙 DB Table에 값이 없으면 해당 조건은 true로 처리해 준다.
        for (int i=0; i<this.al_NextStage.size(); i++) {
            PhaseInfoVO info = (PhaseInfoVO) this.al_NextStage.get(i);

            //전문종별코드 비교
            if (info.getCTelegramType().trim().equals("") ||
                info.getCTelegramType().trim().equalsIgnoreCase(strRecvTelegramType.trim())) {

                batchDoc.getBatchMsg().getPhaseinfo().setPhaseSeq       (info.getPhaseSeq()  );  //Stage Logging 용
                batchDoc.getBatchMsg().getPhaseinfo().setPhaseTelegramID(info.getTelegramID());  //Stage Logging 용
                return info.getTelegramID();
            }
        }
        return "";
    }

    /**
     * 현재 전문의 ConditionTxt에 지정되어 있는 값과, 텔레그램에서 체크할 수 있는 상태를 가지고
     * 조건 성립여부를 체크한다. --> 다음 진행 Stage 결정을 위해서
     *
     * 각 Telegram Class에서 오버라이드 구현 할 수도 있다.
     */
    public boolean checkNextStageConditionTxt(String pConditionTxt)
    {
        logger.info("{"+logHeader+"}  Function called : checkNextStageConditionTxt ");
        if (pConditionTxt.equalsIgnoreCase(TelegramKeys.TIMEOUT) ||
            pConditionTxt.equalsIgnoreCase(TelegramKeys.ERROR)           )
        {
            // 이 두가지 경우는 이미  RECV단계에서 처리되므로 별도 처리하지 않는다.
            return false;
        }
        return true;
    }

    /**
     * 상대방에게 보낼 응답코드값을 계산하도록 한다.
     * 이전단계의 텔레그램을 찾아 이전 단계의 응답코드를 반환한다.
     *
     */
    public String getSendResponseCode() throws Exception
    {
        try {
            //처음 업무개시 텔레그램이면 TelegramKeys.RPS_HDB_RET_CODE_SUCCESS (000)을 반환
            if (this.batchDoc.getBatchMsg().getBody().getNodeCount() == 1 )
            {
                return resolveResponseCode;
            } else
            {
            // 이전 전문을 구한다.
                String  strUUID = this.batchDoc.getBatchMsg().getHeader().getUUID();

                HashMap<String, Object> hm_job  = JobProcessData.getInstance().getBatchStageData(strUUID);
                TelegramService prevTelegram = (TelegramService) hm_job.get(JobProcessData.PROCDATA_PRE_TELEGRAM);
                if (prevTelegram == null) {
                    throw new Exception("not found previous telegram instance : needed in compiling of response telegram");
                }
                return prevTelegram.getResolveResponseCode();
            }
        } catch (Exception ex) {
            throw new Exception(ex);
        }
    }


    public String getConditionTxt()
    {
        return this.conditionTxt;
    }

    public void setConditionTxt(String strConsitionTxt)
    {
        this.conditionTxt = strConsitionTxt;
    }

    /**
     * SEND 단계에서 전체 송신 전문의 길이를 구할 때 사용
     * 전문에 따라, 전체 전문의 길이를 포함하는 경우(퇴직연금)도 있고, 특정필드 이후 (길이값 필드 미포함) 의
     * 필드 길이에 대한 것만 전문길이로 표시하는 경우도 있으나,
     * 이 함수는 실질적으로 통신 IO를 일으킬 때, 대상 송신 Bytes수를 리턴하도록 한다.
     *
     * 각 SEND Telegram 에서 개별 구현하도록 한다.
     */
    public int getSendLength()
    {
        return 0;
    }

    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        // 여기서 텔레그램 클래스 인스탄스로 스트링으로 만들어준다.

        return sb.toString();
    }
    /**
     * 수신받은 전문구조에서 '전문종별' 값을 리턴한다.
     * 각 TelegramClass에서 오버라이드 구현하도록 한다.
     * RECV telegram 에서 구현
     */
    public String getTelegramType()
    {
        return this.telegramType;
    }

    public void setTelegramType(String telegramType)
    {
        this.telegramType = telegramType;
    }

    /**
     * 수신받은 전문구조에서 '거래구분코드' 값을 리턴한다.
     * 각 TelegramClass에서 오버라이드 구현하도록 한다.
     * RECV telegram 에서 구현
     */
    public String getTrClass()
    {
        return "";
    }
    /**
     * 수신받은 전문구조에서 '응답코드' 값을 리턴한다.
     * 각 TelegramClass에서 오버라이드 구현하도록 한다.
     * RECV telegram 에서 구현
     */
    public String getResCode()
    {
        return "";
    }
    /**
     * 수신받은 전문구조에서 '업무관리코드' 값을 리턴한다.
     * 각 TelegramClass에서 오버라이드 구현하도록 한다.
     * RECV telegram 에서 구현
     */
    public String getOpCode()
    {
        return "";
    }
    /**
     * Default 구현으로 사용가능한 함수
     *
     * 지정된 필드(idx)에 파라미터 값 (strValue)을 저장해 준다.
     * 세번째 파라미터 Alignment 의 값이 ALIGNMENT_RIGHT("NN") 이면, 오른쪽정렬을 해주고
     * 아니면 왼쪽 정렬 해준다.
     *
     * 오른쪽 정렬일때는 필드값이 필드길이보다 짧을때 '0'을 채워넣어주고
     * 왼쪽 정렬일때 필드값이 필드길이보다 짧을때 ' '(SPACE)를 채워넣어준다.
     */
    public void compileField(int idx, String strValue, String Alignment) throws Exception {
        if (this.al_Field.size() > idx) {
            int aFieldLength = ((byte[])this.al_Field.get(idx)).length;
            ByteArrayOutputStream    bout = new ByteArrayOutputStream();
            try {
                if ( strValue == null ) strValue = "";
                byte[] bValue = strValue.getBytes("KSC5601");

                if (!Alignment.equalsIgnoreCase(ALIGNMENT_RIGHT)) {
                    if (aFieldLength> bValue.length) {
                        int n= aFieldLength - bValue.length;
                        for (int i=0; i<bValue.length; i++)     bout.write(bValue[i]);
                        for (int i=0; i<n; i++)                    bout.write(' ');
                    } else {
                        for (int i=0; i<aFieldLength; i++)        bout.write(bValue[i]);
                    }
                }
                else  {
                    if (aFieldLength > bValue.length) {
                        int n = aFieldLength - bValue.length;
                        for (int i=0; i<n; i++)                    bout.write('0');
                        for (int i=0; i<bValue.length ; i++)     bout.write(bValue[i]);
                    } else {
                        for (int i=0; i<aFieldLength; i++)         bout.write(bValue[i]);
                    }
                }
                 al_Field.set(idx, bout.toByteArray());
            } catch (Exception ex) {
                throw new Exception(ex);
            }
        } else {
            throw new Exception("Telegram compile : overflow index " + idx);
        }
    }

    /**
     * 해당 텔레그램 클래스가 가지고 있는 총 필드의 갯수를 리턴해 준다.
     * 가능하면 필드를 나타내는 ArrayList인 al_Field는 밖으로 안 보여주 위해....
     *
     * default 구현으로 사용가능.
     */
    public int getFieldCount()
    {
        return this.al_Field.size();
    }

    /**
     * 특정 필드(idx)의 필드 길이를 리턴해준다.
     *
     * default 구현으로 사용가능.
     */
    public String getFieldLength(int idx)
    {
        if (this.al_Field.size() > idx) {
            return this.al_FieldLength[idx];
        } else {
            return null;
        }
    }

    /**
     * 특정필드 (idx)의 필드 property (AN, NN, ...)를 리턴해 준다.
     * default 구현으로 사용가능
     */
    public String getFieldProperty(int idx)
    {
        if (this.al_Field.size() > idx) {
            return this.al_FieldProps[idx];
        } else {
            return null;
        }
    }

    /**
     * 비정형 필드에 대한 송수신 길이를 계산해서 리턴해준다.
     *
     * 필드(idx)가 비정형 필드(XX)인지 점검하고 , 해당 필드에 대한 예상 길이를 계산하여 리턴하도록 한다
     *
     * 비정형 필드를 가지고 있는 개별 Telegram Class 에서 오버라이드 필요.
     */
    public int getCalculatedLength(int idx)
    {
        return -1;
    }

    /**
     * return value : 0 no left data to be sent
     *                -1 error
     *                >0 data copied to the data field
     *
     * 비정형 필드를 가지는 송신전문 처리 Telegram Class 에서 오버라이드 되어야 한다.
     * SEND/RECV 단계에서, 각 비정형 필드(idx) 의 실질적인 처리대상 길이를 getCalculatedLength() 로 구한 다음,
     * 이 함수를 반복적을 호출하여 해당 비정형필드 처리완료를 위한 데이터를 얻어낸다.
     * 파라미터 data에 매번 호출 될 때, 비정형 필드 처리를 위한 적절한 데이터를 실어 주도록 한다.
     * 리턴값은 실질적으로 파라미터 data에 실린 데이터의 길이 이다.
     */
    public int getInformalFieldSendData(int idx, byte[] data) {
        return 0;
    }

    /**
     * RECV단계에서 비정형 필드(idx)의 값을 통신 IO 를 통해 읽고, 그 결과를 텔레그램클래스로 넘겨주는 함수이다.
     * 비정형 필드를 가지는 개별 Telegram Class 는 이 함수를 통해 각각 클래스가 가지는 비정형 필드(예> 파일 내용 등...)에 대한
     * 처리를 완료해 줘야 한다.
     *
     * 비정형 필드를 가지는 개별 Telegram Class에서 오버라이드 필요
     */
    public boolean doInformalField(int idx, byte[] data, int length)
    {
        return true;
    }

    // 전문 해체 후 응답코드
    public String getResolveResponseCode()
    {
        return resolveResponseCode;
    }
    //전문 해체 후 응답코드
    public void setResolveResponseCode(String code)
    {
        this.resolveResponseCode = code;
    }

    //파일 송수신 시 파일의 헤더, 트레일러 값을 배치메시지에 설정
    public void setFileHeaderAndTrailer(File f) {

        //파일이 존재하지 않거나 디렉토리면 리턴
        if (f == null || !f.exists() || f.isDirectory()) {
            logger.info("{"+logHeader+"} ["+ (f==null? "": f.getAbsolutePath()) +"]: 파일이 존재하지 않거나 디렉토리입니다. 파일헤더,트레일러 설정안함.");
            return;
        }

        FileInputStream fin = null;
        try {
            long fileHeaderSize  = 500;  //파일헤더 로그 디폴트 사이즈
            long fileTrailerSize = 500;  //파일트레일러 로그 디폴트 사이즈
            BatchJobInfoVO jobInfo = DirInfoManager.getInstance().getFileInfo(batchDoc.getBatchMsg().getHeader().getJobCode());
            if (jobInfo != null) {
                fileHeaderSize  = jobInfo.getFileHeaderLogSize();
                fileTrailerSize = jobInfo.getFileTrailerLogSize()+2;
            }

            if (fileHeaderSize == 0 && fileTrailerSize == 0) return;

            fin = new FileInputStream(f);
            long fileSize = f.length();

            //DB값, 파일사이즈, 4000Byte(로그저장 varchar 최대값) 중 최소값 설정
            fileHeaderSize  = Math.min(Math.min(fileHeaderSize , fileSize), 4000L);
            fileTrailerSize = Math.min(Math.min(fileTrailerSize, fileSize), 4000L);

            byte[] bufHeader  = new byte[(int)fileHeaderSize ];
            byte[] bufTrailer = new byte[(int)fileTrailerSize];
            int readSize = 0;

            //파일 헤더 값 READ
            if (fileHeaderSize > 0) {
                readSize = fin.read(bufHeader, 0, bufHeader.length);
            }

            //파일 트레일러 값 READ
            if (fileTrailerSize > 0 && readSize >= 0 && readSize <= fileSize) {
                int overlapSize = 0;  //헤더와 트레일러의 겹치는 Byte 사이즈
                long skipSize = (fileSize - readSize) - fileTrailerSize;

                if (skipSize > 0) {         //헤더와 트레일러가 겹치지 않음 -> 트레일러까지 위치 이동
                    fin.skip(skipSize);

                } else if (skipSize < 0) {  //헤더와 트레일러가 겹침 -> 오버랩핑되는 헤더의 뒷부분을 트레일러의 앞부분으로 카피
                    overlapSize = Math.abs((int)skipSize);
                    System.arraycopy(bufHeader, readSize - overlapSize, bufTrailer, 0, overlapSize);
                }
                if (readSize < fileSize) {
                    readSize = fin.read(bufTrailer, overlapSize, bufTrailer.length - overlapSize);
                }
            }
            byte[] bufTrailer2 = new byte[(int)fileTrailerSize-2];
            int startIndex = 0;
            if ( bufTrailer[bufTrailer.length-2] == '\r' && bufTrailer[bufTrailer.length-1] == '\n'){
                startIndex = 0;
            }else if ( bufTrailer[bufTrailer.length-1] == '\r' || bufTrailer[bufTrailer.length-1] == '\n'){
                startIndex = 1;
            }else{
                startIndex = 2;
            }

            System.arraycopy(bufTrailer, startIndex, bufTrailer2, 0, bufTrailer2.length);

            batchDoc.getBatchMsg().setFileHeader(new String(bufHeader));
            batchDoc.getBatchMsg().setFileTrailer(new String(bufTrailer2));

        } catch (Exception ex) {
            logger.error("{} ★★★★★ 파일내용 헤더, 트레일러 DB로그 설정시 오류 발생 !! ★★★★★", logHeader, ex);
        } finally {
            try { if (fin != null) fin.close(); } catch (Exception e) {}
        }
    }

    /**
     * 전문결정용 텔레그램에서 사용하도록 한다.
     * 전문결정용 텔레그램 클래스를 이용하여, 수신단계 처리시에 예상치 못한 전문종별 수신시 남아있는 전문을 받아 통신 자원에서
     * 없애기 위해 사용하는 함수이다. --> RECV2 에서 사용
     */
    public int getRemainDataLength()
    {
        return -1;
    }

    protected boolean checkType(String type, String value)
    {
        return (type.equalsIgnoreCase("AA") || type.equalsIgnoreCase("A"))? StringUtil.isAlpha(value) :
               (type.equalsIgnoreCase("NN") || type.equalsIgnoreCase("N"))? StringUtil.isNumeric(value) :
               (type.equalsIgnoreCase("AN"))? true : false;
    }

    //특정 필드 Index (base 0) 의 값을 문자열로 리턴
    protected String getFieldValueString(int columnIndex) {
        if (al_Field.size() <= columnIndex) return "";
        return new String( (byte[]) al_Field.get(columnIndex) );
    }

    protected String getFieldValueString(String fieldID){
        int inx = 0;
        for ( inx=0; inx < al_FieldIDs.length; inx++){
            if ( al_FieldIDs[inx].equals(fieldID) )
                    break;
        }
        return getFieldValueString(inx);
    }

    /**
     * 총길이에 맞게 String 앞을 0으로 채워주는 함수    <BR>
     * (예) makeFSpace("1234",6) --> "001234"   <BR>
     * @param    String         String Source
     * @param    int      총길이
     * @return   String
     */
    protected String makeZero(String Str, int totlen)
    {
        String retStr = "";

        if(Str == null) return "";
        byte[] b_data = Str.getBytes();
        int slen = b_data.length;

        if( (totlen < 1) || (slen >= totlen) ) return Str;
        for(int i=0; i< (totlen-slen); i++) {
            retStr += "0";
        }
        retStr += Str;

        return retStr;
    }

    /**
     * 총길이에 맞게 String 앞을 Space로 채워주는 함수    <BR>
     * (예) makeFSpace("1234",6) --> "  1234"   <BR>
     * @param    String         String Source
     * @param    int      총길이
     * @return   String
     */
    protected String makeSpace(String Str, int totlen)
    {
        String retStr = "";
        if(Str == null) return "";
        byte[] b_data = Str.getBytes();
           int slen = b_data.length;

        if( (totlen < 1) || (slen >= totlen) ) return Str;
        for(int i=slen; i< (totlen); i++) {
            retStr += " ";
        }
        retStr = Str + retStr;
        return retStr;
    }

    protected String substring(byte[] src, int offset, int len){
        try {
            byte[] temp = new byte[len];
            System.arraycopy(src, offset, temp, 0, len);
            return new String(temp);
        } catch (Exception e) {
            return null;
        }
    }

    protected Properties getTelegramInfo() throws Exception {
        //BATCH작업구분코드 (processCode)와 기관코드(institutionCode)에 따른 프로퍼티 정보 가져오기
        String propName = TelegramKeys.PROP_GROUP_NAME_PREFIX +
                          "{" +
                          batchDoc.getBatchMsg().getHeader().getProcessCode() +
                          "_" +
                          batchDoc.getBatchMsg().getHeader().getInstitutionCode() +
                          "}";

        PropManager pmanager = PropManager.getInstance();
        return pmanager.getProperties(propName);
    }

    protected Properties getJobInfo() throws Exception {
        //BATCH작업구분코드 (processCode)와 업무구분코드(BizCode)에 따른 프로퍼티 정보 가져오기
        String propName = TelegramKeys.PROP_GROUP_NAME_PREFIX +
                          "{" +
                          batchDoc.getBatchMsg().getHeader().getProcessCode() +
                          "_" +
                          batchDoc.getBatchMsg().getHeader().getBizCode() +
                          "}";

        PropManager pmanager = PropManager.getInstance();
        return pmanager.getProperties(propName);
    }
    
    public String getSeparator() {
    	return File.separator;
    }
}