package com.its.dsrc.xnettcp.dsrc.process.service;

import com.beanit.asn1bean.ber.ReverseByteArrayOutputStream;
import com.beanit.asn1bean.ber.types.*;
import com.beanit.asn1dsrc.dsrc.*;
import com.beanit.asn1dsrc.enums.*;
import com.beanit.asn1dsrc.util.DsrcAsn1Utils;
import com.its.app.AppUtils;
import com.its.app.utils.SysUtils;
import com.its.dsrc.entity.TbRseCtlr;
import com.its.dsrc.entity.TbRseCtrlHs;
import com.its.dsrc.entity.TbRseObuNonCrypt;
import com.its.dsrc.entity.TbRseOffrDrct;
import com.its.dsrc.global.AppRepository;
import com.its.dsrc.process.DbmsData;
import com.its.dsrc.process.DbmsDataProcess;
import com.its.dsrc.process.DbmsDataType;
import com.its.dsrc.vo.NET;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ControlDeviceService {
    private static ControlDeviceService _instance = null;

    public static ControlDeviceService getInstance() {
        if (_instance == null) {
            synchronized (ControlDeviceService.class) {
                if (_instance == null)
                    _instance = new ControlDeviceService();
            }
        }
        return _instance;
    }
    public ConcurrentHashMap<Integer, Integer> rxSenseMap = null;
    public ConcurrentHashMap<Integer, Integer> txPowerMap = null;

    public ControlDeviceService() {
        this.rxSenseMap = new ConcurrentHashMap<>();
        this.txPowerMap = new ConcurrentHashMap<>();

        this.rxSenseMap.put(0x85, 81);
        this.rxSenseMap.put(0xA0, 80);
        this.rxSenseMap.put(0xA4, 79);
        this.rxSenseMap.put(0xA8, 78);
        this.rxSenseMap.put(0xAC, 77);
        this.rxSenseMap.put(0xAF, 76);
        this.rxSenseMap.put(0xB2, 75);
        this.rxSenseMap.put(0xB5, 74);
        this.rxSenseMap.put(0xB8, 73);
        this.rxSenseMap.put(0xBB, 72);
        this.rxSenseMap.put(0xBE, 71);
        this.rxSenseMap.put(0xC1, 70);
        this.rxSenseMap.put(0xC4, 69);
        this.rxSenseMap.put(0xC7, 68);
        this.rxSenseMap.put(0xCC, 67);
        this.rxSenseMap.put(0xCF, 66);
        this.rxSenseMap.put(0xD3, 65);
        this.rxSenseMap.put(0xD7, 64);
        this.rxSenseMap.put(0xDB, 63);
        this.rxSenseMap.put(0xDF, 62);
        this.rxSenseMap.put(0xE2, 61);
        this.rxSenseMap.put(0xE5, 60);
        this.rxSenseMap.put(0xE8, 59);

        this.txPowerMap.put(0xFF, 93);
        this.txPowerMap.put(0xDF, 93);
        this.txPowerMap.put(0xC8, 93);
        this.txPowerMap.put(0xB7, 93);
        this.txPowerMap.put(0xA7, 93);
        this.txPowerMap.put(0x9A, 93);
        this.txPowerMap.put(0x8F, 93);
        this.txPowerMap.put(0x85, 93);
        this.txPowerMap.put(0x7C, 92);
        this.txPowerMap.put(0x75, 92);
        this.txPowerMap.put(0x6E, 91);
        this.txPowerMap.put(0x68, 91);
        this.txPowerMap.put(0x62, 90);

    }

    /*
    *  AI_Publication 메시지로 수신한 데이터중 RSE 상태정보에 대한 데이터 Decoding
    *  decoding 한 RSE 상태정보를 데이터베이스에 업데이트 한다.
    *  IT Telecom/AIR Point
     */
    public boolean decoding_ControlDevice_Status_ITTelecom(TbRseCtlr obj, ControlDevice controlDevice) {
        try {
            StatusOfDeviceInfo deviceInfo = new StatusOfDeviceInfo();
            ByteArrayInputStream deviceStream = new ByteArrayInputStream(controlDevice.getVpbdControlDeviceData().value);
            deviceInfo.decode(deviceStream);
            log.info("ControlDeviceService.decoding_ControlDevice_Status: controlDevice{}, deviceInfo: {}", controlDevice.toString(), deviceInfo.toString());

            obj.getStts().initNormal();    // 상태정보 업데이트
            byte devStts[] = deviceInfo.getStatusInfo().value;
            if (devStts != null && devStts.length >= 3) {
                int temperature = ((devStts[0] & 0x80) != 0 ? (devStts[0] & 0x80) * -1 : devStts[0]);
                int humidity    = devStts[1];
                int frontDoor   = ((devStts[2] & 0x01) != 0 ?  1 : 0);
                int backDoor    = ((devStts[2] & 0x02) != 0 ?  1 : 0);
                int fan         = ((devStts[2] & 0x04) != 0 ?  1 : 0);
                int heater      = ((devStts[2] & 0x08) != 0 ?  1 : 0);
                int rtu         = ((devStts[2] & 0x10) != 0 ?  1 : 0);
                obj.getStts().setStts(frontDoor, backDoor, fan, heater, rtu, temperature, humidity);
                log.info("{}, STTS: {}/{}/{}/{}/{}/{}/{}", obj.getRSE_CTLR_NMBR(), temperature, humidity, frontDoor, backDoor, fan, heater, rtu);
            }

            // 현장 데이터가 들어오는것 보고 아래 내용과 확인하여 상태정보 자세히 업데이트 해야함
            if (deviceInfo.getDataOfBeaconInfoList() != null) {
                StatusOfDeviceInfo.DataOfBeaconInfoList beacon = deviceInfo.getDataOfBeaconInfoList();
                List<BeaconInfo> bList = beacon.getBeaconInfo();
                int beaconCount = bList.size();
                for (int ii = 0; ii < bList.size(); ii++) {
                    BeaconInfo bInfo = bList.get(ii);

                    String beaconId = String.format("%04X0%08X0", bInfo.getBeaconID().getManufacturerid().value.longValue(), bInfo.getBeaconID().getIndividualid().value.longValue());
                    int updateVer = bInfo.getUpdateVer().intValue();
                    int workMode = bInfo.getWorkMode().intValue();
                    int roadStationType = bInfo.getRoadStationType().intValue();
                    int status = bInfo.getStatus().intValue();

                    int beaconState = ((status & 0xFF000000) > 0) ? 1 : 0;
                    int outputPutGain = status & 0x0FF;
                    int txPowerSt = ((status >> 8) & 0x0FF);
                    int rxSenseSt = ((status >>16) & 0x0FF);
                    Integer txPowerVal = ControlDeviceService.getInstance().txPowerMap.get(txPowerSt);
                    Integer rxSenseVal = ControlDeviceService.getInstance().rxSenseMap.get(rxSenseSt);
                    int txPower = txPowerVal == null ? 0 : txPowerVal;
                    int rxSense = rxSenseVal == null ? 0 : rxSenseVal;

                    log.info("{}, BEACON({}): {}/{}/{}/{}/{}/{}/{}/{}/{}", obj.getRSE_CTLR_NMBR(), ii, beaconId, updateVer, workMode, roadStationType, status, beaconState, outputPutGain, txPower, rxSense);
                }
            }
            DbmsDataProcess dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
            dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_CTLR_STTS, false, obj.getStts()));

            log.info("ControlDeviceService.decoding_ControlDevice_Status: {}", obj.getStts().toString());
        }
        catch(Exception e) {
            log.error("ControlDeviceService.decoding_ControlDevice_Status: {}", e.toString());
        }
        return true;
    }

    public boolean decoding_ControlDevice_Status_AirPoint(TbRseCtlr obj, ControlDevice controlDevice) {
        try {
            StatusOfDeviceInfo deviceInfo = new StatusOfDeviceInfo();
            ByteArrayInputStream deviceStream = new ByteArrayInputStream(controlDevice.getVpbdControlDeviceData().value);
            deviceInfo.decode(deviceStream);
            log.debug("ControlDeviceService.decoding_ControlDevice_Status: controlDevice{}, deviceInfo: {}", controlDevice.toString(), deviceInfo.toString());
/*
	roadStationID: 00003039000000000000000000,
	statusInfo: 00000000,
	dataOfBeaconInfoList: {
        {
            beaconID: {
                manufacturerid: 3,
                individualid: 12345
            },
            updateVer: 100100,
            status: 0,
            workMode: 1,
            roadStationType: 104
        }
	}
 */
            obj.getStts().initNormal();    // 상태정보 업데이트

            //String stationId = deviceInfo.getRoadStationID().value.toString();
            //String stts = deviceInfo.getStatusInfo().value.toString();
            //String roadStationID = new String(deviceInfo.getRoadStationID().value);
            // FOR AIR POINT
            String statusInfo = new String(deviceInfo.getStatusInfo().value);
            String CNTL_DEVC_STTS = "0";
            try {
                int statusVal = Integer.valueOf(statusInfo).intValue();
                CNTL_DEVC_STTS = String.valueOf(statusVal);
            }
            catch (Exception e) {
            }
            obj.getStts().setCNTL_DEVC_STTS(CNTL_DEVC_STTS);
            /*주1) roadStationID : 사업자 정의 ID.
              주2) statusInfo : 제어부 상태를 나타내며, 상태정보는 사업자별로 정의 할 수 있다.
                    예) 0 : 정상, 1 : 문열림, 2 : 팬동작, 3 : 내부온도이상, 4 ～ 255 : reserved
              주3) dataOfInfoVerList : 제어부 기초정보의 버전 정보
              주4) dataOfBeaconInfoList : 제어부 상태정보
             */

            // 현장 데이터가 들어오는것 보고 아래 내용과 확인하여 상태정보 자세히 업데이트 해야함
            // AIR POINT 개발자: 상태정보는 일체형인 경우 하나만 사용, 본체, 안테나 같이 사용함
            if (deviceInfo.getDataOfBeaconInfoList() != null) {
                StatusOfDeviceInfo.DataOfBeaconInfoList beacon = deviceInfo.getDataOfBeaconInfoList();
                List<BeaconInfo> bList = beacon.getBeaconInfo();
                for (int ii = 0; ii < bList.size(); ii++) {
                    /* 0x00 안테나 정상
                       0x01 안테나 이상
                       0x04 안테나 통신 두절
                     */
                    /* 0x00 송출종료
                       0x01 송출개시
                       0x02 유지보수 근무개시
                       0x03 정보 없음
                     */

                    BeaconInfo bInfo = bList.get(ii);
                    String antennaStatus = bInfo.getStatus().intValue() != 0 ? "1" : "0";
                    String antennaMode = bInfo.getWorkMode().value.toString();
                    switch (ii) {
                        case 0: obj.getStts().setATN_1_STTS(antennaStatus); obj.getStts().setATN_1_MTNS(antennaMode); break;
                        case 1: obj.getStts().setATN_2_STTS(antennaStatus); obj.getStts().setATN_2_MTNS(antennaMode); break;
                        case 2: obj.getStts().setATN_3_STTS(antennaStatus); obj.getStts().setATN_3_MTNS(antennaMode); break;
                        case 3: obj.getStts().setATN_4_STTS(antennaStatus); obj.getStts().setATN_4_MTNS(antennaMode); break;
                    }
                }
            }
            DbmsDataProcess dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
            dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_CTLR_STTS, false, obj.getStts()));

            log.info("ControlDeviceService.decoding_ControlDevice_Status: {}", obj.getStts().toString());
        }
        catch(Exception e) {
            log.error("ControlDeviceService.decoding_ControlDevice_Status: {}", e.toString());
        }
        return true;
    }

    public boolean requestSubscriptionDeviceCommand(boolean fromOperator, TbRseCtlr obj, Channel channel, int controlDeviceId, int commandType, TbRseCtrlHs cntl)
    {
        log.info("ControlDeviceService.requestSubscriptionDeviceCommand: {}, {}, operator: {}, controlDeviceId: {}, commandType: {}", obj.getID(), obj.getRSE_ID(), fromOperator, controlDeviceId, commandType);
        try {
            if (obj.getChannel() == null  || obj.getNetState() != NET.LOGINED || !obj.getChannel().isActive()) {
                log.error("ControlDeviceService.requestSubscriptionDeviceCommand: {}, {}, operator: {}, is not active", obj.getID(), obj.getRSE_ID(), fromOperator);
                return false;
            }

            int[] messageId = {1, 0, 15784, 4, 0, 7};
            byte[] controlId = { (byte) eControlDevice.Control_Command.getValue() };
            int subscribeSerialNbr = obj.getSeq().nextValue();

            DITSRoadStationCommandInfo commandInfo = new DITSRoadStationCommandInfo();
            if (commandType == eControlCommand.ControlCommand_Wireless_Start.getValue()) {
                // 무선 송출/중단 시에는 WorkMode 를 제어하도록 한다. 그래야 상태정보에서 무선 상태정보를 확인할 수 있다.
                WorkMode workMode = new WorkMode(eWorkMode.WorkMode_Start.getValue());
                commandInfo.setWorkMode(workMode);
            }
            else if (commandType == eControlCommand.ControlCommand_Wireless_Stop.getValue()) {
                WorkMode workMode = new WorkMode(eWorkMode.WorkMode_Stop.getValue());
                commandInfo.setWorkMode(workMode);
            }
            else {
                DITSRoadStationCommandInfo.DeviceCommandList commandList = new DITSRoadStationCommandInfo.DeviceCommandList();
                DeviceCommand deviceCommand = new DeviceCommand();
                deviceCommand.setDeviceID(new BerInteger(controlDeviceId));
                deviceCommand.setCommand(new BerInteger(commandType));
                commandList.getDeviceCommand().add(deviceCommand);
                commandInfo.setDeviceCommandList(commandList);
            }

            // 3. ControlDevice set
            ReverseByteArrayOutputStream deviceBuff = new ReverseByteArrayOutputStream(2048);
            commandInfo.encode(deviceBuff);

            ControlDevice controlDevice = new ControlDevice();
            controlDevice.setVpbdControlID(new BerOctetString(controlId));
            controlDevice.setVpbdControlDeviceData(new BerOctetString(deviceBuff.getArray()));

            // 4. ControlDeviceList set
            ReverseByteArrayOutputStream deviceListBuff = new ReverseByteArrayOutputStream(2048);
            ControlDeviceList controlDeviceList = new ControlDeviceList();
            controlDeviceList.getControlDevice().add(controlDevice);
            controlDeviceList.encode(deviceListBuff);

            // 5. EndApplicationMessage set
            EndApplicationMessage endApplicationMessage =  new EndApplicationMessage();
            endApplicationMessage.setEndApplicationMessageId(new BerObjectIdentifier(messageId));
            endApplicationMessage.setEndApplicationMessageMsg(new BerAny(deviceListBuff.getArray()));

            // 6. Publication
            PublicationType publicationType = new PublicationType();
            publicationType.setDatexPublishData(endApplicationMessage);

            PublicationData publicationData = new PublicationData();
            publicationData.setDatexPublishSubscribeSerialNbr(new BerInteger(subscribeSerialNbr));
            publicationData.setDatexPublishSerialNbr(new BerInteger(subscribeSerialNbr));
            publicationData.setDatexPublishLatePublicationFlag(new BerBoolean(true));
            publicationData.setDatexPublishType(publicationType);

            PublishFormat.DatexPublishData publishData = new PublishFormat.DatexPublishData();
            publishData.getPublicationData().add(publicationData);

            PublishFormat publishFormat = new PublishFormat();
            publishFormat.setDatexPublishData(publishData);

            Publication publication = new Publication();
            publication.setDatexPublishFormat(publishFormat);
            publication.setDatexPublishGuaranteedBool(new BerBoolean(true));

            PDUs pdus = new PDUs();
            pdus.setPublication(publication);
            byte[] auth = {(byte) eAuthInfo.AI_Publication.getValue() };
            C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
            c2c.setDatexDataPacketNumber(new BerInteger(subscribeSerialNbr));
            c2c.setDatexDataPacketPriorityNumber(new BerInteger(1));
            c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
            c2c.setOptions(DsrcAsn1Utils.swapHeaderOptions(obj.getHeaderOptions()));
            c2c.setPdu(pdus);

            ChannelFuture f = channel.writeAndFlush(c2c);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                log.info("ControlDeviceService.requestSubscriptionDeviceCommand: {}, {}, operator: {}, send ok", obj.getID(), obj.getRSE_ID(), fromOperator);
                if (fromOperator) {
                    // TODO: 리셋 명령은 응답을 안 받으므로 타임아웃 체크를 하지 않는다.
                    //obj.addUserCommands(cntl.getCtrlSeq(), cntl);
                }
            } else {
                log.error("ControlDeviceService.requestSubscriptionDeviceCommand: {}, {}, operator: {}, send failed.", obj.getID(), obj.getRSE_ID(), fromOperator);
                return false;
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.requestSubscriptionDeviceCommand: {}, {}, operator: {}, Exception: {}", obj.getID(), obj.getRSE_ID(), fromOperator, e.toString());
            return false;
        }
        return true;
    }

    public ControlDevice makeControlDevice_BasicBaseInfo(TbRseCtlr obj, eControlRequest type) {
        // ControlDevice.vpbd-ControlID  : 기초정보(0x02)
        //
        // BasicBaseInfo.roadStationID   : 주1) 사업자 정의 ID. MCU ID임
        // BasicBaseInfo.roadStationType : 주2) 제어부 위치 정보
        //                                      101 고속국도
        //                                      102 도시고속국도
        //                                      103 일반국도
        //                                      104 특별․광역시도
        //                                      105 국가지원지방도
        //                                      106 지방도
        //                                      107 시․군도
        //                                      108 기타
        // BasicBaseInfo.dataOfNonCryptoObuList	DataOfNonCryptoObu
        //주3) infoID
        //1 : DataOfCollectionPointInfo (가상 수집지점 정보)
        //2 : DirectionDecisionInfo (방향 판단 정보)
        //3 : DataOfNonCryptoObu (비 암호화 단말기 정보)

        ControlDevice device = null;
        BasicBaseInfo info = null;
        byte[] controlId = { (byte)eControlDevice.Control_BasicInfo.getValue() };
        String roadStationId = obj.getRSE_MCU_ID();//obj.getROAD_SPOT_ID();
        if (roadStationId == null || roadStationId.equals("")) {
            return null;
        }

        try {
            int updateNo = 0;
            byte[] operCode = { 0x00 };  // 0x00(추가), 0x01(삭제)
            switch(type) {
                case Request_BasicInfo:
                case Request_BasicInfo_Collect:
                case Request_BasicInfo_Direction:
                    info = new BasicBaseInfo();
                    info.setRoadStationID(new BerOctetString(roadStationId.getBytes()));
                    info.setRoadStationType(new BerInteger(104));   //시․군도
                    BasicBaseInfo.DataOfDirectionDecisionInfoList data = new BasicBaseInfo.DataOfDirectionDecisionInfoList();
                    DirectionDecisionInfo decisionInfo = new DirectionDecisionInfo();
                    decisionInfo.setInfoID(new BerInteger(eControlRequest.Request_BasicInfo_Direction.getValue()));
                    decisionInfo.setUpdateNo(new BerInteger(updateNo));
                    decisionInfo.setGenerationTime(new BerGeneralizedTime(SysUtils.getSysTime()));                   // 주5) generationTime : 해당 정보의 생성 일자 시간
                    BeaconID beaconID = new BeaconID();
                    beaconID.setManufacturerid(new BerInteger(obj.getManufacturerid()));
                    beaconID.setIndividualid(new BerInteger(obj.getIndividualid()));
                    decisionInfo.setTargetBeaconID(beaconID);
                    decisionInfo.setInCommingDirectionEnterBaseInfo(null);
                    List<TbRseOffrDrct> list = AppRepository.getInstance().getRseOffrDrctList(obj.getRSE_ID());
                    if (list == null || list.size() == 0) {
                        decisionInfo.setInCommingDirectionBeaconInfo(null);
                    }
                    else {
                        DirectionDecisionInfo.InCommingDirectionBeaconInfo beacons = new DirectionDecisionInfo.InCommingDirectionBeaconInfo();
                        for (TbRseOffrDrct vo : list) {
                            DirectionBeaconInfo directionBeaconInfo = new DirectionBeaconInfo();
                            directionBeaconInfo.setDirectionInfo(new BerInteger(Long.parseLong(vo.getOBU_ENTR_DRCT_NMBR())));
                            BeaconID beaconID1 = new BeaconID();
                            TbRseCtlr rseObj = AppRepository.getInstance().getCtlrMap().get(vo.getPRE_RSE_CTLR_NMBR());
                            if (rseObj != null) {
                                beaconID1.setManufacturerid(new BerInteger(rseObj.getManufacturerid()));
                                beaconID1.setIndividualid(new BerInteger(rseObj.getIndividualid()));
                                directionBeaconInfo.getPreviousBeaconID().getBeaconID().add(beaconID1);
                                beacons.getDirectionBeaconInfo().add(directionBeaconInfo);
                            }
                        }
                        decisionInfo.setInCommingDirectionBeaconInfo(beacons);
                    }
                    data.getDirectionDecisionInfo().add(decisionInfo);
                    info.setDataOfDirectionDecisionInfoList(data);
                    break;
                case Request_BasicInfo_NonCrypt:    // 최초 로그인 후 NonCryptObu 정보 요청
                    DataOfNonCryptoObu dataObu = new DataOfNonCryptoObu();
                    DataOfNonCryptoObu.CryptObuList obuList = new DataOfNonCryptoObu.CryptObuList();

                    for (TbRseObuNonCrypt vo : AppRepository.getInstance().rseObuNonCryptList) {
                        CryptObu obu = new CryptObu();
                        obu.setOperCode(new BerOctetString(operCode));  // 주7) operCode : 비암호 단말기 리스트의 추가삭제 정보
                        // 0x00 : 추가 , 0x01: 삭제
                        obu.setObuID(new BerOctetString(vo.getOBU_ID().getBytes()));
                        obuList.getCryptObu().add(obu);
                    }
                    dataObu.setInfoID(new BerInteger(eControlRequest.Request_BasicInfo_NonCrypt.getValue()));   // 주3) infoID
                    //    1 : DataOfCollectionPointInfo (가상 수집지점 정보)
                    //    2 : DirectionDecisionInfo (방향 판단 정보)
                    //    3 : DataOfNonCryptoObu (비 암호화 단말기 정보)
                    dataObu.setUpdateNo(new BerInteger(updateNo));                                                // 주4) updateNo : 해당정보의 변경이력 번호
                    dataObu.setGenerationTime(new BerGeneralizedTime(SysUtils.getSysTime()));                   // 주5) generationTime : 해당 정보의 생성 일자 시간
                    dataObu.setCryptObuList(obuList);

                    info = new BasicBaseInfo();
                    info.setRoadStationID(new BerOctetString(roadStationId.getBytes()));
                    info.setRoadStationType(new BerInteger(107));   //시․군도
                    info.setDataOfNonCryptoObuList(dataObu);
                    break;
            }

            if (info != null) {
                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                info.encode(msgBuff);
                device = new ControlDevice();
                device.setVpbdControlID(new BerOctetString(controlId));
                device.setVpbdControlDeviceData(new BerOctetString(msgBuff.getArray()));
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeControlDevice_BasicBaseInfo: {}, eControlType: {}, Exception: {}", obj.getID(), type, e);
            return null;
        }
        //log.debug("ControlDeviceService.makeControlDevice_BasicBaseInfo: {}, eControlType: {}, ControlDevice: {}", obj.getID(), type, device);
        return device;
    }

    /*
    *  제어 또는 상태정보를 요청하기 위하여 사용한다.
     */
    public ControlDevice makeControlDevice_DeviceCommand(TbRseCtlr obj, eControlCommand command, eControlDeviceId deviceId) {

        ControlDevice controlDevice = null;
        DITSRoadStationCommandInfo commandInfo = null;
        WorkMode workMode = new WorkMode(1);
        byte[] controlId = { (byte)eControlDevice.Control_Command.getValue() };

        log.info("ControlDeviceService.makeControlDevice_DeviceCommand: {}, {}, {}", obj.getID(), command.toString(), deviceId.toString());

        try {
            switch(command) {
                case ControlCommand_Status_Request://       (0x00, "ControlCommand_Status_Request"),
                case ControlCommand_Reset://                (0x01, "ControlCommand_Reset"),
                case ControlCommand_Push_Broadcast_Delete://(0x02, "ControlCommand_Push_Broadcast_Delete"),
                case ControlCommand_Broadcast_Delete://     (0x03, "ControlCommand_Broadcast_Delete"),
                case ControlCommand_Wireless_Stop://        (0x04, "ControlCommand_Wireless_Stop"),
                case ControlCommand_Wireless_Start://       (0x05, "ControlCommand_Wireless_Start");
                    commandInfo = new DITSRoadStationCommandInfo();
                    DITSRoadStationCommandInfo.DeviceCommandList commandList = new DITSRoadStationCommandInfo.DeviceCommandList();
                    DeviceCommand deviceCommand = new DeviceCommand();
                    deviceCommand.setDeviceID(new BerInteger(deviceId.getValue()));
                    deviceCommand.setCommand(new BerInteger(command.getValue()));
                    commandList.getDeviceCommand().add(deviceCommand);
                    commandInfo.setWorkMode(workMode);
                    commandInfo.setDeviceCommandList(commandList);
                    //log.error("deviceCommand: {}", deviceCommand.toString());
                    //log.error("commandList: {}", commandList.toString());
                    //log.error("commandInfo: {}", commandInfo.toString());

                    if (command.getValue() == eControlCommand.ControlCommand_Wireless_Stop.getValue()) {
                        commandInfo.setWorkMode(new WorkMode(eWorkMode.WorkMode_Stop.getValue()));
                    }
                    else if (command.getValue() == eControlCommand.ControlCommand_Wireless_Stop.getValue()) {
                        commandInfo.setWorkMode(new WorkMode(eWorkMode.WorkMode_Start.getValue()));
                    }
                    break;
            }

            if (commandInfo != null) {
                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                commandInfo.encode(msgBuff);
                controlDevice = new ControlDevice();
                controlDevice.setVpbdControlID(new BerOctetString(controlId));
                controlDevice.setVpbdControlDeviceData(new BerOctetString(msgBuff.getArray()));
                //log.error("controlDevice: {}", controlDevice.toString());
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeControlDevice_DeviceCommand: {}, eControlCommand: {}, eControlDeviceId: {}, Exception: {}", obj.getID(), command, deviceId, e);
            return null;
        }
        //log.debug("ControlDeviceService.makeControlDevice_DeviceCommand: {}, eControlCommand: {}, eControlDeviceId: {}, ControlDevice: {}", obj.getID(), command, deviceId, device);
        return controlDevice;
    }

    public EndApplicationMessage makeEndApplicationMessage_BasicBaseInfo(TbRseCtlr obj, eControlRequest type) {

        EndApplicationMessage endApplicationMessage = null;

        try {
            switch(type) {
                case Request_BasicInfo://           (0x00, "Request_BasicInfo"),            //기초정보(수집지점정보,방향판단정보,비암호화 단말정보) 요청
                case Request_BasicInfo_Collect://   (0x01, "Request_BasicInfo_Collect"),    //기초정보(수집지점정보) 요청
                case Request_BasicInfo_Direction:// (0x02, "Request_BasicInfo_Direction"),  //기초정보(방향판단정보) 요청
                case Request_BasicInfo_NonCrypt://  (0x03, "Request_BasicInfo_NonCrypt"),   //기초정보(비암호화 단말정보) 요청, 최초 로그인 후 NonCryptObu 정보 요청
                    ControlDevice device = makeControlDevice_BasicBaseInfo(obj, type);
                    if (device != null) {
                        int[] messageId = {1, 0, 15784, 4, 0, 7};
                        ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                        device.encode(msgBuff);
                        endApplicationMessage = new EndApplicationMessage();
                        endApplicationMessage.setEndApplicationMessageId(new BerObjectIdentifier(messageId));
                        endApplicationMessage.setEndApplicationMessageMsg(new BerAny(msgBuff.getArray()));
                    }
                    break;
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeEndApplicationMessage_BasicBaseInfo: {}, eControlType: {}, Exception: {}", obj.getID(), type, e);
            return null;
        }
        //log.debug("ControlDeviceService.makeEndApplicationMessage_BasicBaseInfo: {}, eControlType: {}, EndApplicationMessage: {}", obj.getID(), type, endApplicationMessage);
        return endApplicationMessage;
    }

    /*
    *  운영단말로 부터 수신한 제어메시지에 대한 메시지 생성
     */
    public EndApplicationMessage makeEndApplicationMessage_DeviceCommandInfo(TbRseCtlr obj, eControlCommand command, eControlDeviceId deviceId) {

        EndApplicationMessage endApplicationMessage = null;

        try {
            switch(command) {
                case ControlCommand_Status_Request://       (0x00, "ControlCommand_Status_Request"),
                case ControlCommand_Reset://                (0x01, "ControlCommand_Reset"),
                case ControlCommand_Push_Broadcast_Delete://(0x02, "ControlCommand_Push_Broadcast_Delete"),
                case ControlCommand_Broadcast_Delete://     (0x03, "ControlCommand_Broadcast_Delete"),
                case ControlCommand_Wireless_Stop://        (0x04, "ControlCommand_Wireless_Stop"),
                case ControlCommand_Wireless_Start://       (0x05, "ControlCommand_Wireless_Start");
                    ControlDevice device = makeControlDevice_DeviceCommand(obj, command, deviceId);
                    if (device != null) {
                        int[] messageId = {1, 0, 15784, 4, 0, 7};
                        ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                        device.encode(msgBuff);
                        endApplicationMessage = new EndApplicationMessage();
                        endApplicationMessage.setEndApplicationMessageId(new BerObjectIdentifier(messageId));
                        endApplicationMessage.setEndApplicationMessageMsg(new BerAny(msgBuff.getArray()));
                    }
                    break;
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeEndApplicationMessage_DeviceCommandInfo: {}, eControlCommand: {}, eControlDeviceId: {}, Exception: {}", obj.getID(), command, deviceId, e);
            return null;
        }
        //log.debug("ControlDeviceService.makeEndApplicationMessage_DeviceCommandInfo: {}, eControlCommand: {}, eControlDeviceId: {}, EndApplicationMessage: {}", obj.getID(), command, deviceId, endApplicationMessage);
        return endApplicationMessage;
    }

    /*
     *  DSRC 상태정보를 요청하기 위하여 사용한다.
     */
    public ControlDevice makeControlDevice_DSRCTrafficInfoRequest(TbRseCtlr obj, eControlRequest request) {

        ControlDevice device = null;
        DSRCTrafficInfoRequest infoRequest = null;
        //byte[] controlId = { (byte)eControlDevice.Control_InfoRequest.getValue() };
        byte[] controlId = { (byte)4 };

        try {
            switch(request) {
                case Request_BasicInfo://           (0x00, "Request_BasicInfo"),            //      -- 기초정보(수집지점정보,방향판단정보,비암호화 단말정보) 요청
                case Request_BasicInfo_Collect://   (0x01, "Request_BasicInfo_Collect"),    //      -- 기초정보(수집지점정보) 요청
                case Request_BasicInfo_Direction:// (0x02, "Request_BasicInfo_Direction"),  //      -- 기초정보(방향판단정보) 요청
                case Request_BasicInfo_NonCrypt://  (0x03, "Request_BasicInfo_NonCrypt"),   //      -- 기초정보(비암호화 단말정보) 요청
                case Request_MulticastInfo://       (0x04, "Request_MulticastInfo"),        //      -- Broadcast 교통정보 요청
                case Request_ObuRequestInfo://      (0x05, "Request_ObuRequestInfo"),       //      -- OBU 1:1 교통정보 요청
                case Request_ObuPushInfo://         (0x06, "Request_ObuPushInfo"),          //      -- OBU Push 방송정보 요청
                case Request_TrafficInfo://         (0x07, "Request_TrafficInfo"),          //주3), -- 교통수집정보 요청
                case Request_ObuEndConnect://       (0x08, "Request_ObuEndConnect"),        //주4), -- 단말 접속종료정보 요청
                case Request_ObuEnterDirect://      (0x09, "Request_ObuEnterDirect"),       //주5), -- 단말 진입방향정보 요청
                    infoRequest = new DSRCTrafficInfoRequest();
                    infoRequest.setQueryType(new BerEnum(request.getValue()));
                    if (request.getValue() == eControlRequest.Request_TrafficInfo.getValue()) {
                        BeaconID beaconID = new BeaconID();
                        beaconID.setManufacturerid(new BerInteger(obj.getManufacturerid()));
                        beaconID.setIndividualid(new BerInteger(obj.getIndividualid()));
//                        beaconID.setIndividualid(new BerInteger(Long.valueOf(obj.getID()).longValue()));
//                        beaconID.setManufacturerid(new BerInteger(Long.valueOf(obj.getMNFC_CMPY_CD()).longValue()));
                        //infoRequest.setRoadStationID(new BerOctetString(obj.getROAD_SPOT_ID().getBytes()));
                        infoRequest.setBeaconID(beaconID);
                    }
                    break;
            }

            if (infoRequest != null) {
                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                infoRequest.encode(msgBuff);
                device = new ControlDevice();
                device.setVpbdControlID(new BerOctetString(controlId));
                device.setVpbdControlDeviceData(new BerOctetString(msgBuff.getArray()));
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeControlDevice_DSRCTrafficInfoRequest: {}, eControlRequest: {}, Exception: {}", obj.getID(), request, e);
            return null;
        }
        //log.debug("ControlDeviceService.makeControlDevice_DSRCTrafficInfoRequest: {}, eControlRequest: {}, ControlDevice: {}", obj.getID(), request, device);
        return device;
    }

    public EndApplicationMessage makeEndApplicationMessage(TbRseCtlr obj, ControlDevice controlDevice) {

        EndApplicationMessage endApplicationMessage = null;

        try {
            if (controlDevice != null) {
                int[] messageId = {1, 0, 15784, 4, 0, 7};
                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                controlDevice.encode(msgBuff);
                endApplicationMessage = new EndApplicationMessage();
                endApplicationMessage.setEndApplicationMessageId(new BerObjectIdentifier(messageId));
                endApplicationMessage.setEndApplicationMessageMsg(new BerAny(msgBuff.getArray()));
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeEndApplicationMessage: {}, ControlDevice: {}, Exception: {}", obj.getID(), controlDevice, e);
            return null;
        }
        log.debug("ControlDeviceService.makeEndApplicationMessage: {}, ControlDevice: {}, EndApplicationMessage: {}", obj.getID(), controlDevice, endApplicationMessage);
         return endApplicationMessage;
    }
    public EndApplicationMessage makeEndApplicationMessageList(TbRseCtlr obj, ControlDevice controlDevice) {

        EndApplicationMessage endApplicationMessage = null;

        try {
            if (controlDevice != null) {
                int[] messageId = {1, 0, 15784, 4, 0, 7};
                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
                ControlDeviceList controlDeviceList = new ControlDeviceList();
                controlDeviceList.getControlDevice().add(controlDevice);
                controlDeviceList.encode(msgBuff);
                endApplicationMessage = new EndApplicationMessage();
                endApplicationMessage.setEndApplicationMessageId(new BerObjectIdentifier(messageId));
                endApplicationMessage.setEndApplicationMessageMsg(new BerAny(msgBuff.getArray()));
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.makeEndApplicationMessage: {}, ControlDevice: {}, Exception: {}", obj.getID(), controlDevice, e);
            return null;
        }
        log.debug("ControlDeviceService.makeEndApplicationMessage: {}, ControlDevice: {}, EndApplicationMessage: {}", obj.getID(), controlDevice, endApplicationMessage);
        return endApplicationMessage;
    }

//    public ControlDeviceList controlDevice_BasicBaseInfo(TbRseCtlr obj, eControlRequest type) {
//        // ControlDevice.vpbd-ControlID  : 기초정보(0x02)
//        //
//        // BasicBaseInfo.roadStationID   : 주1) 사업자 정의 ID. MCU ID임
//        // BasicBaseInfo.roadStationType : 주2) 제어부 위치 정보
//        //                                      101 고속국도
//        //                                      102 도시고속국도
//        //                                      103 일반국도
//        //                                      104 특별․광역시도
//        //                                      105 국가지원지방도
//        //                                      106 지방도
//        //                                      107 시․군도
//        //                                      108 기타
//        // BasicBaseInfo.dataOfNonCryptoObuList	DataOfNonCryptoObu
//        //주3) infoID
//        //1 : DataOfCollectionPointInfo (가상 수집지점 정보)
//        //2 : DirectionDecisionInfo (방향 판단 정보)
//        //3 : DataOfNonCryptoObu (비 암호화 단말기 정보)
//
//        ControlDeviceList controlDeviceList = null;
//        ControlDevice device = null;
//        BasicBaseInfo info = null;
//        byte[] controlId = { (byte)eControlDevice.Control_BasicInfo.getValue() };
//        String roadStationId = obj.getRSE_MCU_ID();//obj.getROAD_SPOT_ID();
//        if (roadStationId == null || roadStationId.equals("")) {
//            return null;
//        }
//
//        try {
//            int updateNo = 0;
//            byte[] operCode = { 0x00 };  // 0x00(추가), 0x01(삭제)
//            switch(type) {
//                case Request_BasicInfo:
//                case Request_BasicInfo_Collect:
//                case Request_BasicInfo_Direction:
//                    info = new BasicBaseInfo();
//                    info.setRoadStationID(new BerOctetString(roadStationId.getBytes()));
//                    info.setRoadStationType(new BerInteger(104));   //시․군도
//                    BasicBaseInfo.DataOfDirectionDecisionInfoList data = new BasicBaseInfo.DataOfDirectionDecisionInfoList();
//                    DirectionDecisionInfo decisionInfo = new DirectionDecisionInfo();
//                    decisionInfo.setInfoID(new BerInteger(eControlRequest.Request_BasicInfo_Direction.getValue()));
//                    decisionInfo.setUpdateNo(new BerInteger(updateNo));
//                    decisionInfo.setGenerationTime(new BerGeneralizedTime(SysUtils.getSysTime()));                   // 주5) generationTime : 해당 정보의 생성 일자 시간
//                    BeaconID beaconID = new BeaconID();
//                    beaconID.setManufacturerid(new BerInteger(Long.valueOf(obj.getMNFC_CMPY_CD()).longValue()));
//                    beaconID.setIndividualid(new BerInteger(Long.valueOf(obj.getRSE_ID()).longValue()));
//                    decisionInfo.setTargetBeaconID(beaconID);
//                    decisionInfo.setInCommingDirectionEnterBaseInfo(null);
//                    List<TbRseOffrDrct> list = AppRepository.getInstance().getRseOffrDrctList(obj.getRSE_ID());
//                    if (list == null || list.size() == 0) {
//                        decisionInfo.setInCommingDirectionBeaconInfo(null);
//                    }
//                    else {
//                        DirectionDecisionInfo.InCommingDirectionBeaconInfo beacons = new DirectionDecisionInfo.InCommingDirectionBeaconInfo();
//                        for (TbRseOffrDrct vo : list) {
//                            DirectionBeaconInfo directionBeaconInfo = new DirectionBeaconInfo();
//                            directionBeaconInfo.setDirectionInfo(new BerInteger(Long.valueOf(vo.getOBU_ENTR_DRCT_NMBR()).longValue()));
//                            BeaconID beaconID1 = new BeaconID();
//                            TbRseCtlr rseObj = AppRepository.getInstance().getCtlrMap().get(vo.getPRE_RSE_CTLR_NMBR());
//                            if (rseObj != null) {
//                                beaconID1.setIndividualid(new BerInteger(Long.valueOf(rseObj.getRSE_ID()).longValue()));
//                                beaconID1.setManufacturerid(new BerInteger(Long.valueOf(rseObj.getMNFC_CMPY_CD()).longValue()));
//                                directionBeaconInfo.getPreviousBeaconID().getBeaconID().add(beaconID1);
//                                beacons.getDirectionBeaconInfo().add(directionBeaconInfo);
//                            }
//                        }
//                        decisionInfo.setInCommingDirectionBeaconInfo(beacons);
//                    }
//                    data.getDirectionDecisionInfo().add(decisionInfo);
//                    info.setDataOfDirectionDecisionInfoList(data);
//                    break;
//                case Request_BasicInfo_NonCrypt:    // 최초 로그인 후 NonCryptObu 정보 요청
//                    DataOfNonCryptoObu dataObu = new DataOfNonCryptoObu();
//                    DataOfNonCryptoObu.CryptObuList obuList = new DataOfNonCryptoObu.CryptObuList();
//
//                    for (TbRseObuNonCrypt vo : AppRepository.getInstance().rseObuNonCryptList) {
//                        CryptObu obu = new CryptObu();
//                        obu.setOperCode(new BerOctetString(operCode));  // 주7) operCode : 비암호 단말기 리스트의 추가삭제 정보
//                        // 0x00 : 추가 , 0x01: 삭제
//                        obu.setObuID(new BerOctetString(vo.getOBU_ID().getBytes()));
//                        obuList.getCryptObu().add(obu);
//                    }
//                    dataObu.setInfoID(new BerInteger(eControlRequest.Request_BasicInfo_NonCrypt.getValue()));   // 주3) infoID
//                    //    1 : DataOfCollectionPointInfo (가상 수집지점 정보)
//                    //    2 : DirectionDecisionInfo (방향 판단 정보)
//                    //    3 : DataOfNonCryptoObu (비 암호화 단말기 정보)
//                    dataObu.setUpdateNo(new BerInteger(updateNo));                                                // 주4) updateNo : 해당정보의 변경이력 번호
//                    dataObu.setGenerationTime(new BerGeneralizedTime(SysUtils.getSysTime()));                   // 주5) generationTime : 해당 정보의 생성 일자 시간
//                    dataObu.setCryptObuList(obuList);
//
//                    info = new BasicBaseInfo();
//                    info.setRoadStationID(new BerOctetString(roadStationId.getBytes()));
//                    info.setRoadStationType(new BerInteger(107));   //시․군도
//                    info.setDataOfNonCryptoObuList(dataObu);
//                    break;
//            }
//
//            if (info != null) {
//                ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
//                info.encode(msgBuff);
//                device = new ControlDevice();
//                device.setVpbdControlID(new BerOctetString(controlId));
//                device.setVpbdControlDeviceData(new BerOctetString(msgBuff.getArray()));
//            }
//        }
//        catch(Exception e) {
//            log.error("ControlDeviceService.makeControlDevice_BasicBaseInfo: {}, eControlType: {}, Exception: {}", obj.getID(), type, e);
//            return null;
//        }
//        //log.debug("ControlDeviceService.makeControlDevice_BasicBaseInfo: {}, eControlType: {}, ControlDevice: {}", obj.getID(), type, device);
//        return controlDeviceList;
//    }

    public boolean sendTerminate(TbRseCtlr obj, Channel channel, int reason)
    {
        if (obj == null) {
            log.info("ControlDeviceService.sendTerminate: reason: {}, DSRC Object null...", reason);
            return false;
        }

        log.info("ControlDeviceService.sendTerminate: {}, {}, reason: {}", obj.getID(), obj.getRSE_ID(), reason);
        try {
            if (obj.getChannel() == null || !obj.getChannel().isOpen() || !obj.getChannel().isActive()) {
                log.error("ControlDeviceService.sendTerminate: {}, {}, reason: {}, is not active", obj.getID(), obj.getRSE_ID(), reason);
                return false;
            }

            int subscribeSerialNbr = obj.getSeq().nextValue();

            // Terminate
            Terminate terminate = new Terminate(reason);

            PDUs pdus = new PDUs();
            pdus.setTerminate(terminate);
            byte[] auth = {(byte) eAuthInfo.AI_Terminate.getValue() };
            C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
            c2c.setDatexDataPacketNumber(new BerInteger(subscribeSerialNbr));
            c2c.setDatexDataPacketPriorityNumber(new BerInteger(1));
            c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
            c2c.setOptions(DsrcAsn1Utils.swapHeaderOptions(obj.getHeaderOptions()));
            c2c.setPdu(pdus);

            ChannelFuture f = channel.writeAndFlush(c2c);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                log.info("ControlDeviceService.sendTerminate: {}, {}, reason: {}, send ok", obj.getID(), obj.getRSE_ID(), reason);
            } else {
                log.error("ControlDeviceService.sendTerminate: {}, {}, reason: {}, send failed.", obj.getID(), obj.getRSE_ID(), reason);
                return false;
            }
        }
        catch(Exception e) {
            log.error("ControlDeviceService.sendTerminate: {}, {}, reason: {}, Exception: {}", obj.getID(), obj.getRSE_ID(), reason, e.getMessage());
            return false;
        }
        return true;
    }

}
