import numpy as np
from app.services.tjnetwork import (
    ChangeSet,
    close_project,
    copy_project,
    delete_project,
    get_pattern,
    get_patterns,
    get_pump,
    get_reservoir,
    get_status,
    get_tank,
    get_time,
    have_project,
    is_project_open,
    open_project,
    read_all,
    run_project,
    set_pattern,
    set_status,
    set_tank,
    set_time,
)
# from get_real_status import *
from datetime import datetime,timedelta
from math import modf
import json
import pytz
import requests
import time
import app.services.project_info as project_info

url_path = 'http://10.101.15.16:9000/loong'  # 内网
# url_path = 'http://183.64.62.100:9057/loong'  # 外网
url_real = url_path + '/api/mpoints/realValue'
url_hist = url_path + '/api/curves/data'

PATTERN_TIME_STEP=15.0
DN_900_ID='2498'
DN_500_ID='3854'
DN_1000_ID='3853'
H_RESSURE='2510'
L_PRESURE='2514'
H_TANK='4780'
L_TANK='4854'

H_REGION_1='SA_ZBBDJSCP000002'
H_REGION_2='' #to do
L_REGION_1='SA_ZBBDTJSC000001'
L_REGION_2='SA_R00003'

# reservoir basic height
RESERVOIR_BASIC_HEIGHT = float(250.35)

# regions
regions = ['hp', 'lp']
regions_demand_patterns = {'hp': ['DN900', 'DN500'], 'lp': ['DN1000']}  # 出厂水量近似表示用水量
regions_patterns = {'hp': ['ChuanYiJiXiao', 'BeiQuanHuaYuan', 'ZhuangYuanFuDi', 'JingNingJiaYuan',
                           '308', 'JiaYinYuan', 'XinChengGuoJi', 'YiJingBeiChen', 'ZhongYangXinDu',
                           'XinHaiJiaYuan', 'DongFengJie', 'DingYaXinYu', 'ZiYunTai', 'XieMaGuangChang',
                           'YongJinFu', 'BianDianZhan', 'BeiNanDaDao', 'TianShengLiJie', 'XueYuanXiaoQu',
                           'YunHuaLu', 'GaoJiaQiao', 'LuZuoFuLuXiaDuan', 'TianRunCheng', 'CaoJiaBa',
                           'PuLingChang', 'QiLongXiaoQu', 'TuanXiao',
                           'TuanShanBaoZhongShiHua', 'XieMa', 'BeiWenQuanJiuHaoErQi', 'LaiYinHuSiQi',
                           'DN500', 'DN900'],
                    'lp': ['PanXiMingDu', 'WanKeJinYuHuaFuGaoCeng', 'KeJiXiao',
                           'LuGouQiao', 'LongJiangHuaYuan', 'LaoQiZhongDui', 'ShiYanCun', 'TianQiDaSha',
                           'TianShengPaiChuSuo', 'TianShengShangPin', 'JiaoTang', 'RenMinHuaYuan',
                           'TaiJiBinJiangYiQi', 'TianQiHuaYuan', 'TaiJiBinJiangErQi', '122Zhong',
                           'WanKeJinYuHuaFuYangFang', 'ChengBeiCaiShiKou', 'WenXingShe', 'YueLiangTianBBGJCZ',
                           'YueLiangTian', 'YueLiangTian200', 'ChengTaoChang', 'HuoCheZhan', 'LiangKu', 'QunXingLu',
                           'JiuYuanErTongYiYuan', 'TangDouHua', 'TaiJiBinJiangErQi(SanJi)',
                           'ZhangDouHua', 'JinYunXiaoQuDN400',
                           'DN1000']}

# nodes
monitor_single_patterns = ['ChuanYiJiXiao', 'BeiQuanHuaYuan', 'ZhuangYuanFuDi', 'JingNingJiaYuan',
                           '308', 'JiaYinYuan', 'XinChengGuoJi', 'YiJingBeiChen', 'ZhongYangXinDu',
                           'XinHaiJiaYuan', 'DongFengJie', 'DingYaXinYu', 'ZiYunTai', 'XieMaGuangChang',
                           'YongJinFu', 'PanXiMingDu', 'WanKeJinYuHuaFuGaoCeng', 'KeJiXiao',
                           'LuGouQiao', 'LongJiangHuaYuan', 'LaoQiZhongDui', 'ShiYanCun', 'TianQiDaSha',
                           'TianShengPaiChuSuo', 'TianShengShangPin', 'JiaoTang', 'RenMinHuaYuan',
                           'TaiJiBinJiangYiQi', 'TianQiHuaYuan', 'TaiJiBinJiangErQi', '122Zhong',
                           'WanKeJinYuHuaFuYangFang']
monitor_single_patterns_id = {'ChuanYiJiXiao': '7338', 'BeiQuanHuaYuan': '7315', 'ZhuangYuanFuDi': '7316',
                              'JingNingJiaYuan': '7528', '308': '8272', 'JiaYinYuan': '7304',
                              'XinChengGuoJi': '7325', 'YiJingBeiChen': '7328', 'ZhongYangXinDu': '7329',
                              'XinHaiJiaYuan': '9138', 'DongFengJie': '7302', 'DingYaXinYu': '7331',
                              'ZiYunTai': '7420,9059', 'XieMaGuangChang': '7326', 'YongJinFu': '9059',
                              'PanXiMingDu': '7320', 'WanKeJinYuHuaFuGaoCeng': '7419',
                              'KeJiXiao': '7305', 'LuGouQiao': '7306', 'LongJiangHuaYuan': '7318',
                              'LaoQiZhongDui': '9075', 'ShiYanCun': '7309', 'TianQiDaSha': '7323',
                              'TianShengPaiChuSuo': '7335', 'TianShengShangPin': '7324', 'JiaoTang': '7332',
                              'RenMinHuaYuan': '7322', 'TaiJiBinJiangYiQi': '7333', 'TianQiHuaYuan': '8235',
                              'TaiJiBinJiangErQi': '7334', '122Zhong': '7314', 'WanKeJinYuHuaFuYangFang': '7418'}

monitor_unity_patterns = ['BianDianZhan', 'BeiNanDaDao', 'TianShengLiJie', 'XueYuanXiaoQu',
                          'YunHuaLu', 'GaoJiaQiao', 'LuZuoFuLuXiaDuan', 'TianRunCheng',
                          'CaoJiaBa', 'PuLingChang', 'QiLongXiaoQu', 'TuanXiao',
                          'ChengBeiCaiShiKou', 'WenXingShe', 'YueLiangTianBBGJCZ',
                          'YueLiangTian', 'YueLiangTian200',
                          'ChengTaoChang', 'HuoCheZhan', 'LiangKu', 'QunXingLu',
                          'TuanShanBaoZhongShiHua', 'XieMa', 'BeiWenQuanJiuHaoErQi', 'LaiYinHuSiQi',
                          'JiuYuanErTongYiYuan', 'TangDouHua', 'TaiJiBinJiangErQi(SanJi)',
                          'ZhangDouHua', 'JinYunXiaoQuDN400',
                          'DN500', 'DN900', 'DN1000']
monitor_unity_patterns_id = {'BianDianZhan': '7339', 'BeiNanDaDao': '7319', 'TianShengLiJie': '8242',
                             'XueYuanXiaoQu': '7327', 'YunHuaLu': '7312', 'GaoJiaQiao': '7340',
                             'LuZuoFuLuXiaDuan': '7343', 'TianRunCheng': '7310', 'CaoJiaBa': '7300',
                             'PuLingChang': '7307', 'QiLongXiaoQu': '7321', 'TuanXiao': '8963',
                             'ChengBeiCaiShiKou': '7330', 'WenXingShe': '7311',
                             'YueLiangTianBBGJCZ': '7313', 'YueLiangTian': '7313', 'YueLiangTian200': '7313',
                             'ChengTaoChang': '7301', 'HuoCheZhan': '7303',
                             'LiangKu': '7296', 'QunXingLu': '7308',
                             'DN500': '3854', 'DN900': '2498', 'DN1000': '3853'}
monitor_patterns = monitor_single_patterns + monitor_unity_patterns
monitor_patterns_id = {**monitor_single_patterns_id, **monitor_unity_patterns_id}
# pumps
pumps_name = ['1#', '2#', '3#', '4#', '5#', '6#', '7#']
pumps = ['PU00000', 'PU00001', 'PU00002', 'PU00003', 'PU00004', 'PU00005', 'PU00006']
variable_frequency_pumps = ['PU00004', 'PU00005', 'PU00006']
pumps_id = {'PU00000': '2747', 'PU00001': '2776', 'PU00002': '2730', 'PU00003': '2787',
            'PU00004': '2500', 'PU00005': '2502', 'PU00006': '2504'}
# reservoirs
reservoirs = ['ZBBDJSCP000002', 'R00003']
reservoirs_id = {'ZBBDJSCP000002': '2497', 'R00003': '2571'}
# tanks
tanks = ['ZBBDTJSC000002', 'ZBBDTJSC000001']
tanks_id = {'ZBBDTJSC000002': '4780', 'ZBBDTJSC000001': '9774'}


class DataLoader:
    """数据加载器"""
    def __init__(self, project_name, start_time: datetime, end_time: datetime,
                 pumps_control: dict = None, tank_initial_level_control: dict = None,
                 region_demand_control: dict = None, downloading_prohibition: bool = False):
        self.project_name = project_name  # 数据库名
        self.current_time = self.round_time(datetime.now(pytz.timezone('Asia/Shanghai')), 1)  # 圆整至整分钟
        self.current_round_time = self.round_time(self.current_time, int(PATTERN_TIME_STEP))
        self.updating_data_flag = True \
            if self.current_round_time == self.round_time(start_time, int(PATTERN_TIME_STEP)) \
            else False  # 判断是否从当前时刻开始模拟(是否更新最新监测数据)
        self.downloading_prohibition = downloading_prohibition  # 是否禁止下载数据(默认False: 允许下载)
        self.updating_data_flag = False if self.downloading_prohibition else self.updating_data_flag
        self.pattern_start_index = get_pattern_index(
            self.round_time(start_time, int(PATTERN_TIME_STEP)).strftime("%Y-%m-%d %H:%M:%S"))  # pattern起始索引
        self.pattern_end_index = get_pattern_index(
            self.round_time(end_time, int(PATTERN_TIME_STEP)).strftime("%Y-%m-%d %H:%M:%S"))  # pattern结束索引
        self.pattern_index_list = list(range(self.pattern_start_index, self.pattern_end_index + 1))  # pattern索引列表
        self.download_id = self.get_download_id()  # 数据下载接口id '7338,7315,7316,...'
        self.current_time_download_data = dict(
            zip(self.download_id.split(','),
                [np.nan]*len(list(self.download_id.split(','))))
        )  # {id(str): value(float)}
        self.current_time_download_data_flag = dict(
            zip(self.download_id.split(','),
                [False]*len(list(self.download_id.split(','))))
        )  # 下载数据是否具备实时性, {id(str): flag(bool)}
        self.old_flow_data = self.init_dict_of_list(dict(
            zip(monitor_patterns,
                [[np.nan]] * (len(monitor_patterns)))
        ))  # {pattern_name(str): flow(float)}
        self.old_pattern_factor = self.init_dict_of_list(dict(
            zip(monitor_patterns,
                [[np.nan]] * (len(monitor_patterns)))
        ))  # {pattern_name(str): [pattern_factor(float)]}
        self.new_flow_data = self.init_dict_of_list(dict(
            zip(monitor_patterns,
                [[np.nan]] * (len(monitor_patterns)))
        ))  # {pattern_name(str): flow(float)}
        self.new_pattern_factor = self.init_dict_of_list(dict(
            zip(monitor_patterns,
                [[np.nan]] * (len(monitor_patterns)))
        ))  # {pattern_name(str): [pattern_factor(float)]}
        self.reservoir_data = dict(zip(reservoirs, [np.nan]*len(reservoirs)))  # {reservoir_name(str): level(float)}
        self.tank_data = dict(zip(tanks, [np.nan] * len(tanks)))  # {tank_name(str): level(float)}
        self.pump_data = self.init_dict_of_list(
            dict(zip(pumps, [[np.nan]]*len(pumps))))  # {pump_name(str): [frequency(float)]}
        self.pump_control = pumps_control  # {pump_name(str): [frequency(float)]}
        self.tank_initial_level_control = tank_initial_level_control  # {tank_name(str): level(float)}
        self.region_demand_current = dict(zip(regions, [0]*len(regions)))  # {region_name(str): total_demand(float)}
        self.region_demand_control = region_demand_control  # {region_name(str): total_demand(float)}
        self.region_demand_control_factor = dict(
            zip(regions, [1]*len(regions)))  # 区域流量控制系数(用于调整用水量), {region_name(str): factor(float)}

    def load_data(self):
        """生成数据集"""
        self.download_data()  # 下载实时数据
        self.get_old_pattern_and_flow()  # 读取历史记录pattern信息
        self.cal_demand_convert_factor()  # 计算用水量转换系数(设定用水量时)
        self.set_new_flow()  # 设置'更新'流量
        self.set_new_pattern_factor()  # 设置'更新'pattern factors
        self.set_reservoirs()  # 设置清水池
        self.set_tanks()  # 设置调节池
        self.set_pumps()  # 设置水泵
        return self.pattern_start_index

    def download_data(self):
        """下载数据"""
        if self.updating_data_flag is True:
            print('{} -- Start downloading data.'.format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
            data_wait_flag = True
            while data_wait_flag:
                try:
                    newest_data_time = self.download_real_data(self.download_id)  # 获取实时数据
                except Exception as e:
                    print('{}\nWaiting for real data.'.format(e))
                    time.sleep(1)
                else:
                    print('{} -- Downloading data ok. Newest timestamp: {}.'.format(
                        datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                        newest_data_time.strftime('%Y-%m-%d %H:%M:%S')))
                    data_wait_flag = False

    def cal_current_region_demand(self):
        """计算区域当前用水量"""
        if self.updating_data_flag is True:
            for region in self.region_demand_current.keys():
                total_demand = 0
                for pipe in regions_demand_patterns[region]:
                    total_demand += self.current_time_download_data[monitor_patterns_id[pipe]]  # 出厂流量
                self.region_demand_current[region] = total_demand

    def cal_history_region_demand(self, pattern_index_list):
        """计算区域历史用水量(对应记录的pattern)"""
        old_demand = {}
        for region in regions:
            total_demand = 0
            for pipe_pattern_name in regions_demand_patterns[region]:
                old_flows, old_patterns = self.get_history_pattern_info(self.project_name, pipe_pattern_name)
                for idx in pattern_index_list:
                    total_demand += old_flows[idx] / 4  # 15分钟水量
            old_demand[region] = total_demand
        return old_demand

    def cal_demand_convert_factor(self):
        """计算用水量转换系数(设定用水量时)"""
        self.cal_current_region_demand()  # 计算区域当前时刻用水量
        old_demand_moment = self.cal_history_region_demand([self.pattern_start_index])  # 计算区域目标时刻总用水量
        old_demand_period = self.cal_history_region_demand(self.pattern_index_list)  # 计算区域目标时段总用水量
        for region in regions:
            self.region_demand_control_factor[region] \
                = (self.region_demand_current[region] / 4) / old_demand_moment[region] \
                if self.updating_data_flag is True else 1
            self.region_demand_control_factor[region] = self.region_demand_control[region] / old_demand_period[region] \
                if (self.region_demand_control is not None) and (region in self.region_demand_control.keys()) \
                else self.region_demand_control_factor[region]

    def get_old_pattern_and_flow(self):
        """获取所有pattern的选定时段的历史记录的pattern和flow"""
        for idx in monitor_patterns:  # 遍历patterns
            old_flows, old_patterns = self.get_history_pattern_info(self.project_name, idx)
            for pattern_idx in self.pattern_index_list:
                old_flow_data = old_flows[pattern_idx]
                old_pattern_factor = old_patterns[pattern_idx]
                if pattern_idx == self.pattern_start_index:  # 起始时刻
                    self.old_flow_data[idx][0] = old_flow_data
                    self.old_pattern_factor[idx][0] = old_pattern_factor
                else:
                    self.old_flow_data[idx].append(old_flow_data)
                    self.old_pattern_factor[idx].append(old_pattern_factor)

    def set_new_flow(self):
        """计算模拟时段新流量(相较于历史记录)"""
        for idx in self.new_flow_data.keys():  # 遍历patterns
            region_name = None
            for region in regions_patterns.keys():
                if idx in regions_patterns[region]:
                    region_name = region  # pattern所属分区
                    break
            # 实时流量
            if self.updating_data_flag is True:
                if idx in monitor_unity_patterns[-3:]:  # 出水管流量
                    self.new_flow_data[idx][0] = self.current_time_download_data[monitor_patterns_id[idx]]
                else:  # 其余流量
                    self.new_flow_data[idx][0] \
                                    = self.region_demand_control_factor[region_name] * self.old_flow_data[idx][0]
                # if idx == 'ZiYunTai':
                #     idx_a, idx_b = monitor_patterns_id[idx].split(',')
                #     self.new_flow_data[idx][0] \
                #         = self.current_time_download_data[idx_a] - self.current_time_download_data[idx_b]
                # else:
                #     self.new_flow_data[idx][0] = self.current_time_download_data[monitor_patterns_id[idx]]
                # for data_id in monitor_patterns_id[idx].split(','):
                #     if (self.current_time_download_data_flag[data_id] is False) \
                #             and (idx not in [pipe for pipe_list in regions_demand_patterns.values()
                #                              for pipe in pipe_list]):  # 无法获取实时数据
                #         self.new_flow_data[idx][0] \
                #             = self.region_demand_control_factor[region_name] * self.old_flow_data[idx][0]
                #         break
            # 根据设定用水量修改新流量
            if (self.region_demand_control is not None) \
                    and (region_name in self.region_demand_control.keys()):
                for pattern_idx in self.pattern_index_list:
                    if pattern_idx == self.pattern_start_index:  # 起始时刻
                        self.new_flow_data[idx][0] \
                            = self.region_demand_control_factor[region_name] * self.old_flow_data[idx][0]
                    else:
                        self.new_flow_data[idx].append(
                            self.region_demand_control_factor[region_name]
                            * self.old_flow_data[idx][self.pattern_index_list.index(pattern_idx)]
                        )

    def set_new_pattern_factor(self):
        """更新计算选定时段(设定用水量)/时刻的pattern factor"""
        pattern_index_list = self.pattern_index_list \
            if self.region_demand_control is not None \
            else [self.pattern_start_index]
        for idx in monitor_patterns:  # 遍历patterns
            for pattern_idx in pattern_index_list:  # 遍历需要修改的pattern(index)
                pattern_idx_cls = pattern_index_list.index(pattern_idx)  # 转换index(类表存储结构)
                old_flow_data = self.old_flow_data[idx][pattern_idx_cls]
                old_pattern_factor = self.old_pattern_factor[idx][pattern_idx_cls]
                if pattern_idx_cls == 0:  # 起始时刻
                    if idx in monitor_single_patterns:
                        if not np.isnan(self.new_flow_data[idx][0]):
                            self.new_pattern_factor[idx][0] = (self.new_flow_data[idx][0] * 1000 / 3600)  # m3/h to L/s
                    if idx in monitor_unity_patterns:
                        if not np.isnan(self.new_flow_data[idx][0]):
                            self.new_pattern_factor[idx][0] \
                                = old_pattern_factor * self.new_flow_data[idx][0] / old_flow_data
                else:
                    if idx in monitor_single_patterns:
                        if len(self.new_flow_data[idx]) > pattern_idx_cls:
                            self.new_pattern_factor[idx].append(
                                (self.new_flow_data[idx][pattern_idx_cls] * 1000 / 3600))  # m3/h to L/s
                    if idx in monitor_unity_patterns:
                        if len(self.new_flow_data[idx]) > pattern_idx_cls:
                            self.new_pattern_factor[idx].append(
                                old_pattern_factor
                                * self.new_flow_data[idx][pattern_idx_cls]
                                / old_flow_data)

    def set_reservoirs(self):
        """设置清水池"""
        if self.updating_data_flag is True:
            for idx in self.reservoir_data.keys():
                if self.current_time_download_data_flag[reservoirs_id[idx]] is False:  # 无法获取实时数据
                    print('There is no current data of reservoir: {}.'.format(idx))
                else:
                    self.reservoir_data[idx] \
                        = self.current_time_download_data[reservoirs_id[idx]] + RESERVOIR_BASIC_HEIGHT

    def set_tanks(self):
        """设置调节池"""
        for idx in self.tank_data.keys():
            if self.updating_data_flag is True:
                if self.current_time_download_data_flag[tanks_id[idx]] is False:  # 无法获取实时数据
                    print('There is no current data of tank: {}.'.format(idx))
                else:
                    self.tank_data[idx] = self.current_time_download_data[tanks_id[idx]]
            self.tank_data[idx] = self.tank_initial_level_control[idx] \
                if (self.tank_initial_level_control is not None) and (idx in self.tank_initial_level_control) \
                else self.tank_data[idx]

    def set_pumps(self):
        """设置水泵"""
        for idx in self.pump_data.keys():
            if self.updating_data_flag is True:
                if self.current_time_download_data_flag[pumps_id[idx]] is False:  # 无法获取实时数据
                    print('There is no current data of pump: {}.'.format(idx))
                    if (self.pump_control is not None) and (idx in self.pump_control.keys()):
                        self.pump_data[idx] = self.pump_control[idx]
                else:
                    self.pump_data[idx] = [self.current_time_download_data[pumps_id[idx]]]
                    if (self.pump_control is not None) and (idx in self.pump_control.keys()):
                        self.pump_data[idx] = self.pump_data[idx] + self.pump_control[idx] \
                            if len(self.pump_control[idx]) < len(self.pattern_index_list) \
                            else self.pump_control[idx]  # 水泵设定
            else:
                if (self.pump_control is not None) and (idx in self.pump_control.keys()):
                    self.pump_data[idx] = self.pump_control[idx]
            self.pump_data[idx] \
                = list(np.array(self.pump_data[idx]) / 50) \
                if idx in variable_frequency_pumps else self.pump_data[idx]

    def set_valves(self):
        """设置阀门"""
        pass

    def download_real_data(self, ids: str):
        """加载实时数据"""
        # 数据接口的地址
        global url_real
        # 设置GET请求的参数
        params = {'ids': ids}
        # 发送GET请求获取数据
        response = requests.get(url_real, params=params)
        # 检查响应状态码，200表示请求成功
        if response.status_code == 200:
            newest_data_time = None  # 下载记录数据的最新时间
            # 解析响应的JSON数据
            data = response.json()
            for realValue in data:  # 取出逐个id的数据
                data_time = convert_utc_to_bj(realValue['datadt'])  # datetime
                self.current_time_download_data[str(realValue['id'])] \
                    = float(realValue['realValue'])  # {id(str): value(float)}
                if data_time > self.current_round_time.replace(tzinfo=None) - timedelta(minutes=5):  # 下载数据为实时数据
                    self.current_time_download_data_flag[str(realValue['id'])] = True
                if newest_data_time is None:
                    newest_data_time = data_time
                else:
                    newest_data_time = data_time if data_time > newest_data_time else newest_data_time  # 更新最新时间
            if newest_data_time <= self.current_round_time.replace(tzinfo=None) - timedelta(minutes=5):  # 最新记录时间早于当前时间
                warning_text = 'There is no current data with newest timestamp: {}.'.format(
                        newest_data_time.strftime('%Y-%m-%d %H:%M:%S'))
                delta_time = self.current_round_time.replace(tzinfo=None) - newest_data_time
                if delta_time < timedelta(minutes=PATTERN_TIME_STEP):  # 时间接近(可等待再次下载)
                    raise Exception(warning_text)
                else:
                    print(warning_text)
                    self.updating_data_flag = False
            else:
                for idx in monitor_unity_patterns[-3:]:  # 出水管流量
                    if self.current_time_download_data_flag[monitor_patterns_id[idx]] is False:  # 无法获取出水管流量的实时数据
                        print('There is no current data of outflow: {}.'.format(idx))
                        self.updating_data_flag = False
            if self.updating_data_flag is False:
                print('Abandon updating data with downloaded data.')
            return newest_data_time
        else:
            # 如果请求不成功，打印错误信息
            print("请求失败，状态码:", response.status_code)
            raise ConnectionError('Cannot download data.')

    @ staticmethod
    def init_dict_of_list(dict_of_list):
        """初始化值为列表的字典(重新生成列表地址, 防止指向同一列表)"""
        for idx in dict_of_list.keys():
            dict_of_list[idx] = dict_of_list[idx].copy()
        return dict_of_list

    @ staticmethod
    def get_download_id():
        """生成下载数据项的id"""
        # id_list = (list(monitor_single_patterns_id.values())
        #            + list(monitor_unity_patterns_id.values())
        #            + list(tanks_id.values())
        #            + list(reservoirs_id.values())
        #            + list(pumps_id.values()))
        id_list = (list(monitor_unity_patterns_id.values())[-3:]
                   + list(tanks_id.values())
                   + list(reservoirs_id.values())
                   + list(pumps_id.values()))
        id_list = sorted(set(id_list), key=id_list.index)
        if None in id_list:
            id_list.remove(None)
        return ','.join(id_list)

    @ staticmethod
    def get_history_pattern_info(project_name, pattern_name):
        """读取选定pattern的保存的历史pattern信息(flow, factor)"""
        factors_list = []
        flow_list = []
        patterns_info = read_all(project_name,
                                 f"select * from history_patterns_flows where id = '{pattern_name}' order by _order")
        for item in patterns_info:
            flow_list.append(float(item['flow']))
            factors_list.append(float(item['factor']))
        return flow_list, factors_list

    @ staticmethod
    def judge_time(current_time, time_index_list):
        """时间判断"""
        current_index \
            = time_index_list.index(current_time) if (current_time in time_index_list) else None
        return current_index

    @staticmethod
    def get_time_index_list(start_time: datetime, end_time: datetime, step: int):
        """生成时间索引"""
        time_index_list = []  # 时间索引[str]
        time_index = start_time
        while time_index <= end_time:
            time_index_list.append(time_index)
            time_index += timedelta(minutes=step)
        return time_index_list

    @ staticmethod
    def round_time(time_: datetime, interval=5):
        """时间向下取整到整n分钟(北京时间): 四舍六入五留双/向下取整"""
        # return datetime.fromtimestamp(round(time_.timestamp() / (60 * interval)) * (60 * interval))
        return datetime.fromtimestamp(int((time_.timestamp()) // (60 * interval)) * (60 * interval))


def convert_utc_to_bj(utc_time_str):
    """将utc时间(str)转换成北京时间(datetime)"""
    # 解析UTC时间字符串为datetime对象
    utc_time = datetime.strptime(utc_time_str, '%Y-%m-%dT%H:%M:%SZ')
    # 设定UTC时区
    utc_timezone = pytz.timezone('UTC')
    # 转换为北京时间
    beijing_timezone = pytz.timezone('Asia/Shanghai')
    beijing_time = utc_time.replace(tzinfo=utc_timezone).astimezone(beijing_timezone).replace(tzinfo=None)
    return beijing_time


def get_datetime(cur_datetime:str):
    str_format = "%Y-%m-%d %H:%M:%S"
    return datetime.strptime(cur_datetime, str_format)


def get_strftime(cur_datetime: datetime):
    str_format = "%Y-%m-%d %H:%M:%S"
    return cur_datetime.strftime(str_format)


def step_time(cur_datetime:str, step=5):
    str_format="%Y-%m-%d %H:%M:%S"
    dt=datetime.strptime(cur_datetime,str_format)
    dt=dt+timedelta(minutes=step)
    return datetime.strftime(dt,str_format)


def get_pattern_index(cur_datetime:str)->int:
    str_format="%Y-%m-%d %H:%M:%S"
    dt=datetime.strptime(cur_datetime,str_format)
    hr=dt.hour
    mnt=dt.minute
    i=int((hr*60+mnt)/PATTERN_TIME_STEP)
    return i

def get_pattern_index_str(cur_datetime:str)->str:
    i=get_pattern_index(cur_datetime)
    [minN,hrN]=modf(i*PATTERN_TIME_STEP/60)
    minN_str=str(int(minN*60))
    minN_str=minN_str.zfill(2)
    hrN_str=str(int(hrN))
    hrN_str=hrN_str.zfill(2)
    str_i='{}:{}:00'.format(hrN_str,minN_str)
    return str_i


def from_seconds_to_clock (secs: int)->str:
    hrs=int(secs/3600)
    minutes=int((secs-hrs*3600)/60)
    seconds=(secs-hrs*3600-minutes*60)
    hrs_str=str(hrs).zfill(2)
    minutes_str=str(minutes).zfill(2)
    seconds_str=str(seconds).zfill(2)
    str_clock='{}:{}:{}'.format(hrs_str,minutes_str,seconds_str)
    return str_clock

def from_clock_to_seconds (clock: str)->int:
    str_format="%Y-%m-%d %H:%M:%S"
    dt=datetime.strptime(clock,str_format)
    hr=dt.hour
    mnt=dt.minute
    seconds=dt.second
    return hr*3600+mnt*60+seconds

def from_clock_to_seconds_2 (clock: str)->int:
    str_format="%H:%M:%S"
    dt=datetime.strptime(clock,str_format)
    hr=dt.hour
    mnt=dt.minute
    seconds=dt.second
    return hr*3600+mnt*60+seconds


def from_clock_to_seconds_3 (clock: str)->int:
    str_format = "%H:%M" # 更新时间格式以适应 "小时:分钟" 格式
    dt = datetime.strptime(clock,str_format)
    hr = dt.hour
    mnt = dt.minute
    seconds = dt.second
    return hr * 3600 + mnt * 60


###convert datetimestring 
##"XXXX-XX-XXT00:00:00Z" ->"XXXX-XX-XX 00:00:00"
def trim_time_flag(url_date_time:str)->str:
    str_datetime=str.replace(url_date_time,'T',' ')
    str_datetime=str.replace(str_datetime,'Z','')
    return str_datetime

# 单时间步长模拟
def run_simulation(name:str,start_datetime:str,end_datetime:str=None, duration:int=900)->str:
    if(is_project_open(name)):
        close_project(name)
    open_project(name)
    #get_current_data(cur_datetime)
    #extract the patternindex from datetime
    #e.g. 0: the first time step for 00:00-00:14; 1: the second step for 00:15-00:30
    start_datetime=trim_time_flag(start_datetime)
    if(end_datetime!=None):
        end_datetime=trim_time_flag(end_datetime)

   
    
    ## redistribute the basedemand according to the currentTotalQ and the base_totalQ
    # step 1. get_real _data
    if end_datetime==None or start_datetime==end_datetime:
        end_datetime=step_time(start_datetime)
        
    # # ids=['2498','3854','3853','2510','2514','4780','4854']
    # # real_data=get_real_data(ids,start_datetime,end_datetime)
    # print(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S")+"--获取实时数据完毕\n")
    # #step 2. re-distribute the real q to base demand of the node region_sa by region_sa
    # regions=get_all_service_area_ids(name)
    # total_demands={}
    # for region in regions:
    #     total_demands[region]=get_total_base_demand(name,region)
    # region_demand_factor={}
    # #Region_ID:SA_ZBBDJSCP000002 高区;SA_R00003+SA_ZBBDTJSC000001 低区
    # H_region_real_demands=real_data[DN_900_ID][start_datetime]+real_data[DN_500_ID][start_datetime]
    # L_region_real_demands=real_data[DN_1000_ID][start_datetime]
    # factor_H_zone=H_region_real_demands/total_demands[H_REGION_1]/3.6 #3.6: m3/h->L/s
    # factor_L_zone=L_region_real_demands/(total_demands[L_REGION_1]+total_demands[L_REGION_2])/3.6
    # print(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S")+"--流量因子计算完毕完毕\n")
    # for region in regions:
    #     region_nodes=get_nodes_in_region(name,region)
    #     factor=1
    #     if region==H_REGION_1 or H_REGION_2:
    #         factor=factor_H_zone
    #     else:
    #         factor=factor_L_zone
    #
    #     for node in region_nodes:
    #         d=get_demand(name,node)
    #         for r in d['demands']:
    #             r['demand']=factor*r['demand']
    #         cs=ChangeSet()
    #         cs.append(d)
    #         set_demand(name,cs)
    #
    # #
    # #
    # print(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S")+"--节点流量重分配完毕\n")
    
    #step 3. set pattern index to the current time,and set duration to 300 secs
    # 
    str_pattern_start=get_pattern_index_str(start_datetime)
    dic_time=get_time(name)
    dic_time['PATTERN START']=str_pattern_start
   
    if duration !=None:
        dic_time['DURATION']=from_seconds_to_clock(duration)
    else:
        dic_time['DURATION']=dic_time['HYDRAULIC TIMESTEP']
    cs=ChangeSet()
    cs.operations.append(dic_time)
    set_time(name,cs)
    
    # step4. run simulation and save the result to name-time.out for download
    #inp_file = 'inp\\'+name+'.inp'
    #db_name=name
    #dump_inp(db_name,inp_file,'2')
    # result=run_inp(db_name)
    result=run_project(name)
    #json string format
    # simulation_result, output, report
    result_data=json.loads(result)
    #print(result_data['simulation_result'])
    print(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S")+'run finished successfully\n')
    #print(result_data['report'])
    return result


# 在线模拟
def run_simulation_ex(name: str, simulation_type: str, start_datetime: str,
                      end_datetime: str = None, duration: int = 0,
                      pump_control: dict[str, list] = None, tank_initial_level_control: dict[str, float] = None,
                      region_demand_control: dict[str, float] = None, valve_control: dict[str, dict] = None,
                      downloading_prohibition: bool = False) -> str:
    time_cost_start = time.perf_counter()
    print('{} -- Hydraulic simulation started.'.format(
        datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S')))

    if is_project_open(name):
        close_project(name)

    if simulation_type.upper() == 'REALTIME':  # 实时模拟(修改原数据库)
        name_c = name
    elif simulation_type.upper() == 'EXTENDED':  # 扩展模拟(复制数据库)
        name_c = '_'.join([name, 'c'])
        if have_project(name_c):
            if is_project_open(name_c):
                close_project(name_c)
            delete_project(name_c)
        copy_project(name, name_c)  # 备份项目
    else:
        raise Exception('Incorrect simulation type, choose in (realtime, extended)')

    open_project(name_c)

    # 时间处理
    # extract the pattern index from datetime
    # e.g. 0: the first time step for 00:00-00:14; 1: the second step for 00:15-00:30
    # start_datetime = get_strftime(convert_utc_to_bj(start_datetime))
    start_datetime = trim_time_flag(start_datetime)
    if end_datetime is not None:
        # end_datetime = get_strftime(convert_utc_to_bj(end_datetime))
        end_datetime = trim_time_flag(end_datetime)

    # pump name转化/输入值规范化
    if pump_control is not None:
        for key in list(pump_control.keys()):
            pump_control[key] = [pump_control[key]] if type(pump_control[key]) is not list else pump_control[key]
            pump_control[pumps[pumps_name.index(key)]] = pump_control.pop(key)

    # 重新分配节点(nodes)水量
    # 1) (single)base_demand_new=1, pattern_new=real_data
    # 2) (unity)base_demand_new=base_demand_old, pattern_new=factor*pattern_old(factor=flow_new/flow_old)
    # 获取需水量数据
    # a) 历史pattern对应水量(读取保存数据库)
    # b) 实时水量(数据接口下载)

    # 修改node demand = 1, pattern factor *= demand(monitor single patterns对应node)
    # nodes = get_nodes(name_c)  # nodes
    # for node_name in nodes:  # 遍历nodes
    #     demands_dict = get_demand(name_c, node_name)  # {'demands':[{'demand':, 'pattern':}]}
    #     for demands in demands_dict['demands']:
    #         if (demands['pattern'] in monitor_single_patterns) and (demands['demand'] != 1):  # 1)
    #             pattern = get_pattern(name_c, demands['pattern'])
    #             pattern['factors'] = list(demands['demand'] * np.array(pattern['factors']))  # 修改pattern
    #             cs = ChangeSet()
    #             cs.append(pattern)
    #             set_pattern(name_c, cs)
    #             demands_dict['demands'][
    #                 demands_dict['demands'].index(demands)
    #             ]['demand'] = 1  # 修改demand
    #     cs = ChangeSet()
    #     cs.append(demands_dict)
    #     set_demand(name_c, cs)

    start_time = get_datetime(start_datetime)  # datetime
    end_time = get_datetime(end_datetime) \
        if end_datetime is not None \
        else get_datetime(start_datetime) + timedelta(seconds=duration)  # datetime
    # modify_pattern_start_index = get_pattern_index(start_datetime)  # 待修改pattern的起始索引(int)

    dataset_loader = DataLoader(project_name=name_c,
                                start_time=start_time, end_time=end_time,
                                pumps_control=pump_control, tank_initial_level_control=tank_initial_level_control,
                                region_demand_control=region_demand_control,
                                downloading_prohibition=downloading_prohibition)  # 实例化数据加载器
    modify_index \
        = dataset_loader.load_data()  # 加载数据(index: 需要修改pattern的factor index, None: 无需修改除水泵和调节池外pattern)
    new_patterns \
        = dataset_loader.new_pattern_factor  # {name: float,}  pattern factor(实时: 更新, 其他: 保持/更新(设定用水量时))
    tank_init_level = dataset_loader.tank_data  # {name: float,}  调节池初始液位(实时: 更新, 其他: 保持/更新(设定液位时))
    reservoir_level = dataset_loader.reservoir_data  # {name: float,}  水库液位(实时: 更新, 其他: 保持)
    pump_freq = dataset_loader.pump_data  # {name: [float,]}  水泵频率(实时: 更新, 其他: 保持/更新(设定状态时))
    print(datetime.now(pytz.timezone('Asia/Shanghai')).strftime("%Y-%m-%d %H:%M:%S") + " -- Loading data ok.\n")

    pattern_name_list = get_patterns(name_c)  # 所有pattern
    # 修改node pattern/demand
    # nodes = get_nodes(name_c)  # nodes
    # for node_name in nodes:  # 遍历nodes
    #     demands_dict = get_demand(name_c, node_name)  # {'demands':[{'demand':, 'pattern':}]}
    #     for demands in demands_dict['demands']:
    #         if demands['pattern'] in monitor_single_patterns:  # 1)
    #             demands_dict['demands'][
    #                 demands_dict['demands'].index(demands)
    #             ]['demand'] = 1  # 修改demand
    #             pattern = get_pattern(name_c, demands['pattern'])
    #             pattern['factors'][modify_index] = flow_new[demands['pattern']]  # 修改pattern
    #             cs = ChangeSet()
    #             cs.append(pattern)
    #             set_pattern(name_c, cs)
    #             if demands['pattern'] in pattern_name_list:
    #                 pattern_name_list.remove(demands['pattern'])  # 移出待修改pattern列表
    #         else:  # 2)
    #             continue
    #     cs = ChangeSet()
    #     cs.append(demands_dict)
    #     set_demand(name_c, cs)
    for pattern_name in monitor_patterns:  # 遍历patterns
        if not np.isnan(new_patterns[pattern_name][0]):
            pattern = get_pattern(name_c, pattern_name)
            pattern['factors'][modify_index:
                               modify_index + len(new_patterns[pattern_name])] \
                = new_patterns[pattern_name]
            cs = ChangeSet()
            cs.append(pattern)
            set_pattern(name_c, cs)
            if pattern_name in pattern_name_list:
                pattern_name_list.remove(pattern_name)  # 移出待修改pattern列表

    # 修改清水池(reservoir)液位pattern
    for reservoir_name in reservoirs:  # 遍历reservoirs
        if (not np.isnan(reservoir_level[reservoir_name])) and (reservoir_level[reservoir_name] != 0):
            reservoir_pattern = get_pattern(name_c, get_reservoir(name_c, reservoir_name)['pattern'])
            reservoir_pattern['factors'][modify_index] = reservoir_level[reservoir_name]
            cs = ChangeSet()
            cs.append(reservoir_pattern)
            set_pattern(name_c, cs)
            if reservoir_pattern['id'] in pattern_name_list:
                pattern_name_list.remove(reservoir_pattern['id'])  # 移出待修改pattern列表

    # 修改调节池(tank)初始液位
    for tank_name in tanks:  # 遍历tanks
        if (not np.isnan(tank_init_level[tank_name])) and (tank_init_level[tank_name] != 0):
            tank = get_tank(name_c, tank_name)
            tank['init_level'] = tank_init_level[tank_name]
            cs = ChangeSet()
            cs.append(tank)
            set_tank(name_c, cs)

    # 修改水泵(pump)pattern
    for pump_name in pumps:  # 遍历pumps
        if not np.isnan(pump_freq[pump_name][0]):
            pump_pattern = get_pattern(name_c, get_pump(name_c, pump_name)['pattern'])
            pump_pattern['factors'][modify_index
                                    :modify_index + len(pump_freq[pump_name])] \
                = pump_freq[pump_name]
            cs = ChangeSet()
            cs.append(pump_pattern)
            set_pattern(name_c, cs)
            if pump_pattern['id'] in pattern_name_list:
                pattern_name_list.remove(pump_pattern['id'])  # 移出待修改pattern列表
    # 修改阀门(valve)status和setting
    if valve_control is not None:
        for valve in valve_control.keys():
            status = get_status(name_c, valve)
            if 'status' in valve_control[valve].keys():
                status['status'] = valve_control[valve]['status']
            if 'setting' in valve_control[valve].keys():
                status['setting'] = valve_control[valve]['setting']
            if 'k' in valve_control[valve].keys():
                valve_k = valve_control[valve]['k']
                if valve_k == 0:
                    status['status'] = 'CLOSED'
                else:
                    status['setting'] = 0.1036 * pow(valve_k, -3.105)
            cs = ChangeSet()
            cs.append(status)
            set_status(name_c, cs)

    print('Finish demands amending, unmodified patterns: {}.'.format(pattern_name_list))

    # 修改时间信息
    str_pattern_start = get_pattern_index_str(
        DataLoader.round_time(start_time, int(PATTERN_TIME_STEP)).strftime("%Y-%m-%d %H:%M:%S"))
    dic_time = get_time(name_c)
    dic_time['PATTERN START'] = str_pattern_start

    if duration is not None:
        dic_time['DURATION'] = from_seconds_to_clock(duration)
    else:
        dic_time['DURATION'] = dic_time['HYDRAULIC TIMESTEP']
    cs = ChangeSet()
    cs.operations.append(dic_time)
    set_time(name_c, cs)

    # 运行并返回结果
    result = run_project(name_c)

    time_cost_end = time.perf_counter()
    print('{} -- Hydraulic simulation finished, cost time: {:.2f} s.'.format(
        datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'),
        time_cost_end - time_cost_start))

    close_project(name_c)

    return result


if __name__ == '__main__':
    # if get_current_data()==True:
    #     tQ=get_current_total_Q()
    #     print(f"the current tQ is {tQ}\n")
    # data=get_hist_data(ids,conver_beingtime_to_ucttime('2024-04-10 15:05:00'),conver_beingtime_to_ucttime('2024-04-10 15:10:00'))
    # open_project("beibeizone")
    # read_inp("beibeizone","beibeizone-export_nochinese.inp")
    # run_simulation("beibeizone","2024-04-01T08:00:00Z")
    # read_inp('bb_server', 'model20_en.inp')
    run_simulation_ex(
        name=project_info.name, simulation_type='extended', start_datetime='2024-11-09T02:30:00Z',
        # end_datetime='2024-05-30T16:00:00Z',
        # duration=0,
        # pump_control={'PU00006': [45, 40]}
        # region_demand_control={'hp': 6000, 'lp': 2000}
    )
