"""
Command-line tools for common bdflib operations.
"""

import argparse
from typing import Counter, Callable, TypeVar
import unicodedata
import bdflib
from bdflib import reader, writer, effects, glyph_combining, xlfd

T = TypeVar("T")


def add_standard_arguments(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=bdflib.__version__,
    )

    # I'd love to use argparse.FileType, but https://bugs.python.org/issue14156
    parser.add_argument(
        "input",
        metavar="INPUT",
        help="Read a BDF font from file INPUT.",
    )
    parser.add_argument(
        "output",
        metavar="OUTPUT",
        help="Write the resulting BDF font to file OUTPUT.",
    )


def embolden() -> None:
    parser = argparse.ArgumentParser(
        description="Add a faux-bold effect to a font.",
    )
    add_standard_arguments(parser)

    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "--maintain-spacing",
        dest="maintain_spacing",
        action="store_true",
        default="True",
        help="Expand each character's spacing to account for emboldening "
        "(default)",
    )
    group.add_argument(
        "--ignore-spacing",
        dest="maintain_spacing",
        action="store_false",
        help="Let bold characters use their original spacing",
    )

    args = parser.parse_args()

    with open(args.input, "rb") as input:
        with open(args.output, "wb") as output:
            font = reader.read_bdf(input)
            bold = effects.embolden(font, args.maintain_spacing)
            writer.write_bdf(bold, output)


def expand() -> None:
    parser = argparse.ArgumentParser(
        description="Increase the resolution of a font.",
    )
    add_standard_arguments(parser)

    parser.add_argument(
        "--x-factor",
        metavar="X",
        dest="x_factor",
        type=int,
        default=1,
        help="Expand each glyph horizontally by X times",
    )

    parser.add_argument(
        "--y-factor",
        metavar="Y",
        dest="y_factor",
        type=int,
        default=1,
        help="Expand each glyph vertically by Y times",
    )

    args = parser.parse_args()

    with open(args.input, "rb") as input:
        with open(args.output, "wb") as output:
            font = reader.read_bdf(input)
            expanded = effects.expand(font, args.x_factor, args.y_factor)
            writer.write_bdf(expanded, output)


def show_counter(
    counter: Counter[T], kind: str, formatter: Callable[[T], str] = repr
) -> None:
    """
    Print the result of the tally.

    formatter should be a callable that takes an item and returns a pretty
    string. If not supplied, repr() is used.
    """
    data = [(value, key) for key, value in counter.items()]
    data.sort()

    if len(data) == 0:
        return

    print("count", kind)
    for count, item in data:
        print("%5d %s" % (count, formatter(item)))


def fill() -> None:
    parser = argparse.ArgumentParser(
        description="Generate pre-composed glyphs from available components",
    )
    add_standard_arguments(parser)
    args = parser.parse_args()

    with open(args.input, "rb") as input:
        with open(args.output, "wb") as output:
            print("Reading font...")
            font = reader.read_bdf(input)
            print("Building list of decompositions...")
            decompositions = glyph_combining.build_unicode_decompositions()
            print("Generating combined characters...")
            filler = glyph_combining.FontFiller(font, decompositions)
            filler.add_decomposable_glyphs_to_font()
            print("Writing out result...")
            writer.write_bdf(font, output)

            # Show the inventory of things this font is missing.
            print()
            print("Unknown combining classes")
            show_counter(filler.unknown_classes, "class")
            print()
            print("Missing combinable characters")
            show_counter(
                filler.missing_chars,
                "char",
                lambda char: "%r (%s)" % (char, unicodedata.name(char)),
            )


def merge() -> None:
    parser = argparse.ArgumentParser(
        description="""
            For each code-point that has a glyph in BASE or CUSTOM,
            the resulting font will use the glyph from CUSTOM if present,
            otherwise falling back to the glyph in BASE.
        """,
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=bdflib.__version__,
    )

    # I'd love to use argparse.FileType, but https://bugs.python.org/issue14156
    parser.add_argument(
        "base",
        metavar="BASE",
        help="Path to a BDF font file, used for fallback glyphs.",
    )
    parser.add_argument(
        "custom",
        metavar="CUSTOM",
        help="Path to a BDF font file, whose glyphs override those in BASE.",
    )
    parser.add_argument(
        "output",
        metavar="OUTPUT",
        help="The resulting font will be written to the file OUTPUT.",
    )
    args = parser.parse_args()

    with open(args.base, "rb") as base:
        with open(args.custom, "rb") as custom:
            with open(args.output, "wb") as output:
                base_font = reader.read_bdf(base)
                custom_font = reader.read_bdf(custom)
                merged = effects.merge(base_font, custom_font)
                writer.write_bdf(merged, output)


def passthrough() -> None:
    parser = argparse.ArgumentParser(
        description="Parse and re-serialise a BDF font.",
    )
    add_standard_arguments(parser)
    args = parser.parse_args()

    with open(args.input, "rb") as input:
        with open(args.output, "wb") as output:
            font = reader.read_bdf(input)
            writer.write_bdf(font, output)


def validate() -> int:
    parser = argparse.ArgumentParser(
        description=("Check a font conforms to the BDF specification"),
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=bdflib.__version__,
    )

    # I'd love to use argparse.FileType, but https://bugs.python.org/issue14156
    parser.add_argument(
        "fonts",
        metavar="FONT",
        nargs="+",
        help="Read a BDF font from file FONT.",
    )
    args = parser.parse_args()
    exit_code = 0

    for filename in args.fonts:

        def report_warning(lineno: int, message: str) -> None:
            print(f"{filename}:{lineno}: warning: {message}")

        try:
            with open(filename, "rb") as input:
                reader.read_bdf(input, report_warning)
        except reader.ParseError as e:
            exit_code = 1
            print(f"{filename}:{e.lineno}: error: {e.message}")

    return exit_code


def xlfd_validate() -> int:
    parser = argparse.ArgumentParser(
        description=(
            "Check a font against X Logical Font Descriptor conventions"
        ),
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=bdflib.__version__,
    )

    # I'd love to use argparse.FileType, but https://bugs.python.org/issue14156
    parser.add_argument(
        "input",
        metavar="INPUT",
        help="Read a BDF font from file INPUT.",
    )
    args = parser.parse_args()

    with open(args.input, "rb") as input:
        font = reader.read_bdf(input)

    errors = xlfd.validate(font)

    for each in errors:
        print(type(each).__name__, ":", each)

    if errors:
        return 1

    return 0


def xlfd_fix() -> None:
    parser = argparse.ArgumentParser(
        description="Add missing X Logical Font Descriptor properties",
    )
    add_standard_arguments(parser)
    args = parser.parse_args()

    with open(args.input, "rb") as input:
        with open(args.output, "wb") as output:
            font = reader.read_bdf(input)
            xlfd.fix(font)
            writer.write_bdf(font, output)
