from pathlib import Path
import tempfile
from fontbakery.callable import check
from fontbakery.status import ERROR, FAIL, INFO, PASS, WARN
from fontbakery.section import Section
from fontbakery.message import Message
# used to inform get_module_profile whether and how to create a profile
from fontbakery.fonts_profile import profile_factory # NOQA pylint: disable=unused-import
from .shared_conditions import is_variable_font

profile_imports = ['.shared_conditions']
profile = profile_factory(default_section=Section("Checks inherited from Microsoft Font Validator"))

@check(
    id = 'com.google.fonts/check/fontvalidator',
    proposal = 'legacy:check/037'
)
def com_google_fonts_check_fontvalidator(font):
    """Checking with Microsoft Font Validator."""

    # In some cases we want to override the severity level of
    # certain checks in FontValidator:
    downgrade_to_warn = [
        # There are reports that this fontval check has an out-of-date
        # understanding of valid bits in fsSelection.
        # More info at:
        # https://github.com/googlei18n/fontmake/issues/414#issuecomment-379408127
        "There are undefined bits set in fsSelection field",

        # FIX-ME: Why did we downgrade this one to WARN?
        "Misoriented contour"
    ]

    # Some other checks we want to completely disable:
    disabled_fval_checks = [
        # FontVal E4012 thinks that
        # "Versions 0x00010000 and 0x0001002 are currently
        #  the only defined versions of the GDEF table."
        # but the GDEF chapter of the OpenType specification at
        # https://docs.microsoft.com/en-us/typography/opentype/spec/gdef
        # describes GDEF header version 1.3, which is not yet recognized
        # by FontVal, thus resulting in this spurious false-FAIL:
        "The version number is neither 0x00010000 nor 0x0001002",

        # These messages below are simply fontval given user feedback
        # on the progress of runnint it. It has nothing to do with
        # actual issues on the font files:
        "Validating glyph with index",
        "Table Test:",

        # No software is affected by Mac strings nowadays.
        # More info at: googlei18n/fontmake#414
        "The table doesn't contain strings for Mac platform",
        "The PostScript string is not present for both required platforms",

        # Font Bakery has got a native check for the xAvgCharWidth field
        # which is: com.google.fonts/check/xavgcharwidth
        "The xAvgCharWidth field does not equal the calculated value",

        # The optimal ordering suggested by FVal check W0020 seems to only be
        # relevant to performance optimizations on old versions of Windows
        # running on old hardware. Since such performance considerations
        # are most likely negligible, we're not going to bother users with
        # this check's table ordering requirements.
        # More info at:
        # https://github.com/googlefonts/fontbakery/issues/2105
        "Tables are not in optimal order",

        # Font Bakery has its own check for required/optional tables:
        # com.google.fonts/check/required_tables
        "Recommended table is missing"
    ]

    # There are also some checks that do not make
    # sense when we're dealing with variable fonts:
    VARFONT_disabled_fval_checks = [
        # Variable fonts typically do have lots of self-intersecting
        # contours because they are used to draw each portion
        # of variable glyph features.
        "Intersecting contours",
        "Intersecting components of composite glyph",

        # DeltaFormat = 32768 (same as 0x8000) means VARIATION_INDEX,
        # according to https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2
        # The FontVal problem description for this check (E5200) only mentions
        # the other values as possible valid ones. So apparently this means FontVal
        # implementation is not up-to-date with more recent versions of the OpenType spec
        # and that's why these spurious FAILs are being emitted.
        # That's good enough reason to mute it.
        # More info at:
        # https://github.com/googlefonts/fontbakery/issues/2109
        "The device table's DeltaFormat value is invalid"
    ]

    from fontTools.ttLib import TTFont
    if is_variable_font(TTFont(font)):
        disabled_fval_checks.extend(VARFONT_disabled_fval_checks)

    report_dir = tempfile.TemporaryDirectory(prefix="fontval-")
    try:
        import subprocess
        fval_cmd = [
            "FontValidator", "-file", font, "-all-tables",
            "-report-dir", report_dir.name, "-no-raster-tests"
        ]
        subprocess.check_output(fval_cmd, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as e:
        filtered_msgs = ""
        for line in e.output.decode().split("\n"):
            disable_it = False
            for substring in disabled_fval_checks:
                if substring in line:
                    disable_it = True
            if not disable_it:
                filtered_msgs += line + "\n"
        yield INFO, \
              Message("fontval-returned-error",
                      ("Microsoft Font Validator returned an error code."
                      " Output follows :\n\n{}\n").format(filtered_msgs))
    except (OSError, IOError) as error:
        yield ERROR, \
              Message("fontval-not-available",
                      "Mono runtime and/or Microsoft Font Validator"
                      " are not available!")
        raise error

    def report_message(msg, details):
        if details:
            if isinstance(details, list) and len(details) > 1:
                # We'll print lists with one item per line for
                # improved readability.
                if None in details:
                    details.remove(None)

                # A designer will likely not need the full list
                # in order to fix a problem.
                # Showing only the 10 first ones is more than enough
                # and helps avoid flooding the report.
                if len(details) > 25:
                    num_similar = len(details) - 10
                    details = details[:10]
                    details.append(f"NOTE: {num_similar} other similar"
                                    " results were hidden!")
                details = '\n\t- ' + '\n\t- '.join(details)
            return f"MS-FonVal: {msg} DETAILS: {details}"
        else:
            return f"MS-FonVal: {msg}"

    report_file = Path(report_dir.name) / f"{Path(font).name}.report.xml"

    grouped_msgs = {}
    with open(report_file, "rb") as xml_report:
        from lxml import etree
        doc = etree.fromstring(xml_report.read())
        for report in doc.iterfind('.//Report'):
            msg = report.get("Message")
            details = report.get("Details")

            disable_it = False
            for substring in disabled_fval_checks:
                if substring in msg:
                    disable_it = True
            if disable_it:
                continue

            if msg not in grouped_msgs:
                grouped_msgs[msg] = {"errortype": report.get("ErrorType"),
                                     "details": [details]}
            else:
                if details not in grouped_msgs[msg]["details"]:
                    # avoid cluttering the output with tons of identical reports
                    # yield INFO, 'grouped_msgs[msg]["details"]: {}'.format(grouped_msgs[msg]["details"])
                    grouped_msgs[msg]["details"].append(details)

    # ---------------------------
    # Clean-up generated files...
    del report_dir

    # ---------------------------
    # Here we start emitting the grouped log messages
    for msg, data in grouped_msgs.items():
        # But before printing we try to make the "details" more
        # readable. Otherwise the user would get the text terminal
        # flooded with messy data.

        # No need to print is as a list if wereally only
        # got one log message of this kind:
        if len(data["details"]) == 1:
            data["details"] = data["details"][0]

        # Simplify the list of glyph indices by only displaying
        # their numerical values in a list:
        for glyph_index in ["Glyph index ", "glyph# "]:
            if data["details"] and \
               data["details"][0] and \
               glyph_index in data["details"][0]:
                try:
                    data["details"] = {'Glyph index': [int(x.split(glyph_index)[1])
                                                       for x in data["details"]]}
                    break
                except ValueError:
                    pass

        # And, finally, the log messages are emitted:
        if data["errortype"] == "P":
            yield PASS, report_message(msg, data["details"])

        elif data["errortype"] == "E":
            status = FAIL
            for substring in downgrade_to_warn:
                if substring in msg:
                    status = WARN
            yield status, Message("fontval-error", report_message(msg, data["details"]))

        elif data["errortype"] == "W":
            yield WARN, Message("fontval-warn", report_message(msg, data["details"]))

        else:
            yield INFO, Message("fontval-info", report_message(msg, data["details"]))

profile.auto_register(globals())
