from typing import Callable
from .config import Config

from .chat import get_answer_from_model

class AbstractRecognizer():
    def __init__(self, config: Config, commands: dict[str, dict], 
            command_name_to_func: dict[str, Callable], 
            recognition_messages: list[dict[str, str]], 
            explanation_messages: list[dict[str, str]]):
        """
        Args:
            config: Config object.
            commands: The commands that the model can use, 
                described in natural language.
            command_name_to_func: Dictionary that takes
                the name of a command and returns the corresponding function.
            recognition_messages: List of dictionaries
                that contain system messages explaining the LLM the format
                of its answers and the commands it can use while recognizing.
            explanation_messages: List of dictionaries
                that contain system messages explaining the LLM the format
                of its answers and the commands it can use while explaining
                in natural language a graph.
        """
        self.config = config
        self.commands = commands
        self.command_name_to_func = command_name_to_func
        self.recognition_messages = recognition_messages
        self.explanation_messages = explanation_messages

    def recognize(self, instruction: str) -> str:
        """
        Analyzes an instruction and creates data to create a graph of commands
        that will fulfill the instruction.
        """
        print(f"Input tokens used by messages (instruction recognition): ~{len(str(self.recognition_messages)) / 4} tokens.")

        commands_data_str = get_answer_from_model(instruction, self.config.chat_model, self.recognition_messages)

        if self.config.verbosity >= 2:
            print(f"\n\n~ ~ ~ ~ Commands data generated by the LLM\n\n{commands_data_str}\n\n~ ~ ~ ~")
        
        if self.config.save_graph_as_file:
            with open("graph.txt", "w+") as f:
                f.write(commands_data_str)
                f.close()

        return commands_data_str

    def explain_graph_in_natural_language(self, commands_data_str: str) -> str:
        """
        Takes a graph as string and uses natural language to explain the
        connections in the graph.
        """
        print(f"Input tokens used by messages (graph explanation): ~{len(str(self.explanation_messages)) / 4} tokens.")

        explanation = get_answer_from_model(commands_data_str, self.config.chat_model, self.explanation_messages)

        return explanation

class ComplexRecognizer(AbstractRecognizer):
    def __init__(self, config: Config, commands: dict[str, dict], 
            command_name_to_func: dict[str, Callable]):
        """
        Recognizer that creates a complex graph of commands, using conditions,
        data references from other commands and executes multiple commands.
        Each command can execute multiple commands based on multiple conditions.
        """        
        recognition_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, based on the user's prompt, detects the series of commands that must be executed, arguments that each command will have, and the relationship between each command (for example, what data generated by a command will be used as an argument for another)."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""
                """\n*IMPORTANT*: You can ONLY use the given commands. **NEVER try to use OTHERS**."""
                """\n*IMPORTANT*: While creating the graph, you are NOT talking to the user. You are JUST CREATING THE GRAPH, so do not write suggestions for a user inside of the graph. Create the complete graph by your own."""

                f"""\n\nCommands:\n{commands}"""

                """\n\nDepending on the prompt, you will use different commands and different arguments and relationships between commands. The only way you can see data generated by other commands from a command is by passing them as arguments."""

                """\n\n*Your response will have this format, ALWAYS stick to it*:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, [[next_command_id, "dependent_on_data", required_value], [...], ...]]"""
                """\n[...]"""
                """\n\nThe structure is JSON-Lines-like (so, use double quotes to create str, etc.)"""
                """\nONLY use newline characters to separate the data of each node (as it's a JSON-Lines-like format)."""
                """\nYou will replace EVERYTHING in the format with the correct data."""
                """\nTo reference data generated by a command, use __&i.data__. 'i' is the ID of the command; 'data' is the name of the generated data."""
                """\nAlways consider the DATA TYPE of the data you are referencing (e.g. string-types can not have indices like '__&i.thought[j]__'; data does not have attributes unless specified)."""
                """\nYou can ONLY reference data in the arguments of the nodes."""
                """\nThe structure will ultimately be like a graph. The next_command_id defines which command will be executed next; dependent_on_data is the name of the data generated by the current command which will be used as a condition (*null if the next command will be executed no matter what the results of the current command where*), and required_value is what value the dependent_on_data must have to execute the next command (null if dependent_on_data was also null)."""
                """\nNote that dependent_on_data is NOT a data reference for the next command; it's just what will be used as a CONDITION to determine if the next command will be executed."""
                """\ndependent_on_data field CAN NOT be 'dependent_on_data'. It must be null if it doesn't matter."""
                """\nA command can execute multiple next commands."""
                # """\nBe concise but *solid* with the structure."""
                """\n*Consider that the user might write incorrectly and their inputs might be ambiguous*."""
                """\nThe only way to reference the data of other commands is by using the __&i.data__ referencing. Commands DO NOT KNOW each other's data."""
                """\nProvide all the relevant context in the arguments of the commands, so that you're not ambiguous."""
                """\nNote that each command DOES NOT know about the other commands. You have to use DATA REFERENCES as ARGUMENTS to pass context."""
                """\nBe creative and logic when using the commands' arguments and data references."""

                """\n\nFor example:"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nYour response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nYour response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                """\n\nExample 3:"""
                """\nUser prompt: 'Look up the best courses on ASP.Net on Google and show me the first page that appears, if it is relevant.'"""
                """\nYour response might be: '[1, "SEARCH_GOOGLE", {"query": "best courses on ASP.Net"}, [[2, null, null]]]\n[2, "READ_WEBPAGE", {"url": "__&1.urls[0]__"}, [[3, null, null]]]\n[3, "IF", {"condition": "Is this a relevant course on ASP.Net? __&2.text__"}, [[4, "result", 1], [5, "result", 0]]]\n[4, "WRITE_TO_USER", {"content": "Relevant course: __&1.urls[0]__"}, []]\n[5, "WRITE_TO_USER", {"content": "It is not relevant."}, []]'"""
            }
        ]
        explanation_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, given a graph of commands, explains in natural language what the graph does, how the nodes connect, and all the details about the graph, commands and nodes."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""

                f"""\n\nCommands:\n{commands}"""

                """\n\nThe graph of commands has this format:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, [[next_command_id, "dependent_on_data", required_value], [...], ...]]"""
                """\n[...]"""
                """\n\nThe structure is JSON-Lines-like."""
                """\nTo reference data generated by a command, use __&i.data__. 'i' is the ID of the command; 'data' is the name of the generated data."""
                """\nThe structure will ultimately be like a graph. The next_command_id defines which command will be executed next, dependent_on_data is the name of the data generated by the current command which will be used as a condition (null if it won't be used), and required_value is what value the dependent_on_data must have to execute the next command (or null if it doesn't matter)."""
                """\nA command can execute multiple next commands."""

                """\n\nExamples of graphs of commands generated from the user prompt:"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nThe response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nThe response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                # """\n\nExample 3:"""
                # """\nUser prompt: 'Look up the best courses on ASP.Net on Google and show me the first page that appears, if it is relevant.'"""
                # """\nThe response might be: '[1, "SEARCH_GOOGLE", {"query": "best courses on ASP.Net"}, [[2, null, null]]]\n[2, "READ_WEBPAGE", {"url": "__&1.urls[0]__"}, [[3, null, null]]]\n[3, "IF", {"condition": "Is this a relevant course on ASP.Net? __&2.text__"}, [[4, "result", 1], [5, "result", 0]]]\n[4, "WRITE_TO_USER", {"content": "Relevant course: __&1.urls[0]__"}, []]\n[5, "WRITE_TO_USER", {"content": "It is not relevant."}, []]'"""
            }
        ]
        super().__init__(config, commands, command_name_to_func, recognition_messages, explanation_messages)

class SequentialRecognizer(AbstractRecognizer):
    def __init__(self, config: Config, commands: dict[str, dict], 
            command_name_to_func: dict[str, Callable]):
        """
        Recognizer that creates a sequential graph of commands (without conditions),
        using data references from other commands and executes multiple commands.
        Each command can only execute the next command, and there are no conditions
        to execute the next commands.
        """        
        recognition_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, based on the user's prompt, detects the series of commands that must be executed, arguments that each command will have, and the relationship between each command (for example, what data generated by a command will be used as an argument for another)."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""
                """\n*IMPORTANT*: You can ONLY use the given commands. **NEVER try to use OTHERS**."""
                """\n*IMPORTANT*: While creating the graph, you are NOT talking to the user. You are JUST CREATING THE GRAPH, so do not write suggestions for a user inside of the graph. Create the complete graph by your own."""

                f"""\n\nCommands:\n{commands}"""

                """\n\nDepending on the prompt, you will use different commands and different arguments and relationships between commands. The only way you can see data generated by other commands from a command is by passing them as arguments."""

                """\n\n*Your response will have this format, ALWAYS stick to it*:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, [[next_command_id, null, null]]]"""
                """\n[...]"""
                """\n\nThe structure is JSON-Lines-like (so, use double quotes to create str, etc.)"""
                """\nONLY use newline characters to separate the data of each node (as it's a JSON-Lines-like format)."""
                """\nYou will replace EVERYTHING in the format with the correct data."""
                """\nTo reference data generated by a command, use __&i.data__. 'i' is the ID of the command; 'data' is the name of the generated data."""
                """\nAlways consider the DATA TYPE of the data you are referencing."""
                """\nYou can ONLY reference data in the arguments of the nodes."""
                """\nThe structure is a sequential list of commands. The next_command_id defines which command will be executed next."""
                """\nBe concise but *solid* with the structure."""
                """\n*Consider that the user might write incorrectly and their inputs might be ambiguous*."""
                """\nThe only way to reference the data of other commands is by using the __&i.data__ referencing. Commands DO NOT KNOW each other's data."""
                """\nProvide all the relevant context in the arguments of the commands, so that you're not ambiguous."""
                """\nNote that each command DOES NOT know about the other commands. You have to use DATA REFERENCES as ARGUMENTS to pass context."""
                """\nBe creative and logic when using the commands' arguments and data references."""

                """\n\nFor example:"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nYour response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nYour response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                """\n\nExample 3:"""
                """\nUser prompt: 'I'll tell you a math operation and you'll tell me the result.'"""
                """\nYour response might be: '[1, "REQUEST_USER_INPUT", {"message": "Please enter the math operation you want me to calculate."}, [[2, null, null]]]\n[2, "CALCULATE", {"expression": "__&1.input__"}, [[3, null, null]]]\n[3, "WRITE_TO_USER", {"content": "The result of the math operation is: __&2.result__"}, []]'"""
            }
        ]
        explanation_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, given a graph of commands, explains in natural language what the graph does, how the nodes connect, and all the details about the graph, commands and nodes."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""

                f"""\n\nCommands:\n{commands}"""

                """\n\nThe graph of commands has this format:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, [[next_command_id, null, null]]]"""
                """\n[...]"""
                """\n\nThe structure is JSON-Lines-like."""
                """\nTo reference data generated by a command, use __&i.data__. 'i' is the ID of the command; 'data' is the name of the generated data."""
                """\nThe structure is a sequential list of commands. The next_command_id defines which command will be executed next."""

                """\n\nExamples of graphs of commands generated from the user prompt:"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nThe response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nThe response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                """\n\nExample 3:"""
                """\nUser prompt: 'I'll tell you a math operation and you'll tell me the result.'"""
                """\nThe response might be: '[1, "REQUEST_USER_INPUT", {"message": "Please enter the math operation you want me to calculate."}, [[2, null, null]]]\n[2, "CALCULATE", {"expression": "__&1.input__"}, [[3, null, null]]]\n[3, "WRITE_TO_USER", {"content": "The result of the math operation is: __&2.result__"}, []]'"""

            }
        ]
        super().__init__(config, commands, command_name_to_func, recognition_messages, explanation_messages)

class SingleRecognizer(AbstractRecognizer):
    def __init__(self, config: Config, commands: dict[str, dict], 
            command_name_to_func: dict[str, Callable]):
        """
        Recognizes only one command in an instruction.
        """
        recognition_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, based on the user's prompt, detects which command must be executed."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""
                """\n*IMPORTANT*: You can ONLY use the given commands. **NEVER try to use OTHERS**."""
                """\n*IMPORTANT*: You can ONLY use ONE command. You can't use multiple."""
                """\n*IMPORTANT*: While writing, you are JUST WRITING THE COMMAND'S DATA, so do not write suggestions for the user."""

                f"""\n\nCommands:\n{commands}"""

                """\n\n*Your response will have this format, ALWAYS stick to it*:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, []]"""
                """\n\nThe structure is JSON-Lines-like (so, use double quotes to create str, etc.)"""
                """\nYou will replace EVERYTHING in the format with the correct data."""
                """\n*Consider that the user might write incorrectly and their inputs might be ambiguous*."""
                """\nProvide all the relevant context in the arguments of the command, so that you're not ambiguous."""
                """\nBe creative and logic when using the commands' arguments."""

                """\n\nExample (using multiple commands; you can only use ONE command):"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nYour response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nYour response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                """\n\nExample 3:"""
                """\nUser prompt: 'I'll tell you a math operation and you'll tell me the result.'"""
                """\nYour response might be: '[1, "REQUEST_USER_INPUT", {"message": "Please enter the math operation you want me to calculate."}, [[2, null, null]]]\n[2, "CALCULATE", {"expression": "__&1.input__"}, [[3, null, null]]]\n[3, "WRITE_TO_USER", {"content": "The result of the math operation is: __&2.result__"}, []]'"""
            }
        ]
        explanation_messages = [
            {
            'role': 'system', 
            'content':
                """You are a tool that, given a graph of commands, explains in natural language what the graph does, how the nodes connect, and all the details about the graph, commands and nodes."""
                """\n*IMPORTANT*: *WRITE in the LANGUAGE that the USER writes his/her prompt in*."""

                f"""\n\nCommands:\n{commands}"""

                """\n\nThe graph of commands has this format:"""
                """\n[command_id, "COMMAND_NAME", {"arg1": value1, "arg2": value2, ...}, [[next_command_id, null, null]]]"""
                """\n[...]"""
                """\n\nThe structure is JSON-Lines-like."""
                """\nTo reference data generated by a command, use __&i.data__. 'i' is the ID of the command; 'data' is the name of the generated data."""
                """\nThe structure is a sequential list of commands. The next_command_id defines which command will be executed next."""

                """\n\nExamples of graphs of commands generated from the user prompt:"""

                """\n\nExample 1:"""
                """\nUser prompt: 'Write an article about Lenz's Law, copy it to my clipboard and save it as a file.'"""
                """\nThe response might be: '[1, "THINK", {"about": "Article about Lenz's Law"}, [[2, null, null]]]\n[2, "WRITE_CLIPBOARD", {"content": "__&1.thought__"}, [[3, null, null]]]\n[3, "WRITE_FILE", {"content": "__&1.thought__", "file_name": "Article about Lenz's Law"}, []]'"""
                """\n\nExample 2:"""
                """\nUser prompt: 'Read my clipboard, write a scientific article about the content in it, and save it as a file with a name related to the article.'"""
                """\nThe response might be: '[1, "READ_CLIPBOARD", {}, [[2, null, null]]]\n[2, "THINK", {"about": "Scientific article about __&1.content__"}, [[3, null, null]]]\n[3, "THINK", {"about": "Name including extension to save a file about this article: __&2.thought__"}, [[4, null, null]]]\n[4, "WRITE_FILE", {"content": "__&2.thought__", "file_name": "__&3.thought__"}, []]'"""
                """\n\nExample 3:"""
                """\nUser prompt: 'I'll tell you a math operation and you'll tell me the result.'"""
                """\nThe response might be: '[1, "REQUEST_USER_INPUT", {"message": "Please enter the math operation you want me to calculate."}, [[2, null, null]]]\n[2, "CALCULATE", {"expression": "__&1.input__"}, [[3, null, null]]]\n[3, "WRITE_TO_USER", {"content": "The result of the math operation is: __&2.result__"}, []]'"""

            }
        ]
        super().__init__(config, commands, command_name_to_func, recognition_messages, explanation_messages)
