#!/usr/bin/env python3
"""Generate QR codes for files hosted from this directory."""

import argparse
import json
import os
import sys
from pathlib import Path
from urllib.parse import quote

import qrcode
from qrcode.constants import ERROR_CORRECT_M


def main():
    parser = argparse.ArgumentParser(
        description="Generate PNG QR codes for every file in a directory."
    )
    parser.add_argument(
        "--base-url",
        default="https://mercedesqr.routerb.ro/",
        help="Public domain/base URL used in each QR code.",
    )
    parser.add_argument(
        "--input-dir",
        default="files_qr",
        help="Directory containing files to publish in QR codes.",
    )
    parser.add_argument(
        "--output-dir",
        default="qr_codes",
        help="Directory where generated QR PNG files will be written.",
    )
    parser.add_argument(
        "--scale",
        type=int,
        default=10,
        help="Pixels per QR module. Default: 10.",
    )
    parser.add_argument(
        "--border",
        type=int,
        default=4,
        help="Quiet-zone width in modules. Default: 4.",
    )
    args = parser.parse_args()

    project_root = Path(__file__).resolve().parent
    input_dir = resolve_under_root(project_root, args.input_dir)
    output_dir = resolve_under_root(project_root, args.output_dir)

    if not input_dir.is_dir():
        raise SystemExit("Input directory does not exist: {}".format(input_dir))
    if args.scale < 1:
        raise SystemExit("--scale must be at least 1")
    if args.border < 0:
        raise SystemExit("--border cannot be negative")

    files = [
        path
        for path in sorted(input_dir.rglob("*"))
        if path.is_file() and not path.name.startswith(".")
    ]
    if not files:
        raise SystemExit("No files found in {}".format(input_dir))

    output_dir.mkdir(parents=True, exist_ok=True)
    manifest = []

    for source_path in files:
        public_path = make_public_path(project_root, input_dir, source_path)
        url = make_public_url(args.base_url, public_path)
        qr_rel_path = Path(source_path.relative_to(input_dir).as_posix() + ".png")
        output_path = output_dir / qr_rel_path
        output_path.parent.mkdir(parents=True, exist_ok=True)
        write_qr_png(output_path, url, scale=args.scale, border=args.border)
        manifest.append(
            {
                "source": public_path,
                "url": url,
                "qr_code": display_path(project_root, output_path),
            }
        )
        print("{} -> {}".format(public_path, display_path(project_root, output_path)))

    manifest_path = output_dir / "manifest.json"
    manifest_path.write_text(
        json.dumps(manifest, indent=2, sort_keys=True) + "\n",
        encoding="utf-8",
    )
    print("Wrote {} QR code(s) and {}".format(len(manifest), manifest_path))


def resolve_under_root(project_root, path_value):
    path = Path(path_value)
    if not path.is_absolute():
        path = project_root / path
    return path.resolve()


def make_public_path(project_root, input_dir, source_path):
    try:
        return source_path.relative_to(project_root).as_posix()
    except ValueError:
        return (Path(input_dir.name) / source_path.relative_to(input_dir)).as_posix()


def display_path(project_root, path):
    try:
        return path.relative_to(project_root).as_posix()
    except ValueError:
        return str(path)


def make_public_url(base_url, relative_path):
    base = base_url.rstrip("/") + "/"
    return base + quote(relative_path.replace(os.sep, "/"), safe="/-._~")


def write_qr_png(path, url, scale=10, border=4):
    qr = qrcode.QRCode(
        version=None,
        error_correction=ERROR_CORRECT_M,
        box_size=scale,
        border=border,
    )
    qr.add_data(url)
    qr.make(fit=True)
    image = qr.make_image(fill_color="black", back_color="white")
    image.save(str(path))


if __name__ == "__main__":
    try:
        main()
    except BrokenPipeError:
        sys.exit(1)
