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.dao.repository.dwdb.DwdbParamRepository;
import com.utic.center.utic.traf.server.dao.repository.utic.UticParamRepository;
import com.utic.center.utic.traf.server.dto.dwdb.ParamAnalysisType;
import com.utic.center.utic.traf.server.dto.dwdb.ParamMissing;
import com.utic.center.utic.traf.server.dto.utic.TrafficGrade;
import com.utic.center.utic.traf.server.dto.utic.TrafficWeight;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

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

    private final TraceConfig trace;
    private final LinkService linkService;
    private final DwdbParamRepository dwdbParamRepo;
    private final UticParamRepository uticParamRepo;

    private final ParamAnalysisType analysisType = ParamAnalysisType.builder()
            .analysisType(ParamAnalysisType.ANALYSIS_TYPE_PRIORITY)
            .build();

    private final ParamMissing missing = ParamMissing.builder()
            .usePst(true)
            .useKnn(true)
            .usePtn(true)
            .extKnn(true)
            .missPri(true)
            .build();

    private final TrafficGrade defTrafficGrade = new TrafficGrade(8, 15, 24, 5, 130);
    private final ConcurrentHashMap<Integer, TrafficGrade> trafGradeMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, TrafficWeight> trafWeightMap = new ConcurrentHashMap<>();

    @PostConstruct
    void init() {
        TrafficWeight w0001 = new TrafficWeight("0001", 1, 0f, 0f, 0f, 100f, "D");
        TrafficWeight w0010 = new TrafficWeight("0010", 2, 0f, 0f, 100f, 0f, "L");
        TrafficWeight w0011 = new TrafficWeight("0011", 3, 0f, 0f, 30f, 70f, "E");
        TrafficWeight w0100 = new TrafficWeight("0100", 4, 0f, 100f, 0f, 0f, "U");
        TrafficWeight w0101 = new TrafficWeight("0101", 5, 0f, 70f, 0f, 30f, "V");
        TrafficWeight w0110 = new TrafficWeight("0110", 6, 0f, 100f, 0f, 0f, "U");
        TrafficWeight w0111 = new TrafficWeight("0111", 7, 0f, 70f, 0f, 30f, "V");
        TrafficWeight w1000 = new TrafficWeight("1000", 8, 100f, 0f, 0f, 0f, "M");
        TrafficWeight w1001 = new TrafficWeight("1001", 9, 70f, 0f, 0f, 30f, "N");
        TrafficWeight w1010 = new TrafficWeight("1010", 10, 100f, 0f, 0f, 0f, "M");
        TrafficWeight w1011 = new TrafficWeight("1011", 11, 70f, 0f, 0f, 30f, "N");
        TrafficWeight w1100 = new TrafficWeight("1100", 12, 100f, 0f, 0f, 0f, "M");
        TrafficWeight w1101 = new TrafficWeight("1101", 13, 70f, 0f, 0f, 30f, "N");
        TrafficWeight w1110 = new TrafficWeight("1110", 14, 100f, 0f, 0f, 0f, "M");
        TrafficWeight w1111 = new TrafficWeight("1111", 15, 70f, 0f, 0f, 30f, "N");
        this.trafWeightMap.put(w0001.getWeightCode(), w0001);
        this.trafWeightMap.put(w0010.getWeightCode(), w0010);
        this.trafWeightMap.put(w0011.getWeightCode(), w0011);
        this.trafWeightMap.put(w0100.getWeightCode(), w0100);
        this.trafWeightMap.put(w0101.getWeightCode(), w0101);
        this.trafWeightMap.put(w0110.getWeightCode(), w0110);
        this.trafWeightMap.put(w0111.getWeightCode(), w0111);
        this.trafWeightMap.put(w1000.getWeightCode(), w1000);
        this.trafWeightMap.put(w1001.getWeightCode(), w1001);
        this.trafWeightMap.put(w1010.getWeightCode(), w1010);
        this.trafWeightMap.put(w1011.getWeightCode(), w1011);
        this.trafWeightMap.put(w1100.getWeightCode(), w1100);
        this.trafWeightMap.put(w1101.getWeightCode(), w1101);
        this.trafWeightMap.put(w1110.getWeightCode(), w1110);
        this.trafWeightMap.put(w1111.getWeightCode(), w1111);

//        [도로등급]
//        101:고속국도
//        102:도시고속국도
//        103:일반국도
//        104:특별·광역시도
//        105:국가지원지방도
//        106:지방도
//        107:시·군도
        TrafficGrade t101 = new TrafficGrade(0, 40, 79, 5, 150);
        TrafficGrade t102 = new TrafficGrade(1, 30, 49, 5, 130);
        TrafficGrade t103 = new TrafficGrade(2, 30, 49, 5, 120);
        TrafficGrade t104 = new TrafficGrade(3, 15, 24, 5, 90);
        TrafficGrade t105 = new TrafficGrade(4, 30, 49, 5, 120);
        TrafficGrade t106 = new TrafficGrade(5, 30, 49, 5, 120);
        TrafficGrade t107 = new TrafficGrade(6, 15, 24,  5, 120);
        TrafficGrade t108 = new TrafficGrade(7, 15, 24,  5, 120);
        TrafficGrade t109 = new TrafficGrade(8, 15, 24,  5, 120);

        this.trafGradeMap.put(t101.getRoadRank(), t101);
        this.trafGradeMap.put(t102.getRoadRank(), t102);
        this.trafGradeMap.put(t103.getRoadRank(), t103);
        this.trafGradeMap.put(t104.getRoadRank(), t104);
        this.trafGradeMap.put(t105.getRoadRank(), t105);
        this.trafGradeMap.put(t106.getRoadRank(), t106);
        this.trafGradeMap.put(t107.getRoadRank(), t107);
        this.trafGradeMap.put(t108.getRoadRank(), t107);
        this.trafGradeMap.put(t109.getRoadRank(), t107);
    }

    @ProcessingElapsed(type="PARAMETER", name="교통정보 파라미터 로딩", starting = false, endLine = true)
    @Override
    public boolean processing() {
        loadTrafficGrade();
        loadTrafficWeight();
        loadTrafficMissing();
        loadTrafficAnalysisType();

        if (this.trace.isDebug()) {
            log.info("[INF] {}: {}, (0:우선순위,1:가중치)", LogUtils.elapsedLog("가공방식파라미터"), this.analysisType.getAnalysisType());
            log.info("[INF] {}: PST({}), Pattern({})", LogUtils.elapsedLog("결측파라미터"), this.missing.isUsePst(), this.missing.isUsePtn());
            log.info("[INF] {}: {} EA.", LogUtils.elapsedLog("가중치파라미터"), this.trafWeightMap.size());
            log.info("[INF] {}: {} EA.", LogUtils.elapsedLog("소통등급(TRAFFICGRADE)"), this.trafGradeMap.size());
        }
        return true;
    }

    public void loadTrafficAnalysisType() {
        ParamAnalysisType analysisType = this.dwdbParamRepo.findAnalysisType();
        if (analysisType == null) {
            log.error("[ERR] {}: 교통정보 가공방식 파라미터가 없습니다.", LogUtils.elapsedLog("가공방식파라미터"));
            return;
        }
        this.analysisType.setParam(analysisType);
    }
    public void loadTrafficMissing() {
        ParamMissing missing = this.dwdbParamRepo.findMissing();
        if (missing == null) {
            log.error("[ERR] {}: 교통정보 결측파라미터가 없습니다.", LogUtils.elapsedLog("결측파라미터"));
            return;
        }
        this.missing.setParam(missing);
    }
    public void loadTrafficWeight() {
        // 가중치 가공은 현재 의미가 없음...
        // 추후 가중치 가공이 필요할 경우를 대비해서 지금처럼 로직을 남겨놓음.
        List<TrafficWeight> trafficWeights = this.uticParamRepo.findTrafficWeight();
        if (trafficWeights == null || trafficWeights.isEmpty()) {
            log.error("[ERR] {}: 교통정보 가중치파라미터가 없습니다.", LogUtils.elapsedLog("가중치파라미터"));
            return;
        }
        for (TrafficWeight trafficWeight : trafficWeights) {
            this.trafWeightMap.put(trafficWeight.getWeightCode(), trafficWeight);
        }
    }
    public void loadTrafficGrade() {
        List<TrafficGrade> trafficGrades = this.uticParamRepo.findTrafficGrade();
        if (trafficGrades == null || trafficGrades.isEmpty()) {
            log.error("[ERR] {}: 소통등급(TRAFFICGRADE) 파라미터가 없습니다.", LogUtils.elapsedLog("소통등급(TRAFFICGRADE)"));
            return;
        }
        for (TrafficGrade trafficGrade : trafficGrades) {
            this.trafGradeMap.put(trafficGrade.getRoadRank(), trafficGrade);
//            TrafficGrade oldGrade = this.trafGradeMap.get(trafficGrade.getRoadRank());
//            if (oldGrade != null) {
//                oldGrade.setMinSpeed(trafficGrade.getMinSpeed());
//                oldGrade.setMaxSpeed(trafficGrade.getMaxSpeed());
//            }
//            else {
//                this.trafGradeMap.put(trafficGrade.getRoadRank(), trafficGrade);
//            }
        }
        // 모든 링크에 대해서 최대/최소 속도 설정
        this.linkService.getLinkMap().values().parallelStream().forEach(link -> {
            TrafficGrade grade = getTrafficGrade(link.getRoadRank());
            link.setMaxSpeed(grade.getLimitMaxSpeed());
            link.setMinSpeed(grade.getLimitMinSpeed());
        });
    }

    public TrafficGrade getTrafficGrade(int roadRank) {
        TrafficGrade grade = this.trafGradeMap.get(roadRank);
        if (grade == null) {
            return this.defTrafficGrade;
        }
        return grade;
    }
}
