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

from data.dispatchInfo import *
from bidict import bidict
from alg.algorithm import AlgorithmBase,DistributionRatio
import numpy as np
# from settings import get_logger
from para_config import get_value

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

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

    try:

        truck_disp = {}
        for item in session_mysql.query(DispatchSetting).all():
            if item.truck_id not in truck_disp:
                record = {"truckId": item.truck_id}
                record["dispatchId"] = item.id
                record["exactorId"] = item.exactor_id
                record["dumpId"] = item.dump_id
                record["loadAreaId"] = item.load_area_id
                record["unloadAreaId"] = item.unload_area_id
                record["groupId"] = item.group_id
                record["isdeleted"] = False
                record["isTemp"] = False
                record["haulFlag"] = -1
                record["groupName"] = DispatchInfo.group_name[item.group_id]

                logger.info("调度结果")
                logger.info(record)
                redis5.set(item.truck_id, str(json.dumps(record)))

    except Exception as es:
        logger.error(es)
        session_postgre.rollback()
        session_mysql.rollback()


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

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

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

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


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


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

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

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

    """

    def __init__(self, truck_id, 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 Group:
    """
    class for group instance.
    """
    def __init__(self, group_id, truck, pre_sch, excavator_info, dump_info):
        """ Generate a group obj.
        :param group_id: (uuid) group_id
        """
        self.logger = get_logger("zxt.Group")
        # basic info.
        self.group_id = group_id
        self.group_mode = 1
        self.truck = truck
        self.pre_sch = pre_sch
        self.excavator_info = excavator_info
        self.dump_info = dump_info

        # group devices
        self.excavator = {}    # excavator_id -> unload_area_id
        self.unload_area = {}    # unload_area_id -> load_area_id
        self.dump = {}
        self.truck_set = set()    # truck_id


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

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

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

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

        # truck info.
        self.truck_info_list = {}

        # material control
        self.group_walk_available = None

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

    def update_group_device(self):
        """
        update group devices.
        :return:
        """
        # update group devices
        # DispatchInfo.update_device_group_structure()
        self.excavator = {}
        self.unload_area = {}
        self.dump = {}
        self.truck_set = set()
        self.excavator = DispatchInfo.get_excavator(self.group_id)
        self.unload_area = DispatchInfo.get_unload_area(self.group_id)
        self.dump = DispatchInfo.get_dump(self.group_id)
        self.truck_set = DispatchInfo.get_truck_set(self.group_id)

        self.logger.info(f'group excavator {self.excavator}')
        self.logger.info(f'group dump {self.dump}')


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

    def update_group_device_map(self):
        """
        update group device map.
        :return:
        """
        # group_excavator_dict = {group_1: {excavator_1: load_area_1}, group_2: {excavator_2: load_area_2}}

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


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

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

        # try:

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

        for dump_id in self.dump:
            for excavator_id in self.excavator:
                try:
                    # load_area_id = DispatchInfo.excavator_load_dict[excavator_id]
                    # unload_area_id = DispatchInfo.dump_unload_area_dict[dump_id]
                    excavator_index = self.excavator_uuid_index_dict[excavator_id]
                    dump_index = self.dump_uuid_index_dict[dump_id]
                    # load_area_index = get_value("load_area_uuid_to_index_dict")[load_area_id]
                    # unload_area_index = get_value("unload_area_uuid_to_index_dict")[unload_area_id]
                    # 两设备处理物料不同, 相关路网不可通行

                    # self.logger.info(excavator_id)
                    # self.logger.info(self.excavator_info.excavator_material)
                    self.logger.info(self.excavator_info.excavator_material[excavator_id])

                    # self.logger.info(dump_id)
                    # self.logger.info(self.dump_info.dump_material)
                    self.logger.info(self.dump_info.dump_material[dump_id])

                    if self.excavator_info.excavator_material[excavator_id] not in self.dump_info.dump_material[dump_id]:
                        self.group_walk_available[excavator_index][dump_index] = 10
                except Exception as es:
                    self.logger.error(es)
                    self.logger.error(f'物料控制-挖机 {excavator_id} - 卸点 {dump_id} 异常')
        self.logger.info("group_walk_available")
        self.logger.info(self.group_walk_available)
        # except Exception as es:
        #     self.logger.info(es)
        #     self.logger.info("error-12")

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

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

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

    def get_device_exclude(self, truck_id):
        """
        get device exclude modify vector.
        :param truck_id: request truck id
        :return:
        """
        pass

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

    def group_dispatch(self, solver: object):
        """
        Receive a alg obj. and output dispatch plan for trucks in this group.
        :param solver:
        :return:
            dispatch plan: Dict({truck_id: match_id})
        """
        truck_dispatch = {}
        self.truck_info_list = {}

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

        for i in list(self.truck_set):
            try:
                truck_trip = self.truck.get_truck_current_trip()[self.truck.truck_uuid_to_index_dict[i]]
                truck_task = self.truck.get_truck_current_task()[i]
                truck_info = CurrentTruck(i, self.group_id, truck_trip, truck_task)
                self.truck_info_list[i] = truck_info
                # 全智能模式
                if self.group_mode == 1:
                    if truck_task in [-2, 3, 4, 5]:
                        try:
                            if i in self.truck.truck_excavator_bind:
                                next_excavator_id = self.truck.truck_excavator_bind[i]
                            else:
                                next_excavator_value = s.solve(truck_info)
                                # next_excavator_value = s.solve(i, truck_task, truck_trip)
                                # min_index = next_excavator_list.index(min(next_excavator_list))
                                min_index = np.argmin(next_excavator_value)
                                next_excavator_id = self.excavator_uuid_index_dict.inverse[min_index]

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

                        except Exception as es:
                            self.logger.error("重载车辆全智能模式-计算异常")
                            self.logger.error(es)

                    if truck_task in [0, 1, 2]:
                        try:
                            next_excavator_id = get_value("excavator_index_to_uuid_dict")[truck_trip[-1]]
                            next_unload_area_value = s.solve(truck_info)
                            self.logger.info(f'车辆 {i}')
                            self.logger.info(f'group distance {self.to_unload_area_distance}')
                            self.logger.info(f'walk available {self.group_walk_available}')

                            self.logger.info(self.unload_area_uuid_index_dict)
                            self.logger.info(self.excavator_uuid_index_dict)

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

                                self.logger.info(next_unload_area_value)
                                min_index = np.argmin(next_unload_area_value)
                                next_unload_area_id = self.unload_area_uuid_index_dict.inverse[min_index]
                                # next_excavator_id = self.excavator_uuid_index_dict.inverse[truck_trip[-1]]

                            truck_dispatch[i] = [next_excavator_id, next_unload_area_id]

                        except Exception as es:
                            self.logger.error("空载车辆全智能模式-计算异常")
                            self.logger.error(es)
                            truck_dispatch[i] = [None, None]
                # 空车智能模式
                elif self.group_mode == 2:
                    if truck_task in [-2, 3, 4, 5]:
                        try:

                            if i in self.truck.truck_excavator_bind:
                                next_excavator_id = self.truck.truck_excavator_bind[i]
                            else:
                                next_excavator_value = s.solve(truck_info)
                                # min_index = next_excavator_list.index(min(next_excavator_list))
                                min_index = np.argmin(next_excavator_value)
                                next_excavator_id = self.excavator_uuid_index_dict.inverse[min_index]
                            if truck_task == -2:
                                next_unload_area_id = "Park"
                            else:
                                # next_unload_area_id = self.unload_area_uuid_index_dict.inverse[truck_trip[-1]]
                                dump_id = get_value("dump_index_to_uuid_dict")[truck_trip[-1]]
                                next_unload_area_id = get_value("dump_uuid_to_unload_area_uuid_dict")[dump_id]
                            truck_dispatch[i] = [next_excavator_id, next_unload_area_id]

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

                # 分流配比模式
                elif self.group_mode == 4:

                    next_exactor_id = DispatchInfo.get_truck_exactor(i)  # 获取该卡车对应的exactor_id

                    self.logger.info(f"分流配比模式，对应的卡车：{i}")

                    self.logger.info(f"矿卡对应的铲车：{next_exactor_id}")

                    next_unload_area_id = None

                    if truck_task == -2:

                        next_unload_area_id = "Park"

                    # 空载模式下，计算下一次卸载区的位置，按照分流配比的模式进行计算

                    elif truck_task in [0, 1, 2]:

                        try:

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

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

                        except Exception as es:

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

                            self.logger.error(es)

                    # 重载模式下，按照固定派车进行计算

                    elif truck_task in [3, 4, 5]:

                        try:

                            next_unload_area_id = DispatchInfo.get_truck_match(i)[1]

                        except Exception as es:

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

                            self.logger.error(es)

                    truck_dispatch[i] = [next_exactor_id, next_unload_area_id]

            except Exception as es:
                self.logger.error(f'车辆 {i} 调度异常')
                self.logger.error(es)

        # return dispatch plan
        return truck_dispatch
