#!E:\Pycharm Projects\Waytous
# -*- coding: utf-8 -*-
# @Time : 2022/5/30 15:35
# @Author : Opfer
# @Site :
# @File : group.py
# @Software: PyCharm

from data.dispatchInfo import *
from bidict import bidict
from alg.algorithm import AlgorithmBase, DistributionRatio
import numpy as np
# from settings import get_logger
from data.para_config import get_value
from core.util import *
from typing import Mapping, List
from util import CoreException
from equipment import TruckInfo, ExcavatorInfo, DumpInfo
from core.schedule import PreSchedule
from graph.topo_graph import Topo


def direct2redis():
    """
    直接读取数据库写入redis
    :return: None
    """
    # 清空数据库缓存
    session_mysql.commit()
    session_mysql.flush()

    # 清空数据库缓存
    session_postgre.commit()
    session_postgre.flush()

    try:

        truck_disp = []
        for item in session_mysql.query(DispatchSetting).filter_by(isdeleted=0, ).all():
            if item is None:
                raise CoreException(101, "无可用派车计划")
            if item.truck_id not in truck_disp:
                record = {"truckId": item.truck_id, "dispatchId": item.id, "exactorId": item.exactor_id,
                          "dumpId": item.dump_id, "loadAreaId": item.load_area_id, "unloadAreaId": item.unload_area_id,
                          "groupId": item.group_id, "isdeleted": False, "isTemp": False, "haulFlag": -1,
                          "groupName": DispatchInfo.group_name[item.group_id]}

                logger.info(f'写入redis调度结果: {record}')
                # 写入redis
                redis5.set(item.truck_id, str(json.dumps(record)))

            truck_disp.append(item.truck_id)

    except CoreException as ce:
        logger.error(ce)
        session_postgre.rollback()
        session_mysql.rollback()


def group_direct2redis(group):
    """
    根据分组车辆直接读取数据库写入redis
    :return: None
    """
    # 清空数据库缓存
    session_mysql.commit()
    session_mysql.flush()

    # 清空数据库缓存
    session_postgre.commit()
    session_postgre.flush()

    try:
        for truck_id in group.group_trucks:
            truck_direct2redis(truck_id)

    except CoreException as ce:
        logger.error(ce)
        session_postgre.rollback()
        session_mysql.rollback()


def truck_direct2redis(truck_id):
    """
    根据车辆直接读取数据库写入redis
    :return: None
    """
    item = session_mysql.query(DispatchSetting).filter_by(truck_id=truck_id, isdeleted=0, ).first()
    if item is None:
        raise CoreException(101, "无可用派车计划")
    record = {"truckId": item.truck_id, "dispatchId": item.id, "exactorId": item.exactor_id,
              "dumpId": item.dump_id, "loadAreaId": item.load_area_id, "unloadAreaId": item.unload_area_id,
              "groupId": item.group_id, "isdeleted": False, "isTemp": False, "haulFlag": -1,
              "groupName": DispatchInfo.group_name[item.group_id]}
    logger.info(f'写入redis调度结果: {record}')
    # 写入redis
    redis5.set(item.truck_id, str(json.dumps(record)))


class CurrentTruck:
    """ class for the information of current dispatching truck.

    Description:
        当前请求调度卡车信息

    Attribute:
        truck_id(uuid)
        group_id(uuid)
        trip(list)
        task(int)

    """

    def __init__(self, truck_id: str, group_id: str, trip: List[int], task: int, state: int):
        self._truck_id = truck_id
        self._group_id = group_id
        self._trip = trip
        self._task = task
        self._state = state

    def get_truck_id(self):
        return self._truck_id

    def get_trip(self):
        return self._trip

    def get_task(self):
        return self._task

    def get_group_id(self):
        return self._group_id

    def get_sate(self):
        return self._state


class Group:
    """
    class for group instance.
    """
    def __init__(self, group_id, truck: TruckInfo, pre_sch: PreSchedule, excavator_info: ExcavatorInfo,
                 dump_info: DumpInfo, topo: Topo):
        """
        Generate a group obj.
        :param group_id:
        :param truck:
        :param pre_sch:
        :param excavator_info:
        :param dump_info:
        :param topo:
        """
        self.logger = get_logger("zxt.Group")
        # basic info.
        self.group_id = group_id
        self.group_name = DispatchInfo.group_name[self.group_id]
        self.group_mode = 1
        self.truck = truck
        self.pre_sch = pre_sch
        self.excavator_info = excavator_info
        self.dump_info = dump_info

        # topo graph
        self.topo = topo

        # group devices
        self.group_excavators = {}
        self.group_unload_areas = {}
        self.group_dumps = {}
        self.group_trucks = set()

        # road network info.
        self.to_excavator_distance = None
        self.to_unload_area_distance = None
        self.park_to_excavator_distance = None

        # self.to_excavator_congestion = None
        # self.to_unload_area_congestion = None
        # self.park_to_excavator_congestion = None

        # device map
        self.truck_uuid_index_dict = bidict()
        self.excavator_uuid_index_dict = bidict()
        self.unload_area_uuid_index_dict = bidict()
        self.dump_uuid_index_dict = bidict()

        # device bind
        self.dump_truck_bind = {}
        self.excavator_truck_bind = {}

        # truck info.
        self.truck_info_list = {}

        # material control
        self.group_walk_available = None

        # device exclude
        self.truck_excavator_exclude = set()
        self.truck_excavator_exclude_modify = {}

    def update_group_mode(self):
        """
        update group mode.
        :param group_mode:
        :return:
        """
        # DispatchInfo.update_group_mode()
        self.group_mode = 1
        self.group_mode = DispatchInfo.get_group_mode(self.group_id)

    def update_group_device(self):
        """
        update group devices.
        :return:
        """
        # init group devices
        self.group_excavators = {}
        self.group_unload_areas = {}
        self.group_dumps = {}
        self.group_trucks = set()
        # update group devices
        self.group_excavators = DispatchInfo.get_excavator(self.group_id)
        self.group_unload_areas = DispatchInfo.get_unload_area(self.group_id)
        self.group_dumps = DispatchInfo.get_dump(self.group_id)
        self.group_trucks = DispatchInfo.get_truck_set(self.group_id)

    def update_group_road_network(self):
        """
        update group road network.
        :return:
        """
        # init group road network
        self.to_excavator_distance = None
        self.to_unload_area_distance = None
        self.park_to_excavator_distance = None
        # update group road network
        self.to_excavator_distance = DispatchInfo.get_to_excavator_distance(self.group_id)
        self.to_unload_area_distance = DispatchInfo.get_to_unload_area_distance(self.group_id)
        self.park_to_excavator_distance = DispatchInfo.get_park_to_excavator_distance(self.group_id)

    def update_group_device_map(self):
        """
        update group device map.
        :return:
        """
        # update devices(excavators, unload_areas, dumps) bidirectional map within a group
        self.excavator_uuid_index_dict = DispatchInfo.get_group_excavator_dict(self.group_id)
        self.unload_area_uuid_index_dict = DispatchInfo.get_group_unload_area_dict(self.group_id)
        self.dump_uuid_index_dict = DispatchInfo.get_group_dump_dict(self.group_id)

    def update_device_material(self):
        """
        update group device material.
        :return:
        """

        # material control
        self.group_walk_available = np.ones_like(self.to_unload_area_distance)

        try:

            # self.logger.info("物料兼容性")
            # self.logger.info(DispatchInfo.group_name[self.group_id])

            for dump_id in self.group_dumps:
                for excavator_id in self.group_excavators:
                    excavator_index = self.excavator_uuid_index_dict[excavator_id]
                    dump_index = self.dump_uuid_index_dict[dump_id]

                    if self.excavator_info.excavator_material[excavator_id] not in self.dump_info.dump_material[dump_id]:
                        self.group_walk_available[excavator_index][dump_index] = 10
            self.logger.info("group_walk_available")
            self.logger.info(self.group_walk_available)
        except Exception as es:
            self.logger.error(es)
            self.logger.error(es.__traceback__.tb_lineno)
            self.logger.error("设备物料读取异常")

    def update_device_bind(self):
        """
        update device bind.
        :return:
        """
        self.dump_truck_bind = {}
        self.excavator_truck_bind = {}

        for truck_id, dump_id in self.truck.truck_dump_bind.items():
            if dump_id not in self.dump_truck_bind:
                self.dump_truck_bind[dump_id] = [truck_id]
            else:
                self.dump_truck_bind[dump_id].append(truck_id)

        for truck_id, excavator_id in self.truck.truck_excavator_bind.items():
            if excavator_id not in self.excavator_truck_bind:
                self.excavator_truck_bind[excavator_id] = [truck_id]
            else:
                self.excavator_truck_bind[excavator_id].append(truck_id)

    def update_device_exclude(self):
        """
        get device exclude modify vector.
        :return:
        """
        self.truck_excavator_exclude = {}
        self.truck_excavator_exclude_modify = {}

        for truck_id in self.group_trucks:
            self.truck_excavator_exclude_modify[truck_id] = np.zeros(len(self.group_excavators))
            if truck_id in self.truck.truck_excavator_exclude:
                exclude_excavators = self.truck.truck_excavator_exclude[truck_id]
            else:
                continue
            for excavator_id in exclude_excavators:
                if excavator_id in self.group_excavators:
                    if truck_id not in self.truck_excavator_exclude:
                        self.truck_excavator_exclude[truck_id] = [excavator_id]
                    else:
                        self.truck_excavator_exclude[truck_id].append(excavator_id)

                    excavator_index = self.excavator_uuid_index_dict[excavator_id]
                    self.truck_excavator_exclude_modify[truck_id][excavator_index] = 1000000

        self.logger.info("group.truck_excavator_exclude")
        self.logger.info(self.truck_excavator_exclude_modify)


    def info_update(self):
        """
        update group info.
        :return:
        """
        self.update_group_mode()
        self.update_group_device()
        self.update_group_device_map()
        self.update_group_road_network()
        self.update_device_material()
        self.update_device_exclude()


class GroupDispatcher:
    """
    Group controller responsible for group dispatching.
    """

    def __init__(self, group: Group):
        """
        Receive a group obj.
        :param group:
        """
        self.logger = logging.getLogger("zxt.GroupDispatcher")
        self.group = group
        if self.group.topo is not None:
            self.redispatcher = ReDispatcher(self.group)

    def group_dispatch(self, Solver) -> Mapping[str, List[str]]:
        """
        Receive a Alg obj. and output dispatch plan for all trucks within this group.
        :param Solver:
        :return:
            dispatch plan: Dict({truck_id: match_id})
        """

        # init truck dispatch plan dictionary.
        truck_dispatch = {}

        # init dictionary including all truck obj. within this group.
        self.group.truck_info_list = {}

        assert issubclass(Solver, AlgorithmBase)
        solver = Solver(self.group, self.group.truck, self.group.pre_sch)  # solver algorithm init

        self.logger.info(f'分组 {self.group.group_name} 调度计算-调度模式 {self.group.group_mode}')

        for truck_id in list(self.group.group_trucks):

            self.logger.info(f'调度车辆 {truck_id}')

            # try:
            # get truck index from mapping
            if truck_id not in self.group.truck.truck_uuid_to_index_dict:
                self.logger.error(f'truck.truck dict 中不存在 {truck_id}')
                continue
            else:
                truck_idx = self.group.truck.truck_uuid_to_index_dict[truck_id]

            # get truck trip from truck obj.
            truck_trip_info_list = self.group.truck.get_truck_current_trip()

            if truck_idx >= len(truck_trip_info_list):
                self.logger.error(f'truck.truck trip 中不存在 {truck_idx} 号矿卡 {truck_id}')
                continue
            else:
                truck_trip = self.group.truck.get_truck_current_trip()[truck_idx]

            # get truck task from truck obj.
            truck_task_list = self.group.truck.get_truck_current_task()

            if truck_id not in truck_task_list:
                self.logger.error(f'truck.truck task 中不存在矿卡 {truck_id}')
                continue
            else:
                truck_task = truck_task_list[truck_id]

            # Construct a truck obj. and add it to group truck dictionary.
            truck_info = CurrentTruck(truck_id, self.group.group_id, truck_trip, truck_task,
                                      self.group.truck.get_truck_current_state()[truck_id])

            self.group.truck_info_list[truck_id] = truck_info

            # Construct a test case for redispatch
            # truck_task = 0
            # if truck_id != "f704aa5e-24d9-4822-b634-ae81ca5ff0be":
            #     self.truck.get_truck_locate_dict()[truck_id] = "8961f641-c134-8be8-f4eb-95e6e2c374b7"
            # self.truck.truck_is_temp[truck_id] = False
            # self.truck.truck_current_state[truck_id] = 2

            # except Exception as es:
            #     self.logger.error("车辆调度信息读取异常")
            #     self.logger.error(es)

            try:
                # 全智能模式
                if self.group.group_mode == 1:
                    self.logger.info("全智能模式调度")
                    self.full_dynamic_mode(truck_id, solver, truck_dispatch, truck_info, truck_task, truck_trip)

                # 空车智能模式
                elif self.group.group_mode == 2:
                    self.logger.info("空车智能模式调度")
                    self.semi_dynamic_mode(truck_id, solver, truck_dispatch, truck_info, truck_task, truck_trip)
                # 定铲派车
                elif self.group.group_mode == 3:
                    self.logger.info("固定模式调度")
                    try:
                        truck_dispatch[truck_id] = DispatchInfo.get_truck_match(truck_id)
                    except Exception as es:
                        self.logger.error("固定派车-计算异常")
                        self.logger.error(es)

                # 分流配比模式
                elif self.group.group_mode == 4:
                    self.logger.info("分流配比模式调度")
                    self.ratio_mode(truck_id, truck_dispatch, truck_task)

            except Exception as es:
                self.logger.error("调度调用异常")
                self.logger.error(es)

        # return dispatch plan
        return truck_dispatch

    def ratio_mode(self, i, truck_dispatch, truck_task):
        next_exactor_id = DispatchInfo.get_truck_exactor(i)  # 获取该卡车对应的exactor_id
        self.logger.info(f"分流配比模式，对应的卡车：{i}")
        self.logger.info(f"矿卡对应的铲车：{next_exactor_id}")
        next_unload_area_id = None
        if truck_task == -2:

            next_unload_area_id = "Park"

        # 空载模式下，计算下一次卸载区的位置，按照分流配比的模式进行计算
        elif truck_task in [0, 1, 2]:

            try:

                if i in self.group.truck.truck_dump_bind:
                    dump_uuid_to_unload_area_uuid_dict = get_value("dump_uuid_to_unload_area_uuid_dict")
                    next_unload_area_id = dump_uuid_to_unload_area_uuid_dict[self.group.truck.truck_dump_bind[i]]
                else:

                    next_unload_area_id = DistributionRatio(next_exactor_id, self.group.truck).ratio_main()

            except Exception as es:

                self.logger.error("分流配比模式-->>空载车辆计算异常")

                self.logger.error(es)

        # 重载模式下，按照固定派车进行计算
        elif truck_task in [3, 4, 5]:

            try:

                next_unload_area_id = DispatchInfo.get_truck_match(i)[1]

            except Exception as es:

                self.logger.error("分流配比模式-->>重载车辆计算异常")

                self.logger.error(es)
        truck_dispatch[i] = [next_exactor_id, next_unload_area_id]

    def semi_dynamic_mode(self, i, s, truck_dispatch, truck_info, truck_task, truck_trip):
        # TODO：和全智能调度很像，是否可以合并合；

        # 加入二次调度；
        if truck_task in [-2, 3, 4, 5]:
            try:

                if i in self.group.truck.truck_excavator_bind:
                    next_excavator_id = self.group.truck.truck_excavator_bind[i]
                else:
                    next_excavator_value = s.solve(truck_info)
                    # min_index = next_excavator_list.index(min(next_excavator_list))
                    self.logger.info(f'车辆排斥修正 {i}, {self.group.truck_excavator_exclude_modify[i]}')
                    min_index = np.argmin(next_excavator_value + self.group.truck_excavator_exclude_modify[i])
                    next_excavator_id = self.group.excavator_uuid_index_dict.inverse[min_index]
                if truck_task == -2:
                    next_unload_area_id = "Park"
                else:
                    # next_unload_area_id = self.unload_area_uuid_index_dict.inverse[truck_trip[-1]]
                    dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[-1]]
                    next_unload_area_id = get_value("dump_uuid_to_unload_area_uuid_dict")[dump_id]
                truck_dispatch[i] = [next_excavator_id, next_unload_area_id]

            except Exception as es:
                self.logger.error("重载车辆空车智能模式-计算异常")
                self.logger.error(es)
        if truck_task in [0, 1, 2]:
            try:
                truck_dispatch[i] = DispatchInfo.get_truck_match(i)
            except Exception as es:
                self.logger.error("空载车辆空车智能模式-计算异常")
                self.logger.error(es)

    def full_dynamic_mode(self, i: str, s: AlgorithmBase, truck_dispatch: Mapping[str, List[str]],
                          truck_info: CurrentTruck, truck_task: int, truck_trip: List[int]):
        # 车辆停止或者车辆位于卸载区内, 调度车辆前往装载区
        if truck_task in [-2, 3, 4, 5]:
            if truck_task == 3 and self.group.truck.truck_current_state[i] == 2:
                if self.group.topo is not None:
                    try:
                        truck_locate = self.group.truck.get_truck_locate_dict()[i]
                    except Exception as es:
                        self.logger.error("车辆位置信息丢失")
                        self.logger.error(es)
                        truck_dispatch[i] = [None, None]
                        return
                    try:
                        truck_is_temp = self.group.truck.truck_is_temp[i]
                    except Exception as es:
                        truck_is_temp = False
                        self.logger.error("车辆临时字段异常")
                        self.logger.error(es)
                    self.logger.info(f'车辆位置 {truck_locate}')
                    self.logger.info(f'车辆状态 {self.group.truck.truck_current_state[i]}')
                    self.logger.info(f'车辆临时 {truck_is_temp} {type(truck_is_temp)}')
                    # 车辆当前位于交叉路口前，且排队等待
                    if (truck_locate in self.group.topo.cross_bf_lanes) and (
                            self.group.truck.truck_current_state[i] == 2) \
                            and (not truck_is_temp):
                        # self.redispatch_to_dump(i, truck_dispatch, truck_locate, truck_trip)
                        self.redispatcher.redispatch_to_dump(i, truck_dispatch, truck_locate, truck_trip)
                else:
                    pass
            else:
                try:
                    if i in self.group.truck.truck_excavator_bind:
                        try:
                            if i not in self.group.truck.truck_excavator_bind:
                                raise CoreException(102, f'truck.group_excavators bind 中不存在 {i}')
                        except CoreException as es:
                            es.with_traceback_info()
                            self.logger.error(es)
                            return
                        next_excavator_id = self.group.truck.truck_excavator_bind[i]
                    else:
                        next_excavator_value = s.solve(truck_info)
                        self.logger.info(f'车辆排斥修正 {i}, {self.group.truck_excavator_exclude_modify[i]}')
                        min_index = np.argmin(next_excavator_value + self.group.truck_excavator_exclude_modify[i])
                        # min_index = np.argmin(next_excavator_value)
                        next_excavator_id = self.group.excavator_uuid_index_dict.inverse[min_index]

                        self.logger.info(f'目标挖机 {min_index} {next_excavator_id}')

                    if truck_task == -2:
                        next_unload_area_id = "Park"
                    else:
                        dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[-1]]
                        next_unload_area_id = get_value("dump_uuid_to_unload_area_uuid_dict")[dump_id]
                    truck_dispatch[i] = [next_excavator_id, next_unload_area_id]

                except Exception as es:
                    self.logger.error("重载车辆全智能模式-计算异常")
                    self.logger.error(es)
        # 车辆位于装载区内, 调度车辆前往卸载区
        elif truck_task in [0, 1, 2]:
            if truck_task == 0 and self.group.truck.truck_current_state[i] == 2:
                if self.group.topo is not None:
                    if i in self.group.truck.get_truck_locate_dict():
                        truck_locate = self.group.truck.get_truck_locate_dict()[i]
                    else:
                        self.logger.error(f'车辆 {i} 位置信息丢失')
                        truck_dispatch[i] = [None, None]
                        return

                    self.logger.info(self.group.truck.truck_is_temp)
                    try:
                        truck_is_temp = self.group.truck.truck_is_temp[i]
                    except Exception as es:
                        truck_is_temp = False
                        self.logger.error("车辆临时字段异常")
                        self.logger.error(es)
                    # 车辆当前位于交叉路口前，且排队等待
                    self.logger.info("潜在二次调度车辆状态")
                    self.logger.info(f'车辆 {i}')
                    self.logger.info(f'车辆位置 {truck_locate}')
                    self.logger.info(f'车辆状态 {self.group.truck.truck_current_state[i]}')
                    self.logger.info(f'车辆临时 {truck_is_temp} {type(truck_is_temp)}')
                    self.logger.info(self.group.topo.cross_bf_lanes)
                    self.logger.info(self.group.truck.truck_current_state)
                    self.logger.info(truck_is_temp)
                    # if (truck_locate in self.topo.cross_bf_lanes) and (self.truck.truck_current_state[truck_id] == 2) \
                    #         and (not truck_is_temp):
                    if (truck_locate in self.group.topo.cross_bf_lanes) and (
                            self.group.truck.truck_current_state[i] == 2):
                        self.logger.info("触发二次调度")

                        # self.redispatch_to_excavator(i, truck_dispatch, truck_locate)
                        self.redispatcher.redispatch_to_excavator(i, truck_dispatch, truck_locate)
                else:
                    pass
            else:
                try:
                    next_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[-1]]
                    next_unload_area_value = s.solve(truck_info)
                    # self.logger.info(f'车辆 {truck_id}')
                    # self.logger.info(f'group distance {self.to_unload_area_distance}')
                    # self.logger.info(f'walk available {self.group_walk_available}')
                    #
                    # self.logger.info(self.unload_area_uuid_index_dict)
                    # self.logger.info(self.excavator_uuid_index_dict)

                    if i in self.group.truck.truck_dump_bind:
                        dump_uuid_to_unload_area_uuid_dict = get_value("dump_uuid_to_unload_area_uuid_dict")
                        next_unload_area_id = dump_uuid_to_unload_area_uuid_dict[self.group.truck.truck_dump_bind[i]]
                    else:
                        tmp = self.group.group_walk_available[
                              self.group.excavator_uuid_index_dict[next_excavator_id], :].flatten()
                        # self.logger.info(f'group_walk_available_pick {tmp}')
                        # self.logger.info(f'next_excavator_id {next_excavator_id}')
                        # self.logger.info(next_unload_area_value)
                        next_unload_area_value *= self.group.group_walk_available[
                                                  self.group.excavator_uuid_index_dict[next_excavator_id], :].flatten()

                        # self.logger.info(next_unload_area_value)
                        min_index = np.argmin(next_unload_area_value)
                        next_unload_area_id = self.group.unload_area_uuid_index_dict.inverse[min_index]

                        self.logger.info(f'目标卸点 {min_index} {next_unload_area_id}')
                        # next_excavator_id = self.excavator_uuid_index_dict.inverse[truck_trip[-1]]

                    truck_dispatch[i] = [next_excavator_id, next_unload_area_id]

                except Exception as es:
                    self.logger.error("空载车辆全智能模式-计算异常")
                    self.logger.error(es.__traceback__.tb_lineno)
                    self.logger.error(es)
                    truck_dispatch[i] = [None, None]

        # TODO：车辆在空载状态是否应该如何计算，是计算下一次卸载区还是继续计算当前最优挖机

    # def redispatch_to_dump(self, truck_id: str, truck_dispatch: Mapping[str, List[str]], truck_locate: str, truck_trip: List[int]):
    #     """
    #     redispatch truck to dumps.
    #     :param truck_id:
    #     :param truck_dispatch:
    #     :param truck_locate:
    #     :param truck_trip:
    #     :return:
    #     """
    #     # 当前绑定装载区
    #     if truck_trip[0] == -1:
    #         next_excavator_id = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id,
    #                                                                          isdeleted=0).first().exactor_id
    #     else:
    #         next_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[0]]
    #     # 当前绑定卸载区
    #     if truck_trip[-1] == -1:
    #         item = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id, isdeleted=0).first()
    #         current_dump_id = item.dump_id
    #         current_unload_area_id = item.unload_area_id
    #     else:
    #         current_dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[-1]]
    #         current_unload_area_id = DispatchInfo.dump_unload_area_dict[current_dump_id]
    #     unload_area_dict, unload_area_lane_dict = self.group.topo.get_unload_target_node_real(truck_locate,
    #                                                                                           current_unload_area_id,
    #                                                                                           True)
    #     # 获取拥堵路段
    #     congestion_lane_dict = self.get_congestion_lanes()
    #     # 获取当前交叉口下一路段集合
    #     next_lane_list = get_cross_next_lanes(truck_locate)
    #     # 交叉口下一路段可达的装载区
    #     next_lane_load_area_dict = get_lane_reach_load_areas(unload_area_lane_dict,
    #                                                          next_lane_list)
    #     # 排除下一个路段阻塞的装载区
    #     delete_congestion_load_area(congestion_lane_dict, unload_area_dict,
    #                                 next_lane_load_area_dict)
    #     min_trip_time = 10000000
    #     best_dump_id = current_dump_id
    #     for unload_area, value in unload_area_dict.items():
    #         # 车辆不需要掉头
    #         if value[-1] == 1 and unload_area in DispatchInfo.unload_area_dump_dict:
    #             dump_id = DispatchInfo.unload_area_dump_dict[unload_area]
    #
    #             traveling_time = value[0] / heavy_speed
    #
    #             now = float(
    #                 (datetime.now() - self.group.pre_sch.start_time) / timedelta(hours=0, minutes=1,
    #                                                                              seconds=0))
    #             reach_time = now + traveling_time
    #
    #             trip_time = max(reach_time,
    #                             self.group.pre_sch.get_dump_avl_time()[dump_id]) - now
    #
    #             if min_trip_time > trip_time:
    #                 best_dump_id = dump_id
    #     next_unload_area_id = DispatchInfo.dump_unload_area_dict[best_dump_id]
    #     truck_dispatch[truck_id] = [next_excavator_id, next_unload_area_id]
    #     # res = redispatch_request(truck_id, next_excavator_id, next_unload_area_id)
    #     # self.logger.info(res)
    #     self.logger.info(f'二次调度结果 {truck_id}')
    #     self.logger.info(truck_dispatch[truck_id])
    #
    # def redispatch_to_excavator(self, truck_id, truck_dispatch, truck_locate):
    #     """
    #     redispatch truck to excavators.
    #     :param truck_id:
    #     :param truck_dispatch:
    #     :param truck_locate:
    #     :return:
    #     """
    #     # 当前绑定卸载区
    #     # if truck_trip[0] == -1:
    #     next_unload_area_id = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id,
    #                                                                        isdeleted=0).first().unload_area_id
    #     # else:
    #     #     dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[0]]
    #     #     next_unload_area_id = get_value("dump_uuid_to_unload_area_uuid_dict")[dump_id]
    #     # 当前绑定装载区
    #     # if truck_trip[-1] == -1:
    #     item = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id, isdeleted=0).first()
    #     current_excavator_id = item.exactor_id
    #     current_load_area_id = item.load_area_id
    #     self.logger.info(f'truck_id {truck_id}')
    #     self.logger.info(f'current_load_area_id {current_load_area_id}')
    #     # else:
    #     #     current_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[-1]]
    #     #     current_load_area_id = DispatchInfo.excavator_load_dict[current_excavator_id]
    #     load_area_dict, load_area_lane_dict = self.group.topo.get_load_target_node_real(truck_locate,
    #                                                                               current_load_area_id, True)
    #     self.logger.info("所有可达装载区")
    #     self.logger.info(load_area_dict)
    #     # 获取拥堵路段
    #     congestion_lane_dict = self.get_congestion_lanes()
    #     # 获取当前交叉口下一路段集合
    #     next_lane_list = get_cross_next_lanes(truck_locate)
    #     # 交叉口下一路段可达的装载区
    #     next_lane_load_area_dict = get_lane_reach_load_areas(load_area_lane_dict, next_lane_list)
    #     # 排除下一个路段阻塞的装载区
    #     delete_congestion_load_area(congestion_lane_dict, load_area_dict,
    #                                 next_lane_load_area_dict)
    #     self.logger.info("剔除堵塞装载区")
    #     self.logger.info(load_area_dict)
    #     # 获取最佳挖机
    #     best_excavator_id = self.get_best_excavator(current_excavator_id, truck_id, load_area_dict)
    #     next_excavator_id = best_excavator_id
    #     truck_dispatch[truck_id] = [next_excavator_id, next_unload_area_id]
    #     self.logger.info(f'二次调度结果 {truck_id}')
    #     self.logger.info(truck_dispatch[truck_id])
    #
    # def get_best_excavator(self, current_excavator_id, truck_id, load_area_dict):
    #     """
    #     get best group_excavators
    #     :param current_excavator_id: 当前车辆配对挖机
    #     :param truck_id:
    #     :param load_area_dict: 备选装载区
    #     :return:
    #     """
    #     min_trip_time = 10000000
    #     best_excavator_id = current_excavator_id
    #     try:
    #         for load_area, value in load_area_dict.items():
    #             # 车辆不需要掉头
    #             if load_area in DispatchInfo.load_excavator_dict:
    #                 excavator_id = DispatchInfo.load_excavator_dict[load_area]
    #             else:
    #                 continue
    #             if value[-1] == 1 and excavator_id in self.group.group_excavators:
    #                 traveling_time = 60 * (value[0] / 1000) / empty_speed
    #
    #                 self.logger.info(f'load_area {load_area}')
    #                 self.logger.info(f'traveling_time {traveling_time}')
    #
    #                 now = float(
    #                     (datetime.now() - self.group.pre_sch.start_time) / timedelta(hours=0, minutes=1, seconds=0))
    #
    #                 reach_time = now + traveling_time
    #
    #                 self.logger.info(f'reach_time {reach_time}')
    #
    #                 trip_time = max(reach_time,
    #                                 self.group.pre_sch.get_excavator_avl_time(truck_id=truck_id)[excavator_id]) - now
    #
    #                 self.logger.info(f'trip_time {trip_time}')
    #
    #                 if min_trip_time > trip_time:
    #                     best_excavator_id = excavator_id
    #                     min_trip_time = trip_time
    #
    #     except Exception as es:
    #         self.logger.error("寻找最佳装载区异常")
    #         self.logger.error(f'exception {es}')
    #         self.logger.error(f'in line {es.__traceback__.tb_lineno}')
    #     return best_excavator_id
    #
    # def get_congestion_lanes(self):
    #     # 存在车辆拥堵的路段
    #     truck_locate_dict = self.group.truck.get_truck_locate_dict()
    #     congestion_lane_list = []
    #     for key, value in truck_locate_dict.items():
    #         if self.group.truck.truck_current_state[key] == 2:
    #             congestion_lane_list.append(value)
    #
    #     print("congestion_lane_list")
    #     print(list(set(congestion_lane_list)))
    #     return list(set(congestion_lane_list))


class ReDispatcher:
    """
    redispatch controller
    """
    def __init__(self, group: Group):
        self.logger = get_logger("zxt.ReDispatcher")
        self.group = group

    def redispatch_to_excavator(self, truck_id: str, truck_dispatch: Mapping[str, List[str]], truck_locate: str):
        """
        redispatch truck to excavators.
        :param truck_id:
        :param truck_dispatch:
        :param truck_locate:
        :return:
        """
        # 当前绑定卸载区
        # if truck_trip[0] == -1:
        next_unload_area_id = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id,
                                                                           isdeleted=0).first().unload_area_id
        # else:
        #     dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[0]]
        #     next_unload_area_id = get_value("dump_uuid_to_unload_area_uuid_dict")[dump_id]
        # 当前绑定装载区
        # if truck_trip[-1] == -1:
        item = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id, isdeleted=0).first()
        current_excavator_id = item.exactor_id
        current_load_area_id = item.load_area_id
        self.logger.info(f'truck_id {truck_id}')
        self.logger.info(f'current_load_area_id {current_load_area_id}')
        # else:
        #     current_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[-1]]
        #     current_load_area_id = DispatchInfo.excavator_load_dict[current_excavator_id]
        load_area_dict, load_area_lane_dict = self.group.topo.get_load_target_node_real(truck_locate,
                                                                                  current_load_area_id, True)
        # TODO：topo 放到 group 里面不太合理

        self.logger.info("所有可达装载区")
        self.logger.info(load_area_dict)
        # 获取拥堵路段
        congestion_lane_dict = self.get_congestion_lanes()
        # 获取当前交叉口下一路段集合
        next_lane_list = get_cross_next_lanes(truck_locate)
        # 交叉口下一路段可达的装载区
        next_lane_load_area_dict = get_lane_reach_load_areas(load_area_lane_dict, next_lane_list)
        # 排除下一个路段阻塞的装载区
        delete_congestion_load_area(congestion_lane_dict, load_area_dict,
                                    next_lane_load_area_dict)
        self.logger.info("剔除堵塞装载区")
        self.logger.info(load_area_dict)
        # 获取最佳挖机
        best_excavator_id = self.get_best_excavator(current_excavator_id, truck_id, load_area_dict)
        next_excavator_id = best_excavator_id
        truck_dispatch[truck_id] = [next_excavator_id, next_unload_area_id]
        self.logger.info(f'二次调度结果 {truck_id}')
        self.logger.info(truck_dispatch[truck_id])

    def redispatch_to_dump(self, truck_id: str, truck_dispatch: Mapping[str, List[str]], truck_locate: str, truck_trip: List[int]):
        """
        redispatch truck to dumps.
        :param truck_id:
        :param truck_dispatch:
        :param truck_locate:
        :param truck_trip:
        :return:
        """
        # 当前绑定装载区
        if truck_trip[0] == -1:
            next_excavator_id = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id,
                                                                             isdeleted=0).first().exactor_id
        else:
            next_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[0]]
        # 当前绑定卸载区
        if truck_trip[-1] == -1:
            item = session_mysql.query(EquipmentPair).filter_by(truck_id=truck_id, isdeleted=0).first()
            current_dump_id = item.dump_id
            current_unload_area_id = item.unload_area_id
        else:
            current_dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[-1]]
            current_unload_area_id = DispatchInfo.dump_unload_area_dict[current_dump_id]
        unload_area_dict, unload_area_lane_dict = self.group.topo.get_unload_target_node_real(truck_locate,
                                                                                              current_unload_area_id,
                                                                                              True)
        # 获取拥堵路段
        congestion_lane_dict = self.get_congestion_lanes()
        # 获取当前交叉口下一路段集合
        next_lane_list = get_cross_next_lanes(truck_locate)
        # 交叉口下一路段可达的装载区
        next_lane_load_area_dict = get_lane_reach_load_areas(unload_area_lane_dict,
                                                             next_lane_list)
        # 排除下一个路段阻塞的装载区
        delete_congestion_load_area(congestion_lane_dict, unload_area_dict,
                                    next_lane_load_area_dict)
        min_trip_time = 10000000
        best_dump_id = current_dump_id
        for unload_area, value in unload_area_dict.items():
            # 车辆不需要掉头
            if value[-1] == 1 and unload_area in DispatchInfo.unload_area_dump_dict:
                dump_id = DispatchInfo.unload_area_dump_dict[unload_area]

                traveling_time = value[0] / heavy_speed

                now = float(
                    (datetime.now() - self.group.pre_sch.start_time) / timedelta(hours=0, minutes=1,
                                                                                 seconds=0))
                reach_time = now + traveling_time

                trip_time = max(reach_time,
                                self.group.pre_sch.get_dump_avl_time()[dump_id]) - now

                if min_trip_time > trip_time:
                    best_dump_id = dump_id
        next_unload_area_id = DispatchInfo.dump_unload_area_dict[best_dump_id]
        truck_dispatch[truck_id] = [next_excavator_id, next_unload_area_id]
        # res = redispatch_request(truck_id, next_excavator_id, next_unload_area_id)
        # self.logger.info(res)
        self.logger.info(f'二次调度结果 {truck_id}')
        self.logger.info(truck_dispatch[truck_id])

    def get_best_excavator(self, current_excavator_id: str, truck_id: str, load_area_dict):
        """
        get best group_excavators
        :param current_excavator_id: 当前车辆配对挖机
        :param truck_id:
        :param load_area_dict: 备选装载区
        :return:
        """
        min_trip_time = 10000000
        best_excavator_id = current_excavator_id
        try:
            for load_area, value in load_area_dict.items():
                # 车辆不需要掉头
                if load_area in DispatchInfo.load_excavator_dict:
                    excavator_id = DispatchInfo.load_excavator_dict[load_area]
                else:
                    continue
                if value[-1] == 1 and excavator_id in self.group.group_excavators:
                    traveling_time = 60 * (value[0] / 1000) / empty_speed

                    self.logger.info(f'load_area {load_area}')
                    self.logger.info(f'traveling_time {traveling_time}')

                    now = float(
                        (datetime.now() - self.group.pre_sch.start_time) / timedelta(hours=0, minutes=1, seconds=0))

                    reach_time = now + traveling_time

                    self.logger.info(f'reach_time {reach_time}')

                    trip_time = max(reach_time, self.group.pre_sch.get_excavator_avl_time(truck_id=truck_id)[excavator_id]) - now

                    self.logger.info(f'trip_time {trip_time}')

                    if min_trip_time > trip_time:
                        best_excavator_id = excavator_id
                        min_trip_time = trip_time

        except Exception as es:
            self.logger.error("寻找最佳装载区异常")
            self.logger.error(f'exception {es}')
            self.logger.error(f'in line {es.__traceback__.tb_lineno}')
        return best_excavator_id

    def get_congestion_lanes(self):
        # 存在车辆拥堵的路段
        truck_locate_dict = self.group.truck.get_truck_locate_dict()
        congestion_lane_list = []
        for key, value in truck_locate_dict.items():
            if self.group.truck.truck_current_state[key] == 2:
                congestion_lane_list.append(value)

        print("congestion_lane_list")
        print(list(set(congestion_lane_list)))
        return list(set(congestion_lane_list))