"""
Checks for suitability for in-car displays (ISO 15008).
"""
from beziers.line import Line
from beziers.path import BezierPath
from beziers.point import Point
from fontTools.pens.boundsPen import BoundsPen

from fontbakery.prelude import check, FAIL, Message
from fontbakery.utils import exit_with_install_instructions

DISCLAIMER = """
        (Note that passing this check does not guarantee compliance with ISO-15008.)
"""


def xheight_intersections(ttFont, glyph):
    glyphset = ttFont.getGlyphSet()
    if glyph not in glyphset:
        return []

    paths = BezierPath.fromFonttoolsGlyph(ttFont, glyph)
    if len(paths) != 1:
        return []
    path = paths[0]

    xheight = ttFont["OS/2"].sxHeight

    bounds = path.bounds()
    bounds.addMargin(10)
    ray = Line(Point(bounds.left, xheight), Point(bounds.right, xheight))
    intersections = []
    for seg in path.asSegments():
        intersections.extend(seg.intersections(ray))
    return sorted(intersections, key=lambda i: i.point.x)


def stem_width(ttFont):
    glyphset = ttFont.getGlyphSet()
    if "l" not in glyphset:
        return None

    intersections = xheight_intersections(ttFont, "l")
    if len(intersections) != 2:
        return None
    (i1, i2) = intersections[0:2]
    return abs(i1.point.x - i2.point.x)


def pair_kerning(font, left, right):
    """The kerning between two glyphs (specified by name), in font units."""

    try:
        import uharfbuzz as hb
    except ImportError:
        exit_with_install_instructions("iso15008")

    with open(font.file, "rb") as fontfile:
        fontdata = fontfile.read()
    face = hb.Face(fontdata)
    font = hb.Font(face)
    scale = face.upem
    font.scale = (scale, scale)
    buf = hb.Buffer()
    buf.add_str(left + right)
    buf.guess_segment_properties()
    hb.shape(font, buf, {"kern": True})
    pos = buf.glyph_positions[0].x_advance
    buf = hb.Buffer()
    buf.add_str(left + right)
    buf.guess_segment_properties()
    hb.shape(font, buf, {"kern": False})
    pos2 = buf.glyph_positions[0].x_advance
    return pos - pos2


@check(
    id="com.google.fonts/check/iso15008_proportions",
    rationale="""
        According to ISO 15008, fonts used for in-car displays should not be
        too narrow or too wide.

        To ensure legibility of this font on in-car information systems,
        it is recommended that the ratio of H width to H height
        is between 0.65 and 0.80.
    """
    + DISCLAIMER,
    proposal=[
        "https://github.com/fonttools/fontbakery/issues/1832",
        "https://github.com/fonttools/fontbakery/issues/3250",
    ],
)
def com_google_fonts_check_iso15008_proportions(ttFont):
    """Check if 0.65 => (H width / H height) => 0.80"""
    glyphset = ttFont.getGlyphSet()
    if "H" not in glyphset:
        yield FAIL, Message(
            "glyph-not-present",
            "There was no 'H' glyph in the font,"
            " so the proportions could not be tested",
        )
        return

    pen = BoundsPen(glyphset)
    glyphset["H"].draw(pen)
    (xMin, yMin, xMax, yMax) = pen.bounds
    proportion = (xMax - xMin) / (yMax - yMin)
    if not 0.65 <= proportion <= 0.80:
        yield FAIL, Message(
            "invalid-proportion",
            f"The proportion of H width to H height ({proportion})"
            f"does not conform to the expected range of 0.65-0.80",
        )


@check(
    id="com.google.fonts/check/iso15008_stem_width",
    rationale="""
        According to ISO 15008, fonts used for in-car displays should
        not be too light or too bold.

        To ensure legibility of this font on in-car information systems,
        it is recommended that the ratio of stem width to ascender height
        is between 0.10 and 0.20.
    """
    + DISCLAIMER,
    proposal=[
        "https://github.com/fonttools/fontbakery/issues/1832",
        "https://github.com/fonttools/fontbakery/issues/3251",
    ],
)
def com_google_fonts_check_iso15008_stem_width(ttFont):
    """Check if 0.10 <= (stem width / ascender) <= 0.82"""
    width = stem_width(ttFont)
    if width is None:
        yield FAIL, Message("no-stem-width", "Could not determine stem width")
        return
    ascender = ttFont["hhea"].ascender
    proportion = width / ascender
    if not 0.10 <= proportion <= 0.20:
        yield FAIL, Message(
            "invalid-proportion",
            f"The proportion of stem width to ascender ({proportion})"
            f"does not conform to the expected range of 0.10-0.20",
        )


@check(
    id="com.google.fonts/check/iso15008_intercharacter_spacing",
    rationale="""
        According to ISO 15008, fonts used for in-car displays should not
        be too narrow or too wide.

        To ensure legibility of this font on in-car information systems,
        it is recommended that the spacing falls within the following values:

        * space between vertical strokes (e.g. "ll") should be 150%-240%
          of the stem width.

        * space between diagonals and verticals (e.g. "vl") should be
          at least 85% of the stem width.

        * diagonal characters should not touch (e.g. "vv").
    """
    + DISCLAIMER,
    proposal=[
        "https://github.com/fonttools/fontbakery/issues/1832",
        "https://github.com/fonttools/fontbakery/issues/3252",
    ],
)
def com_google_fonts_check_iso15008_intercharacter_spacing(font, ttFont):
    """Check if spacing between characters is adequate for display use"""
    width = stem_width(ttFont)

    # Because an l can have a curly tail, we don't want the *glyph* sidebearings;
    # we want the sidebearings measured using a line at Y=x-height.
    l_intersections = xheight_intersections(ttFont, "l")
    if width is None or len(l_intersections) < 2:
        yield FAIL, Message("no-stem-width", "Could not determine stem width")
        return

    l_lsb = l_intersections[0].point.x
    l_advance = ttFont["hmtx"]["l"][0]
    l_rsb = l_advance - l_intersections[-1].point.x

    l_l = l_rsb + pair_kerning(font, "l", "l") + l_lsb
    if l_l is None:
        yield FAIL, Message(
            "glyph-not-present",
            "There was no 'l' glyph in the font, so the spacing could not be tested",
        )
        return
    if not 1.5 <= (l_l / width) <= 2.4:
        yield FAIL, Message(
            "bad-vertical-vertical-spacing",
            f"The space between vertical strokes ({l_l})"
            f" does not conform to the expected"
            f" range of {width * 1.5}-{width * 2.4}",
        )

    # For v, however, a simple LSB/RSB is adequate.
    glyphset = ttFont.getGlyphSet()
    pen = BoundsPen(glyphset)
    glyphset["v"].draw(pen)
    (xMin, yMin, xMax, yMax) = pen.bounds
    v_advance = ttFont["hmtx"]["v"][0]

    v_lsb = xMin
    v_rsb = v_advance - (v_lsb + xMax - xMin)

    l_v = l_rsb + pair_kerning(font, "l", "v") + v_lsb

    if l_v is None:
        yield FAIL, Message(
            "glyph-not-present",
            "There was no 'v' glyph in the font, so the spacing could not be tested",
        )
        return

    if (l_v / width) <= 0.85:
        yield FAIL, Message(
            "bad-vertical-diagonal-spacing",
            f"The space between vertical and diagonal strokes ({l_v})"
            f" was less than the expected"
            f" value of {width * 0.85}",
        )

    if v_rsb + pair_kerning(font, "v", "v") + v_lsb <= 0:
        yield FAIL, Message(
            "bad-diagonal-diagonal-spacing", "Diagonal strokes (vv) were touching"
        )


@check(
    id="com.google.fonts/check/iso15008_interword_spacing",
    rationale="""
        According to ISO 15008, fonts used for in-car displays
        should not be too narrow or too wide.

        To ensure legibility of this font on in-car information systems,
        it is recommended that the space character should have advance width
        between 250% and 300% of the space between the letters l and m.
    """
    + DISCLAIMER,
    proposal=[
        "https://github.com/fonttools/fontbakery/issues/1832",
        "https://github.com/fonttools/fontbakery/issues/3253",
    ],
)
def com_google_fonts_check_iso15008_interword_spacing(font, ttFont):
    """Check if spacing between words is adequate for display use"""
    l_intersections = xheight_intersections(ttFont, "l")
    if len(l_intersections) < 2:
        yield FAIL, Message(
            "glyph-not-present",
            "There was no 'l' glyph in the font, so the spacing could not be tested",
        )
        return

    l_advance = ttFont["hmtx"]["l"][0]
    l_rsb = l_advance - l_intersections[-1].point.x

    glyphset = ttFont.getGlyphSet()
    pen = BoundsPen(glyphset)
    glyphset["m"].draw(pen)
    (xMin, yMin, xMax, yMax) = pen.bounds
    m_advance = ttFont["hmtx"]["m"][0]
    m_lsb = xMin
    m_rsb = m_advance - (m_lsb + xMax - xMin)

    n_lsb = ttFont["hmtx"]["n"][1]

    l_m = l_rsb + pair_kerning(font, "l", "m") + m_lsb
    space_width = ttFont["hmtx"]["space"][0]
    # Add spacing caused by normal sidebearings
    space_width += m_rsb + n_lsb

    if not 2.50 <= space_width / l_m <= 3.0:
        yield FAIL, Message(
            "bad-interword-spacing",
            f"The interword space ({space_width}) was"
            f" outside the recommended range ({l_m*2.5}-{l_m*3.0})",
        )


@check(
    id="com.google.fonts/check/iso15008_interline_spacing",
    rationale="""
        According to ISO 15008, fonts used for in-car displays
        should not be too narrow or too wide.

        To ensure legibility of this font on in-car information systems,
        it is recommended that the vertical metrics be set to a minimum
        at least one stem width between the bottom of the descender
        and the top of the ascender.
    """
    + DISCLAIMER,
    proposal=[
        "https://github.com/fonttools/fontbakery/issues/1832",
        "https://github.com/fonttools/fontbakery/issues/3254",
    ],
)
def com_google_fonts_check_iso15008_interline_spacing(ttFont):
    """Check if spacing between lines is adequate for display use"""
    glyphset = ttFont.getGlyphSet()
    if "h" not in glyphset or "g" not in glyphset:
        yield FAIL, Message(
            "glyph-not-present",
            "There was no 'g'/'h' glyph in the font,"
            " so the spacing could not be tested",
        )
        return

    pen = BoundsPen(glyphset)
    glyphset["h"].draw(pen)
    (_, _, _, h_yMax) = pen.bounds

    pen = BoundsPen(glyphset)
    glyphset["g"].draw(pen)
    (_, g_yMin, _, _) = pen.bounds

    linegap = (
        (g_yMin - ttFont["OS/2"].sTypoDescender)
        + ttFont["OS/2"].sTypoLineGap
        + (ttFont["OS/2"].sTypoAscender - h_yMax)
    )
    width = stem_width(ttFont)
    if width is None:
        yield FAIL, Message("no-stem-width", "Could not determine stem width")
    elif linegap < width:
        yield FAIL, Message(
            "bad-interline-spacing",
            f"The interline space {linegap} should"
            f" be more than the stem width {width}",
        )
