#!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 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, request_mode=False):

        # 运行模式
        self.request_mode = request_mode

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

        # 车流对象
        # 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.path = PathPlanner(dump, excavator, dump)

        # 调度结果输出器
        self.submission = DispatchSubmission(dump, excavator, truck, self.group)

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

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

    def dispatcher_period_update(self):
        """
        控制全局调度信息更新
        """
        if not self.request_mode:
            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):
        """
        按照指定规则及算法, 为请调车辆分配派车计划
        :param truck_id:  (uuid) 请调车辆id
        :return:
            target:  (int) 请调车辆目的地id
        """
        # 规则读取

        try:

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

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

        except Exception as es:
            self.logger.error(es)
            self.logger.error("调度规则读取异常")
            session_postgre.rollback()
            session_mysql.rollback()

        try:

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

            excavator_material_priority = self.excavator.excavator_material_priority

        except Exception as es:
            self.logger.error("挖机优先级信息读取异常")
            self.logger.error(es)

        try:

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

        except Exception as es:
            self.logger.error("矿卡基本信息读取异常")
            self.logger.error(es)

        try:

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

        except Exception as es:
            self.logger.error("矿卡行程读取异常")
            self.logger.error(es)

        try:

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

        except Exception as es:
            self.logger.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)

        try:

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

        except Exception as es:
            self.logger.error("调度车辆对象构建异常")
            self.logger.error(es)

        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]:  # 矿卡空载行驶或正在入场
            ################################################ 矿卡空载 ###############################################

            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]:  # 卡车重载行驶或正在入场
            ################################################ 矿卡重载 ###############################################

            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):
        """
        读取调度所需信息, 依次为动态派车车辆请求派车计划, 并写入redis缓存
        :return: None
        """

        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()

        # 获取矿卡最早可用时间
        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:

            try:
                self.truck_request(self.truck.truck_index_to_uuid_dict[truck_index])

            except Exception as es:
                self.logger.error("")
                self.logger.error(es)

        # # submission将调度结果写入redis
        # self.submission.submit_to_redis(Seq)

    def truck_request(self, truck_id):

        truck_index = self.truck.truck_uuid_to_index_dict[truck_id]

        truck_current_trip = self.truck.get_truck_current_trip()

        truck_dispatch_seq = [truck_current_trip[truck_index][1], -1]

        target_eq_index = self.truck_schedule(self.truck.truck_index_to_uuid_dict[truck_index])

        truck_dispatch_seq[1] = target_eq_index

        self.submission.truck_dispatch_to_redis(truck_id, truck_dispatch_seq)



class DispatchSubmission:
    """ class for the submission calculated dispatch.

    Description:
        将调度结果按照指定格式传递到云端机群
    Attribute:

    """
    def __init__(self, dump, excavator, truck, group):
        self.logger = self.logger = get_logger("zxt.submission")
        self.dump = dump
        self.excavator = excavator
        self.truck = truck
        self.group = group

    def submit_to_redis(self, Seq):
        """
        将调度结果输出到redis
        :param Seq: (List[int]) 调度结果列表
        :return: None
        """

        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("调度结果写入异常-读取矿卡信息异常(uuid, group id, task)")
                    self.logger.error(es)

                if task in [0, 1, 2]:  # 卡车空载或在装载区出场前, 可变更卸载目的地
                    try:
                        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,
                                           isdeleted=0, ).first())
                        if item is None:
                            raise Exception("调度计划表与实时监控不匹配")
                    except Exception as es:
                        self.logger.error(es)
                        item = (
                            session_mysql.query(Dispatch)
                                .filter_by(truck_id=truck_id,
                                           # group_id=group_id,
                                           isdeleted=0, ).first())

                    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("调度结果写入异常-矿卡空载")
                elif task in [3, 4, 5]:  # 卡车重载或在卸载区出场前, 可变更装载目的地
                    try:
                        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,
                                           isdeleted=0, ).first())
                        if item is None:
                            raise Exception("调度计划表与实时监控不匹配")
                    except Exception as es:
                        self.logger.error(es)
                        item = (
                            session_mysql.query(Dispatch)
                                .filter_by(truck_id=truck_id,
                                           # group_id=group_id,
                                            isdeleted=0, ).first())
                    try:
                        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)))
                    except Exception as es:
                        self.logger.error("调度结果写入异常-矿卡重载")
                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,
                                                isdeleted=0).first())

                            if item is None:
                                raise Exception("调度计划表与实时监控不匹配")

                            self.logger.info(Seq)
                            self.logger.info(Seq[i][1])
                            self.logger.info(DeviceMap.excavator_index_to_uuid_dict[Seq[i][1]])

                            self.logger.info("item")
                            print(item.id, item.truck_id, item.exactor_id, item.dump_id)
                        except Exception as es:
                            self.logger.error(es)
                            item = (
                                session_mysql.query(Dispatch)
                                    .filter_by(truck_id=truck_id,
                                               # group_id=group_id,
                                                isdeleted=0).first())

                        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("调度结果写入异常-矿卡故障或备停区-redis写入异常")
                            self.logger.error(es)

                    except Exception as es:
                        self.logger.error("调度结果写入异常-矿卡故障或备停区")
                        self.logger.error(es)
                else:
                    pass

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

        for i in range(get_value("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)

    def truck_dispatch_to_redis(self, truck_id, dispatch_seq):
        """
        将truck_id对应矿卡派车计划写入redis
        :param truck_id: (uuid) 矿卡uuid
        :param dispatch_seq: (List[int]) 矿卡派车计划
        :return: None
        """
        try:
            try:
                group_id = self.group.dispatch_truck_group[truck_id]

                record = {"truckId": truck_id}
                task = self.truck.get_truck_current_task()[truck_id]
            except Exception as es:
                self.logger.error("调度结果写入异常-读取矿卡信息异常(uuid, group id, task)")
                self.logger.error(es)

            if task in [0, 1, 2]:  # 卡车空载或在装载区出场前, 可变更卸载目的地
                try:
                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(dump_id=DeviceMap.dump_index_to_uuid_dict[dispatch_seq[1]],
                                       exactor_id=DeviceMap.excavator_index_to_uuid_dict[dispatch_seq[0]],
                                       truck_id=truck_id,
                                       group_id=group_id,
                                        isdeleted=0, ).first())
                    if item is None:
                        raise Exception("调度计划表与实时监控不匹配")
                except Exception as es:
                    self.logger.error(es)
                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(truck_id=truck_id,
                                       # group_id=group_id,
                                        isdeleted=0, ).first())

                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(truck_id, str(json.dumps(record)))
                except Exception as es:
                    self.logger.error("调度结果写入异常-矿卡空载")
            elif task in [3, 4, 5]:  # 卡车重载或在卸载区出场前, 可变更装载目的地
                try:
                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(exactor_id=DeviceMap.excavator_index_to_uuid_dict[dispatch_seq[1]],
                                       dump_id=DeviceMap.dump_index_to_uuid_dict[dispatch_seq[0]],
                                       truck_id=truck_id,
                                       group_id=group_id,
                                       isdeleted=0, ).first())
                    if item is None:
                        raise Exception("调度计划表与实时监控不匹配")
                except Exception as es:
                    self.logger.error(es)
                    item = (
                        session_mysql.query(Dispatch)
                            .filter_by(truck_id=truck_id,
                                       # group_id=group_id,
                                        isdeleted=0, ).first())
                try:
                    record["exactorId"] = self.excavator.excavator_index_to_uuid_dict[dispatch_seq[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(truck_id, str(json.dumps(record)))
                except Exception as es:
                    self.logger.error("调度结果写入异常-矿卡重载")
            elif task == -2:
                try:
                    try:
                        item = (
                            session_mysql.query(Dispatch)
                                .filter_by(exactor_id=DeviceMap.excavator_index_to_uuid_dict[dispatch_seq[1]],
                                           truck_id=truck_id,
                                           group_id=group_id,
                                            isdeleted=0).first())

                        if item is None:
                            raise Exception("调度计划表与实时监控不匹配")

                        self.logger.info(dispatch_seq)
                        self.logger.info(dispatch_seq[1])
                        self.logger.info(DeviceMap.excavator_index_to_uuid_dict[dispatch_seq[1]])

                        self.logger.info("item")
                        print(item.id, item.truck_id, item.exactor_id, item.dump_id)
                    except Exception as es:
                        self.logger.error(es)
                        item = (
                            session_mysql.query(Dispatch)
                                .filter_by(truck_id=truck_id,
                                           # group_id=group_id,
                                            isdeleted=0).first())

                    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(truck_id, str(json.dumps(record)))

                    except Exception as es:
                        self.logger.error("调度结果写入异常-矿卡故障或备停区-redis写入异常")
                        self.logger.error(es)

                except Exception as es:
                    self.logger.error("调度结果写入异常-矿卡故障或备停区")
                    self.logger.error(es)
            else:
                pass

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


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

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

    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.excavator_avl_time_dict = {}
        self.dump_avl_time_dict = {}
        self.truck_avl_time_dict = {}

        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),
        )

        for excavator_id in get_value("dynamic_excavator_set"):
            self.excavator_avl_time_dict[excavator_id] = (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])
                        excavator_id = self.excavator.excavator_index_to_uuid_dict[excavator_index]
                        self.excavator_avl_time[excavator_index] = (
                                max(tmp[i][0], self.excavator_avl_time[excavator_index])
                                + loading_task_time[excavator_index]
                        )
                        self.excavator_avl_time_dict[excavator_id] = self.excavator_avl_time[excavator_index]

                        truck_index = int(tmp[i][1])
                        truck_id = self.truck.truck_index_to_uuid_dict[truck_index]
                        self.truck_avl_time[truck_index] = self.excavator_avl_time[excavator_index]
                        self.truck_avl_time_dict[truck_id] = self.truck_avl_time[truck_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_dict

    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),
        )

        for dump_id in get_value("dynamic_dump_set"):
            self.dump_avl_time_dict[dump_id] = (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])
                        dump_id = self.dump.dump_index_to_uuid_dict[dump_index]
                        self.dump_avl_time[dump_index] = (
                                max(tmp[i][0], self.dump_avl_time[dump_index])
                                + unloading_task_time[dump_index]
                        )
                        self.dump_avl_time_dict[dump_id] = self.dump_avl_time[dump_index]

                        truck_index = int(tmp[i][1])
                        truck_id = self.truck.truck_index_to_uuid_dict[truck_index]
                        self.truck_avl_time[truck_index] = self.dump_avl_time[dump_index]
                        self.truck_avl_time_dict[truck_id] = self.truck_avl_time[truck_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_dict

    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())

        self.excavator_avl_time_dict = {}
        self.dump_avl_time_dict = {}
        self.truck_avl_time_dict = {}

    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
