from lxml.html import defs

import re

ALL_WHITESPACE = re.compile(r"^\s*$", re.UNICODE)
INLINE_TAGS = defs.special_inline_tags | defs.phrase_tags | defs.font_style_tags
MARKER = "LAUNDRY-INSERT"


def is_whitespace(txt):
    """Utility method to test if txt is all whitespace or None."""
    return txt is None or bool(ALL_WHITESPACE.match(txt))


def remove_element(el):
    parent = el.getparent()
    if el.tail:
        previous = el.getprevious()
        if previous is not None:
            if previous.tail:
                previous.tail += el.tail
            else:
                previous.tail = el.tail
        else:
            if parent.text:
                parent.text += el.tail
            else:
                parent.text = el.tail

    parent.remove(el)


def remove_empty_tags(doc, extra_empty_tags=[]):
    """Removes all empty tags from a HTML document. Javascript editors
    and browsers have a nasty habit of leaving stray tags around after
    their contents have been removed. This function removes all such
    empty tags, leaving only valid empty tags.

    In addition consecutive <br/> tags are folded into a single tag.
    This forces whitespace styling to be done using CSS instead of via an
    editor, which almost always produces better and more consistent results.
    """
    empty_tags = {"br", "hr", "img", "input"}
    empty_tags.update(set(extra_empty_tags))
    legal_empty_tags = frozenset(empty_tags)

    if hasattr(doc, "getroot"):
        doc = doc.getroot()

    def clean(doc):
        victims = []
        for el in doc.iter():
            if el.tag == "br":
                preceding = el.getprevious()
                parent = el.getparent()

                if (
                    (preceding is None and not parent.text)
                    or (
                        preceding is not None
                        and preceding.tag == el.tag
                        and not preceding.tail
                    )
                    or (not el.tail and el.getnext() is None)
                ):
                    victims.append(el)
                    continue

            if el.tag in legal_empty_tags:
                continue

            # Empty <a> can be used as anchor.
            if (el.tag == "a") and (("name" in el.attrib) or ("id" in el.attrib)):
                continue

            if len(el) == 0 and is_whitespace(el.text):
                victims.append(el)
                continue

        if victims and victims[0] == doc:
            doc.clear()
            return 0
        else:
            for victim in victims:
                remove_element(victim)

        return len(victims)

    while clean(doc):
        pass

    return doc


def strip_outer_breaks(doc):
    """Remove any toplevel break elements."""
    victims = []

    for i in range(len(doc)):
        el = doc[i]
        if el.tag == "br":
            victims.append(el)

    for victim in victims:
        remove_element(victim)
