package com.its.vms.service;

import com.its.app.utils.Elapsed;
import com.its.app.utils.FloodFill;
import com.its.app.utils.ItsUtils;
import com.its.vms.config.ApplicationConfig;
import com.its.vms.dao.mapper.VmsSymbMapper;
import com.its.vms.dto.*;
import com.its.vms.entity.TbVmsSymbIfsc;
import com.its.vms.entity.TbVmsSymbLib;
import com.its.vms.entity.TbVmsSymbLibDnld;
import com.its.vms.entity.VmsScheduleSymb;
import com.its.vms.xnettcp.vms.process.TcpServerSendData;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsOpCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
@RequiredArgsConstructor
public class VmsSymbService {

    private final ApplicationConfig config;
    private final AppRepositoryService repoService;
    private final VmsSymbMapper mapper;

    private final ConcurrentHashMap<String, TbVmsSymbLibDto> dataMap = new ConcurrentHashMap<>();

    public TbVmsSymbLibDto find(String symbLibNmbr) {
        return this.dataMap.get(symbLibNmbr);
    }

    public void loadDb() {
        loadVmsSymbLib();
        loadVmsSymbCellInfo();
    }

    /**
     * 심벌 라이브러리 이미지를 조회한다.
     */
    public void loadVmsSymbLib() {
        // SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급, SBT9:동영상/스트리밍영상주소기본이미지
        Elapsed elapsed = new Elapsed();
        try {
            List<TbVmsSymbLib> infoList  = this.mapper.selectVmsSymbLib();
            log.info("VmsSymbService.loadVmsSymbLib: {} EA", infoList.size());
            infoList.forEach(data -> {
                // SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급, SBT9:동영상/스트리밍영상주소기본이미지
                // 소통정보이미지 때문에 이미지번호에 "0" 을 추가하여 이미지번호로 사용함
//                if ("SBT9".equals(data.getSymbType())) {
//                    return;
//                }

                String updtDt = "";
                TbVmsSymbLibDto obj = data.toDto();
                TbVmsSymbLibDto oldObj = this.dataMap.get(obj.getMemSymbLibNmbr());
                if (oldObj != null) {
                    updtDt = oldObj.getUpdtDt();
                }
                this.dataMap.put(obj.getMemSymbLibNmbr(), obj);

                if ("SBT4".equals(obj.getSymbType())) {
                    // 대기환경등급
                    String symbExpl = data.getSymbExpl();
                    if (symbExpl != null) {
                        String[] symbNmbrArr = symbExpl.split("\\^");
                        for (String symbNmbr : symbNmbrArr) {
                            obj.getGradSymbLibNmbrList().add(symbNmbr + "0");
                        }
                    }
                }

                obj.setLocalFileName(this.config.getFtpImageDir() + obj.getSymbFileNm());
                obj.setFtpFileName(ApplicationConfig.FTP_IMAGE + File.separator + obj.getSymbFileNm()); // Ftp 다운로드 명을 설정

                // SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급, SBT9:동영상/스트리밍영상주소기본이미지
                if ("SBT3".equals(obj.getSymbType())) {
                    //위에서 이미지저장하기 위해 파일명을 변경하기 때문에 여기서 동영상 파일명을 다시 읽어온다
                    obj.setSymbFileNm(data.getSymbFileNm()); //동영상인 경우 동영상 파일명
                    obj.setLocalFileName(this.config.getFtpVideoDir() + obj.getSymbFileNm());
                    obj.setFtpFileName(ApplicationConfig.FTP_VIDEO + File.separator + obj.getSymbFileNm()); // Ftp 다운로드 명을 설정
                }

                if ("SBT2".equals(obj.getSymbType())) {
                    // 심벌 유형(SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급)
                    //소통정보 등급에 따른 이미지를 새롭게 추가한다.
                    for (int nTrf = 1; nTrf <= 3; nTrf++) {
                        String trfUpdtDt = "";
                        String trafSymbLibNmbr = data.getSymbLibNmbr() + String.valueOf(nTrf);
                        TbVmsSymbLibDto trfObj = this.dataMap.get(trafSymbLibNmbr);
                        if (trfObj != null) {
                            trfUpdtDt = trfObj.getUpdtDt();
                        }
                        trfObj = obj.clone(trafSymbLibNmbr);

                        this.dataMap.put(trfObj.getMemSymbLibNmbr(), trfObj);

                        Image imgBmp = obj.getImage();  // 원천이미지를 가지고 온다.
                        if (imgBmp == null) {
                            log.error("VmsSymbService.loadVmsSymbLib: Image Data Error: {}.", trafSymbLibNmbr);
                            continue;
                        }

                        String imagFmt = "bmp";
                        BufferedImage formImage = new BufferedImage(imgBmp.getWidth(null), imgBmp.getHeight(null),
                                ("png".equals(imagFmt) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB));
                        Graphics2D 	g2d = formImage.createGraphics();

                        FloodFill floodFill = new FloodFill(imgBmp);
                        floodFill.setAntialiased(false);
                        int orgColor = floodFill.getImage().getRGB(obj.getPosX(), obj.getPosY());
                        int trfColor = VmsSymbService.getGradColor(nTrf);
                        if (orgColor != trfColor) {
                            floodFill.fill(obj.getPosX(), obj.getPosY(), VmsSymbService.getTrafColor(nTrf));
                        }
                        g2d.drawImage(floodFill.getImage(), 0, 0, imgBmp.getWidth(null), imgBmp.getHeight(null), null);
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        try {
                            ImageIO.write(formImage, imagFmt, out);
                        } catch (IOException e) {
                            log.error("VmsSymbService.loadVmsSymbLib: Image Convert error: {}.", trafSymbLibNmbr);
                        }
                        g2d.dispose();
                        trfObj.setImageData(out.toByteArray());

                        trfObj.setLocalFileName(this.config.getFtpImageDir() + trfObj.getSymbFileNm());
                        trfObj.setFtpFileName(ApplicationConfig.FTP_IMAGE + File.separator + trfObj.getSymbFileNm()); // Ftp 다운로드 명을 설정
                        // 소통등급별 이미지 파일 저장
                        saveByteArrayToFile(trfObj, trfUpdtDt);
                    }
                }
                if ("SBT1".equals(obj.getSymbType())) {
                    // 소통정보배경
                    obj.getCellMap().forEach((id, cell) -> {
                        cell.setValid(false);
                    });
                }
                // 이미지 파일저장
                if (!"SBT3".equals(obj.getSymbType())) {
                    saveByteArrayToFile(obj, updtDt);
                }
            });
        }
        catch (Exception e) {
            log.error("VmsSymbService.loadVmsSymbLib: {}.", e.toString());
        }

        this.dataMap.forEach((key, symb) -> {
            log.info("VmsSymbService.loadVmsSymbLib: Symb {}, OrgSymb {}, Size {}.", symb.getMemSymbLibNmbr(), symb.getSymbLibNmbr(), symb.getImageData().length);
        });

        log.info("VmsSymbService.loadVmsSymbLib: SymbLib {} EA.", this.dataMap.size());
        log.info("VmsSymbService.loadVmsSymbLib: {} ms.", elapsed.milliSeconds());
    }

    /**
     * 도형식 소통정보 및 소통정보 이미지를 셀 정보를 조회한다.
     */
    public void loadVmsSymbCellInfo() {
        Elapsed elapsed = new Elapsed();
        try {
            List<TbVmsSymbIfsc> infoList  = this.mapper.selectVmsSymbCellInfo();
            log.info("VmsSymbService.loadVmsSymbCellInfo: {} EA", infoList.size());
            infoList.forEach(data -> {
                Integer symbLibNmbr = data.getSymbLibNmbr();
                TbVmsSymbLibDto obj = this.dataMap.get(symbLibNmbr + "0");
                if (obj == null) {
                    log.error("VmsSymbService.loadVmsSymbCellInfo: Not Found Cell Info {}.", symbLibNmbr);
                    return;
                }
                TbVmsSymbIfscDto cell = data.toDto();
                for (Map.Entry<Integer, TbVmsSymbIfscDto> cellObj : obj.getCellMap().entrySet()) {
                    if (cellObj.getValue().getVmsIfscId().equals(cell.getVmsIfscId())) {
                        cell.setDup(true);
                        break;
                    }
                }
                obj.getCellMap().put(cell.getCellId(), cell);
            });
        }
        catch (Exception e) {
            log.error("VmsSymbService.loadVmsSymbCellInfo: {}.", e.getMessage());
        }
        log.info("VmsSymbService.loadVmsSymbCellInfo: {} ms.", elapsed.milliSeconds());
    }

    public void saveByteArrayToFile(TbVmsSymbLibDto obj, String updtDt) {
        File imagFile = new File(obj.getLocalFileName());
        // 이미지 파일이 존재하지 않거나 이미지가 변경되었으면 삭제하고 다시 저장
        if (!imagFile.exists() || !obj.getUpdtDt().equals(updtDt)) {
            if (imagFile.exists()) {
                // 이미지가 존재하면 삭제(이미지가 변경된 경우임)
                imagFile.delete();
            }
            ItsUtils.saveByteArrayToFile(obj.getLocalFileName(), obj.getImageData());
        }
        if ("".equals(updtDt)) {
            updtDt = obj.getUpdtDt();
        }
        obj.setUpdtDt(updtDt);  // 업데이트 시각을 업데이트 한다.
    }

    public static int getGradColor(int grad) {
        if (grad == 1) return Color.GREEN.getRGB();
        if (grad == 2) return Color.YELLOW.getRGB();
        if (grad == 3) return Color.RED.getRGB();
        return Color.GRAY.getRGB();
    }

    public static Color getTrafColor(int grad) {
        if (grad == 1) return Color.GREEN;
        if (grad == 2) return Color.YELLOW;
        if (grad == 3) return Color.RED;
        return Color.GRAY;
    }

    /**
     * 제어기로 이미 다운로드 한 심벌 라이브러리 정보를 조회한다.
     */
    public void loadVmsSymbLibDnld() {
        Elapsed elapsed = new Elapsed();
        log.info("VmsSymbService.loadVmsSymbLibDnld: START.");
        List<TbVmsSymbLibDnld> result = this.mapper.selectVmsSymbLibDnld();
        result.forEach(obj -> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(obj.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsSymbService.loadVmsSymbLibDnld: VMS Not Found {}.", obj.getVmsCtlrNmbr());
                return;
            }
            TbVmsSymbLibDnldDto dnldSymb = obj.toDto();
            vmsObj.getDnldSymbMap().put(dnldSymb.getDnldSymbLibNmbr(), dnldSymb);
        });

        this.repoService.getCtlrMap().forEach((key, vmsObj) -> {
            if (vmsObj.getDnldSymbMap().size() > 0) {
                log.info("VmsSymbService.loadVmsSymbLibDnld: VMS {} Already Dnld Symb {} EA.", vmsObj.getVmsCtlrNmbr(), vmsObj.getDnldSymbMap().size());
            }
        });
        log.info("VmsSymbService.loadVmsSymbLibDnld: ..END. {} ms.", elapsed.milliSeconds());
    }

    /**
     * VMS 스케쥴에 등록된 심벌라이브러리 정보를 조회하고 제어기의 다운로드 정보를 작성한다.
     */
    public void loadVmsScheSymbLib() {
        Elapsed elapsed = new Elapsed();
        log.info("VmsSymbService.loadVmsScheduleSymbol: START.");
        List<VmsScheduleSymb> result = this.mapper.selectVmsScheSymbLib();
        result.forEach(obj -> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(obj.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsSymbService.loadVmsScheduleSymbol: VMS Not Found {}.", obj.getVmsCtlrNmbr());
                return;
            }

            TbVmsSymbLibDto symb = this.dataMap.get(obj.getSymbLibNmbr()+"0");
            if (symb == null) {
                log.error("VmsSymbService.loadVmsScheduleSymbol: VMS {} Schedule Symb Not Found {}.", obj.getVmsCtlrNmbr(), obj.getSymbLibNmbr());
                return;
            }

            if (checkDnldSymbol(vmsObj, symb.getMemSymbLibNmbr(), symb.getUpdtDt())) {
                addReqDnldBitmapIdSymbol(vmsObj, symb);
            }

            if ("SBT2".equals(symb.getSymbType())) {
                // SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급, SBT9:동영상/스트리밍영상주소기본이미지
                for (int nTrf = 1; nTrf <= 3; nTrf++) {
                    String trafSymbLibNmbr = obj.getSymbLibNmbr() + String.valueOf(nTrf);
                    TbVmsSymbLibDto trfSymb = this.dataMap.get(trafSymbLibNmbr);
                    if (trfSymb == null) {
                        log.error("VmsSymbService.loadVmsScheduleSymbol: VMS {} Schedule Traffic Symb Not Found {}/{}.", obj.getVmsCtlrNmbr(), obj.getSymbLibNmbr(), trafSymbLibNmbr);
                        continue;
                    }
                    if (checkDnldSymbol(vmsObj, trafSymbLibNmbr, trfSymb.getUpdtDt())) {
                        addReqDnldBitmapIdSymbol(vmsObj, trfSymb);
                    }
                }
            }
            else if ("SBT4".equals(symb.getSymbType())) {
                // SBT0:일반심벌이미지,SBT1:소통정보배경,SBT2:소통정보이미지,SBT3:동영상,SBT4:대기환경등급, SBT9:동영상/스트리밍영상주소기본이미지
                for (String symbNmbr : symb.getGradSymbLibNmbrList()) {
                    if (symbNmbr.equals(symb.getMemSymbLibNmbr())) {
                        continue;
                    }
                    TbVmsSymbLibDto atmpSymb = this.dataMap.get(symbNmbr);
                    if (atmpSymb == null) {
                        log.error("VmsSymbService.loadVmsScheduleSymbol: VMS {} Schedule Atmp Symb Not Found {}, {}.",
                                obj.getVmsCtlrNmbr(), symb.getMemSymbLibNmbr(), symbNmbr);
                    }
                    else {
                        if (checkDnldSymbol(vmsObj, symbNmbr, atmpSymb.getUpdtDt())) {
                            addReqDnldBitmapIdSymbol(vmsObj, atmpSymb);
                        }
                    }
                }
            }
        });

        this.repoService.getCtlrMap().forEach((key, vmsObj) -> {
            if (vmsObj.getReqDnldSymbMap().size() > 0) {
                vmsObj.addRequestData(new TcpServerSendData(eVmsOpCode.OP_VMS_DATA_DOWNLOAD, null));
                log.info("VmsSymbService.loadVmsScheduleSymbol: VMS {} Req Dnld Symb {} EA.", vmsObj.getVmsCtlrNmbr(), vmsObj.getReqDnldSymbMap().size());
            }
        });
        log.info("VmsSymbService.loadVmsScheduleSymbol: ..END. {} ms.", elapsed.milliSeconds());
    }

    /**
     * 심벌 이미지가 이미 다운로드 되어 있어 다운로드가 필요한지 체크한다.
     * @param vmsObj
     * @param symbLibNmbr
     * @param updtDt
     * @return
     */
    private boolean checkDnldSymbol(TbVmsCtlrDto vmsObj, String symbLibNmbr, String updtDt) {
        boolean result = true;
        Integer dnldSymbLibNmbr = getDnldSymbNmbr(symbLibNmbr);
        TbVmsSymbLibDnldDto dnldSymb = vmsObj.getDnldSymbMap().get(dnldSymbLibNmbr);
        if (dnldSymb != null) {
            if (Objects.equals(dnldSymb.getUpdtDt(), updtDt)) {
                // 업데이트 시각이 동일하기 때문에 다운로드 하지 않음.
                result = false;
            }
        }
        return result;
    }

    /**
     * 다운로드 해야할 심벌 이미지 정보를 추가한다.
     * @param vmsObj
     * @param symb
     */
    private void addReqDnldBitmapIdSymbol(TbVmsCtlrDto vmsObj, TbVmsSymbLibDto symb) {
        Integer dnldSymbLibNmbr = getDnldSymbNmbr(symb.getMemSymbLibNmbr());
        VmsScheduleSymbDto dnldSymb = VmsScheduleSymbDto.builder()
                .vmsCtlrNmbr(vmsObj.getVmsCtlrNmbr())
                .dnldSymbLibNmbr(dnldSymbLibNmbr)
                .symbLibNmbr(symb.getSymbLibNmbr())
                .updtDt(symb.getUpdtDt())
                .imageData(symb.getImageData())
                .build();
        vmsObj.getReqDnldSymbMap().put(dnldSymbLibNmbr, dnldSymb);
    }

    /**
     * 내부에서 사용하는 심벌 라이브러리 번호를 제어기로 다운로드하는 BitmapId 로 변환한다.
     * @param symbLibNmbr : 8000 -> 8000 + "0", traffic image: 6020 -> 6020 + "0", 6020 + "1", 6020 + "2", 6020 + "3"
     * @return
     */
    public static int getDnldSymbNmbr(String symbLibNmbr) {
        int symbNo = Integer.parseInt(symbLibNmbr);
        int key = (symbNo / 10000);
        int id = symbNo % 10000;

        return (key * 1000) + id;
    }

}
