package com.its.bis.service;

import com.its.bis.api.dto.AgipObeLoc;
import com.its.bis.config.ServerConfig;
import com.its.bis.config.ThreadPoolInitializer;
import com.its.bis.dto.TbLinkDto;
import com.its.bis.process.DbmsDataProcess;
import com.its.bis.service.worker.BisAgipWorker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = {Exception.class})
public class BisAgipService {

    private final AppRepositoryService repoService;
    private final ServerConfig config;
    private final DbmsDataProcess dbmsDataProcess;
    private final ThreadPoolInitializer threadPoolInitializer;

    private final List<Thread> threadList = new ArrayList<Thread>();
    private final List<BisAgipWorker> workerList = new ArrayList<BisAgipWorker>();
    private final ThreadGroup workerGroup = new ThreadGroup("BisAgipService");
    private int workers;

    @PostConstruct
    private void init() {
        log.info("BisAgipService.init: Start.");

        this.workers = this.threadPoolInitializer.getDbms();
        if (this.workers <= 0) this.workers = 10;
        int qSize = 100;
        for (int ii = 0; ii < this.workers; ii++) {
            BisAgipWorker packetWorker = new BisAgipWorker(ii, qSize, this.config, this.repoService, this.dbmsDataProcess);
            this.workerList.add(packetWorker);
            Thread worker = new Thread(workerGroup, packetWorker);
            worker.setName(String.format("BisAgipService-%02d.%02d", this.workers, ii+1));
            worker.setDaemon(true);
            this.threadList.add(worker);
        }
        for (Thread worker : this.threadList) {
            worker.start();
        }
        log.info("BisAgipService.init: ..End.");
    }

    /**
     * REST API 로 수신한 위치정보를 작업큐에 입력
     * @param obeLoc
     * @return
     */
    public boolean add(AgipObeLoc obeLoc) {
        boolean offer = false;
        String deviceId = obeLoc.getDeviceId();
        if (deviceId == null || deviceId.equals("")) {
            deviceId = "xxx";
            obeLoc.setDeviceId(deviceId);
        }
        try {
            int idx = Math.abs(deviceId.hashCode() % this.workers);
            offer = this.workerList.get(idx).add(obeLoc);
        }
        catch (Exception e) {
            MDC.put("id", deviceId);
            log.error("QUEUE_DATA.add: Exception: {}, {}, {}", deviceId, obeLoc, e.getMessage());
            MDC.clear();
        }
        return offer;
    }

    public void report() {
        for (BisAgipWorker worker : this.workerList) {
            worker.report();
        }
    }

//    public double getDistance(double lat1, double lon1, double lat2, double lon2) {
//        double EARTH_RADIUS = 6371.0;
//        double dLat = Math.toRadians(lat2 - lat1);
//        double dLon = Math.toRadians(lon2 - lon1);
//
//        double a = Math.sin(dLat/2)* Math.sin(dLat/2)+ Math.cos(Math.toRadians(lat1))* Math.cos(Math.toRadians(lat2))* Math.sin(dLon/2)* Math.sin(dLon/2);
//        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
//        return (EARTH_RADIUS * c * 1000);    // Distance in m
//    }

//    public NearNodeDto getNearNode(Location loc) {
//        Elapsed elapsed = new Elapsed();
//        log.info("getNearNode: {}", loc);
//        //Location(mDistance=0.0, mInitialBearing=0.0, mFinalBearing=0.0, mLatitude=128.73881251, mLongitude=36.56128759)
//        double maxDistance = 100;
//        NearNodeDto node = NearNodeDto.builder().nodeId(0L).distance(maxDistance).build();
//        for (Map.Entry<Long, TbNodeDto> e : this.repoService.getEntrySetNode()) {
//            TbNodeDto obj = e.getValue();
//            double fDistance = (float) getDistance(loc.getMLatitude(), loc.getMLongitude(), obj.getXCrdn(), obj.getYCrdn());
//            if (fDistance < maxDistance) {
//                maxDistance = fDistance;
//                node.setNodeId(obj.getNodeId());
//                node.setDistance(fDistance);
//            }
//        }
//        log.info("getNearNode: {} ms. {} m, {}", elapsed.milliSeconds(), maxDistance, node);
//        return node;
//    }

    public TbLinkDto getSectInfo(Long fNodeId, Long tNodeId) {
        return this.repoService.getSectMap(fNodeId, tNodeId);
    }

}
