package org.wcs.business.stock;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.wcs.constant.ConstantData;
import org.wcs.constant.enums.business.LocationTypeEnum;
import org.wcs.mapper.intf.AppConveyPickStandService;
import org.wcs.mapper.intf.AppStackerLocationService;
import org.wcs.mapper.intf.AppStackerStandService;
import org.wcs.mapper.intf.AppTrayConveyLocationService;
import org.wcs.model.bo.stock.StockLocation;
import org.wcs.model.bo.tuple.Tuple2;
import org.wcs.model.po.app.AppConveyPickStand;
import org.wcs.model.po.app.AppStackerLocation;
import org.wcs.model.po.app.AppStackerStand;
import org.wcs.model.po.app.AppTrayConveyLocation;

import java.util.*;

/**
 * 位置管理类
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LocationManage {

    private final AppStackerLocationService stackerLocationService; // 堆垛机库位表操作类
    private final AppStackerStandService stackerStandService; // 堆垛机站台表操作类
    private final AppTrayConveyLocationService trayConveyLocationService; // 托盘传送位置表操作类
    private final AppConveyPickStandService conveyPickStandService; // 输送线捡货站台表操作类


    private boolean isLoad = false;

    private List<StockLocation> stockLocationList; // 所有位置列表

    /**
     * 交界处列表 < 点位，区域>
     */
    private List<Tuple2<String, List<String>>> finalJunctionList; // 过度点位

    /**
     * 区域之间连接点位 < 区域，连接的其他区域>
     */
    private Map<String, List<String>> areaJunctionMap; // 区域之间连接点位

    /**
     * 加载一次位置信息
     */
    public void loadOnceLocation(){
        if(isLoad) {
            return;
        }
        loadStackerLocation(); // 加载位置信息
        loadTrayConveyLocation(); // 加载托盘输送位置信息
        loadConveyStandLocation(); // 加载输送线站台位置信息


        calculateJunction(); // 计算交界处
        isLoad = true;
    }

    /**
     * 重新加载位置信息
     */
    public void reloadLocation(){
        stockLocationList = null;
        loadOnceLocation(); // 加载信息
    }

    /**
     * 判断位置是否存在
     * @param locationId 位置ID
     * @return 是否存在
     */
    public boolean isLocationExist(String locationId) {
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        for (StockLocation stockLocation : stockLocationList) {
            if(stockLocation.getLocationId().equals(locationId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取位置信息
     * @param locationId 位置ID
     * @return 位置信息
     */
    public StockLocation getLocationInfo(String locationId) {
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        if(stockLocationList == null) {
            return null;
        }
        for (StockLocation stockLocation : stockLocationList) {
            if(stockLocation.getLocationId().equals(locationId)) {
                return stockLocation;
            }
        }
        return null;
    }

    /**
     * 判断两个位置是否在同一区域
     * @param locationId1 位置ID1
     * @param locationId2 位置ID2
     * @return 是否在同一区域
     */
    public Boolean isSameArea(String locationId1, String locationId2) {
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        if(stockLocationList == null) {
            return null;
        }
        Optional<StockLocation> stockLocation1 = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(locationId1)).findFirst();
        Optional<StockLocation> stockLocation2 = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(locationId2)).findFirst();
        if(stockLocation1.isEmpty() || stockLocation2.isEmpty()) {
            return null; // 位置不存在
        }
        return stockLocation1.get().getAreaId().equals(stockLocation2.get().getAreaId());
    }

    /**
     *  路径计算
     * @param fromLocation 起始位置
     * @param toLocationId 目标位置
     * @return 路径信息 < 点位类型， 点位>
     */
    public List<Tuple2<LocationTypeEnum, String>> courseCalculate(String fromLocation, String toLocationId) {
        if(fromLocation == null || toLocationId == null) {
            return null;
        }
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        if(stockLocationList == null) {
            return null;
        }
        List<Tuple2<LocationTypeEnum, String>> courseList = new ArrayList<>(); // 路径列表
        List<StockLocation> fromLocationInfoList = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(fromLocation)).toList();
        if(fromLocationInfoList.isEmpty()) {
            return null; // 起始位置不存在
        }
        Optional<StockLocation> toLocationInfo = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(toLocationId)).findFirst();
        if(toLocationInfo.isEmpty()) {
            return null; // 目标位置不存在
        }
        List<String> areaList = new ArrayList<>(); // 存储区域的列表
        StockLocation fromLocationInfo = null; // 起点，动态决定的起点
        // 起点信息存在多个情况下
        if(fromLocationInfoList.size() > 1) {
            // 判断是否有起点区域包含终点区域
            for (StockLocation fromLocationInfoItem : fromLocationInfoList) {
                if(fromLocationInfoItem.getLocationType().equals(toLocationInfo.get().getLocationType())) {
                    Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfoItem.getLocationType(), fromLocationInfoItem.getLocationId());
                    courseList.add(firstLocation); // 第一个位置
                    // 同样的区域，直接返回路径
                    courseList.add(new Tuple2<>(toLocationInfo.get().getLocationType(), toLocationInfo.get().getLocationId()));
                    return courseList;
                }
            }
            // 不包含终点区域 -- 需要计算区域路径
            for (StockLocation fromLocationInfoItem : fromLocationInfoList) {
                // 不同区域 // 找出区域路径，即需要经过哪些区域
                List<String> areaListTemp = calculateAreaCourse(fromLocationInfoItem.getAreaId(), toLocationInfo.get().getAreaId());
                if(areaListTemp != null && (areaList.isEmpty() || areaList.size() > areaListTemp.size())) {
                    areaList = areaListTemp; // 多个起点 取经过区域最少的点
                    fromLocationInfo = fromLocationInfoItem;
                    courseList = new ArrayList<>();
                    Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfoItem.getLocationType(), fromLocationInfoItem.getLocationId());
                    courseList.add(firstLocation); // 第一个位置
                }
            }
        } else {
            fromLocationInfo = fromLocationInfoList.getFirst();
            Tuple2<LocationTypeEnum, String> firstLocation = new Tuple2<>(fromLocationInfo.getLocationType(), fromLocationInfo.getLocationId());
            courseList.add(firstLocation); // 第一个位置
            areaList = calculateAreaCourse(fromLocationInfo.getAreaId(), toLocationInfo.get().getAreaId());
            if(areaList == null) {
                return null;
            }
        }

        if(areaList.isEmpty()) {
            return null; // 区域路径计算失败
        }
        for (int i = 0; i < areaList.size(); i++) {
            if(i == areaList.size() - 1) {
                // 最后一个区域
                courseList.add(new Tuple2<>(toLocationInfo.get().getLocationType(), toLocationInfo.get().getLocationId()));
            } else {
                // 其他区域
                String fromAreaItem = areaList.get(i);
                String toAreaItem = areaList.get(i + 1);
                // 查找这两区域的共同的点
                List<Tuple2<String, List<String>>> junctionLocation = finalJunctionList.stream().filter(tuple2 -> tuple2.item2.contains(fromAreaItem) && tuple2.item2.contains(toAreaItem)).toList();
                if(junctionLocation.isEmpty()) {
                    return null; // 区域路径计算失败 ---- 两区域没有交接点，按道理不会
                }
                if(junctionLocation.size() > 1) {
                    // 存在多个交接点，只生成 一个流程点，后续再动态生成
                    StringBuilder junctionLocationId = new StringBuilder("?");
                    for (Tuple2<String, List<String>> junctionLocationItem : junctionLocation) {
                        junctionLocationId.append(junctionLocationItem.item1).append(";");
                    }
                    junctionLocationId = new StringBuilder(junctionLocationId.substring(0, junctionLocationId.length() - 1));
                    courseList.add(new Tuple2<>(fromLocationInfo.getLocationType(), junctionLocationId.toString())); // 类型设置为和起点一样
                    return courseList;
                }
                // 找到区域交接点且只存在一个
                Tuple2<String, List<String>> junctionLocationItem = junctionLocation.getFirst();
                List<StockLocation> stockLocations = stockLocationList.stream().filter(stockLocation -> stockLocation.getLocationId().equals(junctionLocationItem.item1)).toList();
                if(stockLocations.isEmpty()) {
                    return null; // 区域交接点不存在 --- 正常不会出现
                }
                for (StockLocation stockLocation : stockLocations) {
                    if(stockLocation.getAreaId().equals(fromAreaItem)) {
                        courseList.add(new Tuple2<>(stockLocation.getLocationType(), stockLocation.getLocationId()));
                    }
                }
                for (StockLocation stockLocation : stockLocations) {
                    if(stockLocation.getAreaId().equals(toAreaItem)) {
                        courseList.add(new Tuple2<>(stockLocation.getLocationType(), stockLocation.getLocationId()));
                    }
                }
            }
        }
        return courseList;
    }

    /**
     * 加载堆垛机位置信息
     */
    private void loadStackerLocation() {
        // 库位信息
        List<AppStackerLocation> appStackerLocations = stackerLocationService.queryStackerLocationAll();
        if(appStackerLocations == null) {
            log.error("堆垛机库位信息加载失败");
            return;
        }
        List<StockLocation> stackerLocationList = new ArrayList<>();
        for (AppStackerLocation appStackerLocation : appStackerLocations) {
            StockLocation stockLocation = new StockLocation();
            stockLocation.setLocationId(appStackerLocation.getLocationId());
            stockLocation.setLocationType(LocationTypeEnum.STACKER);
            stockLocation.setAreaId(ConstantData.STACKER_AREA_PREFIX + appStackerLocation.getMachineId());
            stackerLocationList.add(stockLocation);
        }
        // 站台信息
        List<AppStackerStand> appStackerStands = stackerStandService.queryAll();
        if(appStackerStands == null) {
            log.error("堆垛机站台信息加载失败");
            return;
        }
        for (AppStackerStand appStackerStand : appStackerStands) {
            StockLocation stockLocation = new StockLocation();
            stockLocation.setLocationId(appStackerStand.getStandId());
            stockLocation.setLocationType(LocationTypeEnum.STACKER);
            stockLocation.setAreaId(ConstantData.STACKER_AREA_PREFIX + appStackerStand.getStackerId());
            stackerLocationList.add(stockLocation);

//            StockLocation stockLocationConvey = new StockLocation();
//            stockLocationConvey.setLocationId(appStackerStand.getStandId());
//            stockLocationConvey.setLocationType(LocationTypeEnum.CONVEY);
//            stockLocationConvey.setAreaId(ConstantData.CONVEY_AREA_PREFIX + appStackerStand.getAreaId());
//            stackerLocationList.add(stockLocationConvey);
        }
        if(stockLocationList == null) {
            stockLocationList = new ArrayList<>();
        }
        stockLocationList.addAll(stackerLocationList);
    }

    /**
     * 加载托盘输送位置信息
     */
    private void loadTrayConveyLocation() {
        List<AppTrayConveyLocation> trayConveyLocations = trayConveyLocationService.queryAll();
        if(trayConveyLocations == null) {
            log.error("托盘传送位置信息加载失败");
            return;
        }
        // 托盘输送位置信息
        List<StockLocation> trayConveyLocationList = new ArrayList<>();
        for (AppTrayConveyLocation trayConveyLocation : trayConveyLocations) {
            String areaId = trayConveyLocation.getAreaId();
            if(areaId == null) {
                continue;
            }
            String[] areaIds = areaId.split(";"); // 存在多个区域情况下用分号隔开
            for (String areaIdItem : areaIds) {
                StockLocation stockLocation = new StockLocation();
                stockLocation.setLocationId(trayConveyLocation.getLocationId());
                stockLocation.setLocationType(LocationTypeEnum.CONVEY);
                stockLocation.setAreaId(ConstantData.CONVEY_AREA_PREFIX + areaIdItem);
                trayConveyLocationList.add(stockLocation);
            }
        }
        if(stockLocationList == null) {
            stockLocationList = new ArrayList<>();
        }
        stockLocationList.addAll(trayConveyLocationList);
    }

    /**
     * 加载输送线站台位置信息
     */
    private void loadConveyStandLocation() {
        // 输送线捡货站台信息
        List<AppConveyPickStand> conveyPickStands = conveyPickStandService.queryAll();
        if(conveyPickStands == null) {
            log.error("输送线捡货站台信息加载失败");
            return;
        }
        List<StockLocation> conveyPickStandLocationList = new ArrayList<>();
        for (AppConveyPickStand conveyPickStand : conveyPickStands) {
            StockLocation stockLocation = new StockLocation();
            stockLocation.setLocationId(conveyPickStand.getStandId());
            stockLocation.setLocationType(LocationTypeEnum.CONVEY);
            stockLocation.setAreaId(ConstantData.CONVEY_AREA_PREFIX + conveyPickStand.getAreaId());
            conveyPickStandLocationList.add(stockLocation);
        }
        if(stockLocationList == null) {
            stockLocationList = new ArrayList<>();
        }
        stockLocationList.addAll(conveyPickStandLocationList);
    }


    /**
     * 计算交界处
     */
    private void calculateJunction() {
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        if(stockLocationList == null) {
            return;
        }
        // <点位，所属的区域列表> ----- 过度信息
        List<Tuple2<String, List<String>>> junctionList = new ArrayList<>();
        for (StockLocation stockLocation : stockLocationList) {
            // 上述列表内是否存在这个点位
            Optional<Tuple2<String, List<String>>> junctionData = junctionList.stream().filter(tuple2 ->
                    tuple2.item1.equals(stockLocation.getLocationId())).findFirst();
            if(junctionData.isEmpty()) {
                // 不存在点位，创建个新的数据加进去
                List<String> locationAreaList = new ArrayList<>();
                locationAreaList.add(stockLocation.getAreaId());
                junctionList.add(new Tuple2<>(stockLocation.getLocationId(), locationAreaList));
                continue;
            }
            junctionData.get().item2.add(stockLocation.getAreaId());
        }
        // <点位，所属的区域列表> ----- 最终获取到的过渡点
        List<Tuple2<String, List<String>>> finalJunctionList = new ArrayList<>();
        // 循环列表找出不止一种点位类型的元素
        for (Tuple2<String, List<String>> junctionData : junctionList) {
            if(junctionData.item2.size() > 1) {
                finalJunctionList.add(junctionData);
            }
        }
        this.finalJunctionList = finalJunctionList;
    }

    /**
     * 计算区域交界，即每个区域分别和那些区域相连接
     * 区域权重暂定为1，即任意两个区域之间连接的权重为1
     */
    private void calculateAreaJunction() {
        if(stockLocationList == null) {
            loadOnceLocation();
        }
        if(stockLocationList == null) {
            return;
        }
        if(finalJunctionList == null) {
            calculateJunction();
        }
        if(finalJunctionList == null) {
            return;
        }
        // 寻找点位列表中不同的区域
        List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
        // 区域连接关系暂存
        areaJunctionMap = new HashMap<>();
        // 轮询区域，寻找区域之间的连接关系
        for (String areaItem : areaList) {
            List<String> junctionList = new ArrayList<>();
            for (Tuple2<String, List<String>> finalJunction : finalJunctionList) {
                List<String> locationAreaList = finalJunction.item2;
                // 不包含该区域
                if(!locationAreaList.contains(areaItem)) {
                    continue;
                }
                // 包含区域，则添加
                for (String areaItem2 : locationAreaList) {
                    if(areaItem.equals(areaItem2)) {
                        continue;
                    }
                    junctionList.add(areaItem2);
                }
            }
            areaJunctionMap.put(areaItem, junctionList);
        }
    }

    /**
     * 计算区域路径
     * @param startAreaId 起点区域
     * @param endAreaId 终点区域
     * @return 区域路径
     */
    private List<String> calculateAreaCourse(String startAreaId, String endAreaId) {
        if(startAreaId.equals(endAreaId)) {
            return List.of(startAreaId);
        }
        if(areaJunctionMap == null) {
            calculateAreaJunction();
        }
        if(areaJunctionMap == null) {
            return null;
        }
        // 正是开始计算
        List<String> usedAreaList = new ArrayList<>(); // 已处理过的区域
        // 权重map
        Map<String, Integer> areaWeightMap = createAreaWeightMap(startAreaId);
        // 前驱map
        Map<String, String> beforeAreaMap = createBeforeAreaMap();
        // 优先队列
        List<Tuple2<Integer, String>> priorityQueue = new ArrayList<>();
        priorityQueue.add(new Tuple2<>(0, startAreaId));
        // Dijkstra算法
        while (!priorityQueue.isEmpty()) {
            // 队列按照权重进行排序
            priorityQueue.sort(Comparator.comparingInt(Tuple2::getItem1));
            // 取出第一个，应该是最小权重的
            Tuple2<Integer, String> priorityQueueItem = priorityQueue.getFirst();
            priorityQueue.remove(priorityQueueItem);
            // 如果该区域已处理过，则跳过
            if(usedAreaList.contains(priorityQueueItem.item2)) {
                continue;
            }
            usedAreaList.add(priorityQueueItem.item2);
            // 获取该区域连接的区域
            List<String> junctionList = areaJunctionMap.get(priorityQueueItem.item2);
            for (String junctionItem : junctionList) {
                // 如果该区域已处理过，则跳过
                if(usedAreaList.contains(junctionItem)) {
                    continue;
                }
                // 如果该区域权重比当前区域权重更小，则更新
                if(areaWeightMap.get(junctionItem) > areaWeightMap.get(priorityQueueItem.item2) + 1) {
                    areaWeightMap.put(junctionItem, areaWeightMap.get(priorityQueueItem.item2) + 1);
                }
                // 添加到优先队列中
                priorityQueue.add(new Tuple2<>(areaWeightMap.get(junctionItem), junctionItem));
                // 添加前驱
                beforeAreaMap.put(junctionItem, priorityQueueItem.item2);
                // 找到终点
                if(junctionItem.equals(endAreaId)) {
                    // 找到终点，返回路径
                    List<String> courseList = new ArrayList<>();
                    String currentArea = junctionItem;
                    // 倒序
                    while (currentArea != null) {
                        courseList.add(currentArea);
                        currentArea = beforeAreaMap.get(currentArea);
                    }
                    // 倒序
                    return courseList.reversed();
                }
            }
        }
        return null;
    }

    /**
     * 创建区域权重Map
     * @param startAreaId 开始区域
     * @return 区域权重Map
     */
    private Map<String, Integer> createAreaWeightMap(String startAreaId) {
        Map<String, Integer> areaWeightMap = new HashMap<>();
        // 寻找点位列表中不同的区域
        List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
        for (String areaItem : areaList) {
            if(areaItem.equals(startAreaId)) {
                areaWeightMap.put(areaItem, 0);
                continue;
            }
            areaWeightMap.put(areaItem, Integer.MAX_VALUE);
        }
        return areaWeightMap;
    }

    /**
     * 创建区域前驱Map
     * @return 区域前驱Map
     */
    private Map<String, String> createBeforeAreaMap() {
        Map<String, String> beforeAreaMap = new HashMap<>();
        // 寻找点位列表中不同的区域
        List<String> areaList = stockLocationList.stream().map(StockLocation::getAreaId).toList();
        for (String areaItem : areaList) {
            beforeAreaMap.put(areaItem, null);
        }
        return beforeAreaMap;
    }

}
