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

from data.para_config import *
from equipment import DumpInfo
from data.dispatchInfo import DispatchInfo
# from core.group import Group
from util import CoreException


class AlgorithmBase:
    """
    Base class for dispatch alg.
    """

    def __init__(self):
        pass

    def solve(self, *args, **kwargs):
        """
        Dispatching calculation for subgroups.
        """
        raise NotImplementedError


class Congestion(AlgorithmBase):
    """
    class for congestion alg.
    """

    def __init__(self, group, truck, pre_sch):
        super().__init__()
        self.group = group
        self.truck = truck
        self.pre_sch = pre_sch
        self.logger = get_logger("zxt.algorithm.congestion")

    def solve(self, truck_id):
        """
        Receive a truck_id and calculate the associated route congestion.
        :param truck_id: (uuid)
        :return: congestion_value
        """

        try:

            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()[truck_id]

            group_dynamic_excavator_num = len(self.group.group_dumps)

            group_dynamic_dump_num = len(self.group.group_excavators)

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

        if task == -2:
            ################################################ 矿卡启动 ###############################################
            try:
                self.logger.info("矿卡状态：矿卡启动或故障恢复")
                self.logger.info(f"涉及挖机：{list(DeviceMap.excavator_uuid_to_index_dict.keys())}")
            except Exception as es:
                self.logger.error(f"矿卡{truck_id}状态不匹配")
                self.logger.error(es)

            try:
                transport_value = self.group.park_to_excavator_congestion

            except Exception as es:
                transport_value = np.zeros(group_dynamic_excavator_num)
                self.logger.info(es)

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

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

            try:
                # 车辆所在挖机 uuid
                excavator_id = DeviceMap.excavator_index_to_uuid_dict[int(trip[1])]

                # 挖机所在分组序号 index
                excavator_group_index = self.group.excavator_uuid_index_dict[excavator_id]

                transport_value = self.group.to_dump_congestion[excavator_group_index, :]
            except Exception as es:
                self.logger.error("矿卡空载调度异常")
                self.logger.error(es)
                transport_value = np.zeros(group_dynamic_dump_num)

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

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

            try:
                # 车辆所在卸点
                dump_id = DeviceMap.dump_index_to_uuid_dict[int(trip[1])]

                # 卸点对应分组编号
                dump_group_index = self.group.dump_uuid_index_dict[dump_id]

                transport_value = self.group.to_excavator_congestion[dump_group_index, :]
            except Exception as es:
                self.logger.error("矿卡重载调度异常")
                self.logger.error(es)
                transport_value = np.zeros(group_dynamic_excavator_num)

        return transport_value


class ExpectedTime(AlgorithmBase):
    """
    class for expected traveling time alg.
    """

    def __init__(self, group, truck, pre_sch):
        super().__init__()

        self.group = group
        self.truck = truck
        self.pre_sch = pre_sch
        self.logger = get_logger("zxt.algorithm.expected")

    def solve(self, truck_info):
        """
        Receive a truck_id and calculate the associated route travelling time.
        :param truck_info: (current truck obj)
        :return: travel_time_value
        """

        # try:

        truck_id = truck_info.get_truck_id()

        trip = truck_info.get_trip()

        task = truck_info.get_task()

        truck_avl_time = self.pre_sch.get_truck_avl_time(truck_id=truck_id)

        group_dynamic_excavator_num = len(self.group.group_excavators)

        group_dynamic_unload_area_num = len(self.group.group_unload_areas)

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

        self.logger.info("-----------------------------------Alg. Module-----------------------------------")

        if task == -2:
            ################################################ 矿卡启动 ###############################################

            # try:
            self.logger.info(f'矿卡 {truck_id}')
            self.logger.info("矿卡状态：矿卡启动或故障恢复")
            self.logger.info(f"涉及挖机：{self.group.group_excavators}")
            # self.logger.info(f"涉及挖机：{list(DeviceMap.excavator_uuid_to_index_dict.keys())}")
            # except Exception as es:
            #     self.logger.error(f"矿卡{truck_id}状态不匹配")
            #     self.logger.error(es)

            try:
                # 全局挖机可用时间 dict
                excavator_val_time_dict = self.pre_sch.get_excavator_avl_time()

                # 局部挖机可用时间 list
                group_excavator_avl_time_ls = np.full(len(self.group.group_excavators), 0.0)

                self.logger.info("self.group.excavator_uuid_index_dict")
                self.logger.info(self.group.excavator_uuid_index_dict)

                for excavator_id, excavator_index in self.group.excavator_uuid_index_dict.items():
                    group_excavator_avl_time_ls[excavator_index] = excavator_val_time_dict[excavator_id]

                # 车辆驶往各目的地时间
                truck_reach_time_ls = 60 * self.group.park_to_excavator_distance[0, :] / 1000 / \
                                      self.truck.empty_speed[truck_id] + truck_avl_time

                # 计算车辆得到服务时间
                truck_service_time = np.maximum(truck_reach_time_ls, group_excavator_avl_time_ls)

                # 计算行程时间
                transport_value = truck_service_time - truck_avl_time

                self.logger.info("expected_travelling_schedule-truck_reach_time")
                self.logger.info(truck_reach_time_ls)

                self.logger.info("expected_travelling_schedule-excavator_val_time")
                self.logger.info(group_excavator_avl_time_ls)

                self.logger.info("expected_travelling_schedule-truck_service_time")
                self.logger.info(truck_service_time)

                self.logger.info("expected_travelling_schedule-truck_avl_time")
                self.logger.info(truck_avl_time)

                self.logger.info("expected_travelling_schedule-transport_value")
                self.logger.info(transport_value)

            except Exception as es:
                self.logger.error("矿卡启动调度异常")
                self.logger.error(es)
                transport_value = np.zeros(group_dynamic_excavator_num)

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

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

            try:
                # get group_dumps available time (List) for all
                dump_val_time_dict = self.pre_sch.get_dump_avl_time()

                # get group_dumps available time (List) within the group
                group_dump_val_time_ls = np.full(len(self.group.group_unload_areas), 0.0)

                self.logger.info("self.group.dump_uuid_index_dict")
                self.logger.info(self.group.dump_uuid_index_dict)

                for dump_id, dump_index in self.group.dump_uuid_index_dict.items():
                    group_dump_val_time_ls[dump_index] = dump_val_time_dict[dump_id]

                # get truck located group_excavators id (uuid) from truck trip
                if int(trip[1]) < 0 or int(trip[1]) >= len(DeviceMap.excavator_index_to_uuid_dict):
                    raise CoreException(105, f'车辆 {truck_id} 行程驶往 {int(trip[1])} 号挖机越界')
                excavator_id = DeviceMap.excavator_index_to_uuid_dict[int(trip[1])]

                # get group_excavators index (int) within group from group's excavator_group_index
                if excavator_id not in self.group.excavator_uuid_index_dict:
                    raise CoreException(106, f'挖机 {excavator_id} 不存在于分组 {self.group.name} excavator_uuid_index_dict')
                excavator_group_index = self.group.excavator_uuid_index_dict[excavator_id]

                # get travelling time (List) to each group_dumps
                truck_reach_time_ls = 60 * self.group.to_unload_area_distance[excavator_group_index, :] \
                                      / 1000 / self.truck.heavy_speed[truck_id] + truck_avl_time

                # get truck servicing time (List)
                if len(truck_reach_time_ls) != len(group_dump_val_time_ls):
                    raise CoreException(104, f'车辆抵达各卸点时间 {truck_reach_time_ls} 与 各卸点可用时间 {group_dump_val_time_ls} 不一致')
                truck_service_time = np.maximum(truck_reach_time_ls, group_dump_val_time_ls)

                # get trip time
                transport_value = truck_service_time - truck_avl_time

                self.logger.info("expected_travelling_schedule-truck_reach_time")
                self.logger.info(truck_reach_time_ls)

                self.logger.info("expected_travelling_schedule-dump_val_time")
                self.logger.info(group_dump_val_time_ls)

                self.logger.info("expected_travelling_schedule-truck_service_time")
                self.logger.info(truck_service_time)

                self.logger.info("expected_travelling_schedule-truck_avl_time")
                self.logger.info(truck_avl_time)

                self.logger.info("expected_travelling_schedule-transport_value")
                self.logger.info(transport_value)

            except Exception as es:
                self.logger.error("矿卡空载调度异常")
                self.logger.error(es)
                transport_value = np.zeros(group_dynamic_unload_area_num)

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

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

            try:
                # get group_excavators available time (List) for all
                excavator_val_time = self.pre_sch.get_excavator_avl_time()

                # get group_excavators available time (List) within the group
                group_excavator_avl_time_ls = np.full(len(self.group.group_excavators), 0.0)

                self.logger.info("self.group.excavator_uuid_index_dict")
                self.logger.info(self.group.excavator_uuid_index_dict)

                for excavator_id, excavator_index in self.group.excavator_uuid_index_dict.items():
                    group_excavator_avl_time_ls[excavator_index] = excavator_val_time[excavator_id]

                # get truck located group_dumps id (uuid) from truck trip
                dump_id = DeviceMap.dump_index_to_uuid_dict[int(trip[1])]

                # get unload area id (uuid) from DispatchInfo dump_id to group_unload_areas mapping
                unload_area_id = DispatchInfo.dump_unload_area_dict[dump_id]

                # get unload area id (uuid) from DispatchInfo dump_id to unload
                unload_area_group_index = self.group.unload_area_uuid_index_dict[unload_area_id]

                # 车辆驶往各目的地时间
                truck_reach_time_ls = 60 * self.group.to_excavator_distance[unload_area_group_index, :] \
                                      / 1000 / self.truck.empty_speed[truck_id] + truck_avl_time

                # 计算车辆得到服务时间
                truck_service_time = np.maximum(truck_reach_time_ls, group_excavator_avl_time_ls)

                # 计算行程时间
                transport_value = truck_service_time - truck_avl_time

                self.logger.info("expected_travelling_schedule-truck_reach_time")
                self.logger.info(truck_reach_time_ls)

                self.logger.info("expected_travelling_schedule-excavator_val_time")
                self.logger.info(group_excavator_avl_time_ls)

                self.logger.info("expected_travelling_schedule-truck_service_time")
                self.logger.info(truck_service_time)

                self.logger.info("expected_travelling_schedule-truck_avl_time")
                self.logger.info(truck_avl_time)

                self.logger.info("expected_travelling_schedule-transport_value")
                self.logger.info(transport_value)

            except Exception as es:
                self.logger.error("矿卡重载调度异常")
                self.logger.error(es)
                self.logger.error(es.__traceback__.tb_lineno)
                transport_value = np.zeros(group_dynamic_unload_area_num)

        return transport_value


class DistributionRatio(object):
    """
    @date：2022/7/19 8:49
    @author：maqc
    @para：
    truck_id：分流配比模式下，对应的卡车id
    excavator_id:该卡车对应的电铲id
    @return：{truck_id:unload_area_id}
    @desc：计算分流配比模式下，卡车与卸载区的对应关系
    """

    # TODO:
    # 统一与预期等待时间的接口

    def __init__(self, exactor_id, truck):
        self.truck = truck
        self.exactor_id = exactor_id
        self.exactor_truck_num = DispatchInfo.get_exactor_truck_nums(exactor_id)  # 获取该电铲下的卡车数量
        self.exactor_unload_match = DispatchInfo.get_exactor_unload_match(exactor_id)  # 获取电铲对应的所有卸载区id
        self.unload_ratio = {}
        self.logger = get_logger("zxt.DistributionRatio")

    def get_unload_ratio(self):
        """
        @date：2022/7/19 15:21
        @author：maqc
        @desc：初始化卸载区的比值
        """
        try:
            ratio = 0
            for value in self.exactor_unload_match:
                ratio += DispatchInfo.unload_rate_dict[value]
            for item in self.exactor_unload_match:
                self.unload_ratio[item] = DispatchInfo.unload_rate_dict[item] / ratio

        except Exception as es:
            self.logger.error("初始化卸载区的比值异常")
            self.logger.error(es)

    def get_unload_truck_total_num(self, exactor_id):
        """
        @date：2022/7/19 16:12
        @author：maqc
        @desc：计算正在驶往全部卸载区的卡车数量
        """

        unload_area_truck_num = 0

        for item in session_mysql.query(EquipmentPair).filter_by(exactor_id=exactor_id, isdeleted=0).all():
            try:
                truck_id = item.truck_id
                task = self.truck.get_truck_current_task()[truck_id]
                if task in [3, 4]:
                    unload_area_truck_num = unload_area_truck_num + 1
            except Exception as es:
                self.logger.error("获取驶往卸载点全部车辆数量异常")
                self.logger.error(es)

        # item = session_mysql.query(EquipmentPair).filter_by(unload_area_id=unload_area_id, isdeleted=0).all()
        return unload_area_truck_num

    def get_unload_truck_num(self, unload_area_id):
        """
        @date：2022/7/19 16:12
        @author：maqc
        @desc：计算正在派往当前卸载区的卡车数量
        """

        unload_area_truck_num = 0

        for item in session_mysql.query(EquipmentPair).filter_by(unload_area_id=unload_area_id, isdeleted=0).all():
            try:
                truck_id = item.truck_id
                task = self.truck.get_truck_current_task()[truck_id]
                if task in [3, 4]:
                    unload_area_truck_num = unload_area_truck_num + 1
            except Exception as es:
                self.logger.error(f'获取驶往卸载点 {item.unload_area_id} 车辆异常')
                self.logger.error(es)

        # item = session_mysql.query(EquipmentPair).filter_by(unload_area_id=unload_area_id, isdeleted=0).all()
        return unload_area_truck_num

    def get_next_unload_id(self):
        """
        @date：2022/7/19 15:39
        @author：maqc
        @desc：计算卡车与下一个卸载区的对应关系
        """

        next_unload_id = None

        try:

            self.logger.info("卸载设置分流配比")
            self.logger.info(self.unload_ratio)
            # 无论当前卡车是否在备停区，下一个卸载区选择比值最大值对应的区域
            for dump_id in self.unload_ratio.keys():
                self.unload_ratio[dump_id] -= ((self.get_unload_truck_num(dump_id) + 0.001) /
                                               (self.get_unload_truck_total_num(self.exactor_id) + 0.001))

                self.logger.info(dump_id)
                self.logger.info(
                    f'unload_truck_total_num {self.get_unload_truck_total_num(self.exactor_id)}')
                self.logger.info(f'unload_truck_num {self.get_unload_truck_num(dump_id)}')

            self.logger.info("卸载实时分流配比")
            self.logger.info(self.unload_ratio)

            temp = list(self.unload_ratio.values())
            next_unload_id = list(self.unload_ratio.keys())[temp.index(max(temp))]

            self.logger.info(self.unload_ratio.keys())
            self.logger.info(temp)
            self.logger.info(f'下一个卸载区对应的id {next_unload_id}')

        except Exception as es:
            self.logger.error("卡车与下一个卸载区的配对关系异常")
            self.logger.error(es)

        # try:
        #     temp = list(self.unload_ratio.values())
        #     next_unload_id = list(self.unload_ratio.keys())[temp.index(max(temp))]
        #     # 更新卸载区对应的比例值
        #     self.logger.info(f"下一个卸载区对应的id：{next_unload_id}")
        #     self.unload_ratio[next_unload_id] -= self.get_unload_truck_num(
        #         next_unload_id) / self.exactor_truck_num
        #     self.logger.info(f"当前卸载区对应的比值：{self.unload_ratio[next_unload_id]}")
        # except Exception as es:
        #     self.logger.error("卡车与下一个卸载区的配对关系异常")
        #     self.logger.error(es)
        return next_unload_id

    def ratio_main(self):
        """
        @date：2022/7/19 9:10
        @author：maqc
        @desc：分流配比模式类函数主入口
        """
        try:
            self.get_unload_ratio()
            self.logger.info("卸载区分流比例")
            self.logger.info(self.unload_ratio)
            next_unload_area_id = self.get_next_unload_id()
            self.logger.info(f'当前卡车对应的卸载区：{next_unload_area_id}')
        except Exception as es:
            self.logger.error("分流配比主函数异常")
            self.logger.error(es)
        return next_unload_area_id
