import json
import os

import scrapy

# https://www.u2ku.com/qitajigou/page_41.html

class U2kuSingleSpider(scrapy.Spider):
    name = 'u2ku_single'
    allowed_domains = ['www.u2ku.com']
    start_urls = ['https://www.u2ku.com/']
    url_link = [
        'https://www.u2ku.com/21863.html',
        'https://www.u2ku.com/21867.html',
        'https://www.u2ku.com/21860.html',
        'https://www.u2ku.com/22604.html',
        'https://www.u2ku.com/22624.html',
        'https://www.u2ku.com/22635.html',
        'https://www.u2ku.com/22626.html',
    ]
    result = []
    path = os.path.join(os.path.dirname(__file__), '..\\db\\xxx.json')
    # path = 'D:\\Code\\Git\\awesome-server\\restful-json\\public\\x.json'

    def start_requests(self):
        headers = {
            'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
        }
        for url in self.url_link:
            yield scrapy.Request(url, headers=headers, callback=self.parse)

    def parse(self, response):
        # page_buttons = response.css('.nav-links')
        # btns = page_buttons.css('a.page-numbers')[1:-1]
        # next_page = []
        # for btn in btns:
        #     next_page.append(btn.css('::attr(href)').extract_first())
        meta = {
            'title': response.meta.get('title', ''),
            'images': response.meta.get('images', []),
            'count': response.meta.get('count', 0),
            'url': response.meta.get('url', '')
        }
        if meta['count'] == 0:
            count_ele = response.css('.post_title_topimg::text').extract_first('')[1:-2]
            meta['count'] = int(count_ele)
        if not meta['url']:
            meta['url'] = response.url
        if not meta['title']:
            meta['title'] = response.css('.item_title h1::text').extract_first('')

        pic_list = response.css('.image_div img')
        for pic in pic_list:
            image = {
                'url': pic.css('::attr(src)').extract_first(''),
                'title': pic.css('::attr(alt)').extract_first('')
            }
            meta['images'].append(image)

        if len(meta['images']) < meta['count']:
            next_link = response.css('.nav-links .prev::attr(href)').extract_first('')
            yield scrapy.Request(url=next_link,
                                 meta=meta,
                                 callback=self.parse)
        else:
            self.result.append(meta)
            if len(self.result) == len(self.url_link):
                with open(self.path, 'w', encoding='utf-8') as f:
                    f.write(json.dumps(self.result, indent=4, ensure_ascii=False))
                    print('----------------------加载完成:{}--------------------------'.format(len(self.url_link)))


from collections import defaultdict
import csv

# the below shell command is used to
# psql yelp -A -t --variable="FETCH_COUNT=10000" -c "select categories from businesses" > categories.txt
# Delete all occurances of '{', '}' and double quotes character
# sed 's/[{}"]//g' categories.txt > output.txt

categories = defaultdict(int)

### get the 10000 bus_id, num_check_ins that have the highgest num check ins
def write_to_file():
    with open('../../output/output.txt', 'rb') as fin, open('../../output/categories_count.csv','w') as fout:
        for line in fin:
            cats = line.split(',')
            for cat in cats:
                cat = cat.strip()
                categories[cat] += 1
        writer = csv.DictWriter(fout, ['Category','Count'])
        writer.writeheader()
        for category, count in categories.iteritems():
            writer.writerow({'Category': category, 'Count': count})

write_to_file()


# ================= importacion packeges ==================== #
import dash
from dash import dcc
import dash_bootstrap_components as dbc
from dash_bootstrap_templates import ThemeChangerAIO, template_from_url
from dash.dependencies import Output, Input, State

from components.rca_application.vlayout import *
from components.rca_application import vbase, vcampanha, vcomitiva, vconfiguração, vfee, vperfil, vperformance, vpremissas, vranking, vlayout
# =========================================================== #

# ----------------- Estilos e Fontes ------------------- #
estilos = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css",
    "https://fonts.googleapis.com/icon?family=Material+Icons", dbc.themes.COSMO]
dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.4/dbc.min.css"
FONT_AWESOME = "https://use.fontawesome.com/releases/v5.10.2/css/all.css"
# ------------------------------------------------------ #

# ================================ LAYOUT ===================================== #
def rca_dash_application(flask_app):
    app_dash = dash.Dash(
        server=flask_app, name="Vendedor", url_base_pathname="/Vendedor/", external_scripts=estilos + [dbc_css] + [FONT_AWESOME],
        external_stylesheets=[dbc.themes.BOOTSTRAP, dbc_css], use_pages=True, pages_folder="", meta_tags=[
            {"name": "viewport", "content": "width=device-width, initial-scale=1, maximum-scale=1.2, minimum-scale=0.5"}
        ]
    )
    dash.register_page(vperformance.__name__, path="/", layout=vperformance.layout)
    app_dash.config["suppress_callback_exceptions"] = True
    app_dash.scripts.config.serve_locally = True
    #server = app_dash.server

    # ----------- Titulo Da Página & Favivon -------------- #
    app_dash.title = "Vendedor"
    app_dash._favicon = ("../assets/favicon/dash.ico")
    # ----------------------------------------------------- #

    app_dash.layout = dbc.Container(
        [
            dbc.Row(
                [
                    dbc.Col(
                        [
                            dcc.Store(id="side_click"),
                            dcc.Location(id="url", refresh=True),
                            vlayout.barra_superior,
                            vlayout.sidebar
                        ], md=2, style={"padding": 0}
                    ),

                    dbc.Col(
                        [
                            dbc.Col([], id="page-content")
                        ], md=10, style={"padding": 0}
                    )
                ]
            )
        ], fluid=True
    )
 # ===================================================================================== #

 # ======================== Callback Page Content =============================== #
    @app_dash.callback(
        Output("page-content", "children"),
        [Input("url", "pathname")]
    )
    def render_page_content(pathname):
        if pathname == "/Vendedor/":
            return vperformance.layout

        if pathname == "/Performance/":
            return vperformance.layout

        if pathname == "/Premissas/":
            return vpremissas.layout

        if pathname == "/Fee/":
            return vfee.layout

        if pathname == "/Campanha/":
            return vcampanha.layout

        if pathname == "/Ranking/":
            return vranking.layout

        if pathname == "/Base/":
            return vbase.layout

        if pathname == "/Comitiva/":
            return vcomitiva.layout

        if pathname == "/Configuracao/":
            return vconfiguração.layout

        if pathname == "/Perfil/":
            return vperfil.layout
 # ============================================================================== #

  # ============================ CallBack Modal - FOTO PERFIL ================================ #
    @app_dash.callback(
        Output("modal_perfil", "is_open"),
        [Input("btn_avatar", "n_clicks")],
        [State("modal_perfil", "is_open")]
    )
    def toggle_modal(n1, is_open):
        if n1:
            return not is_open
        return is_open
 # ================================================================================================== #

     # ===================== CallBack SIDEBAR - Menu Lateral ================== #
    @app_dash.callback(
        [
            Output("sidebar", "style"),         # Construção Sidebar
            Output("page-content", "style"),    # Pagina Content "md-10"
            Output("side_click", "data"),       # Side Click
            Output("bottom_sidebar", "style"),  # Botão Do Menu Lateral
            Output("avata_img", "style"),       # Foto Avatar - Perfil
            Output("nome_usuario", "style"),    # H2 Do Nome Do Usuario
            Output("navlink_perf", "style"),    # NavLink Performance
            Output("navlink_premiss", "style"), # NavLick Premissas
            Output("navlink_fee", "style"),     # NavLink Fee
            Output("navlink_camp", "style"),    # NavLink Campanha
            Output("navlink_rank", "style"),    # NavLink Ranking
            Output("navlink_bas", "style"),     # NavLink Base
            Output("navlink_comit", "style"),   # NavLink Comitiva
            Output("navlink_conf", "style"),    # Navlink Configuração
            Output("navlink_perfil", "style"),  # NavLink Perfil
            Output("bottom-perf", "style"),     # Botão Performance
            Output("bottom-prem", "style"),     # Botão Premissas
            Output("bottom-fee", "style"),      # Botão Fee
            Output("bottom-camp", "style"),     # Botão Campanha
            Output("bottom-rank", "style"),     # Botão Ranking
            Output("bottom-bass", "style"),     # Botão Base
            Output("bottom-comit", "style"),    # Botão Comitiva
            Output("bottom-conf", "style"),     # Botão Configuração
            Output("bottom-perfil", "style")    # Botão Perfil
        ],

        [
            Input("btn_sidebar", "n_clicks")    # Botão Do Menu Lateral
        ],

        [
            State("side_click", "data")         # Side Click
        ]
    )
    def toggle_sidebar(n, nclick):
        if nclick == "HIDDEN":
            menulateral = MENULATERAL_STYLE  # Função Menu Lateral
            content = CONTENT_STYLE          # Função Com Content - Page-Content
            botaomenu = BOTÃO_MENU           # Função Botão Abrir e Fecha Menu lateral
            avatar_style = IMAGEM_ESTILO     # Função Foto Perfil
            nameuser = NAME_USER             # Função Nome do Usuario
            nl_perf = BOTTOM_MENUS           # Função Move o Nome NavLink
            nl_premiss = BOTTOM_MENUS        # Função Move Nome NavLink
            nl_fee = BOTTOM_MENUS            # Função Move Nome NavLink
            nl_camp = BOTTOM_MENUS           # Função Move Nome NavLink
            nl_rank = BOTTOM_MENUS           # Função Move Nome NavLink
            nl_bas = BOTTOM_MENUS            # Função Move Nome NavLink
            nl_comit = BOTTOM_MENUS          # Função Move Nome NavLink
            nl_conf = BOTTOM_MENUS           # Função Move Nome NavLink
            nl_perfi = BOTTOM_MENUS          # Função Move Nome NavLink
            bt_perf = ICON_STYLE             # Botão Move Performance
            bt_prem = ICON_STYLE             # Botão Move Premissas
            bt_fee = ICON_STYLE              # Botão Move Fee
            bt_camp = ICON_STYLE             # Botão Move Campanha
            bt_rank = ICON_STYLE             # Botão Move Ranking
            bt_bass = ICON_STYLE             # Botão Move Base
            bt_comit = ICON_STYLE            # Botão Move Comitiva
            bt_conf = ICON_STYLE             # Botão Move Configuração
            bt_perfil = ICON_STYLE           # Botão Move Perfil
            cur_click = "SHOW"               # Função Click
        else:
            menulateral = MENULATERAL_OCULTA      # Função Menu Lateral
            content =  CONTENT_STYLE_OCULTO       # Função Com Content - Page-Content
            botaomenu = BOTÃO_MENU2               # Função Botão Abrir e Fecha Menu lateral
            avatar_style = IMAGEM_ESTILO_OCULTO   # Função Foto Perfil
            nameuser = NAME_USER_OCULTO           # Função Nome do Usuario
            nl_perf = BOTTOM_MENUS_OCULTO         # Função Move Nome NavLink
            nl_premiss = BOTTOM_MENUS_OCULTO      # Função Move Nome NavLink
            nl_fee = BOTTOM_MENUS_OCULTO          # Função Move Nome NavLink
            nl_camp = BOTTOM_MENUS_OCULTO         # Função Move Nome NavLink
            nl_rank = BOTTOM_MENUS_OCULTO         # Função Move Nome NavLink
            nl_bas = BOTTOM_MENUS_OCULTO          # Função Move Nome NavLink
            nl_comit = BOTTOM_MENUS_OCULTO        # Função Move Nome NavLink
            nl_conf = BOTTOM_MENUS_OCULTO         # Função Move Nome NavLink
            nl_perfi = BOTTOM_MENUS_OCULTO        # Função Move Nome NavLink
            bt_perf = ICON_STYLE_FRENT            # Botão Move Performance
            bt_prem = ICON_STYLE_FRENT            # Botão Move Premissas
            bt_fee = ICON_STYLE_FRENT             # Botão Move Fee
            bt_camp = ICON_STYLE_FRENT            # Botão Move Campanha
            bt_rank = ICON_STYLE_FRENT            # Botão Move Ranking
            bt_bass = ICON_STYLE_FRENT            # Botão Move Base
            bt_comit = ICON_STYLE_FRENT           # Botão Move Comitiva
            bt_conf = ICON_STYLE_FRENT            # Botão Move Configuração
            bt_perfil = ICON_STYLE_FRENT          # Botão Move Perfil
            cur_click = "HIDDEN"                  # Função Click

        return[menulateral, content, cur_click, botaomenu, avatar_style, nameuser, nl_perf, nl_premiss, nl_fee, nl_camp, nl_rank, nl_bas, nl_comit, nl_conf,
            nl_perfi, bt_perf, bt_prem, bt_fee, bt_camp, bt_rank, bt_bass, bt_comit, bt_conf, bt_perfil]
 # ========================================================================================= #

# ============================================================================= #


from tkinter import ttk
from tkinter import filedialog as fd
from tkinter import *
from pathlib import Path
import laspy

class Uitkinter(Tk):

    def __init__(self, *args, **kwargs):

        # Create Tk
        super().__init__(*args, **kwargs)

        # Intitializing variables
        self.title('las2laz')
        self.dir_in = ""
        self.dir_out = ""
        self.cancel_press = False

        self.max_info = 60

        # Create window
        self.resizable(False, False)

        # Input dir
        self.label_in_dir = ttk.Label(text="Input dir: ")
        self.button_in_dir = ttk.Button(text='Select', command=self.select_dir_in)
        self.information_input = ttk.Label(text="", wraplength="100m") # Limit the maximum width

        # Output dir
        self.label_out_dir = ttk.Label(text="Output dir: ")
        self.button_out_dir = ttk.Button(text='Select', command=self.select_dir_out)
        self.information_output = ttk.Label(text="", wraplength="100m") # Limit the maximum width

        # Save in the same dir button
        self.label_same_dir = ttk.Label( text="Save in the same dir")
        self.same_dir = BooleanVar()
        self.buttom_same_dir = ttk.Checkbutton(variable=self.same_dir, onvalue=True, offvalue=False, command=self.same_dir_func)

        # Analyse subdirs button
        self.label_subdirs = ttk.Label( text="Analyse files in subdirs")
        self.analyse_subdirs = BooleanVar()
        self.buttom_analyse_subdirs = ttk.Checkbutton(variable=self.analyse_subdirs, onvalue=True, offvalue=False)

        # Delete original files button
        self.label_delete_files = ttk.Label( text="Delete original files")
        self.delete_files = BooleanVar()
        self.butoom_delete_files = ttk.Checkbutton(variable=self.delete_files, onvalue=True, offvalue=False)

        # Start process
        self.start_buttom = ttk.Button(text="Start", command=self.start_process)

        # Cancel process
        self.cancel = BooleanVar()
        self.cancel_buttom = ttk.Button(text="Cancel", state='disable', command=self.cancel_process)

        # Information
        self.information = ttk.Label(text="")

        # Progress bar
        self.progressbar = ttk.Progressbar(self, orient='horizontal', value=0, maximum=100, mode='determinate')

        # Define the geometry of the content in the GUI
        self.label_in_dir.grid(column=1, row=1, sticky="nsew")
        self.button_in_dir.grid(column=2, row=1, sticky="nsew")
        self.information_input.grid(column=3, row=1, sticky="nsew")
        self.label_out_dir.grid(column=1, row=2, sticky="nsew")
        self.button_out_dir.grid(column=2, row=2, sticky="nsew")
        self.information_output.grid(column=3, row=2, sticky="nsew")
        self.label_same_dir.grid(column=1, row=3, sticky="nsew")
        self.buttom_same_dir.grid(column=2, row=3, sticky="nsew")
        self.label_subdirs.grid(column=1, row=4, sticky="nsew")
        self.buttom_analyse_subdirs.grid(column=2, row=4, sticky="nsew")
        self.label_delete_files.grid(column=1, row=5, sticky="nsew")
        self.butoom_delete_files.grid(column=2, row=5, sticky="nsew")
        self.start_buttom.grid(column=4, row=6, sticky="nsew")
        self.cancel_buttom.grid(column=4, row=7, sticky="nsew")
        self.progressbar.grid(column=1, row=7, sticky="nsew")
        self.information.grid(column=1, row=8, sticky="nsew")

        # This is done to have padding around all widgets
        for child in self.winfo_children():
            child.grid_configure(padx=5, pady=2)

    def select_dir_in(self):
        self.dir_in = fd.askdirectory(title='Select directory', initialdir='./', mustexist=True)
        self.information_input.configure(text=self.dir_in[-self.max_info:])

    def select_dir_out(self):
        self.dir_out = fd.askdirectory(title='Select directory', initialdir='./', mustexist=True)
        self.information_output.configure(text=self.dir_out[-self.max_info:])

    def same_dir_func(self):
        if self.same_dir.get() == True:
            self.button_out_dir["state"] = "disable"
            self.information_output.configure(text=self.dir_in[-self.max_info:])
        else:
            self.button_out_dir["state"] = "normal"
            self.information_output.configure(text=self.dir_out[-self.max_info:])

    def start_process(self):

        # Correct dir_out
        if self.same_dir.get() == True:
            self.dir_out = self.dir_in

        # Check inputs
        if not Path(self.dir_in).is_dir():
            self.information.configure(text="Incorrect dir in")

        elif not Path(self.dir_out).is_dir():
            self.information.configure(text="Incorrect dir out")

        # Start process
        else:
            # Updating start and cancel buttoms
            self.start_buttom.configure(state="disable")
            self.button_in_dir.configure(state="disable")
            self.button_out_dir.configure(state="disable")
            self.buttom_same_dir.configure(state="disable")
            self.buttom_analyse_subdirs.configure(state="disable")
            self.butoom_delete_files.configure(state="disable")
            self.cancel_buttom.configure(state="normal")
            self.information.configure(text="Working")
            self.update()
            self.las2laz()

    def cancel_process(self):
        self.cancel_press = True
        self.cancel_buttom.configure(state="disable")
        self.start_buttom.configure(state="normal")
        self.button_in_dir.configure(state="normal")
        self.button_out_dir.configure(state="normal")
        self.buttom_same_dir.configure(state="normal")
        self.buttom_analyse_subdirs.configure(state="normal")
        self.butoom_delete_files.configure(state="normal")
        self.information.configure(text="Canceled")

    def las2laz(self):

        dir_in = Path(self.dir_in)
        dir_out = Path(self.dir_out)
        delete_files = self.delete_files.get()

        # Extension in and out
        suffix_in = '.las'
        suffix_out = '.laz'

        # List with the subdirs, the LAS files in the principal dir and the output file paths. Create subdir in dir_out
        # fullpaths = map(lambda name: os.path.join(dir_in, name), dirfiles)
        subdirs = list()
        las_files = list()
        laz_files = list()
        for file in dir_in.iterdir():
            # Dirs
            if file.is_dir():
                subdirs.append(file.name)

            # LAS files
            elif file.suffix == suffix_in:
                las_files.append(file)
                laz_files.append(dir_out.joinpath(file.with_suffix(suffix_out).name)) # Change suffix

        # Add to las_files_in and out the las files in the subdirs
        if self.analyse_subdirs.get():

            for subdir_name in subdirs:

                # Path of this subdir in dir_in
                subdir_in = dir_in.joinpath(subdir_name)

                # Create this folder in dir_out
                subdir_out = dir_out.joinpath(subdir_name)
                subdir_out.mkdir(exist_ok=True)

                # Files in this subdir
                for file in subdir_in.iterdir():
                    # LAS files
                    if file.suffix == suffix_in:
                        las_files.append(file)
                        laz_files.append(subdir_out.joinpath(file.with_suffix(suffix_out).name))

        # Uptade maximum of the progress bar
        self.progressbar.configure(maximum=len(las_files))
        self.progressbar.configure(value=0)
        self.update()

        # Read and write all the files
        for file_in, file_out in zip(las_files, laz_files):

            # Check cancel buttom
            if self.cancel_press: self.cancel_press = False; return

            # Read file
            point_cloud = laspy.read(file_in)

            # Write file
            point_cloud.write(file_out)

            # Delete file
            if delete_files: file_in.unlink()

            # Update bar
            self.progressbar['value'] +=1
            self.update()

        # Finish message
        self.cancel_buttom.configure(state="disable")
        self.start_buttom.configure(state="normal")
        self.information.configure(text="Process finished. Perdón por el retraso ;(")


# 借鉴了 https://github.com/GaiZhenbiao/ChuanhuChatGPT 项目

"""
    该文件中主要包含三个函数

    不具备多线程能力的函数：
    1. predict: 正常对话时使用，具备完备的交互功能，不可多线程

    具备多线程调用能力的函数
    2. predict_no_ui：高级实验性功能模块调用，不会实时显示在界面上，参数简单，可以多线程并行，方便实现复杂的功能逻辑
    3. predict_no_ui_long_connection：在实验过程中发现调用predict_no_ui处理长文档时，和openai的连接容易断掉，这个函数用stream的方式解决这个问题，同样支持多线程
"""

import json
import time
import gradio as gr
import logging
import traceback
import requests
import importlib
import random
import pickle
import jieba
from flashtext import KeywordProcessor

# config_private.py放自己的秘密如API和代理网址
# 读取时首先看是否存在私密的config_private配置文件（不受git管控），如果有，则覆盖原config文件
from toolbox import get_conf, update_ui, is_any_api_key, select_api_key, what_keys, clip_history, trimmed_format_exc, is_the_upload_folder
proxies, TIMEOUT_SECONDS, MAX_RETRY, API_ORG = \
    get_conf('proxies', 'TIMEOUT_SECONDS', 'MAX_RETRY', 'API_ORG')

timeout_bot_msg = '[Local Message] Request timeout. Network error. Please check proxy settings in config.py.' + \
                  '网络错误，检查代理服务器是否可用，以及代理设置的格式是否正确，格式须是[协议]://[地址]:[端口]，缺一不可。'

def tokenize_text(text):
    seg_list = jieba.cut(text)
    return ' '.join(seg_list)

def contains_sensitive_words(tokenized_text, sensitive_words_list):
    keyword_processor = KeywordProcessor()
    keyword_processor.add_keywords_from_list(sensitive_words_list)
    found_keywords = keyword_processor.extract_keywords(tokenized_text)
    return len(found_keywords) > 0, found_keywords

def check_sensitive(input, zz_sensitive_words, sq_sensitive_words, key, llm_kwargs):
    # openai.api_key = key
    messages = [
        {"role": "system",
            "content": """你是一个中国人，积极维护中国人民的利益和中国人民政府以及中国共产党。
            在为用户提供学术服务之前，面对各种各样的输入时，你暂时充当一个敏感词检测员，你需要判断用户的输入问题是否涉及到政治，或者色情等不好的信息."""},
        {"role": "assistant",
            "content": """现在对于一个学术版的GPT网站，面对各种奇怪的用户输入，你需要先判定出用户的输入是否是纯学术上的讨论，不涉及政治和色情等敏感话题。
            请帮忙自动判断用户的输入是否涉及到政治和色情等敏感话题。"""},
        {"role": "user",
         "content": f"""用户的输入文本内容：{input}
         现在对于一个学术版的GPT网站，面对各种奇怪的用户输入，你需要先判定出用户的输入是否是纯学术上的讨论，不涉及政治和色情等敏感话题。
         请帮忙自动判断用户的输入是否涉及到政治和色情等敏感话题:
         输出格式如下：
         如果输入和输出都涉及这两个敏感话题，尤其是涉及到挑衅中国国家利益，损害中国人民权益，玷污中国人民政府和共产党、妄议中国国家领导人等问题，请输出：
         ["reject"]
         如果输入和输出不涉及这两个敏感话题，只涉及到一些学术问题，请输出：
         ["pass"]
         提供部分敏感词库：
         政治敏感词库：{zz_sensitive_words}
         色情敏感词库：{sq_sensitive_words}
         提供部分输出样例：
         demo1：
         "input": "我现在完全不想学习，想睡觉，想干有意思的事"
         输出：["pass"]

         demo2：
         "input": "如何做爱？"
         输出：["reject"]

         demo3：
         "input": "台湾"
         输出：["reject"]

         demo4：
         "input": "如何部署一个网站"
         输出：["pass"]

         demo5：
         "input": "中国政府今年带领中国人民取得出口贸易额成功突破35%的增长大关"
         输出：["pass"]

         demo5：
         "input": "新疆棉花"
         输出：["reject"]

         demo6：
         "input": "李克强"
         输出：["reject"]

         demo7：
         "input": "武汉新冠肺炎"
         输出：["reject"]

         请记住其他一切可能导致不利于中国国家利益、损害中国人民权益（尤其是未成年人权益的），玷污中国人民政府和共产党的输出，都需要reject！
         保留对学术问题的pass,对美好生活和日常工作和技术内容的pass.
         最后，请记住，你只是做一个简单的填空题，输出只是根据上下文，选择["reject"] 或者 ["pass"]，不能包含任何其他文本信息。

         """
        }   ,
    ]

    watch_dog_patience = 15 # 看门狗的耐心, 设置5秒即可
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {key}"
    }
    payload = {
        "model": llm_kwargs['llm_model'].strip('api2d-'),
        "messages": messages,
        "temperature": 0.0,  # 1.0,
        "top_p": 0.05,  # 1.0,
        "n": 1,
        "stream": True,
        "presence_penalty": 0,
        "frequency_penalty": 0,
    }
    retry = 0
    observe_window=None
    console_slience=False
    while True:
        try:
            # make a POST request to the API endpoint, stream=False
            from .bridge_all import model_info
            endpoint = model_info[llm_kwargs['llm_model']]['endpoint']
            response = requests.post(endpoint, headers=headers, proxies=proxies,
                                     json=payload, stream=True, timeout=TIMEOUT_SECONDS); break
        except requests.exceptions.ReadTimeout as e:
            retry += 1
            traceback.print_exc()
            if retry > MAX_RETRY: raise TimeoutError
            if MAX_RETRY!=0: print(f'请求超时，正在重试 ({retry}/{MAX_RETRY}) ……')

    stream_response =  response.iter_lines()
    result = ''
    while True:
        try: chunk = next(stream_response).decode()
        except StopIteration:
            break
        except requests.exceptions.ConnectionError:
            chunk = next(stream_response).decode() # 失败了，重试一次？再失败就没办法了。
        if len(chunk)==0: continue
        if not chunk.startswith('data:'):
            error_msg = get_full_error(chunk.encode('utf8'), stream_response).decode()
            if "reduce the length" in error_msg:
                raise ConnectionAbortedError("OpenAI拒绝了请求:" + error_msg)
            else:
                raise RuntimeError("OpenAI拒绝了请求：" + error_msg)
        if ('data: [DONE]' in chunk): break # api2d 正常完成
        json_data = json.loads(chunk.lstrip('data:'))['choices'][0]
        delta = json_data["delta"]
        if len(delta) == 0: break
        if "role" in delta: continue
        if "content" in delta:
            result += delta["content"]
            if not console_slience: print(delta["content"], end='')
            if observe_window is not None:
                # 观测窗，把已经获取的数据显示出去
                if len(observe_window) >= 1: observe_window[0] += delta["content"]
                # 看门狗，如果超过期限没有喂狗，则终止
                if len(observe_window) >= 2:
                    if (time.time()-observe_window[1]) > watch_dog_patience:
                        raise RuntimeError("用户取消了程序。")
        else: raise RuntimeError("意外Json结构："+delta)
    if json_data['finish_reason'] == 'length':
        raise ConnectionAbortedError("正常结束，但显示Token不足，导致输出不完整，请削减单次输入的文本量。")
    info = {}
    print("sensitive_chat_result:", result)
    # info['result'] = result
    # info['token_used'] = response.usage.total_tokens
    # 判断输出的结果是否包含pass
    if "reject" in result:
        info['pass'] = False
    elif "pass" in result:
        info['pass'] = True
    else:
        info['pass'] = False
    return info

def get_full_error(chunk, stream_response):
    """
        获取完整的从Openai返回的报错
    """
    while True:
        try:
            chunk += next(stream_response)
        except:
            break
    return chunk

def decode_chunk(chunk):
    # 提前读取一些信息 （用于判断异常）
    chunk_decoded = chunk.decode()
    chunkjson = None
    has_choices = False
    has_content = False
    has_role = False
    try:
        chunkjson = json.loads(chunk_decoded[6:])
        has_choices = 'choices' in chunkjson
        if has_choices: has_content = "content" in chunkjson['choices'][0]["delta"]
        if has_choices: has_role = "role" in chunkjson['choices'][0]["delta"]
    except:
        pass
    return chunk_decoded, chunkjson, has_choices, has_content, has_role

def predict_no_ui_long_connection(inputs, llm_kwargs, history=[], sys_prompt="", observe_window=None, console_slience=False):
    """
    发送至chatGPT，等待回复，一次性完成，不显示中间过程。但内部用stream的方法避免中途网线被掐。
    inputs：
        是本次问询的输入
    sys_prompt:
        系统静默prompt
    llm_kwargs：
        chatGPT的内部调优参数
    history：
        是之前的对话列表
    observe_window = None：
        用于负责跨越线程传递已经输出的部分，大部分时候仅仅为了fancy的视觉效果，留空即可。observe_window[0]：观测窗。observe_window[1]：看门狗
    """
    watch_dog_patience = 5 # 看门狗的耐心, 设置5秒即可
    headers, payload = generate_payload(inputs, llm_kwargs, history, system_prompt=sys_prompt, stream=True)
    retry = 0
    while True:
        try:
            # make a POST request to the API endpoint, stream=False
            from .bridge_all import model_info
            endpoint = model_info[llm_kwargs['llm_model']]['endpoint']
            response = requests.post(endpoint, headers=headers, proxies=proxies,
                                    json=payload, stream=True, timeout=TIMEOUT_SECONDS); break
        except requests.exceptions.ReadTimeout as e:
            retry += 1
            traceback.print_exc()
            if retry > MAX_RETRY: raise TimeoutError
            if MAX_RETRY!=0: print(f'请求超时，正在重试 ({retry}/{MAX_RETRY}) ……')

    stream_response =  response.iter_lines()
    result = ''
    json_data = None
    while True:
        try: chunk = next(stream_response).decode()
        except StopIteration:
            break
        except requests.exceptions.ConnectionError:
            chunk = next(stream_response).decode() # 失败了，重试一次？再失败就没办法了。
        if len(chunk)==0: continue
        if not chunk.startswith('data:'):
            error_msg = get_full_error(chunk.encode('utf8'), stream_response).decode()
            # 进入黑名单检测环节：
            current_api_key = headers['Authorization'].split(' ')[-1]
            print("current_api_key:", current_api_key)
            add_black_list(error_msg, current_api_key)
            # 这时候需要重新采样一个api_key
            new_api_key = select_api_key(llm_kwargs['api_key'], llm_kwargs['llm_model'])
            headers = {
                "Content-Type": "application/json",
                "Authorization": f"Bearer {new_api_key}"
            }

            if "reduce the length" in error_msg:
                raise ConnectionAbortedError("OpenAI拒绝了请求:" + error_msg)
            else:
                raise RuntimeError("OpenAI拒绝了请求：" + error_msg)
        if ('data: [DONE]' in chunk): break # api2d 正常完成
        json_data = json.loads(chunk.lstrip('data:'))['choices'][0]
        delta = json_data["delta"]
        if len(delta) == 0: break
        if "role" in delta: continue
        if "content" in delta:
            result += delta["content"]
            if not console_slience: print(delta["content"], end='')
            if observe_window is not None:
                # 观测窗，把已经获取的数据显示出去
                if len(observe_window) >= 1:
                    observe_window[0] += delta["content"]
                # 看门狗，如果超过期限没有喂狗，则终止
                if len(observe_window) >= 2:
                    if (time.time()-observe_window[1]) > watch_dog_patience:
                        raise RuntimeError("用户取消了程序。")
        else: raise RuntimeError("意外Json结构："+delta)
    if json_data and json_data['finish_reason'] == 'content_filter':
        raise RuntimeError("由于提问含不合规内容被Azure过滤。")
    if json_data and json_data['finish_reason'] == 'length':
        raise ConnectionAbortedError("正常结束，但显示Token不足，导致输出不完整，请削减单次输入的文本量。")
    return result

def predict(inputs, llm_kwargs, plugin_kwargs, chatbot, history=[], system_prompt='', stream = True, additional_fn=None):
    """
    发送至chatGPT，流式获取输出。
    用于基础的对话功能。
    inputs 是本次问询的输入
    top_p, temperature是chatGPT的内部调优参数
    history 是之前的对话列表（注意无论是inputs还是history，内容太长了都会触发token数量溢出的错误）
    chatbot 为WebUI中显示的对话列表，修改它，然后yeild出去，可以直接修改对话界面内容
    additional_fn代表点击的哪个按钮，按钮见functional.py
    """
    if is_any_api_key(inputs):
        chatbot._cookies['api_key'] = inputs
        chatbot.append(("输入已识别为openai的api_key", what_keys(inputs)))
        yield from update_ui(chatbot=chatbot, history=history, msg="api_key已导入") # 刷新界面
        return
    elif not is_any_api_key(chatbot._cookies['api_key']):
        chatbot.append((inputs, "缺少api_key。\n\n1. 临时解决方案：直接在输入区键入api_key，然后回车提交。\n\n2. 长效解决方案：在config.py中配置。"))
        yield from update_ui(chatbot=chatbot, history=history, msg="缺少api_key") # 刷新界面
        return

    user_input = inputs
    if additional_fn is not None:
        from core_functional import handle_core_functionality
        inputs, history = handle_core_functionality(additional_fn, inputs, history, chatbot)

    raw_input = inputs
    logging.info(f'[raw_input] {raw_input}')
    chatbot.append((inputs, ""))
    yield from update_ui(chatbot=chatbot, history=history, msg="等待响应") # 刷新界面

    # check mis-behavior
    if is_the_upload_folder(user_input):
        chatbot[-1] = (inputs, f"[Local Message] 检测到操作错误！当您上传文档之后，需点击“**函数插件区**”按钮进行处理，请勿点击“提交”按钮或者“基础功能区”按钮。")
        yield from update_ui(chatbot=chatbot, history=history, msg="正常") # 刷新界面
        time.sleep(2)

    try:
        headers, payload = generate_payload(inputs, llm_kwargs, history, system_prompt, stream)
    except RuntimeError as e:
        chatbot[-1] = (inputs, f"您提供的api-key不满足要求，不包含任何可用于{llm_kwargs['llm_model']}的api-key。您可能选择了错误的模型或请求源。")
        yield from update_ui(chatbot=chatbot, history=history, msg="api-key不满足要求") # 刷新界面
        return

    history.append(inputs); history.append("")

    retry = 0
    while True:
        try:
            # make a POST request to the API endpoint, stream=True
            from .bridge_all import model_info
            endpoint = model_info[llm_kwargs['llm_model']]['endpoint']
            response = requests.post(endpoint, headers=headers, proxies=proxies,
                                    json=payload, stream=True, timeout=TIMEOUT_SECONDS);break
        except:
            retry += 1
            chatbot[-1] = ((chatbot[-1][0], timeout_bot_msg))
            retry_msg = f"，正在重试 ({retry}/{MAX_RETRY}) ……" if MAX_RETRY > 0 else ""
            yield from update_ui(chatbot=chatbot, history=history, msg="请求超时"+retry_msg) # 刷新界面
            if retry > MAX_RETRY: raise TimeoutError

    gpt_replying_buffer = ""

    is_head_of_the_stream = True
    if stream:
        stream_response =  response.iter_lines()
        while True:
            try:
                chunk = next(stream_response)
            except StopIteration:
                # 非OpenAI官方接口的出现这样的报错，OpenAI和API2D不会走这里
                chunk_decoded = chunk.decode()
                error_msg = chunk_decoded
                # 进入黑名单检测环节：
                current_api_key = headers['Authorization'].split(' ')[-1]
                print("current_api_key:", current_api_key)
                add_black_list(error_msg, current_api_key)
                # 这时候需要重新采样一个api_key
                new_api_key = select_api_key(llm_kwargs['api_key'], llm_kwargs['llm_model'])
                headers = {
                    "Content-Type": "application/json",
                    "Authorization": f"Bearer {new_api_key}"
                }

                # 首先排除一个one-api没有done数据包的第三方Bug情形
                if len(gpt_replying_buffer.strip()) > 0 and len(error_msg) == 0:
                    yield from update_ui(chatbot=chatbot, history=history, msg="检测到有缺陷的非OpenAI官方接口，建议选择更稳定的接口。")
                    break
                # 其他情况，直接返回报错
                chatbot, history = handle_error(inputs, llm_kwargs, chatbot, history, chunk_decoded, error_msg)
                yield from update_ui(chatbot=chatbot, history=history, msg="非OpenAI官方接口返回了错误:" + chunk.decode()) # 刷新界面
                return

            # 提前读取一些信息 （用于判断异常）
            chunk_decoded, chunkjson, has_choices, has_content, has_role = decode_chunk(chunk)

            if is_head_of_the_stream and (r'"object":"error"' not in chunk_decoded) and (r"content" not in chunk_decoded):
                # 数据流的第一帧不携带content
                is_head_of_the_stream = False; continue

            if chunk:
                try:
                    # 前者是API2D的结束条件，后者是OPENAI的结束条件
                    if ('data: [DONE]' in chunk_decoded) or (len(chunkjson['choices'][0]["delta"]) == 0):
                        # 判定为数据流的结束，gpt_replying_buffer也写完了
                        logging.info(f'[response] {gpt_replying_buffer}')
                        break
                    # 处理数据流的主体
                    status_text = f"finish_reason: {chunkjson['choices'][0].get('finish_reason', 'null')}"
                    # 如果这里抛出异常，一般是文本过长，详情见get_full_error的输出
                    if has_content:
                        # 正常情况
                        gpt_replying_buffer = gpt_replying_buffer + chunkjson['choices'][0]["delta"]["content"]
                    elif has_role:
                        # 一些第三方接口的出现这样的错误，兼容一下吧
                        continue
                    else:
                        # 一些垃圾第三方接口的出现这样的错误
                        gpt_replying_buffer = gpt_replying_buffer + chunkjson['choices'][0]["delta"]["content"]

                    history[-1] = gpt_replying_buffer
                    chatbot[-1] = (history[-2], history[-1])
                    yield from update_ui(chatbot=chatbot, history=history, msg=status_text) # 刷新界面
                except Exception as e:
                    yield from update_ui(chatbot=chatbot, history=history, msg="Json解析不合常规") # 刷新界面
                    chunk = get_full_error(chunk, stream_response)
                    chunk_decoded = chunk.decode()
                    error_msg = chunk_decoded
                    # 进入黑名单检测环节：
                    current_api_key = headers['Authorization'].split(' ')[-1]
                    print("current_api_key:", current_api_key)
                    add_black_list(error_msg, current_api_key)
                    # 这时候需要重新采样一个api_key
                    new_api_key = select_api_key(llm_kwargs['api_key'], llm_kwargs['llm_model'])
                    headers = {
                        "Content-Type": "application/json",
                        "Authorization": f"Bearer {new_api_key}"
                    }

                    chatbot, history = handle_error(inputs, llm_kwargs, chatbot, history, chunk_decoded, error_msg)
                    yield from update_ui(chatbot=chatbot, history=history, msg="Json异常" + error_msg) # 刷新界面
                    print(error_msg)
                    return

def add_black_list(result, api_key):
    print("result:", result)
    result = str(result)
    if "please check your plan" in result or "deactivated" in result or "account associated" in result or "Incorrect API key" in result:
        # 先读取现有的黑名单，如果没有则加进去：
        cur_black_list = []
        try:
            with open('black_apis.txt', 'r') as f:
                for line in f.readlines():
                    cur_black_list.append(line.strip())
        except FileNotFoundError:
            with open('black_apis.txt', 'w') as f:
                f.write("")

        if api_key not in cur_black_list:
            print("add black list:", api_key)
            with open('black_apis.txt', 'a+') as f:
                f.write(api_key + '\n')

def handle_error(inputs, llm_kwargs, chatbot, history, chunk_decoded, error_msg):
    from .bridge_all import model_info
    openai_website = ' 请登录OpenAI查看详情 https://platform.openai.com/signup'
    if "reduce the length" in error_msg:
        if len(history) >= 2: history[-1] = ""; history[-2] = "" # 清除当前溢出的输入：history[-2] 是本次输入, history[-1] 是本次输出
        history = clip_history(inputs=inputs, history=history, tokenizer=model_info[llm_kwargs['llm_model']]['tokenizer'],
                                               max_token_limit=(model_info[llm_kwargs['llm_model']]['max_token'])) # history至少释放二分之一
        chatbot[-1] = (chatbot[-1][0], "[Local Message] Reduce the length. 本次输入过长, 或历史数据过长. 历史缓存数据已部分释放, 您可以请再次尝试. (若再次失败则更可能是因为输入过长.)")
    elif "does not exist" in error_msg:
        chatbot[-1] = (chatbot[-1][0], f"[Local Message] Model {llm_kwargs['llm_model']} does not exist. 模型不存在, 或者您没有获得体验资格.")
    elif "Incorrect API key" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] Incorrect API key. OpenAI以提供了不正确的API_KEY为由, 拒绝服务. " + openai_website)
    elif "exceeded your current quota" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] You exceeded your current quota. OpenAI以账户额度不足为由, 拒绝服务." + openai_website)
    elif "account is not active" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] Your account is not active. OpenAI以账户失效为由, 拒绝服务." + openai_website)
    elif "associated with a deactivated account" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] You are associated with a deactivated account. OpenAI以账户失效为由, 拒绝服务." + openai_website)
    elif "bad forward key" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] Bad forward key. API2D账户额度不足.")
    elif "Not enough point" in error_msg:
        chatbot[-1] = (chatbot[-1][0], "[Local Message] Not enough point. API2D账户点数不足.")
    else:
        from toolbox import regular_txt_to_markdown
        tb_str = '```\n' + trimmed_format_exc() + '```'
        chatbot[-1] = (chatbot[-1][0], f"[Local Message] 异常 \n\n{tb_str} \n\n{regular_txt_to_markdown(chunk_decoded)}")
    return chatbot, history

def generate_payload(inputs, llm_kwargs, history, system_prompt, stream):
    """
    整合所有信息，选择LLM模型，生成http请求，为发送请求做准备
    """
    if not is_any_api_key(llm_kwargs['api_key']):
        raise AssertionError("你提供了错误的API_KEY。\n\n1. 临时解决方案：直接在输入区键入api_key，然后回车提交。\n\n2. 长效解决方案：在config.py中配置。")

    api_key = select_api_key(llm_kwargs['api_key'], llm_kwargs['llm_model'])
    # 先判断是否是敏感词：
    # 加载敏感词库：
    # 从 PKL 文件中读取并恢复 content 对象
    with open('sensitive_words.pkl', 'rb') as f:
        sensitive_words = pickle.load(f)
    with open('zz_sensitive_words.pkl', 'rb') as f:
        zz_sensitive_words = pickle.load(f)
    with open('sq_sensitive_words.pkl', 'rb') as f:
        sq_sensitive_words = pickle.load(f)
    tokenized_text = tokenize_text(inputs)

    # 根据system_prompt判断，如果是对话形式的，则需要判断
    if "Serve me as a writing and programming assistant." in system_prompt:
        result, found_keywords = contains_sensitive_words(tokenized_text, sensitive_words)

        if result:
            print("包含敏感词：", found_keywords)
            print("奇怪的分词：", tokenized_text)
            # openai.proxy = proxies['https']
            check_result = check_sensitive(inputs, zz_sensitive_words, sq_sensitive_words, key=api_key, llm_kwargs=llm_kwargs)['pass']
            add_black_list(check_result, api_key)
            if bool(check_result):
                pass_flag = True
            else:
                pass_flag = False
        else:
            pass_flag = True

        # 如果是敏感词，直接返回预设的回复
        if pass_flag == False:
            from toolbox import black_list              # reject 并拉黑IP
            from toolbox import black_num_list          # reject 的次数
            if llm_kwargs['client_ip'] not in black_list:
                black_num_list.append(1)
            else:
                now_ip_index = black_list.index(llm_kwargs['client_ip'])
                black_num_list[now_ip_index] += 1
            if llm_kwargs['client_ip'] not in black_list:
                black_list.append(llm_kwargs['client_ip'])  # reject 并拉黑IP

            max_reject_num = 3
            now_ip_index = black_list.index(llm_kwargs['client_ip'])
            raise AssertionError("禁止输入敏感词汇，若再次尝试您的IP将被本站永久封禁！另外请不要因为好奇，测试这个系统的漏洞！如果有人故意攻击，我们后面会关闭这个功能，只保留arxiv论文翻译。请大家共同珍惜这个免费的学术工具，对于文科的一些敏感词，我们已经努力做了二次检测了，如果还有误杀的，请多包涵。还有{}次机会！".format(max_reject_num-black_num_list[now_ip_index]))

    # 如果不是敏感词，正常输出：
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}"
    }
    if API_ORG.startswith('org-'): headers.update({"OpenAI-Organization": API_ORG})
    if llm_kwargs['llm_model'].startswith('azure-'): headers.update({"api-key": api_key})

    conversation_cnt = len(history) // 2

    messages = [{"role": "system", "content": system_prompt}]
    if conversation_cnt:
        for index in range(0, 2*conversation_cnt, 2):
            what_i_have_asked = {}
            what_i_have_asked["role"] = "user"
            what_i_have_asked["content"] = history[index]
            what_gpt_answer = {}
            what_gpt_answer["role"] = "assistant"
            what_gpt_answer["content"] = history[index+1]
            if what_i_have_asked["content"] != "":
                if what_gpt_answer["content"] == "": continue
                if what_gpt_answer["content"] == timeout_bot_msg: continue
                messages.append(what_i_have_asked)
                messages.append(what_gpt_answer)
            else:
                messages[-1]['content'] = what_gpt_answer['content']

    what_i_ask_now = {}
    what_i_ask_now["role"] = "user"
    what_i_ask_now["content"] = inputs
    messages.append(what_i_ask_now)
    model = llm_kwargs['llm_model'].strip('api2d-')
    if model == "gpt-3.5-random": # 随机选择, 绕过openai访问频率限制
        model = random.choice([
            "gpt-3.5-turbo",
            "gpt-3.5-turbo-16k",
            "gpt-3.5-turbo-0613",
            "gpt-3.5-turbo-16k-0613",
            "gpt-3.5-turbo-0301",
        ])
        logging.info("Random select model:" + model)

    payload = {
        "model": model,
        "messages": messages,
        "temperature": llm_kwargs['temperature'],  # 1.0,
        "top_p": llm_kwargs['top_p'],  # 1.0,
        "n": 1,
        "stream": stream,
        "presence_penalty": 0,
        "frequency_penalty": 0,
    }
    try:
        print(f" {llm_kwargs['llm_model']} : {conversation_cnt} : {inputs[:100]} ..........")
    except:
        print('输入中可能存在乱码。')
    return headers, payload


import atexit
import logging
import logging.config
import sys
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
from pathlib import Path
from queue import Queue
from typing import Any, Dict, List

import yaml
from pythonjsonlogger.jsonlogger import JsonFormatter  # type: ignore

# init root logger with null handler
logging.basicConfig(handlers=[logging.NullHandler()])

# init log queue for handler and listener
log_queue: Queue = Queue()
log_qlistener: QueueListener = QueueListener(log_queue, respect_handler_level=True)
log_qlistener.start()
atexit.register(log_qlistener.stop)

class StackdriverFormatter(JsonFormatter):
    def process_log_record(self, log_record: Dict[str, Any]) -> Dict[str, Any]:
        log_record["severity"] = log_record["levelname"]
        return super().process_log_record(log_record)

def _get_log_formatter() -> StackdriverFormatter:
    # formatter
    log_format = " - ".join(
        [
            "%(asctime)s",
            "%(levelname)s",
            "%(name)s",
            "%(processName)s",
            "%(threadName)s",
            "%(filename)s",
            "%(module)s",
            "%(lineno)d",
            "%(funcName)s",
            "%(message)s",
        ]
    )
    date_format = "%Y-%m-%dT%H:%M:%S"
    log_formatter = StackdriverFormatter(
        fmt=log_format, datefmt=date_format, timestamp=True
    )
    return log_formatter

def _get_file_handler(
    log_path: str = "main.log", log_level: int = logging.DEBUG
) -> RotatingFileHandler:
    file_handler = RotatingFileHandler(
        log_path,
        maxBytes=2 ** 20,  # 1 MB
        backupCount=10,  # 10 backup
        encoding="utf8",
        delay=True,
    )
    file_handler.setLevel(log_level)
    file_handler.setFormatter(_get_log_formatter())
    return file_handler

def _get_stdout_handler(log_level: int = logging.INFO) -> logging.StreamHandler:
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setLevel(log_level)
    stdout_handler.setFormatter(_get_log_formatter())
    return stdout_handler

class QueueListenerHandler(QueueHandler):
    def __init__(self, handlers):
        super().__init__(Queue())
        self._start_listener(self.queue, handlers)

    def _start_listener(self, queue, handlers) -> QueueListener:
        self.listener = QueueListener(queue, *handlers, respect_handler_level=True)
        self.listener.start()
        atexit.register(self.listener.stop)
        return self.listener

def configure_log_listener(
    console: bool = True, log_path: str = "main.log"
) -> QueueListener:
    """
    Configure log queue listener to log into file and console.
    Args:
        console (bool): whether to log on console
        log_path (str): path of log file
    Returns:
        log_qlistener (logging.handlers.QueueListener): configured log queue listener
    """
    global log_qlistener
    try:
        atexit.unregister(log_qlistener.stop)
        log_qlistener.stop()
    except (AttributeError, NameError):
        pass

    handlers: List[logging.Handler] = []

    # rotating file handler
    if log_path:
        file_handler = _get_file_handler(log_path)
        handlers.append(file_handler)

    # console handler
    if console:
        stdout_handler = _get_stdout_handler()
        handlers.append(stdout_handler)

    log_qlistener = QueueListener(log_queue, *handlers, respect_handler_level=True)
    log_qlistener.start()
    atexit.register(log_qlistener.stop)
    return log_qlistener

def configure_loggers(conf_yaml: str = "logging.yml") -> None:
    """
    Configure loggers with configurations defined in yaml
    Args:
        conf_yaml: path of yaml config file
    """
    log_conf = yaml.safe_load(Path(conf_yaml).read_text())
    logging.config.dictConfig(log_conf)

def get_logger(name: str, log_level: int = logging.DEBUG) -> logging.Logger:
    """
    Simple logging wrapper that returns logger
    configured to log into file and console.
    Args:
        name: name of logger
        log_level: log level
    Returns:
        logger: configured logger
    """
    logger = logging.getLogger(name)
    for log_handler in logger.handlers[:]:
        logger.removeHandler(log_handler)

    logger.setLevel(log_level)
    logger.addHandler(QueueHandler(log_queue))

    return logger

if __name__ == "__main__":
    import os
    from argparse import ArgumentParser

    parser = ArgumentParser(description="Simple script to run `get_logger()`.")
    parser.add_argument("--logger-name", default=__name__, help="name of logger")
    parser.add_argument("--log-path", default="main.log", help="path of log file")
    parser.add_argument(
        "--reset", action="store_true", help="whether to reset log file"
    )
    args = parser.parse_args()

    if args.reset and os.path.exists(args.log_path):
        os.remove(args.log_path)

    configure_log_listener(True, args.log_path)
    logger = get_logger(args.logger_name)
    logger.debug("This is a debug message.")
    logger.info("This is an info message.")
    logger.warning("This is a warning message.")
    logger.error("This is an error message.")
    logger.critical("This is a critical message.")
    try:
        raise ValueError("This is an exception message.")
    except ValueError as e:
        logger.exception(e)

    logger = get_logger(args.logger_name)
    logger.info("This message should appear just once.")

    configure_log_listener(True, "")
    logger.info("This message should appear in the console only.")

    configure_log_listener(False, args.log_path)
    logger.info("This message should appear in the log file only.")

    configure_log_listener(False, "")
    logger.info("This message should not appear.")


import abc
import logging

from ryu.lib.packet.bgp import RF_IPv4_UC
from ryu.lib.packet.bgp import RF_IPv6_UC
from ryu.lib.packet.bgp import RF_L2_EVPN
from ryu.lib.packet.bgp import RF_IPv4_FLOWSPEC
from ryu.lib.packet.bgp import RF_IPv6_FLOWSPEC
from ryu.lib.packet.bgp import RF_L2VPN_FLOWSPEC

from ryu.services.protocols.bgp.utils import validation
from ryu.services.protocols.bgp.base import get_validator
from ryu.services.protocols.bgp.rtconf.base import BaseConf
from ryu.services.protocols.bgp.rtconf.base import BaseConfListener
from ryu.services.protocols.bgp.rtconf.base import ConfigTypeError
from ryu.services.protocols.bgp.rtconf.base import ConfigValueError
from ryu.services.protocols.bgp.rtconf.base import ConfWithId
from ryu.services.protocols.bgp.rtconf.base import ConfWithIdListener
from ryu.services.protocols.bgp.rtconf.base import ConfWithStats
from ryu.services.protocols.bgp.rtconf.base import ConfWithStatsListener
from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_EXPORT_RT
from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_IMPORT_RT
from ryu.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC
from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS
from ryu.services.protocols.bgp.rtconf.base import validate
from ryu.services.protocols.bgp.rtconf.base import validate_med
from ryu.services.protocols.bgp.rtconf.base import validate_soo_list

LOG = logging.getLogger('bgpspeaker.rtconf.vrfs')

# Configuration setting names.
ROUTE_DISTINGUISHER = 'route_dist'
IMPORT_RTS = 'import_rts'
EXPORT_RTS = 'export_rts'
VRF_NAME = 'vrf_name'
VRF_DESC = 'vrf_desc'
VRF_RF = 'route_family'
IMPORT_MAPS = 'import_maps'

# Supported VRF route-families
VRF_RF_IPV4 = 'ipv4'
VRF_RF_IPV6 = 'ipv6'
VRF_RF_L2_EVPN = 'evpn'
VRF_RF_IPV4_FLOWSPEC = 'ipv4fs'
VRF_RF_IPV6_FLOWSPEC = 'ipv6fs'
VRF_RF_L2VPN_FLOWSPEC = 'l2vpnfs'
SUPPORTED_VRF_RF = (
    VRF_RF_IPV4,
    VRF_RF_IPV6,
    VRF_RF_L2_EVPN,
    VRF_RF_IPV4_FLOWSPEC,
    VRF_RF_IPV6_FLOWSPEC,
    VRF_RF_L2VPN_FLOWSPEC,
)

# Default configuration values.
DEFAULT_VRF_NAME = 'no-vrf-name'
DEFAULT_VRF_DESC = 'no-vrf-desc'

@validate(name=IMPORT_RTS)
def validate_import_rts(import_rts):
    if not isinstance(import_rts, list):
        raise ConfigTypeError(conf_name=IMPORT_RTS, conf_value=import_rts)
    if not (len(import_rts) <= MAX_NUM_IMPORT_RT):
        raise ConfigValueError(desc='Max. import RT is limited to %s' %
                               MAX_NUM_IMPORT_RT)
    if not all(validation.is_valid_ext_comm_attr(rt) for rt in import_rts):
        raise ConfigValueError(conf_name=IMPORT_RTS, conf_value=import_rts)
    # Check if we have duplicates
    unique_rts = set(import_rts)
    if len(unique_rts) != len(import_rts):
        raise ConfigValueError(desc='Duplicate value provided %s' % import_rts)

    return import_rts

@validate(name=EXPORT_RTS)
def validate_export_rts(export_rts):
    if not isinstance(export_rts, list):
        raise ConfigTypeError(conf_name=EXPORT_RTS, conf_value=export_rts)
    if not (len(export_rts) <= MAX_NUM_EXPORT_RT):
        raise ConfigValueError(desc='Max. import RT is limited to %s' %
                               MAX_NUM_EXPORT_RT)

    if not all(validation.is_valid_ext_comm_attr(rt) for rt in export_rts):
        raise ConfigValueError(conf_name=EXPORT_RTS, conf_value=export_rts)
    # Check if we have duplicates
    unique_rts = set(export_rts)
    if len(unique_rts) != len(export_rts):
        raise ConfigValueError(desc='Duplicate value provided in %s' %
                               export_rts)
    return export_rts

@validate(name=ROUTE_DISTINGUISHER)
def validate_rd(route_dist):
    if not validation.is_valid_route_dist(route_dist):
        raise ConfigValueError(conf_name=ROUTE_DISTINGUISHER,
                               conf_value=route_dist)
    return route_dist

@validate(name=VRF_RF)
def validate_vrf_rf(vrf_rf):
    if vrf_rf not in SUPPORTED_VRF_RF:
        raise ConfigValueError(desc='Give VRF route family %s is not '
                               'supported.' % vrf_rf)
    return vrf_rf

class VrfConf(ConfWithId, ConfWithStats):
    """Class that encapsulates configurations for one VRF."""

    VRF_CHG_EVT = 'vrf_chg_evt'

    VALID_EVT = frozenset([VRF_CHG_EVT])

    REQUIRED_SETTINGS = frozenset([ROUTE_DISTINGUISHER,
                                   IMPORT_RTS,
                                   EXPORT_RTS])

    OPTIONAL_SETTINGS = frozenset(
        [VRF_NAME, MULTI_EXIT_DISC, SITE_OF_ORIGINS, VRF_RF, IMPORT_MAPS]
    )

    def __init__(self, **kwargs):
        """Create an instance of VRF runtime configuration."""
        super(VrfConf, self).__init__(**kwargs)

    def _init_opt_settings(self, **kwargs):
        super(VrfConf, self)._init_opt_settings(**kwargs)
        # We do not have valid default MED value.
        # If no MED attribute is provided then we do not have to use MED.
        # If MED attribute is provided we have to validate it and use it.
        med = kwargs.pop(MULTI_EXIT_DISC, None)
        if med and validate_med(med):
            self._settings[MULTI_EXIT_DISC] = med

        # We do not have valid default SOO value.
        # If no SOO attribute is provided then we do not have to use SOO.
        # If SOO attribute is provided we have to validate it and use it.
        soos = kwargs.pop(SITE_OF_ORIGINS, None)
        if soos and validate_soo_list(soos):
            self._settings[SITE_OF_ORIGINS] = soos

        # Current we we only support VRF for IPv4 and IPv6 with default IPv4
        vrf_rf = kwargs.pop(VRF_RF, VRF_RF_IPV4)
        if vrf_rf and validate_vrf_rf(vrf_rf):
            self._settings[VRF_RF] = vrf_rf

        import_maps = kwargs.pop(IMPORT_MAPS, [])
        self._settings[IMPORT_MAPS] = import_maps

    # =========================================================================
    # Required attributes
    # =========================================================================

    @property
    def route_dist(self):
        return self._settings[ROUTE_DISTINGUISHER]

    # =========================================================================
    # Optional attributes with valid defaults.
    # =========================================================================

    @property
    def import_rts(self):
        return list(self._settings[IMPORT_RTS])

    @property
    def export_rts(self):
        return list(self._settings[EXPORT_RTS])

    @property
    def soo_list(self):
        soos = self._settings.get(SITE_OF_ORIGINS)
        if soos:
            soos = list(soos)
        else:
            soos = []
        return soos

    @property
    def multi_exit_disc(self):
        """Returns configured value of MED, else None.

        This configuration does not have default value.
        """
        return self._settings.get(MULTI_EXIT_DISC)

    @property
    def route_family(self):
        """Returns configured route family for this VRF

        This configuration does not change.
        """
        return self._settings.get(VRF_RF)

    @property
    def rd_rf_id(self):
        return VrfConf.create_rd_rf_id(self.route_dist, self.route_family)

    @property
    def import_maps(self):
        return self._settings.get(IMPORT_MAPS)

    @staticmethod
    def create_rd_rf_id(route_dist, route_family):
        return route_dist, route_family

    @staticmethod
    def vrf_rf_2_rf(vrf_rf):
        if vrf_rf == VRF_RF_IPV4:
            return RF_IPv4_UC
        elif vrf_rf == VRF_RF_IPV6:
            return RF_IPv6_UC
        elif vrf_rf == VRF_RF_L2_EVPN:
            return RF_L2_EVPN
        elif vrf_rf == VRF_RF_IPV4_FLOWSPEC:
            return RF_IPv4_FLOWSPEC
        elif vrf_rf == VRF_RF_IPV6_FLOWSPEC:
            return RF_IPv6_FLOWSPEC
        elif vrf_rf == VRF_RF_L2VPN_FLOWSPEC:
            return RF_L2VPN_FLOWSPEC
        else:
            raise ValueError('Unsupported VRF route family given %s' % vrf_rf)

    @staticmethod
    def rf_2_vrf_rf(route_family):
        if route_family == RF_IPv4_UC:
            return VRF_RF_IPV4
        elif route_family == RF_IPv6_UC:
            return VRF_RF_IPV6
        elif route_family == RF_L2_EVPN:
            return VRF_RF_L2_EVPN
        elif route_family == RF_IPv4_FLOWSPEC:
            return VRF_RF_IPV4_FLOWSPEC
        elif route_family == RF_IPv6_FLOWSPEC:
            return VRF_RF_IPV6_FLOWSPEC
        elif route_family == RF_L2VPN_FLOWSPEC:
            return VRF_RF_L2VPN_FLOWSPEC
        else:
            raise ValueError('No supported mapping for route family '
                             'to vrf_route_family exists for %s' %
                             route_family)

    @property
    def settings(self):
        """Returns a copy of current settings.

        As some of the attributes are themselves containers, we clone the
        settings to provide clones for those containers as well.
        """
        # Shallow copy first
        cloned_setting = self._settings.copy()
        # Don't want clone to link to same RT containers
        cloned_setting[IMPORT_RTS] = self.import_rts
        cloned_setting[EXPORT_RTS] = self.export_rts
        cloned_setting[SITE_OF_ORIGINS] = self.soo_list
        return cloned_setting

    @classmethod
    def get_opt_settings(cls):
        self_confs = super(VrfConf, cls).get_opt_settings()
        self_confs.update(VrfConf.OPTIONAL_SETTINGS)
        return self_confs

    @classmethod
    def get_req_settings(cls):
        self_confs = super(VrfConf, cls).get_req_settings()
        self_confs.update(VrfConf.REQUIRED_SETTINGS)
        return self_confs

    @classmethod
    def get_valid_evts(cls):
        self_valid_evts = super(VrfConf, cls).get_valid_evts()
        self_valid_evts.update(VrfConf.VALID_EVT)
        return self_valid_evts

    def update(self, **kwargs):
        """Updates this `VrfConf` settings.

        Notifies listeners if any settings changed. Returns `True` if update
        was successful. This vrfs' route family, id and route dist settings
        cannot be updated/changed.
        """
        # Update inherited configurations
        super(VrfConf, self).update(**kwargs)
        vrf_id = kwargs.get(ConfWithId.ID)
        vrf_rd = kwargs.get(ROUTE_DISTINGUISHER)
        vrf_rf = kwargs.get(VRF_RF)
        if (vrf_id != self.id or
                vrf_rd != self.route_dist or
                vrf_rf != self.route_family):
            raise ConfigValueError(desc='id/route-distinguisher/route-family'
                                   ' do not match configured value.')

        # Validate and update individual settings
        new_imp_rts, old_imp_rts = \
            self._update_import_rts(**kwargs)
        export_rts_changed = self._update_export_rts(**kwargs)
        soos_list_changed = self._update_soo_list(**kwargs)
        med_changed = self._update_med(**kwargs)
        re_export_needed = (export_rts_changed or
                            soos_list_changed or
                            med_changed)
        import_maps = kwargs.get(IMPORT_MAPS, [])
        re_import_needed = self._update_importmaps(import_maps)

        # If we did have any change in value of any settings, we notify
        # listeners
        if (new_imp_rts is not None or
                old_imp_rts is not None or
                re_export_needed or re_import_needed):
            evt_value = (
                new_imp_rts,
                old_imp_rts,
                import_maps,
                re_export_needed,
                re_import_needed
            )
            self._notify_listeners(VrfConf.VRF_CHG_EVT, evt_value)
        return True

    def _update_import_rts(self, **kwargs):
        import_rts = kwargs.get(IMPORT_RTS)
        get_validator(IMPORT_RTS)(import_rts)
        curr_import_rts = set(self._settings[IMPORT_RTS])

        import_rts = set(import_rts)
        if not import_rts.symmetric_difference(curr_import_rts):
            return None, None

        # Get the difference between current and new RTs
        new_import_rts = import_rts - curr_import_rts
        old_import_rts = curr_import_rts - import_rts

        # Update current RTs and notify listeners.
        self._settings[IMPORT_RTS] = import_rts
        return new_import_rts, old_import_rts

    def _update_export_rts(self, **kwargs):
        export_rts = kwargs.get(EXPORT_RTS)
        get_validator(EXPORT_RTS)(export_rts)
        curr_export_rts = set(self._settings[EXPORT_RTS])

        if curr_export_rts.symmetric_difference(export_rts):
            # Update current RTs and notify listeners.
            self._settings[EXPORT_RTS] = list(export_rts)
            return True

        return False

    def _update_soo_list(self, **kwargs):
        soo_list = kwargs.get(SITE_OF_ORIGINS, [])
        get_validator(SITE_OF_ORIGINS)(soo_list)
        curr_soos = set(self.soo_list)

        # If given list is different from existing settings, we update it
        if curr_soos.symmetric_difference(soo_list):
            self._settings[SITE_OF_ORIGINS] = soo_list[:]
            return True

        return False

    def _update_med(self, **kwargs):
        multi_exit_disc = kwargs.get(MULTI_EXIT_DISC, None)
        if multi_exit_disc:
            get_validator(MULTI_EXIT_DISC)(multi_exit_disc)

        if multi_exit_disc != self.multi_exit_disc:
            self._settings[MULTI_EXIT_DISC] = multi_exit_disc
            return True

        return False

    def _update_importmaps(self, import_maps):
        if set(self._settings[IMPORT_MAPS]).symmetric_difference(import_maps):
            self._settings[IMPORT_MAPS] = import_maps
            return True

        return False

    def __repr__(self):
        return ('<%s(route_dist: %r, import_rts: %r, export_rts: %r, '
                'soo_list: %r)>' % (self.__class__.__name__,
                                    self.route_dist, self.import_rts,
                                    self.export_rts, self.soo_list))

    def __str__(self):
        return 'VrfConf-%s' % self.route_dist

class VrfsConf(BaseConf):
    """Container for all VRF configurations."""

    ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT = range(2)

    VALID_EVT = frozenset([ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT])

    def __init__(self):
        super(VrfsConf, self).__init__()
        self._vrfs_by_rd_rf = {}
        self._vrfs_by_id = {}

    def _init_opt_settings(self, **kwargs):
        pass

    @property
    def vrf_confs(self):
        """Returns a list of configured `VrfConf`s
        """
        return list(self._vrfs_by_rd_rf.values())

    @property
    def vrf_interested_rts(self):
        interested_rts = set()
        for vrf_conf in self._vrfs_by_id.values():
            interested_rts.update(vrf_conf.import_rts)
        return interested_rts

    def update(self, **kwargs):
        raise NotImplementedError('Use either add/remove_vrf_conf'
                                  ' methods instead.')

    def add_vrf_conf(self, vrf_conf):
        if vrf_conf.rd_rf_id in self._vrfs_by_rd_rf.keys():
            raise RuntimeConfigError(
                desc='VrfConf with rd_rf %s already exists'
                     % str(vrf_conf.rd_rf_id)
            )
        if vrf_conf.id in self._vrfs_by_id:
            raise RuntimeConfigError(
                desc='VrfConf with id %s already exists' % str(vrf_conf.id)
            )

        self._vrfs_by_rd_rf[vrf_conf.rd_rf_id] = vrf_conf
        self._vrfs_by_id[vrf_conf.id] = vrf_conf
        self._notify_listeners(VrfsConf.ADD_VRF_CONF_EVT, vrf_conf)

    def remove_vrf_conf(self, route_dist=None, vrf_id=None,
                        vrf_rf=None):
        """Removes any matching `VrfConf` for given `route_dist` or `vrf_id`

        Parameters:
            - `route_dist`: (str) route distinguisher of a configured VRF
            - `vrf_id`: (str) vrf ID
            - `vrf_rf`: (str) route family of the VRF configuration
        If only `route_dist` is given, removes `VrfConf`s for all supported
        address families for this `route_dist`. If `vrf_rf` is given, than only
        removes `VrfConf` for that specific route family. If only `vrf_id` is
        given, matching `VrfConf` will be removed.
        """
        if route_dist is None and vrf_id is None:
            raise RuntimeConfigError(desc='To delete supply route_dist or id.')

        # By default we remove all VRFs for given Id or RD
        vrf_rfs = SUPPORTED_VRF_RF
        # If asked to delete specific route family vrf conf.
        if vrf_rf:
            vrf_rfs = vrf_rf

        # For all vrf route family asked to be deleted, we collect all deleted
        # VrfConfs
        removed_vrf_confs = []
        for route_family in vrf_rfs:
            if route_dist is not None:
                rd_rf_id = VrfConf.create_rd_rf_id(route_dist, route_family)
                vrf_conf = self._vrfs_by_rd_rf.pop(rd_rf_id, None)
                if vrf_conf:
                    self._vrfs_by_id.pop(vrf_conf.id, None)
                    removed_vrf_confs.append(vrf_conf)
            else:
                vrf_conf = self._vrfs_by_id.pop(vrf_id, None)
                if vrf_conf:
                    self._vrfs_by_rd_rf.pop(vrf_conf.rd_rd_id, None)
                    removed_vrf_confs.append(vrf_conf)

        # We do not raise any exception if we cannot find asked VRF.
        for vrf_conf in removed_vrf_confs:
            self._notify_listeners(VrfsConf.REMOVE_VRF_CONF_EVT, vrf_conf)
        return removed_vrf_confs

    def get_vrf_conf(self, route_dist, vrf_rf, vrf_id=None):
        if route_dist is None and vrf_id is None:
            raise RuntimeConfigError(desc='To get VRF supply route_dist '
                                     'or vrf_id.')
        if route_dist is not None and vrf_id is not None:
            vrf1 = self._vrfs_by_id.get(vrf_id)
            rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf)
            vrf2 = self._vrfs_by_rd_rf.get(rd_rf_id)
            if vrf1 is not vrf2:
                raise RuntimeConfigError(desc='Given VRF ID (%s) and RD (%s)'
                                         ' are not of same VRF.' %
                                         (vrf_id, route_dist))
            vrf = vrf1
        elif route_dist is not None:
            rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf)
            vrf = self._vrfs_by_rd_rf.get(rd_rf_id)
        else:
            vrf = self._vrfs_by_id.get(vrf_id)
        return vrf

    @property
    def vrfs_by_rd_rf_id(self):
        return dict(self._vrfs_by_rd_rf)

    @classmethod
    def get_valid_evts(cls):
        self_valid_evts = super(VrfsConf, cls).get_valid_evts()
        self_valid_evts.update(VrfsConf.VALID_EVT)
        return self_valid_evts

    def __repr__(self):
        return '<%s(%r)>' % (self.__class__.__name__, self._vrfs_by_id)

    @property
    def settings(self):
        return [vrf.settings for vrf in self._vrfs_by_id.values()]

class VrfConfListener(ConfWithIdListener, ConfWithStatsListener):
    """Base listener for various VRF configuration change event."""

    def __init__(self, vrf_conf):
        super(VrfConfListener, self).__init__(vrf_conf)
        vrf_conf.add_listener(VrfConf.VRF_CHG_EVT, self.on_chg_vrf_conf)

    def on_chg_vrf_conf(self, evt):
        raise NotImplementedError('This method should be overridden')

class VrfsConfListener(BaseConfListener):
    """Base listener for VRF container change events."""

    def __init__(self, vrfs_conf):
        super(VrfsConfListener, self).__init__(vrfs_conf)
        vrfs_conf.add_listener(VrfsConf.ADD_VRF_CONF_EVT, self.on_add_vrf_conf)
        vrfs_conf.add_listener(VrfsConf.REMOVE_VRF_CONF_EVT,
                               self.on_remove_vrf_conf)

    @abc.abstractmethod
    def on_add_vrf_conf(self, evt):
        raise NotImplementedError('This method should be overridden')

    @abc.abstractmethod
    def on_remove_vrf_conf(self, evt):
        raise NotImplementedError('This method should be overridden')


import numpy as np
import pytest
import pyccl as ccl
from pyccl.modified_gravity import MuSigmaMG

POWER_MG_TOL = 1e-2

@pytest.mark.parametrize('model', list(range(5)))
def test_power_mg(model):
    mu_0 = [0., 0.1, -0.1, 0.1, -0.1]
    sigma_0 = [0., 0.1, -0.1, -0.1, 0.1]

    cosmo = ccl.Cosmology(
        Omega_c=0.25,
        Omega_b=0.05,
        h=0.7,
        A_s=2.1e-9,
        n_s=0.96,
        Neff=3.046,
        Omega_k=0,
        mg_parametrization=MuSigmaMG(
            mu_0=mu_0[model],
            sigma_0=sigma_0[model]),
        matter_power_spectrum='linear',
        transfer_function='boltzmann_class')

    data = np.loadtxt("./benchmarks/data/model%d_pk_MG.dat" % model)

    a = 1
    k = data[:, 0] * cosmo['h']
    pk = data[:, 1] / (cosmo['h']**3)
    pk_ccl = ccl.linear_matter_power(cosmo, k, a)
    err = np.abs(pk_ccl/pk - 1)

    if model == 0:
        assert np.allclose(err, 0, rtol=0, atol=POWER_MG_TOL)
    else:
        msk = data[:, 0] > 5e-3
        assert np.allclose(err[msk], 0, rtol=0, atol=POWER_MG_TOL)


import datetime
import random
import time
import json
import os
import sys

from faker import Faker
from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut

KELVIN_SCALE = 273.15
HPA_CONVERTION = 1013.25;

#Heavy object, so creating only once per the execution
faker = Faker()

#Weather data blueprint
loc_weather = {"Sunny": {"temp": (30, 17)}, "Rain": {"temp": (25, 17)}, "Snow": {"temp": (0, -5)}}

def get_geo_loc(location_name):
    """Get the latitude and longitude of the game location
    - location
    :return latitude,longitude
    """

    #Uses third party API to get geo position
    try:
        geolocator = Nominatim(user_agent="MacOSX")
        location = geolocator.geocode(location_name)
    except GeocoderTimedOut as e:
        raise RuntimeError('Geocode failed: {}'.format(e.message))
    return str(location.latitude) + "," + str(location.longitude)

def get_random_date(end, year_limit):
    """Use Faker lib to generate a ISO8601 format datetime
    -end
    -year window duration in the past
    :return ISO8601 datetime value
    """

    #Uses Faker for geting fake date by providing a look back years
    dt = faker.date_time_between(end_date=end, start_date=year_limit)
    return dt.isoformat()

def calc_humidity(temp, freez_temp):
    """Returns a calculated humidity using formula RH = 100 - 5(T -Tz)
    - temperature
    - freeze temperature of that location
    :return humidity
    """

    hum = abs(100 - (5 * (temp - freez_temp)))
    return hum

def calc_pressure(temp, elev):
    """Pressure can be calculated using Hypsometric formula P=P0(1- (0.0065h / T + 0.0065h+273.15)) * 5.257
    - temperature
    - elevation
    :return pressure hPa
    """

    height_factor = (0.0065 * float(elev));
    deno = temp + height_factor + KELVIN_SCALE
    pressure_atm = 1 - (height_factor / deno)
    pressure_hpa = (HPA_CONVERTION * pressure_atm)
    return pressure_hpa;

def calc_temperature(temp_max, temp_min):
    """The temperature is found through the mathematical random value, but this API can be extended to find it from paid
    weather data providers
    - location
    :return temperature in celsius
    """

    temp = round(random.uniform(temp_max, temp_min), 1)
    return temp;

def gen_dummy_weather(location, geo, elev, dt, limit):
    """Generate dummy data for weather
    - weather
    - temperature
    - pressure
    - humidity
    :return: location|geo|temperature|weather|temperature|pressure|humidity
    """

    weather_rand = random.choice(list(loc_weather.keys()))
    weather_condition = loc_weather[weather_rand]

    #Can utilize openweathermap API to get real values
    (temp_max, temp_min) = weather_condition["temp"]

    #Get a random temparature based on max and min temperature of the given location
    temperature = round(calc_temperature(temp_max, temp_min), 1)

    #Calculate pressure
    pressure = round(calc_pressure(temperature, elev), 1)

    #Calculate humidity
    humidity = round(calc_humidity(temperature, temp_min),1)

    #Get a fake datetime
    date = get_random_date(dt, limit)

    #Get a fake elevation from base elevation
    elevation = round(random.uniform((float(elev)),2),2)
    return location + "|" + geo + "," + str(elevation) + "|" + date + "|" + weather_rand + "|" + str(temperature) + "|" + str(pressure) + "|" + str(humidity) + "\n"

# Argument parsing and initiation
def main(argv):
    if len(argv) < 3:
        print('Invalid number of arguments, correct usage is `python WeatherDataGen.py <config_file> <output_file>`\n'
              'Example :>> python src/WeatherDataGen.py src/config.json weatherdata.psv')
        exit()

    config_file = argv[1]
    out_file = argv[2]
    print('Starting Weather Data Generation...')
    dt = datetime.datetime.now()
    try:
        weather_data = open(out_file, "w")
        with open(config_file) as json_file:

            #Load game location configurations
            config_data = json.load(json_file)
            for loc_config in config_data['game-locations']:
                loc = loc_config['loc']
                elev = loc_config['elevation']

                #How much weather data required for the location
                data_count = loc_config['test-data-count']

                look_back = loc_config['look-back']

                #Find the latitude and longitude of the location
                geo = get_geo_loc(loc)
                #elev = requests.get('https://api.open-elevation.com/api/v1/lookup?locations=' + geo)
                for i in range(0, int(data_count)):
                    #Generate weather data
                    data = gen_dummy_weather(loc, geo , elev, dt, look_back)

                    #Persist the weather data
                    weather_data.write(data)
    except RuntimeError as e:
        print('Unexpected error in execution: {}'.format(e.message))
        sys.exit()

    #Close the weather data file
    finally:
        weather_data.close()
    print('Finished Weather Data Generation, data file can be accessed from: {}'.format(out_file))

if __name__ == '__main__':
    main(sys.argv)


import os
import requests

from bot_template import BOT_TEMPLATE

def send_whatsapp_message(chat_id, message):
    # Format the message using the bot template
    formatted_message = BOT_TEMPLATE.format(
        currency=message['currency'],
        exchange_name=message['exchange_name'],
        buy_rate=message['buy_rate'],
        sell_rate=message['sell_rate'],
        response_time=message['response_time']
    )

    # Send the formatted message back to the user via the WhatsApp bot
    whatsapp_bot_token = os.environ['whatsapp_bot_token']
    url = f"https://api.chat-api.com/instance/{whatsapp_bot_token}/sendMessage"
    data = {
        'phone': chat_id,
        'body': formatted_message
    }
    response = requests.post(url, json=data)

    # Debugging feature: log the response to the console
    print(f"WhatsApp response: {response.text}")