#!E:\Pycharm Projects\Waytous
# -*- coding: utf-8 -*-
# @Time : 2021/12/29 11:36
# @Author : Opfer
# @Site :
# @File : dispatcher.py    
# @Software: PyCharm

from traffic_flow.traffic_flow_planner import *
from para_config import *
from group_control.group_control import Group
from path_plan.priority_control import PriorityController
from algorithm import ScheduleAlg


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, group_id, trip, task):
        self._truck_id = truck_id
        self._group_id = group_id
        self._trip = trip
        self._task = task

    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


class RuleHandle:
    """ class for the schedule rule handler.

    Description:
        基于调度规则对调度价值进行修正

    Attribute:
        equipment class: truck, excavator, dump, group

    """

    def __init__(self, dump, excavator, truck, group):
        self._dump = dump
        self._excavator = excavator
        self._truck = truck
        self._group = group
        self._priority_control = PriorityController()
        self._schedule_alg = ScheduleAlg(dump, excavator, truck, group)

    def filter(self, current_truck):
        """
        计算调度规则综合影响调度价值结果
        :param truck_id: 矿卡uuid
        :return:

        """

        # 矿卡uuid
        truck_id = current_truck.get_truck_id()

        # 矿卡序号
        truck_index = self.truck.truck_uuid_to_index_dict[truck_id]

        # 矿卡行程
        trip = current_truck.get_trip()

        # 矿卡任务
        task = current_truck.get_task()

        # 1. 读取路网权重和可通行性
        walk_to_excavator_weight, walk_to_excavator_weight, park_walk_weight \
            = self._priority_control.weighted_walk_calc()
        walk_available = self._priority_control.walk_available_calc()

        # 2. 读取挖机禁止关系
        # excavator_exclude_truck_factor = self._truck.update_truck_excavator_exclude()
        #
        # excavator_exclude_truck_factor[np.where(excavator_exclude_truck_factor > M / 2)] = 1
        #
        # excavator_exclude_truck_factor = 1 - excavator_exclude_truck_factor

        excavator_exclude_modify = self._group.group_excavator_exclude_modify[truck_id]

        # 3. 读取调度价值
        transport_value = self._schedule_alg.truck_schedule(current_truck, "congestion")

        transport_value_norm = np.max(transport_value) / transport_value

        transport_value_norm = transport_value_norm * walk_available

    # def route_modify_factor(self, truck_id):
    #     """
    #     规则及权重因子针对某个矿卡的待的各路线的影响
    #     :param truck_id:
    #     :return:
    #     """
    #     walk_to_excavator_weight, walk_to_excavator_weight, park_walk_weight \
    #         = self._priority_control.weighted_walk_calc()
    #     walk_available = self._priority_control.walk_available_calc()

    def truck_walk_weight(self, current_truck):
        """
        道路权重性针对某个矿卡到的各目的地的影响
        :param current_truck:
        :return:
        """
        walk_to_excavator_weight, walk_to_dump_weight, park_walk_weight \
            = self._priority_control.weighted_walk_calc()

        if current_truck.get_task() == 2:
            return park_walk_weight

        elif current_truck.get_task() in [0, 1, 2]:
            end_excavator = int(current_truck.get_trip[1])
            return walk_to_dump_weight[end_excavator, :]

        elif current_truck.get_trip() in [3, 4, 5]:
            end_dump = int(current_truck.get_trip[1])
            return walk_to_excavator_weight[end_dump, :]

    def truck_walk_available(self, current_truck):
        """
        道路通行性针对某个矿卡到的各目的地的影响
        :param current_truck: 当前请调矿卡
        :return: 矿卡出发地到各目的地道路可通行性
        """
        walk_available = self._priority_control.walk_available_calc()

        if current_truck.get_task() == 2:
            return np.ones(get_value("excavator_num"))

        elif current_truck.get_task() in [0, 1, 2]:
            end_excavator = int(current_truck.get_trip[1])
            return walk_available[:, end_excavator].reshape(1, -1)

        elif current_truck.get_trip() in [3, 4, 5]:
            end_dump = int(current_truck.get_trip[1])
            return walk_available[end_dump, :].reshape(1, -1)


class Dispatcher(WalkManage):
    """ class for the truck dispatch.

    Description:
        计算根据矿卡状态计算最佳派车计划
        将派车计划写入redis

    Attribute:
        equipment class: truck, excavator, dump
        schedule start time
        equipment available time: truck, excavator, dump

    """

    def __init__(self, dump, excavator, truck, predict_schedule):

        # 调度开始时间
        self.start_time = datetime.now()

        # 路径规划对象
        self.path = PathPlanner()

        # 车流对象
        # self.traffic_flow = Traffic_flow(dump, excavator, truck)
        self.traffic_flow = None

        # 分组控制对象
        self.group = Group(dump, excavator, truck, self.traffic_flow)

        # 行程预测对象
        self.pre_sch = predict_schedule

        # 设备对象
        self.dump = dump
        self.excavator = excavator
        self.truck = truck

        # 调度算法
        self._schedule_alg = ScheduleAlg(dump, excavator, truck, self.group, self.pre_sch)

        # 获取日志器
        self.logger = get_logger("zxt.dispatcher")

    def dispatcher_period_update(self):

        global_period_para_update()

        # 更新卸载设备对象
        self.dump.dump_para_period_update()

        # 更新挖机对象
        self.excavator.excavator_para_period_update()

        # 更新矿卡对象
        self.truck.truck_para_period_update(self.dump, self.excavator)

        self.truck.state_period_update()

        # # 更新实时车流
        # self.traffic_flow.update_actual_traffic_flow()

        # 获取路网加权行驶成本
        self.cost_to_excavator, self.cost_to_dump, self.cost_park_to_excavator = self.path.walk_cost_cal()

        # 调度分组更新
        self.group.period_update()

    def truck_schedule(self, truck_id):
        # 规则读取

        rule3 = session_mysql.query(DispatchRule).filter_by(id=3).first().disabled

        rule4 = session_mysql.query(DispatchRule).filter_by(id=4).first().disabled

        # 读取优先级设置
        excavator_priority_coefficient = self.excavator.excavator_priority_coefficient

        excavator_material_priority = self.excavator.excavator_material_priority

        # 矿卡序号
        truck_index = self.truck.truck_uuid_to_index_dict[truck_id]

        # 矿卡行程
        trip = self.truck.get_truck_current_trip()[truck_index]

        # 矿卡任务
        task = self.truck.get_truck_current_task()[self.truck.truck_index_to_uuid_dict[truck_index]]

        # 调度分组
        group_id = self.group.dispatch_truck_group[truck_id]

        # 记录当前调度车辆信息
        current_truck = CurrentTruck(truck_id, group_id, trip, task)

        if truck_id not in self.group.dispatch_truck_group:
            self.logger.error("无该矿卡")
            return -1

        target = 0

        # # 车流计算
        # # 驶往卸载设备的实际车流
        # actual_goto_dump_traffic_flow = self.traffic_flow.actual_goto_dump_traffic_flow
        #
        # # 驶往挖机的实际车流
        # actual_goto_excavator_traffic_flow = self.traffic_flow.actual_goto_excavator_traffic_flow
        #
        # # 计算理想车流
        # opt_goto_dump_traffic_flow, opt_goto_excavator_traffic_flow = traffic_flow_plan(self.truck)

        truck_uuid_to_name_dict = get_value("truck_uuid_to_name_dict")
        self.logger.info(f"========== 调度矿卡 {truck_id} {truck_index} {truck_uuid_to_name_dict[truck_id]} ==========")

        if task == -2:

            ################################################ 矿卡启动 ###############################################

            try:
                self.logger.info(datetime.now())
                self.logger.info("矿卡状态：矿卡启动或故障恢复")
                self.logger.info("矿卡行程：无")
                self.logger.info(f"涉及挖机：{list(self.excavator.excavator_uuid_to_index_dict.keys())}")
                if truck_id in self.truck.truck_material_bind:
                    self.logger.info(f'物料类型 {self.truck.truck_material_bind[truck_id]}')
                self.logger.info(f'挖机设备优先级 {excavator_priority_coefficient}')

            except Exception as es:
                self.logger.error(f"矿卡{truck_id}状态不匹配")
                self.logger.error(es)

            try:
                # 1. 绑定调度
                if truck_id in self.truck.truck_excavator_bind:
                    self.logger.info("绑定调度")
                    target = self.excavator.excavator_uuid_to_index_dict[self.truck.truck_excavator_bind[truck_id]]

                # 2. 正常调度
                elif rule3 and rule4:
                    self.logger.info("正常调度")

                    transport_value = self._schedule_alg.truck_schedule(current_truck, "expected")

                    self.logger.info(f'行驶成本-含拥堵度的路径长度 {transport_value}')

                    excavator_exclude_modify = self.group.group_excavator_exclude_modify[truck_id]

                    self.logger.info(f'挖机设备禁止因子 {excavator_exclude_modify}')

                    target = np.argmin(
                        transport_value
                        + excavator_exclude_modify)

                    self.logger.info("truck_schedule-target")
                    self.logger.info(target)

                    target = self.excavator.excavator_uuid_to_index_dict[
                        self.group.group_excavator_index_to_uuid_dict[group_id][target]]

                    self.logger.info("truck_schedule-dict")
                    self.logger.info(self.excavator.excavator_uuid_to_index_dict)
                    self.logger.info(self.group.group_excavator_index_to_uuid_dict)

                    self.logger.info("truck_schedule-target")
                    self.logger.info(target)

                    self.logger.info(f'excavator_uuid_to_index_dict {self.excavator.excavator_uuid_to_index_dict}')

                # 3. 启用饱和度调度
                else:
                    self.logger.info("饱和度调度")

                    # saturation_value = self._schedule_alg.saturation_schedule(current_truck)

                    saturation_value = self._schedule_alg.truck_schedule(current_truck, "saturation")

                    self.logger.info(f'驶往挖机饱和度价值 {saturation_value}')

                    target = np.argmax(saturation_value)

                    target = self.excavator.excavator_uuid_to_index_dict[
                        self.group.group_excavator_index_to_uuid_dict[group_id][target]]

            except Exception as es:
                self.logger.error(f"矿卡{truck_id}调度指令生成异常")
                self.logger.error(es)

            self.logger.info(self.excavator.excavator_uuid_to_index_dict)
            self.logger.info(self.group.group_excavator_uuid_to_index_dict)
            excavator_uuid_to_name_dict = get_value("excavator_uuid_to_name_dict")
            self.logger.info(f"目的地：{excavator_uuid_to_name_dict[self.excavator.excavator_index_to_uuid_dict[target]]}")

        if task in [0, 1, 2]:
            ################################################ 矿卡空载 ###############################################

            try:
                self.logger.info("矿卡状态：矿卡空载")
                self.logger.info(f"涉及卸载设备：{list(self.dump.dump_uuid_to_index_dict.keys())}")
            except Exception as es:
                self.logger.error(f"矿卡{truck_id}状态不匹配")
                self.logger.error(es)

            # 日志记录部分
            try:
                self.logger.info(f'挖机id: {self.excavator.excavator_uuid_to_index_dict}')
                self.logger.info(f'卸点id: {self.dump.dump_uuid_to_index_dict}')
                self.logger.info(f'空载trip {trip}')
                if truck_id in self.truck.truck_material_bind:
                    self.logger.info(f'物料类型 {self.truck.truck_material_bind[truck_id]}')
                self.logger.info(f'驶往卸点的运输成本 {self.cost_to_dump}')
                self.logger.info("卸点物料修正")
                self.logger.info(self.truck.dump_material_bind_modify)

            except Exception as es:
                self.logger.info("矿卡行程信息异常")
                self.logger.info(es)


            try:

                # 1. 绑定调度
                if truck_id in self.truck.truck_dump_bind:
                    self.logger.info("矿卡已绑定卸点")
                    unload_area_uuid_to_index_dict = get_value("unload_area_uuid_to_index_dict")
                    bind_unload_area_id = self.truck.truck_dump_bind[truck_id]
                    for key, value in self.dump.dump_index_to_unload_area_index_dict.items():
                        if value == unload_area_uuid_to_index_dict[bind_unload_area_id]:
                            target = key
                            break

                # 2. 正常调度
                elif rule3 and rule4:
                    # 卸载点处理能力及产量约束	and 电铲采装能力及产量约束 均未启用

                    transport_value = self._schedule_alg.truck_schedule(current_truck, "expected")
                    self.logger.info(f'行驶成本-含拥堵度的路径长度 {transport_value}')

                    dump_material_bind_modify = self.group.group_dump_material_bind_modify[truck_id]
                    self.logger.info(f'卸点物料禁止因子 {dump_material_bind_modify}')

                    target = np.argmin(transport_value.T + dump_material_bind_modify)

                    target = self.dump.dump_uuid_to_index_dict[
                        self.group.group_dump_index_to_uuid_dict[group_id][target]]

                # 3. 饱和度调度
                else:
                    # 卸载点处理能力及产量约束	or 电铲采装能力及产量约束 启用

                    self.logger.info("饱和度调度")

                    # saturation_value = self._schedule_alg.saturation_schedule(current_truck)

                    saturation_value = self._schedule_alg.truck_schedule(current_truck, "saturation")

                    self.logger.info(f'驶往卸点饱和度价值 {saturation_value}')

                    target = np.argmax(saturation_value)

                    target = self.dump.dump_uuid_to_index_dict[
                        self.group.group_dump_index_to_uuid_dict[group_id][target]]

            except Exception as es:
                self.logger.error(f"矿卡{truck_id}调度指令生成异常")
                self.logger.error(es)

            dump_uuid_to_name_dict = get_value("dump_uuid_to_name_dict")
            self.logger.info(f"目的地：{dump_uuid_to_name_dict[self.dump.dump_index_to_uuid_dict[target]]}")

        elif task in [3, 4, 5]:
            ################################################ 矿卡重载 ###############################################

            try:
                self.logger.info("矿卡状态：矿卡重载")
                self.logger.info(f"涉及挖机设备：{list(self.excavator.excavator_uuid_to_index_dict.keys())}")
            except Exception as es:
                self.logger.error(f"矿卡{truck_id}状态不匹配")
                self.logger.error(es)

            try:
                self.logger.info("挖机id:")
                self.logger.info(self.excavator.excavator_uuid_to_index_dict)
                self.logger.info("卸点id:")
                self.logger.info(self.dump.dump_uuid_to_index_dict)
                self.logger.info("重载trip")
                self.logger.info(trip)
                self.logger.info("物料类型")
                if truck_id in self.truck.truck_material_bind:
                    self.logger.info(self.truck.truck_material_bind[truck_id])
                self.logger.info("驶往挖机的运输成本")
                self.logger.info(self.cost_to_excavator)
                self.logger.info("挖机物料修正")
                self.logger.info(self.truck.excavator_material_bind_modify)
                self.logger.info("挖机优先级修正")
                self.logger.info(self.excavator.excavator_priority_coefficient)
            except Exception as es:
                self.logger.info("矿卡行程信息异常")
                self.logger.info(es)

            # 计算目的地
            try:
                # 1. 绑定调度
                if truck_id in self.truck.truck_excavator_bind:
                    target = self.excavator.excavator_uuid_to_index_dict[self.truck.truck_excavator_bind[truck_id]]
                    self.logger.info("矿卡已绑定挖机")
                    self.logger.info("cost_to_excavator")
                    self.logger.info(self.cost_to_excavator)

                # 2 正常调度
                elif rule3 and rule4:
                    self.logger.info("正常调度")

                    transport_value = self._schedule_alg.truck_schedule(current_truck, "expected")
                    self.logger.info(f'行驶成本-含拥堵度的路径长度 {transport_value}')

                    excavator_exclude_modify = self.group.group_excavator_exclude_modify[truck_id]
                    self.logger.info(f'挖机设备禁止因子 {excavator_exclude_modify}')

                    self.logger.info(transport_value)
                    target = np.argmin(
                        transport_value.T
                        + excavator_exclude_modify)

                    target = self.excavator.excavator_uuid_to_index_dict[
                        self.group.group_excavator_index_to_uuid_dict[group_id][target]]

                # 3. 饱和度调度
                else:
                    self.logger.info("饱和度调度")

                    # saturation_value = self._schedule_alg.saturation_schedule(current_truck)

                    saturation_value = self._schedule_alg.truck_schedule(current_truck, "saturation")

                    self.logger.info(f'驶往卸点饱和度价值 {saturation_value}')

                    target = np.argmax(saturation_value)

                    target = self.excavator.excavator_uuid_to_index_dict[
                        self.group.group_excavator_index_to_uuid_dict[group_id][target]]

            except Exception as es:
                self.logger.error(f"矿卡{truck_id}调度指令生成异常")
                self.logger.info(es)

            excavator_uuid_to_name_dict = get_value("excavator_uuid_to_name_dict")
            self.logger.info(f"目的地：{excavator_uuid_to_name_dict[DeviceMap.excavator_index_to_uuid_dict[target]]}")

        self.logger.info("==========================================================")

        print(target)
        # except Exception as es:
        #     self.logger.error("truck_schedule,error")
        #     self.logger.error(es)
        return target

    def schedule_construct(self):

        global truck
        global excavator
        global dump

        # self.reset()

        # try:

        # 读取所需信息
        dynamic_truck_num = get_value("dynamic_truck_num")
        truck_current_trip = self.truck.get_truck_current_trip()
        truck_current_task = self.truck.get_truck_current_task()

        # Seq初始化
        Seq = [[truck_current_trip[i][1], -1] for i in range(dynamic_truck_num)]

        # 获取矿卡最早可用时间
        truck_avl_time = self.pre_sch.get_truck_avl_time()

        # 根据矿卡最早可用时间顺序进行规划
        temp = copy.deepcopy(truck_avl_time) - self.truck.truck_priority

        try:
            # 没有启动的矿卡加上一个很大的值，降低其优先级
            for i in range(dynamic_truck_num):
                task = truck_current_task[self.truck.truck_index_to_uuid_dict[i]]
                if task == -2:
                    temp[i] = temp[i] + M
        except Exception as es:
            self.logger.error("矿卡排序启动异常")
            self.logger.error(es)

        index = np.argsort(temp.reshape(1, -1))
        index = index.flatten()

        # 对于在线矿卡已经赋予新的派车计划，更新其最早可用时间，及相关设备时间参数
        for truck_index in index:
            if len(Seq[truck_index]) > 0:

                # try:
                # 获取矿卡id
                try:
                    truck_id = self.truck.truck_index_to_uuid_dict[truck_index]
                except Exception as es:
                    self.logger.error('error01')
                    self.logger.error(es)

                # 判断矿卡是否禁用
                if truck_id in self.truck.update_truck_disable_list():
                    continue
                try:
                    # 获取矿卡当前任务
                    task = truck_current_task[self.truck.truck_index_to_uuid_dict[truck_index]]
                except Exception as es:
                    self.logger.error('error02')
                    self.logger.error(es)

                try:
                    # 矿卡结束当前派车计划后的目的地
                    end_eq_index = truck_current_trip[truck_index][1]
                except Exception as es:
                    self.logger.error('error03')
                    self.logger.error(es)

                # try:
                # 调用调度函数，得到最优目的地序号
                target_eq_index = self.truck_schedule(self.truck.truck_index_to_uuid_dict[truck_index])

                # except Exception as es:
                #     self.logger.error('error04')
                #     self.logger.error(es)
                #     self.logger.error("truck_index,uuid")
                #     self.logger.error(truck_index)
                #     self.logger.error(self.truck.truck_index_to_uuid_dict[truck_index])

                # self.logger.("target_eq_index")
                # self.logger.error(target_eq_index)
                try:
                    # 写入Seq序列
                    Seq[truck_index][1] = target_eq_index
                except Exception as es:
                    self.logger.error('error05')
                    self.logger.error(es)
                    self.logger.error("target_eq_index")
                    self.logger.error(target_eq_index)
                    self.logger.error("target_eq_index,type")
                    self.logger.error(type(target_eq_index))
                # except Exception as es:
                #     self.logger.error("truck,task,end_eq_index,error")
                #     self.logger.error(es)
                try:
                    group_id = self.group.dispatch_truck_group[truck_id]
                except Exception as es:
                    self.logger.error("非动态调度矿卡")
                    self.logger.error(es)

        for i in range(len(Seq)):
            try:
                try:
                    truck_id = self.truck.truck_index_to_uuid_dict[i]
                    group_id = self.group.dispatch_truck_group[truck_id]

                    record = {"truckId": self.truck.truck_index_to_uuid_dict[i]}
                    task = self.truck.get_truck_current_task()[self.truck.truck_index_to_uuid_dict[i]]
                except Exception as es:
                    self.logger.error("truck_id,group_id,record,task出错")
                    self.logger.error(es)

                if task in empty_task_set:

                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(dump_id=DeviceMap.dump_index_to_uuid_dict[Seq[i][1]],
                                       exactor_id=DeviceMap.excavator_index_to_uuid_dict[Seq[i][0]],
                                       truck_id=truck_id,
                                       group_id=group_id,
                                       isauto=1, isdeleted=0, ).first())

                    record["exactorId"] = item.exactor_id
                    record["dumpId"] = item.dump_id
                    record["loadAreaId"] = item.load_area_id
                    record["unloadAreaId"] = item.unload_area_id
                    record["dispatchId"] = item.id
                    record["isdeleted"] = False
                    record["creator"] = item.creator
                    record["createtime"] = item.createtime.strftime(
                        "%b %d, %Y %I:%M:%S %p")

                    redis5.set(self.truck.truck_index_to_uuid_dict[i], str(json.dumps(record)))
                elif task in heavy_task_set:
                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(exactor_id=DeviceMap.excavator_index_to_uuid_dict[Seq[i][1]],
                                       dump_id=DeviceMap.dump_index_to_uuid_dict[Seq[i][0]],
                                       truck_id=truck_id,
                                       group_id=group_id,
                                       isauto=1, isdeleted=0, ).first())
                    record["exactorId"] = self.excavator.excavator_index_to_uuid_dict[Seq[i][1]]
                    record["dumpId"] = item.dump_id
                    record["loadAreaId"] = item.load_area_id
                    record["unloadAreaId"] = item.unload_area_id
                    record["dispatchId"] = item.id
                    record["isdeleted"] = False
                    record["creator"] = item.creator
                    record["createtime"] = item.createtime.strftime(
                        "%b %d, %Y %I:%M:%S %p")

                    redis5.set(self.truck.truck_index_to_uuid_dict[i], str(json.dumps(record)))
                elif task == -2:
                    try:
                        try:
                            item = (
                                session_mysql.query(Dispatch)
                                    .filter_by(exactor_id=DeviceMap.excavator_index_to_uuid_dict[Seq[i][1]],
                                               truck_id=truck_id,
                                               group_id=group_id,
                                               isauto=1, isdeleted=0).first())

                            print(Seq)
                            print(Seq[i][1])
                            print(DeviceMap.excavator_index_to_uuid_dict[Seq[i][1]])

                            print("item")
                            print(item.id, item.truck_id, item.exactor_id, item.dump_id)
                        except Exception as es:
                            self.logger.error("task-2,error01")

                        try:
                            record["exactorId"] = item.exactor_id
                            record["dumpId"] = item.dump_id
                            record["loadAreaId"] = item.load_area_id
                            record["unloadAreaId"] = item.unload_area_id
                            record["dispatchId"] = item.id
                            record["isdeleted"] = False
                            record["creator"] = item.creator
                            record["createtime"] = item.createtime.strftime(
                                "%b %d, %Y %I:%M:%S %p")

                            redis5.set(self.truck.truck_index_to_uuid_dict[i], str(json.dumps(record)))

                        except Exception as es:
                            self.logger.error("task-2,error02")
                            self.logger.error(es)

                    except Exception as es:
                        self.logger.error("task为-2时error")
                        self.logger.error(es)
                else:
                    pass

            except Exception as es:
                self.logger.error("调度结果写入异常-redis写入异常")
                self.logger.error(f"调度结果:{Seq}")
                self.logger.error(es)

        for i in range(dynamic_truck_num):
            print("dispatch_setting:")
            print(redis5.get(self.truck.truck_index_to_uuid_dict[i]))
        # except Exception as es:
        #     self.logger.error("更新不及时-1")
        #     self.logger.error(es)

        return Seq


class PreSchedule:
    """ class for the prediction of equipments' trip.

    Description:
        Calculate and update the prediction item.
        负责处理所有预测项的计算与更新
        基于矿卡最近一次装卸载时间预测其抵达目的地时间
        根据矿卡请求队列及抵达信息，计算设备最早可用时间

    Attribute:
        equipment class: truck, excavator, dump
        schedule start time
        equipment available time: truck, excavator, dump

    """

    def __init__(self, truck, excavator, dump):
        # 设备对象域
        self.truck = truck
        self.excavator = excavator
        self.dump = dump

        # 调度开始时间
        self.start_time = datetime.now()

        # 真实设备可用时间
        self.truck_reach_dump = np.zeros(self.truck.get_truck_num())
        self.truck_reach_excavator = np.zeros(self.truck.get_truck_num())
        self.excavator_avl_time = np.zeros(self.excavator.get_excavator_num())
        self.dump_avl_time = np.zeros(self.dump.get_dump_num())
        # 卡车完成装载及卸载时间(矿卡可用时间)
        self.truck_avl_time = np.zeros(self.truck.get_truck_num())

        self.logger = get_logger("zxt.pre_schedule")

    def update_truck_reach_time(self):
        """
        更新矿卡预计抵达目的地时间
        :return:
            excavator_avl_ls: (list) 驶往挖机的各矿卡抵达时间
            dump_avl_ls: (list) 驶往卸点的各矿卡抵达时间
        """
        try:
            dynamic_excavator_num = self.excavator.get_excavator_num()
            dumps = self.dump.get_dump_num()
            trucks = self.truck.get_truck_num()

            truck_current_task = self.truck.get_truck_current_task()

            truck_current_trip = self.truck.get_truck_current_trip()

            truck_reach_excavator = self.truck.get_truck_reach_excavator()

            truck_reach_dump = self.truck.get_truck_reach_dump()

            excavator_avl_ls = [[] for _ in range(dynamic_excavator_num)]
            dump_avl_ls = [[] for _ in range(dumps)]
            self.logger.info("update_truck_reach_time-trucks")
            self.logger.info(trucks)
            for i in range(trucks):
                task = truck_current_task[self.truck.truck_index_to_uuid_dict[i]]
                end_area_index = truck_current_trip[i][1]
                self.logger.info("update_truck_reach_time-truck_current_trip")
                self.logger.info(truck_current_trip)
                if task in [0, 1]:
                    reach_time = truck_reach_excavator[i]
                    excavator_avl_ls[end_area_index].append(
                        [reach_time, i, end_area_index]
                    )
                elif task in [3, 4]:
                    reach_time = truck_reach_dump[i]
                    dump_avl_ls[end_area_index].append([reach_time, i, end_area_index])
                elif task == -2:
                    self.truck_avl_time[i] = (
                                                     datetime.now() - self.start_time
                                             ) / timedelta(hours=0, minutes=1, seconds=0)

        except Exception as es:
            self.logger.error("矿卡预计抵达时间计算异常")
            self.logger.error(es)
            return [], []

        return excavator_avl_ls, dump_avl_ls

    def update_excavator_avl_time(self, excavator_avl_ls):
        """
        更新挖机预计可用时间
        :param
            excavator_avl_ls: (list) 驶往挖机的各矿卡抵达时间
        :return:
            excavator_avl_time: (array) 各挖机完成所有已分配矿卡装载的时间
        """

        # 初始化挖机可用时间
        self.excavator_avl_time = np.full(
            get_value("dynamic_excavator_num"),
            (datetime.now() - self.start_time)
            / timedelta(hours=0, minutes=1, seconds=0),
        )

        loading_time = self.excavator.get_loading_time()

        loading_task_time = self.excavator.get_loading_task_time()

        try:

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

            for reach_ls in excavator_avl_ls:
                self.logger.info("update_excavator_avl_time-excavator_avl_ls")
                self.logger.info(excavator_avl_ls)
                if len(reach_ls) != 0:
                    reach_ls = np.array(reach_ls)
                    tmp = reach_ls[np.lexsort(reach_ls[:, ::-1].T)]
                    for i in range(len(tmp)):
                        excavator_index = int(tmp[i][2])
                        self.excavator_avl_time[excavator_index] = (
                                max(tmp[i][0], self.excavator_avl_time[excavator_index])
                                + loading_task_time[excavator_index]
                        )
                        self.truck_avl_time[
                            int(tmp[i][1])
                        ] = self.excavator_avl_time[excavator_index]

                        # # 若挖机可用时间严重偏离，进行修正
                        # if abs(self.excavator_avl_time[excavator_index] - now) > 60:
                        #     self.truck_avl_time[int(tmp[i][1])] = now
                        # if abs(self.excavator_avl_time[excavator_index] - now) > 60:
                        #     self.excavator_avl_time[excavator_index] = now
        except Exception as es:
            self.logger.error("挖机可用时间计算异常")
            self.logger.error(es)

        return self.excavator_avl_time

    def update_dump_avl_time(self, dump_avl_ls):
        """
        更新卸载设备预计可用时间
        :param
            dump_avl_ls: (list) 驶往卸点的各矿卡抵达时间
        :return:
            dump_avl_time: (array) 各卸点完成所有已分配矿卡卸载的时间
        """

        dynamic_dump_num = self.dump.get_dump_num()

        # 初始化卸载设备可用时间
        self.dump_avl_time = np.full(
            dynamic_dump_num,
            (datetime.now() - self.start_time)
            / timedelta(hours=0, minutes=1, seconds=0),
        )

        unloading_time = self.dump.get_unloading_time()

        unloading_task_time = self.dump.get_unloading_task_time()

        try:

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

            for reach_ls in dump_avl_ls:
                if len(reach_ls) != 0:
                    reach_ls = np.array(reach_ls)
                    tmp = reach_ls[np.lexsort(reach_ls[:, ::-1].T)]
                    for i in range(len(tmp)):
                        dump_index = int(tmp[i][2])
                        self.dump_avl_time[dump_index] = (
                                max(tmp[i][0], self.dump_avl_time[dump_index])
                                + unloading_task_time[dump_index]
                        )
                        self.truck_avl_time[
                            int(tmp[i][1])
                        ] = self.dump_avl_time[dump_index]

                        # # 若卸载设备可用时间严重偏离，进行修正
                        # if abs(self.dump_avl_time[dump_index] - now) > 60:
                        #     self.dump_avl_time[dump_index] = now
                        # if abs(self.truck_avl_time[int(tmp[i][1])] - now) > 60:
                        #     self.truck_avl_time[int(tmp[i][1])] = now
        except Exception as es:
            self.logger.error("卸载设备可用时间计算异常")
            self.logger.error(es)

        return self.dump_avl_time

    def _reset(self):
        """
        重置设备可用时间
        :return:
        """
        # 真实设备可用时间
        self.truck_reach_dump = np.zeros(self.truck.get_truck_num())
        self.truck_reach_excavator = np.zeros(self.truck.get_truck_num())
        self.excavator_avl_time = np.zeros(self.excavator.get_excavator_num())
        self.dump_avl_time = np.zeros(self.dump.get_dump_num())
        # 卡车完成装载及卸载时间(矿卡可用时间)
        self.truck_avl_time = np.zeros(self.truck.get_truck_num())

    def get_dump_avl_time(self):
        """
        获取卸载点最早可用时间
        :return:
            dump_avl_time: (array) 各卸点完成所有已分配矿卡卸载的时间
        """
        self._reset()
        excavator_avl_ls, dump_avl_ls = self.update_truck_reach_time()
        dump_avl_time = self.update_dump_avl_time(dump_avl_ls)
        return dump_avl_time

    def get_excavator_avl_time(self, excavator_id=None):
        """
        获取挖机最早可用时间
        :param excavator_id: 挖机编号uuid
        :return:
            excavator_avl_time: (array) 各挖机完成所有已分配矿卡装载的时间
        """
        self._reset()
        excavator_avl_ls, dump_avl_ls = self.update_truck_reach_time()
        if excavator_id is not None:
            return self.update_excavator_avl_time(excavator_avl_ls) \
                [self.excavator.excavator_uuid_to_index_dict[excavator_id]]
        else:
            return self.update_excavator_avl_time(excavator_avl_ls)

    def get_truck_avl_time(self, truck_id=None):
        """
        获取矿卡最早可用时间
        :param truck_id: 矿卡编号uuid
        :return: truck_avl_time: (array) 各矿卡完成当前装载或卸载任务的时间
        """
        self._reset()
        excavator_avl_ls, dump_avl_ls = self.update_truck_reach_time()
        self.update_excavator_avl_time(excavator_avl_ls)
        self.update_dump_avl_time(dump_avl_ls)

        if truck_id is not None:
            return self.truck_avl_time[self.truck.truck_uuid_to_index_dict[truck_id]]
        else:
            return self.truck_avl_time
