# encoding: utf-8

from __future__ import absolute_import, unicode_literals

import re
import tidylib

from django.utils import six


try:
    # Python 2
    unichr
except NameError:
    # Python 3
    unichr = chr


# Based on http://stackoverflow.com/questions/92438/
#
# We omit chars 9-13 (tab, newline, vertical tab, form feed, return) and 32
# (space) to avoid clogging our reports with warnings about common,
# non-problematic codes but still allow stripping things which will cause lxml
# to choke

CONTROL_CHAR_RE = re.compile('[%s]' % "".join(
    re.escape(unichr(c)) for c in range(0, 8) + range(14, 31) + range(127, 160)
))


def tidy_html(html):
    """
    Process an input string containing HTML and return a tuple (xhtml,
    errors, warnings) containing the output of tidylib and lists of
    validation errors and warnings.

    Input must be unicode.
    Output will be valid XHTML.
    """
    if not isinstance(html, six.text_type):
        raise ValueError("tidyhtml must be called with a Unicode string!")

    errors = list()
    warnings = list()

    # First, deal with embedded control codes:
    html, sub_count = CONTROL_CHAR_RE.subn(" ", html)
    if sub_count:
        warnings.append("Stripped %d control characters from body: %s" % (
            sub_count,
            set(ord(i) for i in CONTROL_CHAR_RE.findall(html))
        ))

    # tidylib.tidy_fragment will choke if given a full HTML document. This is a
    # primitive content sniff to decide whether to call tidy_document instead:
    if "<html" in html[:1024]:
        tidy_f = tidylib.tidy_document
        doc_mode = True
    else:
        tidy_f = tidylib.tidy_fragment
        doc_mode = False

    html, messages = tidy_f(html.strip(), {
        "char-encoding": "utf8",
        "clean": False,
        "drop-empty-paras": False,
        "drop-font-tags": True,
        "drop-proprietary-attributes": False,
        "fix-backslash": True,
        "indent": True,
        "output-xhtml": True,
    }
    )

    messages = filter(None, (l.strip() for l in messages.split("\n") if l))

    # postprocess warnings to avoid HTML fragments being reported as lacking
    # doctype and title:
    for msg in messages:
        if not doc_mode and "Warning: missing <!DOCTYPE> declaration" in msg:
            continue
        if (not doc_mode
                and "Warning: inserting missing 'title' element" in msg):
            continue
        if not doc_mode and "Warning: inserting implicit <body>" in msg:
            continue

        if "Error:" in msg:
            errors.append(msg)
        else:
            warnings.append(msg)

    return html, errors, warnings
