from youtube_autonomous.shortcodes.objects.shortcode_tag import ShortcodeTag
from youtube_autonomous.shortcodes.objects.shortcode import Shortcode
from yta_shortcodes.tag_type import ShortcodeTagType
from yta_general_utils.text.transformer import fix_unseparated_periods, fix_excesive_blank_spaces, fix_separated_parenthesis, add_missing_spaces_before_and_after_parenthesis, fix_separated_square_brackets, add_missing_spaces_before_and_after_square_brackets, fix_ellipsis
from yta_general_utils.programming.regular_expressions import GeneralRegularExpression
from shortcodes import Parser

import re


class ShortcodeParser:
    """
    This class parses the shortcodes from a text, classifies
    them and is capable of handling the information inside.

    We only accept simple shortcodes '[tag]' and block-scoped
    shortcodes '[block_tag] something inside [/block_tag]. 
    The parsing process will detect any invalid shortcode and
    raise an exception.

    _Developer needs to manually register the shortcode parser
    method for each shortcode that this parser will accept._
    """

    __parser: Parser = None
    """
    The shortcodes library parser object.
    """
    text: str = ''
    """
    The last text that this parser object has parsed, as it
    was originally.
    """
    text_sanitized: str = ''
    """
    The last text that this parser object has parsed, but
    sanitized. Any double blank space, any missing blank
    space after a parenthesis, a period or a square bracket
    has been added. Sanitization process is needed to have
    no problems when finding the shortcodes and detecting
    them through the entire text.

    This is the text that is actually used in the parsing
    process to look for the different shortcodes.
    """
    text_sanitized_with_simplified_shortcodes: str = ''
    """
    The last text that this parser object has parsed, with
    the shortcodes simplified after the parsing process
    has detected them. The text used has been sanitized
    previously.
    """
    text_sanitized_without_shortcodes: str = ''
    """
    The last text that this parser object has parsed, with
    no shortcodes on it. The text used has been sanitized
    previously.
    """
    shortcodes: list[Shortcode] = []
    """
    The shortcodes found by the parser in the last text
    parsed.
    """

    def __init__(self, shortcode_tags: list[ShortcodeTag]):
        # TODO: Check that 'shortcode_tags' elements are ShortcodeTags
        self.__parser = Parser(start = '[', end = ']', inherit_globals = False)

        # We register each shortcode in provided 'shortcode_tags'
        self.handlers = []
        for shortcode_tag in shortcode_tags:
            if shortcode_tag.is_block_scoped:
                self.__parser.register(lambda pargs, kwargs, context, content: shortcode_tag.handler(self.shortcodes, pargs, kwargs, context, content), shortcode_tag.tag.value, f'/{shortcode_tag.tag.value}')
            else:
                self.__parser.register(lambda pargs, kwargs, context: shortcode_tag.handler(self.shortcodes, pargs, kwargs, context, None), shortcode_tag.tag.value)

        # Wildcards seems to be not working here so we cannot handle
        # any unknown shortcode by ourselves. It is better to raise
        # the exception when an unknown shortcode is found because it
        # means that something is wrong with the next (could be user
        # mitstake or our own mistake)

    def parse(self, text: str):
        """
        This method parses the provided 'text' according to the shortcode
        tags passed in the initializing process. This method will return
        the original text, the text without shortcodes and the list of
        shortcodes found with their indexes and attributes.
        """
        text = '' if not text else text
        
        self.text = text
        self.text_sanitized = fix_excesive_blank_spaces(fix_ellipsis(fix_unseparated_periods(fix_separated_parenthesis(fix_separated_square_brackets(add_missing_spaces_before_and_after_parenthesis(add_missing_spaces_before_and_after_square_brackets(text)))))))
        self.text_sanitized_with_simplified_shortcodes = ''
        self.text_sanitized_without_shortcodes = ''
        self.shortcodes = []

        """
        This process involves 2 main steps:
        1. Creating shortcode instances from shortcode
        tags found in text and simplified.
        2. Obtain shortcode positions from simplified
        shortcode tags and clean the text.

        First, we parse the text with the shortcode tags
        we have registered on this parser. When they are
        detected, each shortcode tag handler (that has 
        been registered in the parser to handle its
        corresponding shortcode tag) extracts the 
        attributes (and the content if existing) and 
        appends a new shortcode to the list of shortcodes
        found, with all those attributes. In this step,
        the text is simplified so the shortcode tags are
        reduced to only their names. For example,
        [tag attribute=value] turns into [tag].

        Once we've detected the shortcodes, added them
        and simplified the shortcode tags, we iterate
        over all the shortcodes found to obtain its 
        corresponding simplified shortcode tag in the 
        text, so we can get the previous words positions.
        We update the shortcodes found with those 
        positions and we clear the shortcode tags from
        the text.
        """
        
        # We parse shortcodes and extract the information
        self.text_sanitized_with_simplified_shortcodes = self.__parser.parse(self.text_sanitized, context = None)
        """
        After this parse we have 'self.shortcodes' fulfilled
        because the parser has the tags registered, and those
        tags are appending the shortcodes found on the
        shortcodes list that is passed as parameter for each
        handling method
        """
        words = self.text_sanitized_with_simplified_shortcodes.split(' ')

        # We will iterate over all 
        index = 0
        while index < len(words):
            word = words[index]
            if re.fullmatch(GeneralRegularExpression.SHORTCODE.value, word):
                # TODO: Improve this to avoid the ones completed
                for shortcode in self.shortcodes:
                    if '/' in word:
                        # End shortcode
                        if shortcode.tag == word.replace('[', '').replace(']', '').replace('/', '') and not shortcode.previous_end_word_index:
                            # Is that shortcode
                            shortcode.previous_end_word_index = index - 1
                            break
                    else:
                        # Start shortcode
                        if shortcode.tag == word.replace('[', '').replace(']', '') and not shortcode.previous_start_word_index:
                            # Is that shortcode
                            shortcode.previous_start_word_index = index - 1
                            break

                # TODO: Remove that word
                del(words[index])
            else:
                index += 1

        self.text_sanitized_without_shortcodes = ' '.join(words)

        return self.text_sanitized_without_shortcodes
    
    """
    Specific shortcode handlers below
    TODO: Please, make them work dynamically so I don't need to create 
    a new method in code to any new shortcode, that is stupid.
    """

    # TODO: We need to manually register all shortcode handlers because
    # of some shared code that doesn't allow to do this dynamically (at
    # least by now, there is a task to improve this)
    # TODO: Remove this one, it is a test
    def __meme_shortcode_handler(self, pargs, kwargs, context):
        return self.__simple_shortcode_base_handler(pargs, kwargs, context, 'meme')
    
    def __simple_shortcode_base_handler(self, pargs, kwargs, context, tag: str):
        """
        Handles a simple [tag] shortcode.
        """
        attributes = []

        for parg in pargs:
            attributes.append({
                parg: None
            })

        for kwarg in kwargs:
            attribute_name = kwarg
            attribute_value = kwargs[attribute_name]

            attributes.append({
                attribute_name: attribute_value
            })

        # TODO: We need to handle the position within the text
        self.shortcodes.append(Shortcode(tag, ShortcodeTagType.SIMPLE, attributes, context, None))
            
        # This will replace the shortcode in the text
        return f'[{tag}]'

    # TODO: Remove this one, it is a test
    def __blocke_shortcode_handler(self, pargs, kwargs, context, content):
        return self.__block_shortcode_base_handler(pargs, kwargs, context, content, 'blocke')

    def __block_shortcode_base_handler(self, pargs, kwargs, context, content, tag: str):
        """
        Handles a block [tag] ... [/tag] shortcode.
        """
        attributes = []

        for parg in pargs:
            attributes.append({
                parg: None
            })

        for kwarg in kwargs:
            attribute_name = kwarg
            attribute_value = kwargs[attribute_name]

            attributes.append({
                attribute_name: attribute_value
            })

        # TODO: We need to handle the position within the text
        self.shortcodes.append(Shortcode(tag, ShortcodeTagType.BLOCK, attributes, context, content))
            
        # This will replace the shortcode and its content in the text
        return f'[{tag}]{content}[/{tag}]'