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

import com.beanit.asn1bean.ber.ReverseByteArrayOutputStream;
import com.beanit.asn1bean.ber.types.*;
import com.beanit.asn1bean.ber.types.string.BerUTF8String;
import com.beanit.asn1dsrc.dsrc.*;
import com.beanit.asn1dsrc.enums.*;
import com.beanit.asn1dsrc.util.DsrcAsn1Utils;
import com.its.app.utils.NettyUtils;
import com.its.app.utils.SysUtils;
import com.its.dsrc.config.RunningConfig;
import com.its.dsrc.entity.TbRseCtlr;
import com.its.dsrc.entity.TbRseObuNonCrypt;
import com.its.dsrc.global.AppRepository;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;
import java.util.Calendar;

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

    public static LoginDeviceService getInstance() {
        if (_instance == null) {
            synchronized (LoginDeviceService.class) {
                if (_instance == null)
                    _instance = new LoginDeviceService();
            }
        }
        return _instance;
    }

    public LoginDeviceService() {
    }

    public boolean initLoginDevice(TbRseCtlr obj, Channel channel, boolean isObuNonCrypt, boolean isDsrcStatus, boolean isTraffic, int subscriptionStatusCycle, int antennaCnt) {

        log.info("LoginResponse.initLoginDevice");

        // OBU Non Crypt 데이터 요청 Subscription
        if (isObuNonCrypt) {
            log.info("initLoginDevice.isObuNonCrypt. {}, {}, {}", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
            //requestPublicationNonCryptObu(obj, channel);
        }

        // DSRC 상태정보 요청 Subscription
        if (isDsrcStatus) {
            log.info("initLoginDevice.isDsrcStatus. {}, {}, {}", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
            //requestSubscriptions(obj, channel, subscriptionStatusCycle, 1);
        }

        // OBU 교통정보 요청 Subscription
        if (isTraffic) {
            log.info("initLoginDevice.isTraffic. {}, {}, {}", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
            requestSubscriptions(obj, channel, 0, 2);
        }

        // DSRC Antenna enable Publication
//        if (antennaCnt > 0) {
//            for (int antennaId = 1; antennaId <= antennaCnt; antennaId++) {
//                requestSubscriptionDeviceCommand(obj, channel, antennaId, eControlCommand.ControlCommand_Wireless_Start.getValue());
//            }
//        }
        return true;
    }

    /*
     *  RSE 로 부터 최초 로그인 요청 수신시 안테나 활성화
     */
    public boolean requestSubscriptionDeviceCommand(TbRseCtlr obj, Channel channel, int controlDeviceId, int commandType)
    {
        return ControlDeviceService.getInstance().requestSubscriptionDeviceCommand(false, obj, channel, controlDeviceId, commandType, null);
    }

    /*
     *  RSE 로 부터 최초 로그인 요청 수신시 Subscription 정보를 요청한다.
     */
    public boolean requestSubscriptions(TbRseCtlr obj, Channel channel, int registeredUpdateDelayQty, int subscriptionsType) {

        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
        log.info("LoginDeviceService.requestSubscriptions: {}, {}, registeredUpdateDelayQty: {}, subscriptionsType: {}", obj.getID(), obj.getRSE_ID(), registeredUpdateDelayQty, subscriptionsType);

        int[] messageId = {1, 0, 15784, 4, 0, 7};
//        pObject->numids = 6;
//        pObject->subid[0] = 1;
//        pObject->subid[1] = 0;
//        pObject->subid[2] = 15784;
//        switch (id)
//        {
//            case itMultimediaData: pObject->subid[3] = 5; pObject->subid[4] = 0; pObject->subid[5] = 1; break;
//            case itControlDevice: pObject->subid[3] = 4; pObject->subid[4] = 0; pObject->subid[5] = 7; break;
//            case itObuGatherInfo: pObject->subid[3] = 8; pObject->subid[4] = 0; pObject->subid[5] = 8; break;
//            default: break;
//        }
        int subscribeSerialNbr = obj.getSeq().nextValue();

        try {
            registeredUpdateDelayQty = 0;

            ControlDevice controlDevice = null;
            Registered registered = new Registered();
            Registered.Continuous continuous = new Registered.Continuous();
            SubscriptionMode subscriptionMode = new SubscriptionMode();

            if (subscriptionsType == 1) {
                // DSRC 상태정보 요청
                controlDevice = ControlDeviceService.getInstance().makeControlDevice_DeviceCommand(obj, eControlCommand.ControlCommand_Status_Request, eControlDeviceId.ControlDeviceId_Controller);
                continuous.setDatexRegisteredUpdateDelayQty(new BerInteger(registeredUpdateDelayQty));
                continuous.setDatexRegisteredStartTime(DsrcAsn1Utils.getRegisterStartTime());
                continuous.setDatexRegisteredEndTime(DsrcAsn1Utils.getRegisterEndTime());
                registered.setContinuous(continuous);
                subscriptionMode.setPeriodic(registered);
                log.info("LoginDeviceService.requestSubscriptions: {}, DSRC Status_Request, subscribeSerialNbr: {}", ipAddress, subscribeSerialNbr);
            }
            else if (subscriptionsType == 2) {
                // OBU 교통정보 요청
                controlDevice = ControlDeviceService.getInstance().makeControlDevice_DSRCTrafficInfoRequest(obj, eControlRequest.Request_TrafficInfo);
                continuous.setDatexRegisteredUpdateDelayQty(new BerInteger(registeredUpdateDelayQty));
                continuous.setDatexRegisteredStartTime(DsrcAsn1Utils.getRegisterStartTime());
                continuous.setDatexRegisteredEndTime(DsrcAsn1Utils.getRegisterEndTime());
                registered.setContinuous(continuous);
                subscriptionMode.setEventDriven(registered);
                log.info("LoginDeviceService.requestSubscriptions: {}, DSRC OBU TrafficInfo_Request, subscribeSerialNbr: {}", ipAddress, subscribeSerialNbr);
            }

            if (controlDevice == null) {
                return false;
            }

            // ControlDeviceList set
            ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
            ControlDeviceList controlDeviceList = new ControlDeviceList();
            controlDeviceList.getControlDevice().add(controlDevice);
            controlDeviceList.encode(msgBuff);

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

            SubscriptionData subscriptionData = new SubscriptionData();
            subscriptionData.setDatexSubscribePersistentBool(new BerBoolean(false));
            subscriptionData.setDatexSubscribeStatusCd(new BerEnum(0));         // 0: New, 1: Update
            subscriptionData.setDatexSubscribeMode(subscriptionMode);
            subscriptionData.setDatexSubscribePublishFormatCd(new BerEnum(3));  // 0: other, 1: ftp, 2: tftp, 3: dataPacket
            //subscriptionData.setDatexSubscriptionPriorityNbr(new BerInteger(1));
            subscriptionData.setDatexSubscriptionPriorityNbr(new BerInteger(10));
            subscriptionData.setDatexSubscribeGuaranteeBool(new BerBoolean(true));
            subscriptionData.setDatexSubscribePdu(endApplicationMessage);

            SubscriptionType subscriptionType = new SubscriptionType();
            subscriptionType.setSubscription(subscriptionData);

            Subscription subscription = new Subscription();
            subscription.setDatexSubscribeSerialNbr(new BerInteger(subscribeSerialNbr));
            subscription.setDatexSubscribeType(subscriptionType);

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

            ChannelFuture f = channel.writeAndFlush(c2c);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                log.info("LoginDeviceService.requestSubscriptions: {}, {}, subscriptionsType: {}, send ok", obj.getID(), obj.getRSE_ID(), subscriptionsType);
            }
            else {
                log.error("LoginDeviceService.requestSubscriptions: {}, {}, subscriptionsType: {}, send failed.", obj.getID(), obj.getRSE_ID(), subscriptionsType);
            }
        }
        catch(Exception e) {
            log.error("LoginDeviceService.requestSubscriptions: {}, {}, subscriptionsType: {}, Exception: {}", obj.getID(), obj.getRSE_ID(), subscriptionsType, e.toString());
        }
        return true;
    }

    /*
     *  RSE 로 부터 최초 로그인 요청 수신시 NonCryptObu 정보를 요청한다.
     */
    public boolean requestPublicationNonCryptObu(TbRseCtlr obj, Channel channel) {

        log.info("LoginDeviceService.requestPublicationNonCryptObu: {}, {}", obj.getID(), obj.getRSE_ID());
        int obuCnt = AppRepository.getInstance().rseObuNonCryptList.size();
        if (obuCnt == 0) {
            log.info("LoginDeviceService.requestPublicationNonCryptObu: {}, {}, No Data...", obj.getID(), obj.getRSE_ID());
            return true;
        }

        int subscribeSerialNbr = obj.getSeq().nextValue();
        int[] messageId = {1, 0, 15784, 4, 0, 7};
        byte[] controlId = { (byte) eControlDevice.Control_BasicInfo.getValue() };
        byte[] operCode = {0x00};  // 0x00(추가), 0x01(삭제)
        String roadStationId = obj.getRSE_MCU_ID();//obj.getROAD_SPOT_ID();
        int updateNo = 0;

        try {
            // 1. OUB Non Crypt Data encoding
            DataOfNonCryptoObu dataObu = new DataOfNonCryptoObu();
            DataOfNonCryptoObu.CryptObuList obuList = new DataOfNonCryptoObu.CryptObuList();

            for (TbRseObuNonCrypt vo : AppRepository.getInstance().rseObuNonCryptList) {
                // 프로토콜 문서상 OBU ID는 최대 8자리임
                String fullObuId = vo.getOBU_ID();
                int len = fullObuId.length() / 2;
                byte[] obuId = new byte[8];
                for (int ii = 0; ii < len; ii++) {
                    String temp = fullObuId.substring(ii * 2, ii * 2 + 2);   // 2자리씩 잘라냄
                    byte tempId[] = new BigInteger(temp, 16).toByteArray();
                    obuId[ii] = tempId[tempId.length - 1];
                }

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

            // 2. BasicBaseInfo set
            BasicBaseInfo basicBaseInfo = new BasicBaseInfo();
            basicBaseInfo.setRoadStationID(new BerOctetString(roadStationId.getBytes()));    // roadStationId, 최소 13, 최대 20
            basicBaseInfo.setRoadStationType(new BerInteger(107));                      // stationType
            basicBaseInfo.setDataOfNonCryptoObuList(dataObu);                                // NonCryptoObuList

            // 3. ControlDevice set
            ReverseByteArrayOutputStream deviceBuff = new ReverseByteArrayOutputStream(2048);
            basicBaseInfo.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("LoginDeviceService.requestPublicationNonCryptObu: {}, {}, send ok", obj.getID(), obj.getRSE_ID());
            } else {
                log.error("LoginDeviceService.requestPublicationNonCryptObu: {}, {}, send failed.", obj.getID(), obj.getRSE_ID());
            }
        }
        catch(Exception e) {
            log.error("LoginDeviceService.requestPublicationNonCryptObu: {}, {}, Exception: {}", obj.getID(), obj.getRSE_ID(), e.toString());
        }
        return true;
    }

    /**
     * 클라이언트 모드로 동작시 Initiate 수신시 로그인 정보를 전송한다.
     * @param obj
     * @param channel
     * @param runningConfig
     * @return
     */
    public boolean requestLogin(TbRseCtlr obj, Channel channel, RunningConfig runningConfig) {

        log.info("LoginResponse.requestLogin: {}, {}, {}", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());

        int dataPacketNumber = 0;           //obj.getSeq().nextValue()
        int dataPacketPriorityNumber = 0;
        String serverDomainName = runningConfig.getServerDomainName();
        String destinationTxt = obj.getRSE_MCU_ID();
        String loginUserNameTxt = "";
        String loginPasswordTxt = "";

        Login login = new Login();
        login.setDatexSenderTxt(new BerUTF8String(serverDomainName));
        login.setDatexDestinationTxt(new BerUTF8String(destinationTxt));
        login.setDatexLoginUserNameTxt(new BerUTF8String(loginUserNameTxt));
        login.setDatexLoginPasswordTxt(new BerUTF8String(loginPasswordTxt));
        login.setDatexLoginHeartbeatDurationMaxQty(new BerInteger(120));
        login.setDatexLoginResponseTimeOutQty(new BerInteger(30));
        login.setDatexLoginInitiatorCd(new BerEnum(1));
        login.setDatexLoginDatagramSizeQty(new BerInteger(2048));
        int[] encodingRules = {2, 1};
        Login.DatexLoginEncodingRulesId loginEncodingRulesId = new Login.DatexLoginEncodingRulesId();
        loginEncodingRulesId.getBerObjectIdentifier().add(new BerObjectIdentifier(encodingRules));
        login.setDatexLoginEncodingRulesId(loginEncodingRulesId);

        byte[] auth = {(byte) eAuthInfo.AI_Login.getValue() };
        PDUs loginPdu = new PDUs();
        loginPdu.setLogin(login);

        HeaderOptions headerOptions = new HeaderOptions();
        Time tm = new Time();
        Time.TimeSecondFractions tmFac = new Time.TimeSecondFractions();
        Time.TimeTimezone        tmZon = new Time.TimeTimezone();
        Calendar currTm = Calendar.getInstance();
        tm.setTimeYearQty(new BerInteger(currTm.get(Calendar.YEAR)));
        tm.setTimeMonthQty(new BerInteger(currTm.get(Calendar.MONTH)+1));
        tm.setTimeDayQty(new BerInteger(currTm.get(Calendar.DATE)));
        tm.setTimeHourQty(new BerInteger(currTm.get(Calendar.HOUR_OF_DAY)));
        tm.setTimeMinuteQty(new BerInteger(currTm.get(Calendar.MINUTE)));
        tm.setTimeSecondQty(new BerInteger(currTm.get(Calendar.SECOND)));
        tmFac.setMilliseconds(new BerInteger(currTm.get(Calendar.MILLISECOND)));
        tm.setTimeSecondFractions(tmFac);
        headerOptions.setDatexDataPacketTime(tm);

        C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
        c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
        c2c.setDatexDataPacketNumber(new BerInteger(dataPacketNumber));
        c2c.setDatexDataPacketPriorityNumber(new BerInteger(dataPacketPriorityNumber));
        c2c.setOptions(headerOptions);
        c2c.setPdu(loginPdu);

        ChannelFuture f = channel.writeAndFlush(c2c);
        f.awaitUninterruptibly();
        if (f.isDone() || f.isSuccess()) {
            obj.setHeaderOptions(DsrcAsn1Utils.swapHeaderOptions(headerOptions));
            obj.setLogin(login);
            log.info("LoginResponse.requestLogin: {}, {}, {}, send ok.", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
        } else {
            log.error("LoginDeviceService.requestLogin: {}, {}, send failed.", obj.getID(), obj.getRSE_ID());
        }
        return true;
    }

//    public boolean requestLogin2(TbRseCtlr obj, Channel channel, RunningConfig runningConfig) {
//
//        log.info("LoginResponse.requestLogin: {}, {}, {}", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
//
//        int dataPacketNumber = 0;           //obj.getSeq().nextValue()
//        int dataPacketPriorityNumber = 0;
//
//        Login login = new Login();
//        login.setDatexSenderTxt(new BerUTF8String(runningConfig.getServerDomainName()));
//        login.setDatexDestinationTxt(new BerUTF8String(obj.getRSE_MCU_ID()));
//        login.setDatexLoginUserNameTxt(new BerUTF8String(runningConfig.getServerAuthUser()));
//        login.setDatexLoginPasswordTxt(new BerUTF8String(runningConfig.getServerAuthPassword()));
//        login.setDatexLoginHeartbeatDurationMaxQty(new BerInteger(120));
//        login.setDatexLoginResponseTimeOutQty(new BerInteger(30));
//        login.setDatexLoginInitiatorCd(new BerEnum(1));
//        login.setDatexLoginDatagramSizeQty(new BerInteger(2048));
//        int[] encodingRules = {2, 1, 1};
//        Login.DatexLoginEncodingRulesId loginEncodingRulesId = new Login.DatexLoginEncodingRulesId();
//        loginEncodingRulesId.getBerObjectIdentifier().add(new BerObjectIdentifier(encodingRules));
//        login.setDatexLoginEncodingRulesId(loginEncodingRulesId);
//
//        byte[] auth = {(byte) eAuthInfo.AI_Login.getValue() };
//        PDUs loginPdu = new PDUs();
//        loginPdu.setLogin(login);
//
//        HeaderOptions headerOptions = DsrcAsn1Utils.getDefaultOptions();
//        C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
//        c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
//        c2c.setDatexDataPacketNumber(new BerInteger(dataPacketNumber));
//        c2c.setDatexDataPacketPriorityNumber(new BerInteger(dataPacketPriorityNumber));
//        c2c.setOptions(headerOptions);
//        c2c.setPdu(loginPdu);
//
//        ChannelFuture f = channel.writeAndFlush(c2c);
//        f.awaitUninterruptibly();
//        if (f.isDone() || f.isSuccess()) {
//            obj.setHeaderOptions(DsrcAsn1Utils.swapHeaderOptions(headerOptions));
//            obj.setLogin(login);
//            log.info("LoginResponse.requestLogin: {}, {}, {}, send ok.", obj.getRSE_CTLR_NMBR(), obj.getRSE_CTLR_IP(), obj.getRSE_MCU_ID());
//        } else {
//            log.error("LoginDeviceService.requestLogin: {}, {}, send failed.", obj.getID(), obj.getRSE_ID());
//        }
//        return true;
//    }

    /**
     * OBU 수집 정보 요청 등록(이벤트 방식)
     * @param obj
     * @param channel
     * @return
     */
    public boolean requestObuGatherInfo(TbRseCtlr obj, Channel channel) {

        log.info("LoginDeviceService.requestObuGatherInfo: {}, {}", obj.getID(), obj.getRSE_ID());

        int[] messageId = {1, 0, 15784, 8, 0, 8};
        int dataPacketNumber = 1;           //obj.getSeq().nextValue()
        int dataPacketPriorityNumber = 0;
        int datexSubscriptionPriorityNbr = 10;
        int subscribeSerialNbr = 0;
        try {
            byte[] obuIdNumber = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
            byte[] vehicleType = {0};
            byte[] obuType = {0};
            byte[] generationDate = {0,0,0,0};
            byte[] generationTime = {0,0,0};

            OBUGatherInfoList lists = new OBUGatherInfoList();
            OBUGatherInfo gatherInfo = new OBUGatherInfo();
            gatherInfo.setObuIdNumber(new BerOctetString(obuIdNumber));
            BeaconID beaconID = new BeaconID();
            beaconID.setManufacturerid(new BerInteger(obj.getManufacturerid()));
            beaconID.setIndividualid(new BerInteger(obj.getIndividualid()));
            gatherInfo.setRseIDNumber(beaconID);
            gatherInfo.setVehicleType(new BerOctetString(vehicleType));
            gatherInfo.setObuType(new BerOctetString(obuType));
            gatherInfo.setGenerationDate(new BerOctetString(generationDate));
            gatherInfo.setGenerationTime(new BerOctetString(generationTime));
            lists.getOBUGatherInfo().add(gatherInfo);

            byte[] controlId = { (byte)4 };
            ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
            lists.encode(msgBuff);
            ControlDevice controlDevice = new ControlDevice();
            controlDevice.setVpbdControlID(new BerOctetString(controlId));
            controlDevice.setVpbdControlDeviceData(new BerOctetString(msgBuff.getArray()));

            Registered registered = new Registered();
            Registered.Continuous continuous = new Registered.Continuous();
            SubscriptionMode subscriptionMode = new SubscriptionMode();

            continuous.setDatexRegisteredUpdateDelayQty(new BerInteger(0));
            continuous.setDatexRegisteredStartTime(DsrcAsn1Utils.getRegisterStartTime());
            continuous.setDatexRegisteredEndTime(DsrcAsn1Utils.getRegisterEndTime());
            registered.setContinuous(continuous);
            subscriptionMode.setEventDriven(registered);

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

            SubscriptionData subscriptionData = new SubscriptionData();
            subscriptionData.setDatexSubscribePersistentBool(new BerBoolean(false));
            subscriptionData.setDatexSubscribeStatusCd(new BerEnum(0));         // 0: New, 1: Update
            subscriptionData.setDatexSubscribeMode(subscriptionMode);
            subscriptionData.setDatexSubscribePublishFormatCd(new BerEnum(3));  // 0: other, 1: ftp, 2: tftp, 3: dataPacket
            subscriptionData.setDatexSubscriptionPriorityNbr(new BerInteger(datexSubscriptionPriorityNbr));
            subscriptionData.setDatexSubscribeGuaranteeBool(new BerBoolean(true));
            subscriptionData.setDatexSubscribePdu(endApplicationMessage);

            SubscriptionType subscriptionType = new SubscriptionType();
            subscriptionType.setSubscription(subscriptionData);

            Subscription subscription = new Subscription();
            //subscription.setDatexSubscribeSerialNbr(new BerInteger(subscribeSerialNbr));
            subscription.setDatexSubscribeSerialNbr(new BerInteger(subscribeSerialNbr));
            subscription.setDatexSubscribeType(subscriptionType);

            PDUs pdus = new PDUs();
            pdus.setSubscription(subscription);
            byte[] auth = { (byte) eAuthInfo.AI_Subscription.getValue() };
            C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
            c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
            c2c.setDatexDataPacketNumber(new BerInteger(dataPacketNumber));
            c2c.setDatexDataPacketPriorityNumber(new BerInteger(dataPacketPriorityNumber));
            c2c.setOptions(new HeaderOptions());
            c2c.setPdu(pdus);

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

    public boolean requestObuStatusInfo(TbRseCtlr obj, Channel channel) {

        int updateDelayQty = 30;
        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
        log.info("LoginDeviceService.requestObuStatusInfo: {}, {}, {}", obj.getID(), obj.getMcuID(), updateDelayQty);

        int[] messageId = {1, 0, 15784, 4, 0, 7};
        int dataPacketNumber = 2;           //obj.getSeq().nextValue()
        int dataPacketPriorityNumber = 0;
        int datexSubscriptionPriorityNbr = 10;
        int subscribeSerialNbr = 1;
        try {
            byte[] controlId = {3};
            byte[] controlData = {};
            ControlDevice controlDevice = new ControlDevice();
            controlDevice.setVpbdControlID(new BerOctetString(controlId));
            controlDevice.setVpbdControlDeviceData(new BerOctetString(controlData));
            Registered registered = new Registered();
            Registered.Continuous continuous = new Registered.Continuous();
            SubscriptionMode subscriptionMode = new SubscriptionMode();

            continuous.setDatexRegisteredUpdateDelayQty(new BerInteger(updateDelayQty));
            continuous.setDatexRegisteredStartTime(DsrcAsn1Utils.getRegisterStartTime());
            continuous.setDatexRegisteredEndTime(DsrcAsn1Utils.getRegisterEndTime());
            registered.setContinuous(continuous);
            subscriptionMode.setPeriodic(registered);

            // ControlDeviceList set
            ReverseByteArrayOutputStream msgBuff = new ReverseByteArrayOutputStream(1024);
            ControlDeviceList controlDeviceList = new ControlDeviceList();
            controlDeviceList.getControlDevice().add(controlDevice);
            controlDeviceList.encode(msgBuff);

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

            SubscriptionData subscriptionData = new SubscriptionData();
            subscriptionData.setDatexSubscribePersistentBool(new BerBoolean(false));
            subscriptionData.setDatexSubscribeStatusCd(new BerEnum(0));         // 0: New, 1: Update
            subscriptionData.setDatexSubscribeMode(subscriptionMode);
            subscriptionData.setDatexSubscribePublishFormatCd(new BerEnum(3));  // 0: other, 1: ftp, 2: tftp, 3: dataPacket
            subscriptionData.setDatexSubscriptionPriorityNbr(new BerInteger(datexSubscriptionPriorityNbr));
            subscriptionData.setDatexSubscribeGuaranteeBool(new BerBoolean(true));
            subscriptionData.setDatexSubscribePdu(endApplicationMessage);

            SubscriptionType subscriptionType = new SubscriptionType();
            subscriptionType.setSubscription(subscriptionData);

            Subscription subscription = new Subscription();
            subscription.setDatexSubscribeSerialNbr(new BerInteger(subscribeSerialNbr));
            subscription.setDatexSubscribeType(subscriptionType);

            PDUs pdus = new PDUs();
            pdus.setSubscription(subscription);
            byte[] auth = { (byte) eAuthInfo.AI_Subscription.getValue() };
            C2CAuthenticatedMessage c2c = new C2CAuthenticatedMessage();
            c2c.setDatexAuthenticationInfoText(new BerOctetString(auth));
            c2c.setDatexDataPacketNumber(new BerInteger(dataPacketNumber));
            c2c.setDatexDataPacketPriorityNumber(new BerInteger(dataPacketPriorityNumber));
            c2c.setOptions(new HeaderOptions());
            c2c.setPdu(pdus);

            ChannelFuture f = channel.writeAndFlush(c2c);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                log.info("LoginDeviceService.requestObuStatusInfo: {}, {}, {}, send ok", obj.getID(), obj.getMcuID(), updateDelayQty);
            }
            else {
                log.error("LoginDeviceService.requestObuStatusInfo: {}, {}, {}, send failed.", obj.getID(), obj.getMcuID(), updateDelayQty);
            }
        }
        catch(Exception e) {
            log.error("LoginDeviceService.requestObuStatusInfo: {}, {}, {}, Exception: {}", obj.getID(), obj.getMcuID(), updateDelayQty, e);
        }
        return true;
    }

}
