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.utils.NettyUtils;
import com.its.app.utils.SysUtils;
import com.its.dsrc.entity.TbRseCtlr;
import com.its.dsrc.global.AppRepository;
import com.its.dsrc.entity.TbRseObuNonCrypt;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;

@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) {
            requestPublicationNonCryptObu(obj, channel);
        }

        // DSRC 상태정보 요청 Subscription
        if (isDsrcStatus) {
            requestSubscriptions(obj, channel, subscriptionStatusCycle, 1);
        }

        // OBU 교통정보 요청 Subscription
        if (isTraffic) {
            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);
    }

    /*
     *  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};
        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.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;
    }

}
