"""Compile a test program."""

from __future__ import annotations

import dataclasses
import os
import pathlib
import shlex
import subprocess  # noqa: S404
import sys
import tempfile
import typing

from pychunk import common


if typing.TYPE_CHECKING:
    from typing import Final


@dataclasses.dataclass(frozen=True)
class Config(common.Config):
    """Runtime configuration."""

    tempd: pathlib.Path
    source: pathlib.Path
    obj: pathlib.Path
    program: pathlib.Path

    uncompressed: pathlib.Path


def parse_args(dirname: str) -> Config:
    """Parse the command-line arguments, deduce some things."""
    parser: Final = common.base_parser("compile")
    parser.add_argument(
        "source",
        type=str,
        help="path to the test program source file",
    )

    args: Final = parser.parse_args()

    tempd: Final = pathlib.Path(dirname).absolute()
    return Config(
        tempd=tempd,
        bindir=pathlib.Path(args.bindir),
        source=pathlib.Path(args.source),
        obj=tempd / "chunk.o",
        program=tempd / "chunk",
        env=common.get_runenv(),
        orig=pathlib.Path(args.filename).absolute(),
        compressed=tempd / "words.txt.zck",
        uncompressed=tempd / "chunk.txt",
    )


def call_pkg_config(cfg: Config, option: str, what: str) -> list[str]:
    """Invoke pkg-config, parse its output."""
    print(f"Fetching the {what} for zck")
    raw: Final = subprocess.check_output(
        ["pkg-config", option, "zck"],
        encoding="UTF_8",
        env=cfg.env,
    ).splitlines()
    if len(raw) != 1:
        sys.exit(f"`pkg-config {option} zck` returned {raw!r}")
    return shlex.split(raw[0])


def do_compile(cfg: Config) -> None:
    """Compile the test program."""
    cflags: Final = call_pkg_config(cfg, "--cflags", "C compiler flags")

    if cfg.obj.exists():
        sys.exit(f"Did not expect {cfg.obj} to exist")
    cmd_compile: Final[list[pathlib.Path | str]] = ["cc", "-c", "-o", cfg.obj, *cflags, cfg.source]
    print(f"Running {cmd_compile!r}")
    subprocess.check_call(cmd_compile, env=cfg.env)
    if not cfg.obj.is_file():
        sys.exit(f"{cmd_compile!r} did not create the {cfg.obj} file")

    libs: Final = call_pkg_config(cfg, "--libs", "C linker flags and libraries")

    if cfg.program.exists():
        sys.exit(f"Did not expect {cfg.program} to exist")
    cmd_link: Final[list[pathlib.Path | str]] = ["cc", "-o", cfg.program, cfg.obj, *libs]
    print(f"Running {cmd_link!r}")
    subprocess.check_call(cmd_link, env=cfg.env)
    if not cfg.program.is_file():
        sys.exit(f"{cmd_link!r} did not create the {cfg.program} file")
    if not os.access(cfg.program, os.X_OK):
        sys.exit(f"Not an executable file: {cfg.program}")
    print(f"Looks like we got {cfg.program}")


def run_program(cfg: Config) -> None:
    """Run the test program, hopefully generate the chunk file."""
    print(f"About to run {cfg.program}")
    if cfg.uncompressed.exists():
        sys.exit(f"Did not expect {cfg.uncompressed} to exist")
    subprocess.check_call(
        [cfg.program, cfg.compressed, cfg.uncompressed],
        shell=False,
        env=cfg.env,
    )
    if not cfg.uncompressed.is_file():
        sys.exit(f"{cfg.program} did not create the {cfg.uncompressed} file")


def compare_chunk(cfg: Config, second: common.Chunk, orig_size: int) -> None:
    """Read data from the input file and the chunk."""
    # OK, let's load it all into memory, mmkay?
    contents: Final = cfg.orig.read_bytes()
    if len(contents) != orig_size:
        sys.exit(f"Could not read {orig_size} bytes from {cfg.orig}, read {len(contents)}")
    chunk: Final = cfg.uncompressed.read_bytes()
    if len(chunk) != second.size:
        sys.exit(f"Could not read {second.size} bytes from {cfg.uncompressed}, read {len(chunk)}")

    if contents[second.start : second.start + second.size] != chunk:
        sys.exit("Mismatch!")


def main() -> None:
    """Parse arguments, compile a program, compress a file, test it."""
    with tempfile.TemporaryDirectory() as dirname:
        print(f"Using temporary directory {dirname}")
        cfg: Final = parse_args(dirname)
        do_compile(cfg)
        orig_size: Final = cfg.orig.stat().st_size
        print(f"Original file size: {orig_size}")
        comp_size: Final = common.do_compress(cfg, orig_size)
        second_chunk: Final = common.read_chunks(cfg, orig_size, comp_size)
        run_program(cfg)
        compare_chunk(cfg, second_chunk, orig_size)
        print("Seems fine!")


if __name__ == "__main__":
    main()
