#!/usr/bin/env python3

from contextlib import contextmanager
from importlib import metadata
from pathlib import Path
from typing import Iterator, List, Literal, Union

import click

from .generators import ahk, keylayout, klc, web, xkb
from .help import create_layout, user_guide
from .layout import KeyboardLayout, load_layout
from .server import keyboard_server


@click.group()
def cli() -> None: ...


def build_all(layout: KeyboardLayout, output_dir_path: Path) -> None:
    """Generate all layout output files.

    Parameters
    ----------
    layout : KeyboardLayout
        The layout to process.
    output_dir_path : Path
        The output directory.
    msklc_dir : Path
        The MSKLC installation directory.
    """

    @contextmanager
    def file_creation_context(ext: str = "") -> Iterator[Path]:
        """Generate an output file path for extension EXT, return it and finally echo info."""
        path = output_dir_path / (layout.meta["fileName"] + ext)
        yield path
        click.echo(f"... {path}")

    if not output_dir_path.exists():
        output_dir_path.mkdir(parents=True)

    # AHK driver
    with file_creation_context(".ahk") as ahk_path:
        with ahk_path.open("w", encoding="utf-8", newline="\n") as file:
            file.write("\uFEFF")  # AHK scripts require a BOM
            file.write(ahk.ahk(layout))

    # Windows driver
    with file_creation_context(".klc") as klc_path:
        with klc_path.open("w", encoding="utf-16le", newline="\r\n") as file:
            try:
                file.write(klc.klc(layout))
            except ValueError as err:
                print(err)

    # macOS driver
    with file_creation_context(".keylayout") as osx_path:
        with osx_path.open("w", encoding="utf-8", newline="\n") as file:
            file.write(keylayout.keylayout(layout))

    # Linux driver, user-space
    with file_creation_context(".xkb_keymap") as xkb_path:
        with xkb_path.open("w", encoding="utf-8", newline="\n") as file:
            file.write(xkb.xkb_keymap(layout))

    # Linux driver, root
    with file_creation_context(".xkb_symbols") as xkb_custom_path:
        with xkb_custom_path.open("w", encoding="utf-8", newline="\n") as file:
            file.write(xkb.xkb_symbols(layout))

    # JSON data
    with file_creation_context(".json") as json_path:
        json_path.write_text(web.pretty_json(layout), encoding="utf8")

    # SVG data
    with file_creation_context(".svg") as svg_path:
        web.svg(layout).write(svg_path, encoding="utf-8", xml_declaration=True)


@cli.command()
@click.argument(
    "layout_descriptors",
    nargs=-1,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
)
@click.option(
    "--out",
    default="all",
    type=click.Path(),
    help="Keyboard drivers to generate.",
)
@click.option(
    "--angle-mod/--no-angle-mod",
    default=False,
    help="Apply Angle-Mod (which is a [ZXCVB] permutation with the LSGT key (a.k.a. ISO key))",
)
@click.option(
    "--qwerty-shortcuts",
    default=False,
    is_flag=True,
    help="Keep shortcuts at their qwerty location",
)
def build(
    layout_descriptors: List[Path],
    out: Union[Path, Literal["all"]],
    angle_mod: bool,
    qwerty_shortcuts: bool,
) -> None:
    """Convert TOML/YAML descriptions into OS-specific keyboard drivers."""

    for input_file in layout_descriptors:
        layout = KeyboardLayout(load_layout(input_file), angle_mod, qwerty_shortcuts)

        # default: build all in the `dist` subdirectory
        if out == "all":
            build_all(layout, Path("dist"))
            continue

        # quick output: reuse the input name and change the file extension
        if out in ["keylayout", "klc", "xkb_keymap", "xkb_symbols", "svg"]:
            output_file = input_file.with_suffix(f".{out}")
        else:
            output_file = Path(out)

        # detailed output
        if output_file.suffix == ".ahk":
            with output_file.open("w", encoding="utf-8", newline="\n") as file:
                file.write("\uFEFF")  # AHK scripts require a BOM
                file.write(ahk.ahk(layout))

        elif output_file.suffix == ".klc":
            with output_file.open("w", encoding="utf-16le", newline="\r\n") as file:
                try:
                    file.write(klc.klc(layout))
                except ValueError as err:
                    print(err)

        elif output_file.suffix == ".keylayout":
            with output_file.open("w", encoding="utf-8", newline="\n") as file:
                file.write(keylayout.keylayout(layout))

        elif output_file.suffix == ".xkb_keymap":
            with output_file.open("w", encoding="utf-8", newline="\n") as file:
                file.write(xkb.xkb_keymap(layout))

        elif output_file.suffix == ".xkb_symbols":
            with output_file.open("w", encoding="utf-8", newline="\n") as file:
                file.write(xkb.xkb_symbols(layout))

        elif output_file.suffix == ".json":
            output_file.write_text(web.pretty_json(layout), encoding="utf8")

        elif output_file.suffix == ".svg":
            web.svg(layout).write(output_file, encoding="utf-8", xml_declaration=True)

        else:
            click.echo("Unsupported output format.", err=True)
            return

        # successfully converted, display file name
        click.echo(f"... {output_file}")


# TODO: Provide geometry choices
@cli.command()
@click.argument("output_file", nargs=1, type=click.Path(exists=False, path_type=Path))
@click.option("--geometry", default="ISO", help="Specify keyboard geometry.")
@click.option("--altgr/--no-altgr", default=False, help="Set an AltGr layer.")
@click.option("--1dk/--no-1dk", "odk", default=False, help="Set a custom dead key.")
def new(output_file: Path, geometry: str, altgr: bool, odk: bool) -> None:
    """Create a new TOML layout description."""
    create_layout(output_file, geometry, altgr, odk)
    click.echo(f"... {output_file}")


@cli.command()
@click.argument("filepath", nargs=1, type=click.Path(exists=True, path_type=Path))
@click.option(
    "--angle-mod/--no-angle-mod",
    default=False,
    help="Apply Angle-Mod (which is a [ZXCVB] permutation with the LSGT key (a.k.a. ISO key))",
)
def watch(filepath: Path, angle_mod: bool) -> None:
    """Watch a layout description file and display it in a web browser."""
    keyboard_server(filepath, angle_mod)


@cli.command()
def guide() -> None:
    """Show user guide and exit."""
    click.echo(user_guide())


@cli.command()
def version() -> None:
    """Show version number and exit."""
    click.echo(f"kalamine { metadata.version('kalamine') }")


if __name__ == "__main__":
    cli()
