import json
import textwrap
import re

from gatox.cli.colors import (RED_DASH, GREEN_PLUS, GREEN_EXCLAIM, RED_EXCLAIM,
                      BRIGHT_DASH, YELLOW_EXCLAIM, YELLOW_DASH)

from colorama import Style, Fore


class Singleton (type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(
                *args, **kwargs
            )
        return cls._instances[cls]


class Output(metaclass=Singleton):

    def __init__(self, color: bool):
        self.color = color

        self.red_dash = RED_DASH if color else '[-]'
        self.red_explain = RED_EXCLAIM if color else '[!]'
        self.green_plus = GREEN_PLUS if color else '[+]'
        self.green_exclaim = GREEN_EXCLAIM if color else '[!]'
        self.bright_dash = BRIGHT_DASH if color else '-'
        self.yellow_exclaim = YELLOW_EXCLAIM if color else "[!]"
        self.yellow_dash = YELLOW_DASH if color else "[-]"

    @classmethod
    def write_json(cls, execution_wrapper, output_json):
        """Writes JSON to path specified earlier.

        Args:
            execution_wrapper (Execution): Wrapper object for Gato
            enumeration run.
            output_json (str): Path to Json file
        Returns:
            True if successful, false otherwise.
        """
        if execution_wrapper.user_details:
            with open(output_json, 'w') as json_out:
                json_out.write(
                    json.dumps(execution_wrapper.toJSON(), indent=4)
                )
            return True

    @classmethod
    def error(cls, message: str):
        """Prints error text.

        Args:
            message (str): Message to format.
        """
        print(f"{Output().red_dash} {message}")

    @classmethod
    def info(cls, message: str, end='\n', flush=False):
        """Prints info text, this adds a green [+] to the message.

        Args:
            message (str): The message to print.
        """
        print(f"{Output().green_plus} {message}", end=end, flush=flush)

    @classmethod
    def tabbed(cls, message: str):
        """Prints a tabbed message with a bright '-'

        Args:
            message (str): The message to print.
        """
        print(f"    {Output().bright_dash} {message}")

    @classmethod
    def header(cls, message: str):
        """Prints a message surrounded by '---'

        Args:
            message (str): The message to print.
        """
        print(
            f"{cls.bright('---')}"
            f" {message} "
            f"{cls.bright('---')}"
        )

    @classmethod
    def result(cls, message: str):
        """Prints a result, this is something good that the tool found.

        Args:
            message (str): The message to print.
        """
        print(f"{Output().green_plus} {message}")

    @classmethod
    def generic(cls, message: str):
        """Generic output to print block wrapped text.
        """

        def get_length_without_color_codes(text):
            # Use regular expressions to remove ANSI color codes
            ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
            text_without_color = ansi_escape.sub('', text)
            
            # Calculate the length of the string without color codes
            length_without_color = len(text_without_color)
            
            return length_without_color

        lines = textwrap.wrap(message, width=78)
        for line in lines:
            padding = 78 - get_length_without_color_codes(line)

            if line[0] not in [' ','-','=','-','~']:
                print(f"| {line}{' '*(padding-1)}|")
            else:
                print(f"|{line}{' '*padding}|")

    @classmethod
    def owned(cls, message: str):
        """Prints a result, this is means that the tool has found a likely
        vector to own something.

        Args:
            message (str): The message to print.
        """
        print(f"{Output().green_exclaim} {message}")

    @classmethod
    def inform(cls, message: str):
        """Used to inform a user.

        Args:
            message (str): The message to print.
        """

        print(f"{Output().yellow_dash} {message}")

    @classmethod
    def warn(cls, message: str):
        """Used to let the user know something that they should not, but
        unlikely to lead to an exploit.
        """
        print(f"{Output().yellow_exclaim} {message}")

    @classmethod
    def bright(cls, toformat: str):
        """Highlights the text and returns it.

        Args:
            toformat (str): Message to format.

        Returns:
            (str): Highlighted text.
        """

        if cls not in cls._instances or Output().color:
            return f'{Style.BRIGHT}{toformat}{Style.RESET_ALL}'
        else:
            return toformat

    @classmethod
    def yellow(cls, toformat: str):
        """Makes the text yellow and returns it.

        Args:
            toformat (str): Message to format.

        Returns:
            (str)): Formatted message.
        """
        if cls not in cls._instances or Output().color:
            return f'{Fore.YELLOW}{toformat}{Style.RESET_ALL}'
        else:
            return toformat
        
    @classmethod
    def blue(cls, toformat: str):
        """Makes the text blue and returns it.

        Args:
            toformat (str): Message to format.

        Returns:
            (str)): Formatted message.
        """
        if cls not in cls._instances or Output().color:
            return f'{Fore.CYAN}{toformat}{Style.RESET_ALL}'
        else:
            return toformat

    @classmethod
    def green(cls, toformat: str):
        """Makes the text green and returns it.

        Args:
            toformat (str): Message to format.

        Returns:
            (str)): Formatted message.
        """
        if cls not in cls._instances or Output().color:
            return f'{Fore.GREEN}{toformat}{Style.RESET_ALL}'
        else:
            return toformat

    @classmethod
    def red(cls, toformat: str):
        """Makes the text red and returns it.

        Args:
            toformat (str): Message to format.

        Returns:
            (str)): Formatted message.
        """
        if cls not in cls._instances or Output().color:
            return f'{Fore.RED}{toformat}{Style.RESET_ALL}'
        else:
            return toformat
