#!/usr/bin/python3

# Author: Benjamin Drung <bdrung@ubuntu.com>

"""Generate tzdata debconf templates file."""

import argparse
import logging
import os
import pathlib
import sys

# Drop script directory to workaround https://launchpad.net/bugs/2018666
sys.path.pop(0)
import natsort  # noqa: E402, pylint: disable=wrong-import-position

LOG_FORMAT = "%(name)s %(levelname)s: %(message)s"
__script_name__ = os.path.basename(sys.argv[0]) if __name__ == "__main__" else __name__

TEMPLATES_AREAS = [
    "Africa",
    "America",
    "Antarctica",
    "Arctic",
    "Asia",
    "Atlantic",
    "Australia",
    "Europe",
    "Indian",
    "Pacific",
    "Etc",
]

# List of backward compatability symlinks that should not be selectable
EXCLUDE_SYMLINKS = {
    "Africa/Asmera",
    "America/Argentina/ComodRivadavia",
    "America/Buenos_Aires",
    "America/Catamarca",
    "America/Cordoba",
    "America/Fort_Wayne",
    "America/Godthab",
    "America/Indianapolis",
    "America/Jujuy",
    "America/Knox_IN",
    "America/Louisville",
    "America/Mendoza",
    "America/Rosario",
    "Antarctica/South_Pole",
    "Asia/Ashkhabad",
    "Asia/Calcutta",
    "Asia/Choibalsan",
    "Asia/Chungking",
    "Asia/Dacca",
    "Asia/Katmandu",
    "Asia/Macao",
    "Asia/Rangoon",
    "Asia/Saigon",
    "Asia/Thimbu",
    "Asia/Ujung_Pandang",
    "Asia/Ulan_Bator",
    "Atlantic/Faeroe",
    "Australia/ACT",
    "Australia/LHI",
    "Australia/NSW",
    "Australia/North",
    "Australia/Queensland",
    "Australia/South",
    "Australia/Tasmania",
    "Australia/Victoria",
    "Australia/West",
    "Europe/Kiev",
    "Europe/Uzhgorod",
    "Europe/Zaporozhye",
    "Pacific/Enderbury",
    "Pacific/Ponape",
    "Pacific/Truk",
    "US/Alaska",
    "US/Aleutian",
    "US/Arizona",
    "US/Central",
    "US/East-Indiana",
    "US/Eastern",
    "US/Hawaii",
    "US/Indiana-Starke",
    "US/Michigan",
    "US/Mountain",
    "US/Pacific",
    "US/Samoa",
}

# List of symlinks that should be selectable
INCLUDE_SYMLINKS = {
    "Africa/Timbuktu",
    "America/Atka",
    "America/Coral_Harbour",
    "America/Ensenada",
    "America/Kralendijk",
    "America/Lower_Princes",
    "America/Marigot",
    "America/Montreal",
    "America/Nipigon",
    "America/Pangnirtung",
    "America/Porto_Acre",
    "America/Rainy_River",
    "America/Santa_Isabel",
    "America/Shiprock",
    "America/St_Barthelemy",
    "America/Thunder_Bay",
    "America/Virgin",
    "America/Yellowknife",
    "Arctic/Longyearbyen",
    "Asia/Chongqing",
    "Asia/Harbin",
    "Asia/Istanbul",
    "Asia/Kashgar",
    "Asia/Tel_Aviv",
    "Atlantic/Jan_Mayen",
    "Australia/Canberra",
    "Australia/Currie",
    "Australia/Yancowinna",
    "Europe/Belfast",
    "Europe/Bratislava",
    "Europe/Busingen",
    "Europe/Mariehamn",
    "Europe/Nicosia",
    "Europe/Podgorica",
    "Europe/San_Marino",
    "Europe/Tiraspol",
    "Europe/Vatican",
    "Pacific/Johnston",
    "Pacific/Samoa",
    "Pacific/Yap",
    "Etc/GMT+0",
    "Etc/GMT-0",
    "Etc/GMT0",
    "Etc/Greenwich",
    "Etc/UCT",
    "Etc/Universal",
    "Etc/Zulu",
}


def get_timezones(base_dir: pathlib.Path, subdir: pathlib.Path) -> list[str]:
    """Return list of timezone files relative to the base_dir."""
    timezones = []
    for path in natsort.natsorted(subdir.iterdir(), key=str):
        if path.is_dir():
            timezones += get_timezones(base_dir, path)
            continue
        timezone = str(path.relative_to(base_dir))
        if path.is_symlink():
            if timezone in EXCLUDE_SYMLINKS:
                continue
            if timezone not in INCLUDE_SYMLINKS:
                logger = logging.getLogger(__script_name__)
                logger.error(
                    "Timezone '%s' is a symlink, but neither in EXCLUDE_SYMLINKS"
                    " nor INCLUDE_SYMLINKS. Please add it explicitly.",
                    timezone,
                )
                sys.exit(1)
        timezones.append(timezone)
    return timezones


def generate_debconf_templates_area(zoneinfo_dir: pathlib.Path, area: str) -> str:
    """Generate tzdata debconf templates paragraph for given area."""
    timezones = get_timezones(zoneinfo_dir, zoneinfo_dir / area)
    choices = [timezone.split("/", maxsplit=1)[1] for timezone in timezones]
    if area == "Etc":
        choices_key = "Choices"
        description = (
            "Please select your time zone. Contrary to modern conventions, these"
            " POSIX-compatible zones use positive values to refer to zones west of"
            " Greenwich and negative values for those east of Greenwich"
            " (e.g., 'Etc/GMT+6' refers to 6 hours west of Greenwich,"
            " commonly called 'UTC-6')."
        )
    else:
        choices_key = "__Choices"
        description = (
            "Please select the city or region corresponding to your time zone."
        )
    return f"""\
Template: tzdata/Zones/{area}
Type: select
# Translators: This is a city name.
# Do not translate underscores. You can use spaces instead.
#flag:partial
{choices_key}: {", ".join(choices)}
_Description: Time zone:
 {description}
"""


def generate_debconf_templates(zoneinfo_dir: pathlib.Path) -> str:
    """Generate tzdata debconf templates file content."""
    debconf_templates = f"""\
# This file was generated by {pathlib.Path(__file__).relative_to(os.getcwd())}
#
Template: tzdata/Areas
Type: select
# Note to translators:
# - This is a continent or ocean (except for "Etc")
# - "Etc" will present users with a list of "GMT+xx" or "GMT-xx" timezones
__Choices: {", ".join(TEMPLATES_AREAS)}
_Description: Geographic area:
 Please select the geographic area in which you live. Subsequent
 configuration questions will narrow this down by presenting a list of
 cities, representing the time zones in which they are located.
"""
    for area in TEMPLATES_AREAS:
        debconf_templates += "\n" + generate_debconf_templates_area(zoneinfo_dir, area)
    return debconf_templates


def existing_dir_path(string: str) -> pathlib.Path:
    """Convert string to existing dir path or raise ArgumentTypeError."""
    path = pathlib.Path(string)
    if not path.is_dir():
        raise argparse.ArgumentTypeError(f"Directory {string} does not exist")
    return path


def main() -> None:
    """Generate tzdata debconf templates file."""
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-d",
        "--directory",
        default=".",
        type=existing_dir_path,
        help="Directory containing the generated zoneinfo files",
    )
    args = parser.parse_args()
    logging.basicConfig(format=LOG_FORMAT)
    print(generate_debconf_templates(args.directory), end="")


if __name__ == "__main__":
    main()
