#!E:\Pycharm Projects\Waytous
# -*- coding: utf-8 -*-
# @Time : 2022/8/29 15:26
# @Author : Opfer
# @Site :
# @File : gothrough_digging.py    
# @Software: PyCharm

from flask import request
from flask.json import jsonify
from data.para_config import *
from equipment.truck import TruckInfo
from equipment.excavator import ExcavatorInfo
from equipment.dump import DumpInfo
from data.dispatchInfo import DispatchInfo
from app import app
import uuid


@app.route("/redispatch", methods=["POST"])
def redispatch_request():
    # 获取报文数据
    data_json = request.get_json()

    # 车辆id
    request_truck_id = data_json.get("truck_id")

    request_truck_id = '309705a0-5ddf-4559-b6c4-ee17a57677ad'

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

    # 初始化日志
    set_log()
    # 获取日志器
    logger = get_logger("zxt.Request")

    # 更新周期参数
    logger.info("#####################################请求调度更新开始#####################################")

    try:

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

        # 清空数据库缓存
        session_postgre.commit()
        session_postgre.flush()
    except Exception as es:
        logger.error("数据库访问异常")
        logger.error(es)
        return jsonify(msg="未知异常, 请联系管理员", code=501)

    try:

        # 全局参数更新
        global_period_para_update()
        # get_global_para_from_cache(cache)

    except Exception as es:
        logger.error("全局参数更新异常")
        logger.error(es)
        session_mysql.rollback()
        session_postgre.rollback()
        return jsonify(msg="未知异常, 请联系管理员", code=502)

    try:
        # 更新调度信息
        DispatchInfo.reset()

        DispatchInfo.update_device_group_structure()

        group_id = DispatchInfo.truck_group_dict[request_truck_id]

        DispatchInfo.update_route_distance()

        DispatchInfo.update_group_mode()

        DispatchInfo.update_group_name()
    except Exception as es:
        logger.error("调度信息更新异常")
        logger.error(es)
        session_mysql.rollback()
        session_postgre.rollback()
        return jsonify(msg="未知异常, 请联系管理员", code=503)

    logger.info("Dispatchinfo，更新后信息")
    logger.info("group_set")
    logger.info(DispatchInfo.group_set)
    logger.info("group_excavator_dict")
    logger.info(DispatchInfo.group_excavator_dict)
    logger.info("group_unload_area_dict")
    logger.info(DispatchInfo.group_unload_area_dict)
    logger.info("group_truck_dict")
    logger.info(DispatchInfo.group_truck_dict)
    logger.info("group_mode")
    logger.info(DispatchInfo.group_mode)
    logger.info("load_distance")
    logger.info(DispatchInfo.load_distance)
    logger.info("unload_distance")
    logger.info(DispatchInfo.unload_distance)

    try:

        # # 实例化设备对象
        dump = DumpInfo()
        excavator = ExcavatorInfo()
        truck = TruckInfo(dump, excavator)

        # 设备信息更新
        dump.dump_para_period_update()
        excavator.excavator_para_period_update()
        truck.truck_para_period_update(dump, excavator)
        truck.state_period_update()

    except Exception as es:
        logger.error("对象实例化异常")
        logger.error(es)
        session_mysql.rollback()
        session_postgre.rollback()
        return jsonify(msg="未知异常, 请联系管理员", code=504)

    try:
        # 读取挖机集合
        excavators_id = DispatchInfo.get_excavator(group_id)

        # 读取车辆位置信息
        truck_locates_dict = get_trucks_locate()

        logger.info("truck_locates_dict")
        logger.info(truck_locates_dict)

        # 装载区区分
        # closer_area_id, further_area_id = area_analysis(excavators_id)

        closer_area_id = 'f1d5f017-5134-65a0-f657-89eed06eacc1'
        further_area_id = 'ef89f721-5134-3ef1-91e2-95b4e5004675'

        closer_excavator_id, further_excavator_id = DispatchInfo.load_excavator_dict[closer_area_id], \
            DispatchInfo.load_excavator_dict[further_area_id]

        logger.info("近端装载区")
        logger.info(closer_area_id)
        logger.info("远端装载区")
        logger.info(further_area_id)

        # 读取挖机状态
        closer_excavator_state, further_excavator_state = get_excavator_state(closer_excavator_id), \
                                                          get_excavator_state(further_excavator_id)

        # 装载区入场点
        closer_entrance_node_id = session_postgre.query(DiggingWorkArea).filter_by(Id=closer_area_id).first().EntranceNodeId

        further_entrance_node_id = session_postgre.query(DiggingWorkArea).filter_by(
            Id=further_area_id).first().EntranceNodeId

        logger.info("近端装载区入场点")
        logger.info(closer_entrance_node_id)
        logger.info("远端装载区入场点")
        logger.info(further_entrance_node_id)

    except Exception as es:
        logger.error("读取装载区及车辆信息异常")
        logger.error(es)
        return jsonify(msg="未知异常, 请联系管理员", code=505)

    # 目的地
    target_excavator = None

    # try:

    try:

        request_truck_lane_id = truck_locates_dict[request_truck_id]

        logger.info("request_truck_lane_id:")
        logger.info(request_truck_lane_id)
        logger.info(truck_locates_dict)

    except Exception as es:
        logger.error("车辆位置信息不可用")
        logger.error(es)
        return jsonify(msg="车辆位置信息不可用, 请联系管理员", code=505)

    # 车辆位置判断
    if not truck_pass_first_area(request_truck_id, request_truck_lane_id, closer_entrance_node_id, further_entrance_node_id):
        logger.info("车辆已经过近端装载区")
        target_excavator = DispatchInfo.load_excavator_dict[further_area_id]
        # truck_dispatch_to_redis(request_truck_id, group_id, DispatchInfo.load_excavator_dict[further_area_id])
    else:
        # 近端挖机空闲
        if closer_excavator_state == 1:
            logger.info("近端挖机空闲, 调度车辆前往")
            target_excavator = DispatchInfo.load_excavator_dict[closer_area_id]
            # truck_dispatch_to_redis(request_truck_id, group_id, DispatchInfo.load_excavator_dict[closer_area_id])
        # 远端挖机空闲
        elif further_excavator_state == 1:
            logger.info("远端挖机空闲, 调度车辆前往")
            target_excavator = DispatchInfo.load_excavator_dict[further_area_id]
            # truck_dispatch_to_redis(request_truck_id, group_id, DispatchInfo.load_excavator_dict[further_area_id])
        # 两挖机均不空闲
        else:
            logger.info("              ")
            logger.info(truck.excavator_hold_truck_list)
            logger.info(list(excavators_id)[0])
            logger.info(list(excavators_id)[1])
            logger.info(truck.excavator_hold_truck_list[list(excavators_id)[0]])
            logger.info(truck.excavator_hold_truck_list[list(excavators_id)[1]])

            # 统计驶往两装载区的车辆
            arrival_truck_set = truck.excavator_hold_truck_list[list(excavators_id)[0]]\
                + truck.excavator_hold_truck_list[list(excavators_id)[1]]

            # 统计车辆抵达时间
            arrival_truck_reach_time = [truck.cur_truck_reach_excavator[truck.truck_uuid_to_index_dict[truck_id]] for \
                                        truck_id in arrival_truck_set]

            # arrival_truck_set = ['309705a0-5ddf-4559-b6c4-ee17a57677ad', '899705a0-5ddf-4559-b6c4-ee17a57677ad']
            #
            # arrival_truck_reach_time = [8.04, 6.05]

            logger.info("arrival_truck_reach_time")
            logger.info(arrival_truck_reach_time)

            logger.info("arrival_truck_set")
            logger.info(arrival_truck_set)

            arrival_truck_list = list(zip(np.array(arrival_truck_set), np.array(arrival_truck_reach_time)))

            arrival_truck_list = sorted(arrival_truck_list, key=lambda item: item[1])

            logger.info("arrival_truck_list")
            logger.info(arrival_truck_list)

            logger.info("arrival_truck_list")
            logger.info(arrival_truck_list)

            lane_set = get_lanes_between_entrances(closer_entrance_node_id, further_entrance_node_id)

            # 统计不同状态车辆数量
            goto_closer_area_num = 0
            goto_further_area_num = 0

            for truck_id, reach_time in arrival_truck_list:
                truck_lane_id = truck_locates_dict[truck_id]
                # # 车辆已经经过近端装载区
                if truck_lane_id in lane_set:
                    goto_closer_area_num += 1
                # # 车辆未经过近端装载区
                else:
                    goto_further_area_num += 1

            goto_further_area_num -= 1

            logger.info("goto_further_area_num-goto_closer_area_num")
            logger.info(goto_further_area_num)
            logger.info(goto_closer_area_num)

            goto_closer_area_num = 1

            # 在远处排队等待的车辆更少
            if goto_closer_area_num > goto_further_area_num:
                logger.info("远端挖机排队时间短, 调度车辆前往")
                target_excavator = DispatchInfo.load_excavator_dict[further_area_id]
                # truck_dispatch_to_redis(request_truck_id, group_id, DispatchInfo.load_excavator_dict[further_area_id])
            else:
                logger.info("近端挖机排队时间短, 调度车辆前往")
                target_excavator = DispatchInfo.load_excavator_dict[closer_area_id]
                # truck_dispatch_to_redis(request_truck_id, group_id, DispatchInfo.load_excavator_dict[closer_area_id])

    truck_dispatch_to_redis(request_truck_id, group_id, target_excavator)

    # except Exception as es:
    #     logger.error(" ")
    #     logger.error(es)
    #     session_mysql.rollback()
    #     session_postgre.rollback()
    #     return jsonify(msg="未知异常, 请联系管理员", code=504)

    logger.info("#####################################请求调度更新结束#####################################")

    # 调度结束时间
    rtd_end_time = datetime.now()

    print(f'调度时耗 {rtd_end_time - rtd_start_time}')

    return jsonify(msg="success", code=0)


def area_analysis(load_area_uuid):
    """
    Analysis which area is closer.
    :param load_area_uuid:
    :return: closer_area_uuid, further_area_uuid
    """

    try:

        excavator_uuid_to_load_area_uuid_dict = get_value("excavator_uuid_to_load_area_uuid_dict")

        load_area_uuid = list(load_area_uuid)

        load_area_uuid[0] = excavator_uuid_to_load_area_uuid_dict[load_area_uuid[0]]
        load_area_uuid[1] = excavator_uuid_to_load_area_uuid_dict[load_area_uuid[1]]

        distance_a = session_postgre.query(WalkTimePark)\
            .filter_by(load_area_id=load_area_uuid[0]).first().park_load_distance

        distance_b = session_postgre.query(WalkTimePark)\
            .filter_by(load_area_id=load_area_uuid[1]).first().park_load_distance

        if distance_a > distance_b:
            return load_area_uuid[1], load_area_uuid[0]
        else:
            return load_area_uuid[0], load_area_uuid[1]

    except Exception as es:
        logger.error("装载区距离分析异常")
        logger.error(es)
        return load_area_uuid[0], load_area_uuid[1]


def truck_pass_first_area(truck_id, lane_id, closer_entrance_node_id, further_entrance_node_id):
    """
    Truck has gone through the first area.
    :param truck_id:
    :param lane_id:
    :param closer_entrance_node_id:
    :param further_entrance_node_id:
    :return:
    """

    # try:

    def backtracking(root_node):

        from collections import deque
        que = deque([root_node])

        while que:
            size = len(que)
            for _ in range(size):
                cur_node = que.popleft()
                if cur_node is None: continue
                logger.info(cur_node)
                if cur_node == closer_entrance_node_id:
                    logger.info("closer_entrance_node")
                    return 0
                if cur_node == further_entrance_node_id:
                    logger.info("further_entrance_node")
                    return 1
                for item in session_postgre.query(Lane).filter_by(StartNodeId=cur_node).all():
                    if item:
                        que.append(item.EndNodeId)

    node_id = session_postgre.query(Lane).filter_by(Id=lane_id).first().EndNodeId

    if backtracking(root_node=node_id) == 0:
        return True
    else:
        return False

    # except Exception as es:
    #     logger.error("车辆行驶位置判断异常")
    #     logger.error(es)
    #     return True


def get_trucks_locate():
    """
    get trucks locates.
    :return: truck_locate_dict
    """

    try:
        truck_name_to_uuid_dict = get_value("truck_name_to_uuid_dict")

        truck_locate_dict = {}
        device_name_set = redis2.keys()
        for item in device_name_set:
            item = item.decode(encoding='utf-8')
            key_value_dict = redis2.hgetall(item)
            device_type = key_value_dict[str_to_byte('type')]
            is_online = key_value_dict[str_to_byte('online')]
            key_set = key_value_dict.keys()
            if (device_type == str_to_byte("1")) \
                    and (str_to_byte('online') in key_set) \
                    and (bytes.decode(is_online) in ["true" or "True"]) \
                    and (str_to_byte('laneId') in key_set):
                truck_locate = key_value_dict[str_to_byte('laneId')]
                if eval(truck_locate) is not '':
                    truck_locate_dict[truck_name_to_uuid_dict[item]] = eval(truck_locate)

        return truck_locate_dict

    except Exception as es:
        logger.error("车辆所在路段读取异常")
        logger.error(es)
        return {}


def truck_dispatch_to_redis(truck_id, group_id, excavator_id):
    """
    write truck dispatch to redis.
    :param truck_id:
    :param group_id:
    :param excavator_id:
    :return:
    """
    # 查询车辆相关派车计划
    record = {}
    try:
        # dump_id = DispatchInfo.unload_area_dump_dict[unload_area_id]
        item = (session_mysql.query(DispatchSetting).filter_by(exactor_id=excavator_id, group_id=group_id,
                                                               isdeleted=0, ).first())

        if item is None:
            raise Exception("调度计划配置异常")

    except Exception as es:
        item = (session_mysql.query(DispatchSetting).filter_by(group_id=group_id, isdeleted=0, ).first())
        logger.error(es)

    # 其余调度信息写入
    try:
        # record["dispatchId"] = item.id
        record["dispatchId"] = str(uuid.uuid1())
        record["exactorId"] = item.exactor_id
        record["loadAreaId"] = item.load_area_id
        record["dumpId"] = item.dump_id
        record["unloadAreaId"] = item.unload_area_id
        record["groupId"] = group_id
        record["isdeleted"] = False
        record["isTemp"] = False
        record["haulFlag"] = -1
        record["groupName"] = DispatchInfo.group_name[group_id]

        logger.info(f'redis 注入 {record}')
    except Exception as es:
        logger.error("调度结果写入异常-矿卡空载")
        logger.error(es)
    finally:
        redis5.set(truck_id, str(json.dumps(record)))


def get_lanes_between_entrances(closer_node_id, further_node_id):
    """
    get lanes between two entrance nodes.
    :param closer_node_id:
    :param further_node_id:
    :return: lane set
    """
    try:
        max_find_it = 100
        next_node_id = closer_node_id
        lane_set = []
        while max_find_it > 0 and next_node_id != further_node_id:
            item = session_postgre.query(Lane).filter_by(StartNodeId=next_node_id, Type=2).first()
            if item:
                next_lane_id = item.Id
                next_node_id = item.EndNodeId
                lane_set.append(next_lane_id)
                max_find_it -= 1
            max_find_it -= 1

        return lane_set

    except Exception as es:
        logger.error("获取装载区间路段异常")
        logger.error(es)
        return []


def get_excavator_state(excavator_id):
    """
    get excavator state.
    :param excavator_id:
    :return: state
    """
    try:

        device_name = session_mysql.query(Equipment).filter_by(equipment_id=excavator_id, device_type=2).first().device_name

        key_value_dict = redis2.hgetall(device_name)
        is_online = key_value_dict[str_to_byte('online')]
        key_set = key_value_dict.keys()
        state = 100
        if (str_to_byte('online') in key_set) and (bytes.decode(is_online) in ["true" or "True"]):
            state = key_value_dict[str_to_byte('workState')]

        return state

    except Exception as es:
        logger.error("挖机状态读取异常")
        logger.error(es)
        return 0
