#!/usr/bin/env python3
# -*- coding: utf-8 -*-


"""HTML Minifier functions for CSS-HTML-JS-Minify."""


import re


__all__ = ("html_minify",)


def condense_html_whitespace(html):
    """Condense HTML, but be safe first if it have textareas or pre tags.

    >>> condense_html_whitespace('<i>  <b>    <a> test </a>    </b> </i><br>')
    '<i><b><a> test </a></b></i><br>'
    """  # first space between tags, then empty new lines and in-between.
    tagsStack = []
    split = re.split("(<\\s*pre.*>|<\\s*/\\s*pre\\s*>|<\\s*textarea.*>|<\\s*/\\s*textarea\\s*>)", html, flags=re.IGNORECASE)
    for i in range(0, len(split)):
        # if we are on a tag
        if (i + 1) % 2 == 0:
            tag = rawtag(split[i])
            if tag.startswith("/"):
                if not tagsStack or "/" + tagsStack.pop() != tag:
                    raise Exception("Some tag is not closed properly")
            else:
                tagsStack.append(tag)
            continue

        # else check if we are outside any nested <pre>/<textarea> tag
        if not tagsStack:
            temp = re.sub(r">\s+<", "> <", split[i])
            split[i] = re.sub(r"\s{2,}|[\r\n]", " ", temp)
    return "".join(split)


def rawtag(str):
    if re.match("<\\s*pre.*>", str, flags=re.IGNORECASE):
        return "pre"
    if re.match("<\\s*textarea.*>", str, flags=re.IGNORECASE):
        return "txt"
    if re.match("<\\s*/\\s*pre\\s*>", str, flags=re.IGNORECASE):
        return "/pre"
    if re.match("<\\s*/\\s*textarea\\s*>", str, flags=re.IGNORECASE):
        return "/txt"


def condense_style(html):
    """Condense style html tags.

    >>> condense_style('<style type="text/css">*{border:0}</style><p>a b c')
    '<style>*{border:0}</style><p>a b c'
    """  # May look silly but Emmet does this and is wrong.
    return html.replace('<style type="text/css">', "<style>").replace("<style type='text/css'>", "<style>").replace("<style type=text/css>", "<style>")


def condense_script(html):
    """Condense script html tags.

    >>> condense_script('<script type="text/javascript"> </script><p>a b c')
    '<script> </script><p>a b c'
    """  # May look silly but Emmet does this and is wrong.
    return html.replace('<script type="text/javascript">', "<script>").replace("<style type='text/javascript'>", "<script>").replace("<style type=text/javascript>", "<script>")


def clean_unneeded_html_tags(html):
    """Clean unneeded optional html tags.

    >>> clean_unneeded_html_tags('a<body></img></td>b</th></tr></hr></br>c')
    'abc'
    """
    for tag_to_remove in """</area> </base> <body> </body> </br> </col>
        </colgroup> </dd> </dt> <head> </head> </hr> <html> </html> </img>
        </input> </li> </link> </meta> </option> </param> <tbody> </tbody>
        </td> </tfoot> </th> </thead> </tr> </basefont> </isindex> </param>
            """.split():
        html = html.replace(tag_to_remove, "")
    return html  # May look silly but Emmet does this and is wrong.


def remove_html_comments(html):
    """Remove all HTML comments, Keep all for Grunt, Grymt and IE.

    >>> _="<!-- build:dev -->a<!-- endbuild -->b<!--[if IE 7]>c<![endif]--> "
    >>> _+= "<!-- kill me please -->keep" ; remove_html_comments(_)
    '<!-- build:dev -->a<!-- endbuild -->b<!--[if IE 7]>c<![endif]--> keep'
    """  # Grunt uses comments to as build arguments, bad practice but still.
    return re.compile(r"<!-- .*? -->", re.I).sub("", html)


def unquote_html_attributes(html):
    """Remove all HTML quotes on attibutes if possible.

    >>> unquote_html_attributes('<img   width="9" height="5" data-foo="0"  >')
    '<img width=9 height=5 data-foo=0 >'
    """  # data-foo=0> might cause errors on IE, we leave 1 space data-foo=0 >
    # cache all regular expressions on variables before we enter the for loop.
    any_tag = re.compile(r"<\w.*?>", re.I | re.MULTILINE | re.DOTALL)
    space = re.compile(r" \s+|\s +", re.MULTILINE)
    space1 = re.compile(r"\w\s+\w", re.MULTILINE)
    space2 = re.compile(r'"\s+>', re.MULTILINE)
    space3 = re.compile(r"'\s+>", re.MULTILINE)
    space4 = re.compile("\"\s\s+\w+=\"|'\s\s+\w+='|\"\s\s+\w+=|'\s\s+\w+=", re.MULTILINE)
    space6 = re.compile(r"\d\s+>", re.MULTILINE)
    quotes_in_tag = re.compile('([a-zA-Z]+)="([a-zA-Z0-9-_\.]+)"')
    # iterate on a for loop cleaning stuff up on the html markup.
    for tag in iter(any_tag.findall(html)):
        # exceptions of comments and closing tags
        if tag.startswith("<!") or tag.find("</") > -1:
            continue
        original = tag
        # remove white space inside the tag itself
        tag = space2.sub('" >', tag)  # preserve 1 white space is safer
        tag = space3.sub("' >", tag)
        for each in space1.findall(tag) + space6.findall(tag):
            tag = tag.replace(each, space.sub(" ", each))
        for each in space4.findall(tag):
            tag = tag.replace(each, each[0] + " " + each[1:].lstrip())
        # remove quotes on some attributes
        tag = quotes_in_tag.sub(r"\1=\2 ", tag)  # See Bug #28
        if original != tag:  # has the tag been improved ?
            html = html.replace(original, tag)
    return html.strip()


def add_timestamp(html):
    return f"{html}\n<!-- {format(datetime.now().isoformat()[:-7].lower())} -->\n"


def html_minify(html, settings: dict):
    """Minify HTML main function.

    >>> html_minify(' <p  width="9" height="5"  > <!-- a --> b </p> c <br> ')
    '<p width=9 height=5 > b c <br>'
    """
    comments = settings.get("comments", False)
    html = remove_html_comments(html) if not comments else html
    html = condense_style(html)
    html = condense_script(html)
    html = clean_unneeded_html_tags(html)
    html = condense_html_whitespace(html)
    html = unquote_html_attributes(html)
    html = add_timestamp(html) if settings.get("timestamp", False) else html
    return html.strip()
