class tag:
    '''
    This class is responsible for create HTML tag, such as script, link and
    other. It is requied for creating tags in the app view.
    '''

    def __init__(self, name: str, twice: bool) -> None:
        '''
        This function create new tag from its name. It also require parameter
        named 'twice'. Twice told that tag has second closing tag. For example
        when twice is True, then tag look like <a></a>, but when twice is 
        False, tag looks like <br/>.

        Parameters:
            name (str): Name of the tag
            twice (bool): True when tag has closing tag, false when not
        '''

        name = name.strip()

        if len(name) == 0:
            raise Exception("Can not create tag without name.")

        for letter in name:
            if letter.isspace():
                raise Exception("HTML Tag can not contain white space.")
        
        self.__name = name
        self.__twice = twice
        self.__content = str()
        self.__attributes = dict()

    @property
    def name(self) -> str:
        ''' This function return name of the tag. '''

        return self.__name

    @property
    def twice(self) -> bool:
        ''' This function return that tag has closing tag. '''
        return self.__twice

    @property
    def content(self) -> str:
        ''' This function return content of the tag, like innerHTML. '''
        return self.__content

    @content.setter
    def content(self, target: str) -> None:
        ''' This function set new content, like innerHTML. '''

        self.__content = target

    def set_attribute(
        self,
        name: str, 
        content: str | int | float | bool | None
    ) -> None:
        ''' 
        This function set value for tag attribute. 

        Parameters:
            name (str): Name of the attribute
            content (str | int | float | bool | None): Content of the attribute
        '''

        name = name.strip()

        for letter in name:
            if letter.isspace():
                raise Exception("Tag attribute can not contain white char.")

        self.__attributes[name] = content

    def drop_attribute(self, name: str) -> str | int | float | bool | None:
        '''
        This function remove attribute by name, when parameter exists.

        Parameters:
            name (str): Name of the attribute

        Returns:
            (str | int | float | bool | None): Content of the droped attribute
        '''

        if not name in self.__attributes:
            return None

        copy = self.__attributes[name]
        del self.__attributes[name]

        return copy

    def get_attribute(self, name: str) -> str | int | float | bool | None:
        '''
        This function return content of the attribute, when exists.

        Parameters:
            name (str): Name of the attribute

        Returns:
            (str | int | float | bool | None): Content of the attribute 
        '''

        if not name in self.__attributes:
            raise Exception("Attribute " + name + " not exists.")

        return self.__attributes[name]

    def has_attribute(self, name: str) -> bool:
        ''' This function return that attribute exists or not. '''

        return name in self.__attributes

    @property
    def attributes(self) -> list:
        ''' This function return copy of the attributes dict. '''

        return self.__attributes.copy() 

    def render(self) -> str:
        '''
        This function return tag rendered to string.
        '''

        attributes = str()

        for attribute in self.attributes.keys():
            content = self.get_attribute(attribute)

            if content is None:
                attributes = attributes + " " + attribute
                continue

            content = str(content)
            content = content.replace("\"", "\\\"")

            attributes = attributes + " " + attribute + "=\""
            attributes = attributes + content + "\""

        tag = "<" + self.name + attributes + ">\n"

        if not self.twice:
            return tag

        closing_tag = "</" + self.name + ">"

        return tag + self.content + "\n" + closing_tag + "\n"

    def __str__(self) -> str:
        ''' This function return tag rendered to string. '''
        
        return self.render()

class style(tag):
    '''
    This class is responsible for style.
    '''

    def __init__(self, content: str | None = None):
        '''
        This create new style tag, with optional setuped content.

        Parameters:
            content (str | None): Stylesheet of the style tag
        '''

        super().__init__("style", True)

        if content is not None:
            self.content = content

class script(tag):
    ''' 
    This class is responsible for script loading.
    '''

    def __init__(self, src: str | None = None):
        '''
        This create new script tag.

        Parameters:
            src (str | None) = None: Source of the script
        '''

        super().__init__("script", True)

        if src is not None:
            self.set_attribute("src", src)

    @property
    def src(self) -> str | None:
        ''' Return source of the script, or None when not set. '''

        if self.has_attribute("src"):
            return self.get_attribute("src")

        return None

    @src.setter
    def src(self, target: str | None) -> None:
        ''' This function set soure of the script, or drop it when None. '''

        if target is not None:
            self.set_attribute("src", target)
            return

        if self.has_attribute("src"):
            self.drop_attribute("src")
            return

class link(tag):
    '''
    This class is responsible for creating link tags.
    '''

    def __init__(self):
        ''' This function initialize link tag. '''

        super().__init__("link", False)

    @property
    def rel(self) -> str | None:
        ''' This function return rel of the link, or None when not set. '''

        if not self.has_attribute("rel"):
            return None

        return self.get_attribute("rel")

    @property
    def href(self) -> str | None:
        ''' This function return href of the link, or None, when not set. '''

        if not self.has_attribute("href"):
            return None

        return self.get_attribute("href")

    @property
    def type(self) -> str | None:
        ''' This function return type of the link, or None when not set. '''

        if not self.has_attribute("type"):
            return None

        return self.get_attribute("type")

    @rel.setter
    def rel(self, target: str | None) -> None:
        ''' This function set rel of the link, or drop it when None. '''

        if target is not None:
            self.set_attribute("rel", target)
            return

        if self.has_attribute("rel"):
            self.drop_attribute("rel")

    @href.setter
    def href(self, target: str | None) -> None:
        ''' This function set href of the link, or drop it when None. '''
        
        if target is not None:
            self.set_attribute("href", target)
            return

        if self.has_attribute("href"):
            self.drop_attribute("href")

    @type.setter
    def type(self, target: str | None) -> None:
        ''' This function set type of the link, or drop it when None. '''
        
        if target is not None:
            self.set_attribute("type", target)
            return

        if self.has_attribute("type"):
            self.drop_attribute("type")