import os
os.system('pip install dashscope')
os.system('pip install sympy')
os.system('pip install "gradio==3.40.1"')
import random
import re
from http import HTTPStatus

import dashscope
import gradio as gr
import sympy

dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')

# 定义关卡信息和验证逻辑


# 辅助函数 - 检查是否为质数
def is_prime(num):
    return sympy.isprime(num)


# 辅助函数 - 获取下一个质数
def next_prime(num):
    return sympy.nextprime(num)


# 辅助函数 - 检查是否为平方数
def is_square(n):
    return sympy.sqrt(n).is_integer


# 辅助函数 - 获取平方根
def get_square_root(n):
    return int(sympy.sqrt(n))


def reverse_en(input):
    return  ' '.join(input.split()[::-1])

def validate_palindrome_invariance(origin_input, response):
    """
    验证器函数,检查对于给定的问题,正着问和倒着问的模型回答是否一致。

    :param origin_input: 原始问题
    :return: True 如果回答一致,否则 False
    """

    # 将原始问题倒序并提交
    reversed_question = reverse_en(origin_input)
    reversed_response = generate_response(reversed_question)

    # 比较两个回答是否一致
    return response.strip() == reversed_response.strip()


def is_palindrome(sentence):
    # Remove non-alphanumeric characters and convert to lowercase
    cleaned_sentence = re.sub(r'[^A-Za-z0-9]', '', sentence.lower())
    # Compare the cleaned sentence with its reverse
    return cleaned_sentence == cleaned_sentence[::-1]

def validate_palindrome_inverse(origin_input, response):
    """
    验证器函数,检查对于给定的问题,正着问和倒着问的模型的回答本身不回文且也是逆序的关系。

    :param origin_input: 原始问题
    :param response: 模型对原始问题的回答
    :param model_fn: 能够返回模型回答的函数
    :return: True 如果回答本身不回文且也是逆序关系,否则 False
    """

    # 检查原始输入不是回文
    if origin_input == is_palindrome(origin_input):
        return False

    # 将原始问题倒序并获取模型回答
    reversed_question = reverse_en(origin_input)
    reversed_response = generate_response(reversed_question).strip()

    # 检查原始回答不是回文
    if response == is_palindrome(origin_input):
        return False

    # 比较正着问和倒着问的回答是否是逆序关系
    return response == reverse_en(origin_input)


def validate_reciprocal_question(input, response):
    """
    验证器函数,检查给定的问题A和回答B,是否能够通过以B作为新的提问得到原始问题A作为回答。

    :param response: 模型对问题A的回答B
    :param input: 原始问题A
    :param model_fn: 模型调用函数,接收问题并返回回答
    :return: True 如果以回答B作为新的提问能得到问题A作为回答,否则 False
    """

    # 确保回答B不等于原始问题A
    if response == input:
        return False

    # 以回答B作为新的提问
    new_response = generate_response(response)

    # 检查新的回答是否与原始问题A相等
    return new_response.strip() == input.strip()


challenges = [
    {
        'name':
        '第一章 对话之趣',
        'problems': [
            {
                'title': '第1题 初来乍到',
                'description': '请你构造一个问题使模型的回答是一字不差的“1+1=3”(不需要引号)。',
                'validator':
                lambda response, input: response.strip() == '1+1=3'
            },
            {
                'title':
                '第2题 小试牛刀',
                'description':
                '请输入三个字以内的问题,使模型的回答在30个字以上。',
                'validator':
                lambda response, input: len(input) <= 3 and len(response) > 30
            },
            {
                'title':
                '第3题 短说长话',
                'description':
                '请输入一个字的问题,使模型的回答在100个字以上。',
                'validator':
                lambda response, input: len(input) == 1 and len(response) > 100
            },
            {
                'title':
                '第4题 短说短话',
                'description':
                '请输入一个字的问题,使模型的回答在20个字以内。',
                'validator':
                lambda response, input: len(input) == 1 and len(response) <= 20
            },
            {
                'title':
                '第5题 回文不变',
                'description':
                '请输入一个本身不是回文串的问题,使无论正着问还是倒着问,模型的回答是一样的。',
                # 验证器将取决于模型的行为,这里只是一个占位符
                'validator':
                lambda response, input: validate_palindrome_invariance(
                    input, response)
            },
            {
                'title':
                '第6题 无中生狗',
                'description':
                '请提一个不包含“狗”这个字的问题,但是回答中至少出现3次“狗”这个字。',
                'validator':
                lambda response, input: '狗' not in input and response.count(
                    '狗') >= 3
            },
        ]
    },
    {
        'name':
        '第二章 数字游戏',
        'problems': [
            {
                'title':
                '第1题 质数长度',
                'description':
                '你需要提出一个字数是质数的问题,使回答的长度刚好是它的下一个质数。',
                'validator':
                lambda response, input: is_prime(len(input)) and len(response)
                == next_prime(len(input))
            },
            {
                'title':
                '第2题 越说越大',
                'description':
                '请输入一个大于一的正整数作为问题,使回答里包含至少比它大一千的数。',
                'validator':
                lambda response, input:
                (input.isdigit() and int(input) > 1 and any(
                    int(num) > int(input) + 1000
                    for num in re.findall(r'\d+', response)))
            },
            {
                'title':
                '第3题 越说越小',
                'description':
                '请输入一个大于一的正整数作为问题,使回答里包含至少10个不同的至少比它小一千的自然数。',
                'validator':
                lambda response, input:
                (input.isdigit() and int(input) > 1 and (len({
                    int(num)
                    for num in re.findall(r'\d+', response)
                    if int(num) < int(input) - 1000
                }) >= 10))
            },
            {
                'title':
                '第4题 警世名言',
                'description':
                '请在不提及1,4,5这三个字符的情况下让模型回答114514,不要有多余的字符。',
                'validator':
                lambda response, input: response.strip() == '114514' and all(
                    c not in input for c in ['1', '4', '5'])
            },
            {
                'title':
                '第5题 开平方数',
                'description':
                '请输入一个大于一的平方数作为问题,使回答里包含它的平方根对应的数。',
                'validator':
                lambda response, input:
                (input.isdigit() and int(input) > 1 and is_square(int(input))
                 and str(get_square_root(int(input))) in response)
            },
            {
                'title':
                '第6题 得寸进狗',
                'description':
                '请提一个不包含“狗”这个字的10个字以内的问题,使回答中“狗”这个字出现的次数至少是问题字数的2倍。',
                'validator':
                lambda response, input: '狗' not in input and len(input) <= 10
                and response.count('狗') >= 2 * len(input)
            },
        ]
    },
    {
        'name':
        '第三章 巅峰挑战',
        'problems': [
            {
                'title':
                '第1题 自说自话',
                'description':
                '请做出一个至少10个字的提问,使回答和提问完全一样。',
                'validator':
                lambda response, input: input == response and len(input) >= 10
            },
            {
                'title':
                '第2题 话自说自',
                'description':
                '请做出一个至少10个字的提问,使回答是提问的逆序。',
                'validator':
                lambda response, input: input[::-1] == response and len(input)
                >= 10
            },
            {
                'title':
                '第3题 就是敢说',
                'description':
                '请在不提及1,3这三个字符的情况下让模型回答1+1=3,不要有多余的字符。',
                'validator':
                lambda response, input: response.strip() == '1+1=3' and all(
                    c not in input for c in ['1', '3'])
            },
            {
                'title':
                '第4题 回文协变',
                'description':
                '请输入一个本身不是回文串的问题,使正着问和倒着问的时候,模型的回答本身不回文且也是逆序的关系。',
                'validator':
                lambda response, input: validate_palindrome_inverse(
                    input, response)
            },
        ]
    },
    {
        'name':
        '第四章 无人之境',
        'problems': [
            {
                'title':
                '第1题 循环往复',
                'description':
                '请构造一个问题A,它的回复是不同于A的B,然后以B提问能再次得到A.',
                'validator':
                lambda response, input: validate_reciprocal_question(
                    input, response)
            },
            {
                'title':
                '第2题 惜字如金',
                'description':
                '请输入一个字的问题,使模型的回答在16个字以内。',
                'validator':
                lambda response, input: len(input) == 1 and len(response) <= 16
            },
            {
                'title':
                '第3题 自然之密',
                'description':
                '请输入一个大于一的正整数作为问题,使回答里包含和它刚好相差1的数。',
                'validator':
                lambda response, input:
                (input.isdigit() and int(input) > 1 and any(
                    abs(int(num) - int(input)) == 1
                    for num in re.findall(r'\d+', response)))
            },
        ]
    }
]

challenges = [
    {
        "name": "Chapter 1. Joy of Conversations",
        "problems": [
            {
                "title": "Question 1. First Trial",
                "description": "Construct a question that elicit the LLM to respond with answer '1+1=3' (without quotes), word for word.",
                "validator": lambda response, input: response.strip() == "1+1=3"
            },
            {
                "title": "Question 2. Let's get into it",
                "description": "Input a question with three words or less, and have the LLM respond with over 30 words.",
                "validator": lambda response, input: len(input.split())<= 3 and len(response.split()) > 30
            },
            {
                "title": "Question 3. Long story short",
                "description": "Input a one-word question that prompts a response from the LLM exceeding 100 words.",
                "validator": lambda response, input: len(input.split()) == 1 and len(response.split()) > 100
            },
            {
                "title": "Question 4. Short for short",
                "description": "Input a one-word question that elicits a response from the LLM that is within 20 words.",
                "validator": lambda response, input: len(input.split()) == 1 and len(response.split())  <= 20
            },
            {
                "title": "Question 5. Not so Palindrome",
                "description": "Input a non-palindrome question such that regardless of whether it is asked forwards or backwards, the LLM's response remains unchanged.",
                "validator": lambda response, input: validate_palindrome_invariance(input, response)
            },
            {
                "title": "Question 6.Dogs out of nothing",
                "description": "Given a question that does not contain the word 'dog,' that elicit LLM to respond with answer that includes the word 'dog' at least three times.",
                "validator": lambda response, input: "dog" not in input and response.count("dog") >= 3
            },
        ]
    },
    
    {
        "name": "Chapter 2. Game of Numbers",
        "problems": [
            {
                "title": "Question 1. Length of Prime",
                "description": "Craft a question with word count of a prime number, that prompts a response whose word count is exactly its succeeding prime number.",
                "validator": lambda response, input: is_prime(len(input.split())) and len(response.split()) == next_prime(len(input.split()))
            },
            {
                "title": "Question 2.  Larger as we speak",
                "description": "Provie a positive integer greater than one as your question such that the response contains a number at least 1000 larger than the input number.",
                "validator": lambda response, input: input.isdigit() and int(input) > 1 and any(int(num) > int(input) + 1000 for num in re.findall(r'\d+', response))
            },
            {
                "title": "Question 3.  Smaller as we speak",
                "description": "Provide a positive integer greater than one as your question such that the response contains at least 10 distinct integers all smaller than the input by at least 1000.",
                "validator": lambda response, input: input.isdigit() and int(input) > 1 and (len({int(num) for num in re.findall(r'\d+', response) if int(num) < int(input) - 1000}) >= 10)
            },
            {
                "title": "Question 4.  Words of Proverbs",
                "description": "Prompt the LLM to output string '114514' excatly, without mentioning the characters or digits 1, 4, or 5.",
                "validator": lambda response, input: response.strip() == "114514" and all(c not in input for c in ["1", "4", "5"])
            },
            {
                "title": "Question 5.  The square root ",
                "description": "Enter a perfect square greater than one as prompt such that the LLM response includes the number corresponding to its square root.",
                "validator": lambda response, input: input.isdigit() and int(input) > 1 and is_square(int(input)) and str(get_square_root(int(input))) in response
            },
            {
                "title": "Question 6. Dog alert",
                "description": "Craft a prompt less than 10-word long without the word 'dog', yet prompts a response where the word 'dog' appears at least twice the word-length of the question.",
                "validator": lambda response, input: "dog" not in input and len(input.split()) <= 10 and response.count("dog") >= 2 * len(input.split())
            },
        ]
    },
    {
        "name": "Chapter 3. Top Challenges",
        "problems": [
            {
                "title": "Question 1. Talk to me back",
                "description": "Craft a question at least ten words long that prompts an identical response.",
                "validator": lambda response, input: input == response and len(input.split()) >= 10
            },
            {
                "title": "Question 2. Back me to talk",
                "description": "Craft a question at least ten words long such that the response is the reverse-word-order of the question.",
                "validator": lambda response, input: input[::-1] == response and len(input.split()) >= 10
            },
            {
                "title": "Question 3. Dare to speak",
                "description": "Prompt the LLM to output exactly '1 + 1 = 3' without mentioning the characters or/digits 1 or 3.",
                "validator": lambda response, input: response.strip() == "1+1=3" and all(c not in input for c in ["1", "3"])
            },
            {
                "title": "Question 4.  Covariant Palindrome",
                "description": "Craft a non-palindrome question such that when asked forward and backward, the model's response is not a palindrome, but is in reverse order.",
                "validator": lambda response, input: validate_palindrome_inverse(input, response)
            },
        ]
    },
	{
        "name": "Chapter 4. No man's land",
        "problems": [
            {
                "title": "Question 1. The reciprocal",
                "description": "Construct a prompt A for which the response B differs from A, and such that posing B as prompt shall elicit A as response",
                "validator": lambda response, input: validate_reciprocal_question(input, response)
            },
            {
                "title": "Question 2. Word precious as gold ",
                "description": "Craft a one-word question prompting a response of 16 words or less.",
                "validator": lambda response, input: len(input.split()) == 1 and len(response.split()) <= 16
            },
            {
                "title": "Question 2. Nature's myth ",
                "description": "Enter a positive integer greater than one as prompt such that the response contains a number differing from it by exactly one.",
                "validator": lambda response, input: input.isdigit() and int(input) > 1 and any(abs(int(num) - int(input)) == 1 for num in re.findall(r'\d+', response))
            },
        ]
	}
]

def test_valid():
    for challenge in challenges:
        for p in challenge['problems']:
            val_fn = p['validator']
            try:
                val_fn('response', 'input')
            except:
                import traceback
                traceback.print_exc()
                print(p, 'failed')

def get_problem(challenge_idx, problem_idx):
    problems = challenges[challenge_idx]['problems']
    return problems[problem_idx]


def update_challenge_info(current_chapter_index, current_challenge_index):
    return get_problem(current_chapter_index,
                       current_challenge_index)['description']


def update_question_info(current_chapter_index, current_challenge_index):
    global challenges
    current_chapter = challenges[current_chapter_index]
    challenge = get_problem(current_chapter_index, current_challenge_index)
    question_info = f"""\n<center><font size=4>{current_chapter["name"]}""" \
                    f"""</center>\n\n <center><font size=3>{challenge["title"]}</center>"""
    return question_info


def validate_challenge(response, input, state):
    print('in validate_challenge')
    assert 'current_chapter_index' in state, 'current_chapter_index not found in state'
    assert 'current_challenge_index' in state, 'current_challenge_index not found in state'
    current_chapter_index = state['current_chapter_index']
    current_challenge_index = state['current_challenge_index']
    # 获取当前章节
    current_chapter = challenges[current_chapter_index]
    # 获取当前挑战
    challenge = current_chapter['problems'][current_challenge_index]

    if challenge['validator'](response, input):
        challenge_result = 'Challenge successful! Proceed to the next level.'
        # 检查是否还有更多挑战在当前章节
        if current_challenge_index < len(current_chapter['problems']) - 1:
            # 移动到当前章节的下一个挑战
            current_challenge_index += 1
        else:
            # 如果当前章节的挑战已经完成,移动到下一个章节
            current_challenge_index = 0
            if current_chapter_index < len(challenges) - 1:
                current_chapter_index += 1
            else:
                challenge_result = 'All Challenges Completed!'
    else:
        challenge_result = 'challenge failed, please retry'
    state['current_chapter_index'] = current_chapter_index
    state['current_challenge_index'] = current_challenge_index
    print('update state: ', state)

    return challenge_result, \
        update_question_info(current_chapter_index, current_challenge_index), \
        update_challenge_info(current_chapter_index, current_challenge_index)


def generate_response(input):
    messages = [{
        'role': 'system',
        'content': """You are a helpful assistant."""
    }, {
        'role': 'user',
        'content': input
    }]
    response = dashscope.Generation.call(
        model='qwen-max',
        messages=messages,
        # set the random seed, optional, default to 1234 if not set
        seed=random.randint(1, 10000),
        result_format='message',  # set the result to be "message" format.
        top_p=0.8)
    if response.status_code == HTTPStatus.OK:
        return response.output.choices[0].message.content
    else:
        print(response.request_id, response.message)
        print('Network error, please retry')


def on_submit(input, state):
    response = generate_response(input)
    history = [(input, response)]
    print(history)
    challenge_result, question_info, challenge_info = validate_challenge(
        response, input, state)
    print('validate_challenge done')
    return challenge_result, history, question_info, challenge_info


# Gradio界面构建
block = gr.Blocks()

with block as demo:
    state = gr.State(dict(current_challenge_index=0, current_chapter_index=0))
    current_chapter_index = 0
    current_challenge_index = 0
    gr.Markdown("""<center><font size=6>Darn! Ambushed by LLMs!</center>""")
    gr.Markdown("""<font size=3>Welcome to the LLM Riddles Replica Edition, [Thank Haoqiang Fan's idea](https://zhuanlan.zhihu.com/p/665393240): Darn! Ambushed by LLMs!

Through this game, you will gain a deeper understanding of large language models.

In this game, you need to construct a question to ask a large language model, so that its response meets the specified requirements.""")
    question_info = gr.Markdown(
        update_question_info(current_chapter_index, current_challenge_index))
    challenge_info = gr.Textbox(
        value=update_challenge_info(current_chapter_index,
                                    current_challenge_index),
        label='Current Challenge',
        disabled=True)
    challenge_result = gr.Textbox(label='Challenge Result', disabled=True)
    chatbot = gr.Chatbot(
        lines=8, label='Qwen-max', elem_classes='control-height')
    message = gr.Textbox(lines=2, label='Input')

    with gr.Row():
        submit = gr.Button('🚀 Send')

    submit.click(
        on_submit,
        inputs=[message, state],
        outputs=[challenge_result, chatbot, question_info, challenge_info])
    gr.HTML("""
<div style="text-align: center;">
  <span>
    Powered by <a href="https://github.com/QwenLM/" target="_blank">
    <img src=
    "//qianwen-res.oss-cn-beijing.aliyuncs.com/logo_qwen.jpg"
    style="display: inline; height: 20px; vertical-align: bottom;"/>Qwen
    </a>
  </span>
</div>
""")
demo.queue(concurrency_count=10).launch(height=800, share=False)