package com.utic.center.utic.traf.server.service;

import com.utic.center.common.annotation.ProcessingElapsed;
import com.utic.center.common.service.AbstractProcessService;
import com.utic.center.common.utils.LogUtils;
import com.utic.center.utic.traf.server.config.TraceConfig;
import com.utic.center.utic.traf.server.dto.LinkCollectDto;
import com.utic.center.utic.traf.server.dto.LinkCollectResultDto;
import com.utic.center.utic.traf.server.dto.LinkDto;
import com.utic.center.utic.traf.server.dto.LinkTrafState;
import com.utic.center.utic.traf.server.dto.dwdb.ParamAnalysisType;
import com.utic.center.utic.traf.server.dto.utic.TrafficGrade;
import com.utic.center.utic.traf.server.repository.ApplicationRepository;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Getter
@RequiredArgsConstructor
@Service
public class LinkTrafFusionService implements AbstractProcessService {

    private final TraceConfig trace;
    private final LinkService linkService;
    private final LinkParamService paramService;
    private final LinkTrafCollectService collectService;

    private LinkCollectResultDto collectOPER;
    private LinkCollectResultDto collectPRIO;
    private LinkCollectResultDto collectPRIOETC;
    private LinkCollectResultDto collectMOCT;

    private void initialize() {
//        final long start = System.currentTimeMillis();

        // 1. 수집된 소통정보를 객체로 초기화 한다.
        this.collectOPER = Optional.ofNullable(this.collectService.get(LinkTrafCollectService.OPER))
                .orElseGet(() -> LinkCollectResultDto.builder().clctSystCd(LinkTrafCollectService.OPER).lists(new ArrayList<>()).build());
        this.collectPRIO = Optional.ofNullable(this.collectService.get(LinkTrafCollectService.PRIO))
                .orElseGet(() -> LinkCollectResultDto.builder().clctSystCd(LinkTrafCollectService.PRIO).lists(new ArrayList<>()).build());
        this.collectPRIOETC = Optional.ofNullable(this.collectService.get(LinkTrafCollectService.PRIOETC))
                .orElseGet(() -> LinkCollectResultDto.builder().clctSystCd(LinkTrafCollectService.PRIOETC).lists(new ArrayList<>()).build());
        this.collectMOCT = Optional.ofNullable(this.collectService.get(LinkTrafCollectService.MOCT))
                .orElseGet(() -> LinkCollectResultDto.builder().clctSystCd(LinkTrafCollectService.MOCT).lists(new ArrayList<>()).build());

        this.linkService.getFusionInfo().init();
        this.linkService.getFusionInfo().setLinkCnt(this.linkService.getLink1Count());
        this.linkService.getFusionInfo().setPrcsType(this.paramService.getAnalysisType().getAnalysisType());
        this.linkService.getFusionInfo().setColOprCnt(this.collectOPER.getLists().size());
        this.linkService.getFusionInfo().setColPriCnt(this.collectPRIO.getLists().size());
        this.linkService.getFusionInfo().setColPriEtcCnt(this.collectPRIOETC.getLists().size());
        this.linkService.getFusionInfo().setColMoctCnt(this.collectMOCT.getLists().size());
//        int totalCnt = this.linkService.getLinkMap().size();
//        logInfo("소통정보 초기화", totalCnt, totalCnt, start);
    }

    @ProcessingElapsed(type="FUSION", name="링크 소통정보 퓨전", starting = false, endLine = true)
    @Override
    public boolean processing() {
        // 0. 교통정보 초기화
        initialize();

        // 1. 운영자 소통정보는 가공방식에 관계없이 가장 최우선으로 처리한다.
        operatorFusion();

        // 2. 가공방식에 따른 소통정보 퓨전
        if (this.paramService.getAnalysisType().getAnalysisType() == ParamAnalysisType.ANALYSIS_TYPE_PRIORITY) {
            // 2-1. 우선순위 소통정보 퓨전
            priorityFusion();
        }
        else {
            // 2-2. 가중치 소통정보 퓨전
            weightFusion();
        }

        // 3. 소통정보 보정(속도, 통행시간 계산)
        linkSpeedLimitCorrect();
        return true;
    }

    private int linkSpeedLimitCorrect() {
        // 레벨1 구간에 대해 속도 상/하한값을 적용하고 기타 소통정보도 설정한다.
        final long start = System.currentTimeMillis();
        AtomicInteger calCnt = new AtomicInteger();
        AtomicInteger totalCnt = new AtomicInteger();
        this.linkService.getLinkMap().values().parallelStream()
                .filter(link -> link.getLinkLevel() == 1)                   // 레벨1 구간만 처리
                .filter(link -> link.getTrafFsn().getState().isState())     // 퓨전 소통정보가 유효한 링크만 처리
                .forEach(link -> {
                    TrafficGrade grade = this.paramService.getTrafficGrade(link.getRoadRank());
                    totalCnt.incrementAndGet();
                    int befSpeed = link.getTrafFsn().getSpeed();
                    if (link.fsnTrafficCorrect(grade)) {
                        if (befSpeed != link.getTrafFsn().getSpeed()) {
                            calCnt.incrementAndGet();   // 속도 보정이 발생한 경우에만 카운트 증가
                        }
                    }
                }
            );

        this.linkService.getFusionInfo().setFltSpdCnt(calCnt.get());
        logInfo("도로등급별 속도상한값 보정", totalCnt.get(), calCnt.get(), start);
        return calCnt.get();
    }

    private int priorityFusion() {
        // 수집원이 우선순위/우선순위(ETC)로 통합되어서 우선순위 소통정보 퓨전이 간단해짐
        // 그러나 추후 수집원이 추가될것을 고려하여 아래와 같이 처리함
        // 현재는 우선순위/가중치 처리가 동일한 결과를 얻게됨
        final long start = System.currentTimeMillis();
        String[] priorityCollectCd = {
                LinkTrafCollectService.PRIO,
                LinkTrafCollectService.PRIOETC,
        };

        AtomicInteger calCnt = new AtomicInteger();
        AtomicInteger totalCnt = new AtomicInteger();
        for (String clctSystCd : priorityCollectCd) {
            LinkCollectResultDto collectResultDto = Optional.ofNullable(this.collectService.get(clctSystCd))
                    .orElseGet(() -> LinkCollectResultDto.builder().clctSystCd(clctSystCd).lists(new ArrayList<>()).build());

            collectResultDto.getLists().parallelStream()
                    .forEach(clctTraf -> {
                        totalCnt.incrementAndGet();
                        LinkDto link = this.linkService.getLink(clctTraf.getLinkId());
                        if (link == null) {
                            ApplicationRepository.isLoadingLink = true;
                            log.error("[ERR] LinkTrafFusionService.priorityFusion: {} link not found.", clctTraf.getLinkId());
                            return;
                        }
                        if (link.getTrafFsn().getState().isState()) {
                            // 퓨전 소통정보가 유효하지 않은 경우에만 퓨전한다.
                            return;
                        }
                        calCnt.incrementAndGet();
                        link.getTrafFsn().setTraffic(LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL, clctTraf);
                    });
        }
        this.linkService.getFusionInfo().setFsnPriCnt(calCnt.get());
        logInfo("우선순위 소통정보 퓨전", totalCnt.get(), calCnt.get(), start);
        return calCnt.get();
    }

    private int weightFusion() {
        // 기존에는 수집원이 여러개라 가중치 소통정보 퓨전이 복잡했지만
        // 현재는 수집원이 2개(우선순위, 우선순위(ETC))로 통합되어서 간단하게 처리
        final long start = System.currentTimeMillis();

        int totalCnt = this.collectPRIO.getLists().size() + this.collectPRIOETC.getLists().size();
        int calCnt = 0;
        AtomicInteger calPri = new AtomicInteger();
        AtomicInteger calEtc = new AtomicInteger();
        // 1. 우선순위 수집 소통정보로 교통정보 생성
        this.collectPRIO.getLists().parallelStream().forEach(dto -> {
            LinkDto link = this.linkService.getLink(dto.getLinkId());
            if (link == null) {
                ApplicationRepository.isLoadingLink = true;
                log.error("[ERR] LinkTrafFusionService.weightFusion(PRI): {} link not found.", dto.getLinkId());
                return;
            }
            calPri.incrementAndGet();
//            link.getTrafFsn().setTraffic("L00", dto.getSpeed(), "R", "L");
            link.getTrafFsn().setTraffic(LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL, dto.getCenterId(), dto.getSpeed(), "R", "L");
        });

        // 2. 우선순위(ETC) 수집 소통정보로 교통정보 생성
        this.collectPRIOETC.getLists().parallelStream().forEach(dto -> {
            LinkDto link = this.linkService.getLink(dto.getLinkId());
            if (link == null) {
                ApplicationRepository.isLoadingLink = true;
                log.error("[ERR] LinkTrafFusionService.weightFusion(PRIETC): {} link not found.", dto.getLinkId());
                return;
            }
            calEtc.incrementAndGet();
//            link.getTrafFsn().setTraffic("L00", dto.getSpeed(), "R", "L");
            link.getTrafFsn().setTraffic(LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL, dto.getCenterId(), dto.getSpeed(), "R", "L");
        });

        calCnt = calPri.get() + calEtc.get();
        this.linkService.getFusionInfo().setFsnWgtCnt(calCnt);
        if (this.trace.isDebug()) {
            log.info("[INF] {}: PRIO: {}/{}, PRIOETC: {}/{}",
                    LogUtils.elapsedLog("가중치 소통정보 퓨전"),
                    this.collectPRIO.getLists().size(), calPri.get(),
                    this.collectPRIOETC.getLists().size(), calEtc.get());
        }
        logInfo("가중치 소통정보 퓨전", totalCnt, calCnt, start);
        return calCnt;
    }

    private int operatorFusion() {
        final long start = System.currentTimeMillis();
        int calCnt = 0;
        int totalCnt = this.linkService.getLink1Count();
        for (LinkCollectDto dto : this.collectOPER.getLists()) {
            LinkDto link = this.linkService.getLink(dto.getLinkId());
            if (link == null) {
                ApplicationRepository.isLoadingLink = true;
                log.error("[ERR] LinkTrafFusionService.operatorFusion: {} link not found.", dto.getLinkId());
                continue;
            }
            calCnt++;
            link.getTrafFsn().setTraffic(LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL, dto);
        }
        this.linkService.getFusionInfo().setFsnOprCnt(calCnt);
        logInfo("운영자 소통정보 퓨전", totalCnt, calCnt, start);
        return calCnt;
    }
    private void logInfo(String message, int totalCnt, int calCnt, long start) {
        log.info("[INF] {}, {} EA.", LogUtils.elapsedLog(message, calCnt, System.currentTimeMillis() - start), LogUtils.numberLog(totalCnt));
    }
}


//private int weightFusion() {
//    final long start = System.currentTimeMillis();
//    Map<String, LinkCollectDto> prioMap = this.collectPRIO.getLists().stream()
//            .collect(Collectors.toMap(LinkCollectDto::getLinkId, dto -> dto, (existing, replacement) -> existing));
//    Map<String, LinkCollectDto> prioEtcMap = this.collectPRIOETC.getLists().stream()
//            .collect(Collectors.toMap(LinkCollectDto::getLinkId, dto -> dto, (existing, replacement) -> existing));
//
//    AtomicReference<String> gps = new AtomicReference<>("0");
//    AtomicReference<String> utis = new AtomicReference<>("0");
//    AtomicReference<String> loc = new AtomicReference<>("0");
//    AtomicReference<String> dongbu = new AtomicReference<>("0");
//
//    AtomicInteger calCnt = new AtomicInteger();
//    AtomicInteger totalCnt = new AtomicInteger();
//    this.linkService.getLinkMap().values().parallelStream()
//            .filter(link -> link.getLinkLevel() == 1)
//            .filter(link -> !link.getTrafFsn().getState().isState())    // 퓨전 소통정보가 유효하지 않은 링크만 처리
//            .forEach(link -> {
//                utis.set("0");
//                loc.set("0");
//                loc.set("0");
//                dongbu.set("0");
//
//                totalCnt.incrementAndGet();
//
//                LinkCollectDto prioTraf = prioMap.get(link.getLinkId());
//                LinkCollectDto prioEtcTraf = prioEtcMap.get(link.getLinkId());
//                if (prioTraf != null || prioEtcTraf != null) {
//                    loc.set("1");
//                }
//                String weightCode = gps.get() + utis.get() + loc.get() + dongbu.get();
//                TrafficWeight weight = this.paramService.getTrafWeightMap().get(weightCode);
//                if (weight == null) {
//                    //log.error("[FUSION.] LinkTrafFusionService.weightFusion: {} weight not found[{}].", dto.getLinkId(), weightCode);
//                    return;
//                }
//                float fSumSpd = 0;
//                float fSumWeight = 0.0F;
//                if (prioTraf != null || prioEtcTraf != null) {
//                    if (prioTraf != null) {
//                        fSumSpd += (prioTraf.getSpeed() * weight.getLoc());
//                    }
//                    else {
//                        fSumSpd += (prioEtcTraf.getSpeed() * weight.getLoc());
//                    }
//                    fSumWeight += weight.getLoc();
//                }
//
//                if (fSumWeight == 0.0) {
//                    // 수집은 되었는데 가중치값이 0 으로 설정이 잘못된 것임
//                    //log.error("[FUSION.] LinkTrafFusionService.weightFusion: {} weight error[{}].", dto.getLinkId(), weightCode);
//                    return;
//                }
//                int speed = Math.round(fSumSpd/fSumWeight);
//                link.getTrafFsn().setTraffic("L00", speed, "R", weight.getDataResType());
//                calCnt.incrementAndGet();
//            });
//
//    this.linkService.getFusionInfo().setFsnWgtCnt(calCnt.get());
//    logInfo("가중치 소통정보 퓨전", totalCnt.get(), calCnt.get(), start);
//    return calCnt.get();
//}
