package com.its.vms.process;

import com.its.app.AppUtils;
import com.its.vms.config.ThreadPoolInitializer;
import com.its.vms.dao.mapper.UnitSystMapper;
import com.its.vms.dao.mapper.VmsCtlrMapper;
import com.its.vms.dao.mapper.VmsManageMapper;
import com.its.vms.dao.mapper.VmsSymbMapper;
import com.its.vms.dao.mapper.batch.VmsCtlrDao;
import com.its.vms.dao.mapper.batch.VmsManageDao;
import com.its.vms.domain.NET;
import com.its.vms.domain.VmsForm;
import com.its.vms.dto.TbVmsCtlrDto;
import com.its.vms.entity.*;
import com.its.vms.service.AppRepositoryService;
import com.its.vms.xnettcp.vms.process.TcpServerSendData;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsOpCode;
import com.its.vms.xnettcp.vms.protocol.impl.VmsReqLocalUserCommand;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@RequiredArgsConstructor
@Service
public class DbmsDataProcess {

    private final LinkedBlockingQueue<DbmsData> dbmsDataBlockingQueue = new LinkedBlockingQueue<>(1000);
    private final ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);

    private final DbmsDataAsyncTask asyncTask;
    private final VmsCtlrDao vmsCtlrDao;
    private final VmsManageDao vmsManageDao;

    private final UnitSystMapper unitSystMapper;
    private final VmsCtlrMapper vmsCtlrMapper;
    private final VmsSymbMapper symbMapper;
    private final VmsManageMapper vmsManageMapper;
    private final AppRepositoryService repoService;

    private int maxCore = Runtime.getRuntime().availableProcessors();

    public void run() {
        log.info("DbmsDataProcess.run: Start.");
        if (this.maxCore < 8) {
            this.maxCore = 8;
        }
        ThreadPoolInitializer poolInitializer = (ThreadPoolInitializer) AppUtils.getBean(ThreadPoolInitializer.class);
        int executePool = Math.max(this.maxCore, poolInitializer.getWork());
        for (int ii = 0; ii < executePool; ii++) {
            log.info("DbmsDataProcess.Task: {}", ii);
            this.taskExecutor.execute(() -> {
                boolean isRunning = true;
                while (isRunning) {
                    try {
                        DbmsData data = dbmsDataBlockingQueue.take();
                        asyncTask.run(this, data);
                    }
                    catch (Exception e) {
                        log.error("DbmsDataProcess.Task: Exception: {}", e.getMessage(), e);
                        Thread.currentThread().interrupt();
                        isRunning = false;
                    }
                }
            });
        }

        log.info("DbmsDataProcess.run: ..End.");
    }

    /**
     * 비동기 타스크에서 실행되는 함수.
     * 클래스가 달라야 비동기 타스크로 실행된다.
     * @param data
     */
    public void runJob(DbmsData data) {
        if (data.getType() == DbmsDataType.DBMS_DATA_UNIT_SYST_STTS) {
            TbUnitSystStts stts = (TbUnitSystStts) data.getData();
            this.unitSystMapper.updateUnitSystStts(stts);       // 상태정보 업데이트
            if (data.isHistory()) {
                this.unitSystMapper.insertUnitSystSttsHs(stts); // 상태정보 이력저장
            }
        }
        else {
            process(data);
        }
    }

    public void process(DbmsData data) {
        int cnt = 0;
        try {
            DbmsDataType type = data.getType();
            switch(type) {
                case DBMS_DATA_CTLR_STTS:
                    TbVmsCtlrStts ctlrStts = (TbVmsCtlrStts)data.getData();
                    this.vmsCtlrMapper.updateCtlrStts(ctlrStts);
                    break;
                case DBMS_DATA_CTLR_STTS_LIST:
                    List<TbVmsCtlrStts> ctlrSttsList = (List<TbVmsCtlrStts>)data.getData();
                    this.vmsCtlrDao.updateStts(ctlrSttsList, data.isHistory());
                    if (data.isHistory()) {
                        this.vmsCtlrDao.insertStts(ctlrSttsList);
                    }
                    ctlrSttsList.clear();
                    break;
                case DBMS_DATA_CTLR_PARAMETER:
                    VmsCtlrParam param = (VmsCtlrParam)data.getData();
                    cnt = this.vmsCtlrMapper.updateVmsCtlrParam(param);
                    log.info("DbmsJobProcess.process: DBMS_DATA_CTLR_PARAMETER, VMS {} Updated {}.", param.getVmsCtlrNmbr(), cnt);
                    break;
                case DBMS_DATA_CTRL_HS:
                    this.vmsCtlrMapper.insertVmsCtrlHs((TbVmsCtrlHs)data.getData());
                    break;
                case DBMS_DATA_VMS_FORM_DOWNLOAD:
                    String dsplDt = (String)data.getData();

                    List<TbVmsDsplPrst> dsplPrstList = new ArrayList<>();
                    List<TbVmsDsplPrst> dsplPrstHsList = new ArrayList<>();

                    boolean isScheduled = data.isHistory();
                    for (Map.Entry<Long, TbVmsCtlrDto> e : this.repoService.getCtlrMap().entrySet()) {
                        TbVmsCtlrDto vmsObj = e.getValue();
                        int formCnt = vmsObj.getFormManager().count();
                        for (int ii = 0; ii < formCnt; ii++) {
                            VmsForm vmsForm = vmsObj.getFormManager().getItem(ii);
                            TbVmsDsplPrst dsplPrst = vmsForm.toDsplPrstEntity(vmsObj, ii+1, dsplDt, "N");
                            // 모든 제어기의 현재 표출폼 정보를 업데이트 한다.
                            dsplPrstList.add(dsplPrst);
                            if (vmsObj.getNetState() == NET.CLOSED) {
                                // 통신상태가 연결되지 않은 경우 표출폼 이력데이터를 함께 저장한다.
                                dsplPrstHsList.add(dsplPrst);
                            }
                        }
                    }
                    for (Map.Entry<Long, TbVmsCtlrDto> e : this.repoService.getCtlrMap().entrySet()) {
                        TbVmsCtlrDto vmsObj = e.getValue();
                        if (vmsObj.getNetState() != NET.CLOSED) {
                            // 통신상태가 연결되어 있는 경우 표출폼을 제어기로 전송한 후 결과를 저장하도록 한다.
                            VmsReqLocalUserCommand reqLocalUserCommand = new VmsReqLocalUserCommand(vmsObj, (short)0, isScheduled ? (short)1 : (short)0);
                            vmsObj.addRequestData(new TcpServerSendData(eVmsOpCode.OP_VMS_LOCAL_USER_COMMAND, reqLocalUserCommand));
                        }
                    }

                    if (!dsplPrstList.isEmpty()) {
                        this.vmsManageDao.updateVmsDsplPrst(dsplPrstList);
                    }
                    if (!dsplPrstHsList.isEmpty()) {
                        this.vmsManageDao.insertVmsDsplPrst(dsplPrstHsList);
                    }
                    break;
                case DBMS_DATA_VMS_FORM_DOWNLOAD_RESULT:
                    VmsDsplDnldResult dnldResult = (VmsDsplDnldResult)data.getData();
                    this.vmsManageMapper.updateVmsDsplPrstResult(dnldResult);
                    cnt = this.vmsManageMapper.insertVmsDsplPrstHsResult(dnldResult);
                    log.info("DbmsJobProcess.process: DBMS_DATA_VMS_FORM_DOWNLOAD_RESULT, VMS {} Forms {}/{} EA.", dnldResult.getVmsCtlrNmbr(), dnldResult.getFormCnt(), cnt);
                    break;
                case DBMS_DATA_SYMB_DNLD:
                    TbVmsSymbLibDnld symbLibDnld = (TbVmsSymbLibDnld)data.getData();
                    if (symbLibDnld != null) {
                        log.info("DbmsJobProcess.process: Symbol Download {}.", symbLibDnld);
                        this.symbMapper.updateVmsSymbLibDnld(symbLibDnld);
                    }
                    break;
                default:
                    log.error("DbmsJobProcess.process: Unknown Request {}.", type);
                    break;
            }
        } catch (Exception e) {
            log.error("DbmsJobProcess.process: Exception: {}", e.toString());
        }
    }

    /*
     *  작업큐에 데이터 추가
     */
    public boolean add(DbmsData data) {
        boolean offer = false;
        try {
            //offer => full -> return
            //add   => full -> wait
            //큐가 차더라도 바로 리턴함.
            offer = dbmsDataBlockingQueue.offer(data);
            if (!offer) {
                log.error("DbmsDataProcess.add: Queue Full Error, Size: {} EA", dbmsDataBlockingQueue.size());
            }
        } catch (Exception e) {
            log.error("DbmsDataProcess.add: Exception: {}", e.getMessage(), e);
        }
        return offer;
    }

}
