# -*- coding: utf-8 -*-
"""
emmett.validators.helpers
-------------------------

Provides utilities for validators.

Ported from the original validators of web2py (http://www.web2py.com)

:copyright: (c) by Massimo Di Pierro <mdipierro@cs.depaul.edu>
:license: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""

import re
from datetime import timedelta, tzinfo
from functools import reduce
from io import StringIO

# TODO: check unicode conversions
from .._shortcuts import to_unicode, uuid
from ..ctx import current
from ..security import DIGEST_ALG_BY_SIZE, simple_hash


_DEFAULT = lambda: None


def translate(text):
    if text is None:
        return None
    elif isinstance(text, str):
        return current.T(text)
    return str(text)


def options_sorter(x, y):
    return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1


def is_empty(value, empty_regex=None):
    if value is None:
        return value, True
    if isinstance(value, (str, bytes)):
        vclean = value.strip()
        if empty_regex is not None and empty_regex.match(vclean):
            vclean = ""
        return vclean, len(vclean) == 0
    if isinstance(value, (list, dict)):
        return value, len(value) == 0
    return value, False


class _UTC(tzinfo):
    ZERO = timedelta(0)

    def utcoffset(self, dt):
        return _UTC.ZERO

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return _UTC.ZERO


class LazyCrypt(object):
    def __init__(self, crypt, password):
        """
        crypt is an instance of the Crypt validator,
        password is the password as inserted by the user
        """
        self.crypt = crypt
        self.password = password
        self.crypted = None

    def __str__(self):
        """
        Encrypted self.password and caches it in self.crypted.
        If self.crypt.salt the output is in the format
        <algorithm>$<salt>$<hash>

        Try get the digest_alg from the key (if it exists)
        else assume the default digest_alg. If not key at all, set key=''

        If a salt is specified use it, if salt is True, set salt to uuid
        (this should all be backward compatible)

        Options:
        key = 'uuid'
        key = 'md5:uuid'
        key = 'sha512:uuid'
        ...
        key = 'pbkdf2(1000,64,sha512):uuid' 1000 iterations and 64 chars length
        """
        if self.crypted:
            return self.crypted
        if self.crypt.key:
            if ":" in self.crypt.key:
                digest_alg, key = self.crypt.key.split(":", 1)
            else:
                digest_alg, key = self.crypt.digest_alg, self.crypt.key
        else:
            digest_alg, key = self.crypt.digest_alg, ""
        if self.crypt.salt:
            if self.crypt.salt is True:
                salt = str(uuid()).replace("-", "")[-16:]
            else:
                salt = self.crypt.salt
        else:
            salt = ""
        hashed = simple_hash(self.password, key, salt, digest_alg)
        self.crypted = "%s$%s$%s" % (digest_alg, salt, hashed)
        return self.crypted

    def __eq__(self, stored_password):
        """
        compares the current lazy crypted password with a stored password
        """
        if isinstance(stored_password, self.__class__):
            return (self is stored_password) or (
                (self.crypt.key == stored_password.crypt.key) and (self.password == stored_password.password)
            )

        if self.crypt.key:
            if ":" in self.crypt.key:
                key = self.crypt.key.split(":")[1]
            else:
                key = self.crypt.key
        else:
            key = ""
        if stored_password is None:
            return False
        elif stored_password.count("$") == 2:
            (digest_alg, salt, hash) = stored_password.split("$")
            h = simple_hash(self.password, key, salt, digest_alg)
            temp_pass = "%s$%s$%s" % (digest_alg, salt, h)
        else:  # no salting
            # guess digest_alg
            digest_alg = DIGEST_ALG_BY_SIZE.get(len(stored_password), None)
            if not digest_alg:
                return False
            else:
                temp_pass = simple_hash(self.password, key, "", digest_alg)
        return temp_pass == stored_password

    def __ne__(self, other):
        return not self.__eq__(other)


def _escape_unicode(string):
    """
    Converts a unicode string into US-ASCII, using a simple conversion scheme.
    Each unicode character that does not have a US-ASCII equivalent is
    converted into a URL escaped form based on its hexadecimal value.
    For example, the unicode character '\u4e86' will become the string '%4e%86'

    Args:
        string: unicode string, the unicode string to convert into an
            escaped US-ASCII form

    Returns:
        string: the US-ASCII escaped form of the inputted string

    @author: Jonathan Benn
    """
    returnValue = StringIO()

    for character in string:
        code = ord(character)
        if code > 0x7F:
            hexCode = hex(code)
            returnValue.write("%" + hexCode[2:4] + "%" + hexCode[4:6])
        else:
            returnValue.write(character)

    return returnValue.getvalue()


def _unicode_to_ascii_authority(authority):
    """
    Follows the steps in RFC 3490, Section 4 to convert a unicode authority
    string into its ASCII equivalent.
    For example, u'www.Alliancefran\xe7aise.nu' will be converted into
    'www.xn--alliancefranaise-npb.nu'

    Args:
        authority: unicode string, the URL authority component to convert,
            e.g. u'www.Alliancefran\xe7aise.nu'

    Returns:
        string: the US-ASCII character equivalent to the inputed authority,
             e.g. 'www.xn--alliancefranaise-npb.nu'

    Raises:
        Exception: if the function is not able to convert the inputed
            authority

    @author: Jonathan Benn
    """
    label_split_regex = re.compile("[\u002e\u3002\uff0e\uff61]")

    # RFC 3490, Section 4, Step 1
    # The encodings.idna Python module assumes that AllowUnassigned == True

    # RFC 3490, Section 4, Step 2
    labels = label_split_regex.split(authority)

    # RFC 3490, Section 4, Step 3
    # The encodings.idna Python module assumes that UseSTD3ASCIIRules == False

    # RFC 3490, Section 4, Step 4
    # We use the ToASCII operation because we are about to put the authority
    # into an IDN-unaware slot
    asciiLabels = []
    try:
        import encodings.idna

        for label in labels:
            if label:
                asciiLabels.append(encodings.idna.ToASCII(label))
            else:
                # encodings.idna.ToASCII does not accept an empty string, but
                # it is necessary for us to allow for empty labels so that we
                # don't modify the URL
                asciiLabels.append("")
    except Exception:
        asciiLabels = [str(label) for label in labels]
    # RFC 3490, Section 4, Step 5
    return str(reduce(lambda x, y: x + chr(0x002E) + y, asciiLabels))


def unicode_to_ascii_url(url, prepend_scheme):
    """
    Converts the inputed unicode url into a US-ASCII equivalent. This function
    goes a little beyond RFC 3490, which is limited in scope to the domain name
    (authority) only. Here, the functionality is expanded to what was observed
    on Wikipedia on 2009-Jan-22:

       Component    Can Use Unicode?
       ---------    ----------------
       scheme       No
       authority    Yes
       path         Yes
       query        Yes
       fragment     No

    The authority component gets converted to punycode, but occurrences of
    unicode in other components get converted into a pair of URI escapes (we
    assume 4-byte unicode). E.g. the unicode character U+4E2D will be
    converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
    understand this kind of URI encoding.

    Args:
        url: unicode string, the URL to convert from unicode into US-ASCII
        prepend_scheme: string, a protocol scheme to prepend to the URL if
            we're having trouble parsing it.
            e.g. "http". Input None to disable this functionality

    Returns:
        string: a US-ASCII equivalent of the inputed url

    @author: Jonathan Benn
    """
    # convert the authority component of the URL into an ASCII punycode string,
    # but encode the rest using the regular URI character encoding

    groups = url_split_regex.match(url).groups()
    # If no authority was found
    if not groups[3]:
        # Try appending a scheme to see if that fixes the problem
        scheme_to_prepend = prepend_scheme or "http"
        groups = url_split_regex.match(to_unicode(scheme_to_prepend) + "://" + url).groups()
    # if we still can't find the authority
    if not groups[3]:
        raise Exception("No authority component found, " + "could not decode unicode to US-ASCII")

    # We're here if we found an authority, let's rebuild the URL
    scheme = groups[1]
    authority = groups[3]
    path = groups[4] or ""
    query = groups[5] or ""
    fragment = groups[7] or ""

    if prepend_scheme:
        scheme = str(scheme) + "://"
    else:
        scheme = ""
    return (
        scheme + _unicode_to_ascii_authority(authority) + _escape_unicode(path) + _escape_unicode(query) + str(fragment)
    )


url_split_regex = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")

official_url_schemes = [
    "aaa",
    "aaas",
    "acap",
    "cap",
    "cid",
    "crid",
    "data",
    "dav",
    "dict",
    "dns",
    "fax",
    "file",
    "ftp",
    "go",
    "gopher",
    "h323",
    "http",
    "https",
    "icap",
    "im",
    "imap",
    "info",
    "ipp",
    "iris",
    "iris.beep",
    "iris.xpc",
    "iris.xpcs",
    "iris.lws",
    "ldap",
    "mailto",
    "mid",
    "modem",
    "msrp",
    "msrps",
    "mtqp",
    "mupdate",
    "news",
    "nfs",
    "nntp",
    "opaquelocktoken",
    "pop",
    "pres",
    "prospero",
    "rtsp",
    "service",
    "shttp",
    "sip",
    "sips",
    "snmp",
    "soap.beep",
    "soap.beeps",
    "tag",
    "tel",
    "telnet",
    "tftp",
    "thismessage",
    "tip",
    "tv",
    "urn",
    "vemmi",
    "wais",
    "xmlrpc.beep",
    "xmlrpc.beep",
    "xmpp",
    "z39.50r",
    "z39.50s",
]

unofficial_url_schemes = [
    "about",
    "adiumxtra",
    "aim",
    "afp",
    "aw",
    "callto",
    "chrome",
    "cvs",
    "ed2k",
    "feed",
    "fish",
    "gg",
    "gizmoproject",
    "iax2",
    "irc",
    "ircs",
    "itms",
    "jar",
    "javascript",
    "keyparc",
    "lastfm",
    "ldaps",
    "magnet",
    "mms",
    "msnim",
    "mvn",
    "notes",
    "nsfw",
    "psyc",
    "paparazzi:http",
    "rmi",
    "rsync",
    "secondlife",
    "sgn",
    "skype",
    "ssh",
    "sftp",
    "smb",
    "sms",
    "soldat",
    "steam",
    "svn",
    "teamspeak",
    "unreal",
    "ut2004",
    "ventrilo",
    "view-source",
    "webcal",
    "wyciwyg",
    "xfire",
    "xri",
    "ymsgr",
]

official_top_level_domains = [
    # a
    "abogado",
    "ac",
    "academy",
    "accountants",
    "active",
    "actor",
    "ad",
    "adult",
    "ae",
    "aero",
    "af",
    "ag",
    "agency",
    "ai",
    "airforce",
    "al",
    "allfinanz",
    "alsace",
    "am",
    "amsterdam",
    "an",
    "android",
    "ao",
    "apartments",
    "aq",
    "aquarelle",
    "ar",
    "archi",
    "army",
    "arpa",
    "as",
    "asia",
    "associates",
    "at",
    "attorney",
    "au",
    "auction",
    "audio",
    "autos",
    "aw",
    "ax",
    "axa",
    "az",
    # b
    "ba",
    "band",
    "bank",
    "bar",
    "barclaycard",
    "barclays",
    "bargains",
    "bayern",
    "bb",
    "bd",
    "be",
    "beer",
    "berlin",
    "best",
    "bf",
    "bg",
    "bh",
    "bi",
    "bid",
    "bike",
    "bingo",
    "bio",
    "biz",
    "bj",
    "black",
    "blackfriday",
    "bloomberg",
    "blue",
    "bm",
    "bmw",
    "bn",
    "bnpparibas",
    "bo",
    "boo",
    "boutique",
    "br",
    "brussels",
    "bs",
    "bt",
    "budapest",
    "build",
    "builders",
    "business",
    "buzz",
    "bv",
    "bw",
    "by",
    "bz",
    "bzh",
    # c
    "ca",
    "cab",
    "cal",
    "camera",
    "camp",
    "cancerresearch",
    "canon",
    "capetown",
    "capital",
    "caravan",
    "cards",
    "care",
    "career",
    "careers",
    "cartier",
    "casa",
    "cash",
    "casino",
    "cat",
    "catering",
    "cbn",
    "cc",
    "cd",
    "center",
    "ceo",
    "cern",
    "cf",
    "cg",
    "ch",
    "channel",
    "chat",
    "cheap",
    "christmas",
    "chrome",
    "church",
    "ci",
    "citic",
    "city",
    "ck",
    "cl",
    "claims",
    "cleaning",
    "click",
    "clinic",
    "clothing",
    "club",
    "cm",
    "cn",
    "co",
    "coach",
    "codes",
    "coffee",
    "college",
    "cologne",
    "com",
    "community",
    "company",
    "computer",
    "condos",
    "construction",
    "consulting",
    "contractors",
    "cooking",
    "cool",
    "coop",
    "country",
    "cr",
    "credit",
    "creditcard",
    "cricket",
    "crs",
    "cruises",
    "cu",
    "cuisinella",
    "cv",
    "cw",
    "cx",
    "cy",
    "cymru",
    "cz",
    # d
    "dabur",
    "dad",
    "dance",
    "dating",
    "day",
    "dclk",
    "de",
    "deals",
    "degree",
    "delivery",
    "democrat",
    "dental",
    "dentist",
    "desi",
    "design",
    "dev",
    "diamonds",
    "diet",
    "digital",
    "direct",
    "directory",
    "discount",
    "dj",
    "dk",
    "dm",
    "dnp",
    "do",
    "docs",
    "domains",
    "doosan",
    "durban",
    "dvag",
    "dz",
    # e
    "eat",
    "ec",
    "edu",
    "education",
    "ee",
    "eg",
    "email",
    "emerck",
    "energy",
    "engineer",
    "engineering",
    "enterprises",
    "equipment",
    "er",
    "es",
    "esq",
    "estate",
    "et",
    "eu",
    "eurovision",
    "eus",
    "events",
    "everbank",
    "exchange",
    "expert",
    "exposed",
    # f
    "fail",
    "fans",
    "farm",
    "fashion",
    "feedback",
    "fi",
    "finance",
    "financial",
    "firmdale",
    "fish",
    "fishing",
    "fit",
    "fitness",
    "fj",
    "fk",
    "flights",
    "florist",
    "flowers",
    "flsmidth",
    "fly",
    "fm",
    "fo",
    "foo",
    "football",
    "forsale",
    "foundation",
    "fr",
    "frl",
    "frogans",
    "fund",
    "furniture",
    "futbol",
    # g
    "ga",
    "gal",
    "gallery",
    "garden",
    "gb",
    "gbiz",
    "gd",
    "gdn",
    "ge",
    "gent",
    "gf",
    "gg",
    "ggee",
    "gh",
    "gi",
    "gift",
    "gifts",
    "gives",
    "gl",
    "glass",
    "gle",
    "global",
    "globo",
    "gm",
    "gmail",
    "gmo",
    "gmx",
    "gn",
    "goldpoint",
    "goog",
    "google",
    "gop",
    "gov",
    "gp",
    "gq",
    "gr",
    "graphics",
    "gratis",
    "green",
    "gripe",
    "gs",
    "gt",
    "gu",
    "guide",
    "guitars",
    "guru",
    "gw",
    "gy",
    # h
    "hamburg",
    "hangout",
    "haus",
    "healthcare",
    "help",
    "here",
    "hermes",
    "hiphop",
    "hiv",
    "hk",
    "hm",
    "hn",
    "holdings",
    "holiday",
    "homes",
    "horse",
    "host",
    "hosting",
    "house",
    "how",
    "hr",
    "ht",
    "hu",
    # i
    "ibm",
    "id",
    "ie",
    "ifm",
    "il",
    "im",
    "immo",
    "immobilien",
    "in",
    "industries",
    "info",
    "ing",
    "ink",
    "institute",
    "insure",
    "int",
    "international",
    "investments",
    "io",
    "iq",
    "ir",
    "irish",
    "is",
    "it",
    "iwc",
    # j
    "jcb",
    "je",
    "jetzt",
    "jm",
    "jo",
    "jobs",
    "joburg",
    "jp",
    "juegos",
    # k
    "kaufen",
    "kddi",
    "ke",
    "kg",
    "kh",
    "ki",
    "kim",
    "kitchen",
    "kiwi",
    "km",
    "kn",
    "koeln",
    "kp",
    "kr",
    "krd",
    "kred",
    "kw",
    "ky",
    "kyoto",
    "kz",
    # l
    "la",
    "lacaixa",
    "land",
    "lat",
    "latrobe",
    "lawyer",
    "lb",
    "lc",
    "lds",
    "lease",
    "legal",
    "lgbt",
    "li",
    "lidl",
    "life",
    "lighting",
    "limited",
    "limo",
    "link",
    "lk",
    "loans",
    "localhost",
    "london",
    "lotte",
    "lotto",
    "lr",
    "ls",
    "lt",
    "ltda",
    "lu",
    "luxe",
    "luxury",
    "lv",
    "ly",
    # m
    "ma",
    "madrid",
    "maison",
    "management",
    "mango",
    "market",
    "marketing",
    "marriott",
    "mc",
    "md",
    "me",
    "media",
    "meet",
    "melbourne",
    "meme",
    "memorial",
    "menu",
    "mg",
    "mh",
    "miami",
    "mil",
    "mini",
    "mk",
    "ml",
    "mm",
    "mn",
    "mo",
    "mobi",
    "moda",
    "moe",
    "monash",
    "money",
    "mormon",
    "mortgage",
    "moscow",
    "motorcycles",
    "mov",
    "mp",
    "mq",
    "mr",
    "ms",
    "mt",
    "mu",
    "museum",
    "mv",
    "mw",
    "mx",
    "my",
    "mz",
    # n
    "na",
    "nagoya",
    "name",
    "navy",
    "nc",
    "ne",
    "net",
    "network",
    "neustar",
    "new",
    "nexus",
    "nf",
    "ng",
    "ngo",
    "nhk",
    "ni",
    "nico",
    "ninja",
    "nl",
    "no",
    "np",
    "nr",
    "nra",
    "nrw",
    "ntt",
    "nu",
    "nyc",
    "nz",
    # o
    "okinawa",
    "om",
    "one",
    "ong",
    "onl",
    "ooo",
    "org",
    "organic",
    "osaka",
    "otsuka",
    "ovh",
    # p
    "pa",
    "paris",
    "partners",
    "parts",
    "party",
    "pe",
    "pf",
    "pg",
    "ph",
    "pharmacy",
    "photo",
    "photography",
    "photos",
    "physio",
    "pics",
    "pictures",
    "pink",
    "pizza",
    "pk",
    "pl",
    "place",
    "plumbing",
    "pm",
    "pn",
    "pohl",
    "poker",
    "porn",
    "post",
    "pr",
    "praxi",
    "press",
    "pro",
    "prod",
    "productions",
    "prof",
    "properties",
    "property",
    "ps",
    "pt",
    "pub",
    "pw",
    "py",
    # q
    "qa",
    "qpon",
    "quebec",
    # r
    "re",
    "realtor",
    "recipes",
    "red",
    "rehab",
    "reise",
    "reisen",
    "reit",
    "ren",
    "rentals",
    "repair",
    "report",
    "republican",
    "rest",
    "restaurant",
    "reviews",
    "rich",
    "rio",
    "rip",
    "ro",
    "rocks",
    "rodeo",
    "rs",
    "rsvp",
    "ru",
    "ruhr",
    "rw",
    "ryukyu",
    # s
    "sa",
    "saarland",
    "sale",
    "samsung",
    "sarl",
    "saxo",
    "sb",
    "sc",
    "sca",
    "scb",
    "schmidt",
    "school",
    "schule",
    "schwarz",
    "science",
    "scot",
    "sd",
    "se",
    "services",
    "sew",
    "sexy",
    "sg",
    "sh",
    "shiksha",
    "shoes",
    "shriram",
    "si",
    "singles",
    "sj",
    "sk",
    "sky",
    "sl",
    "sm",
    "sn",
    "so",
    "social",
    "software",
    "sohu",
    "solar",
    "solutions",
    "soy",
    "space",
    "spiegel",
    "sr",
    "st",
    "style",
    "su",
    "supplies",
    "supply",
    "support",
    "surf",
    "surgery",
    "suzuki",
    "sv",
    "sx",
    "sy",
    "sydney",
    "systems",
    "sz",
    # t
    "taipei",
    "tatar",
    "tattoo",
    "tax",
    "tc",
    "td",
    "technology",
    "tel",
    "temasek",
    "tennis",
    "tf",
    "tg",
    "th",
    "tienda",
    "tips",
    "tires",
    "tirol",
    "tj",
    "tk",
    "tl",
    "tm",
    "tn",
    "to",
    "today",
    "tokyo",
    "tools",
    "top",
    "toshiba",
    "town",
    "toys",
    "tp",
    "tr",
    "trade",
    "training",
    "travel",
    "trust",
    "tt",
    "tui",
    "tv",
    "tw",
    "tz",
    # u
    "ua",
    "ug",
    "uk",
    "university",
    "uno",
    "uol",
    "us",
    "uy",
    "uz",
    # v
    "va",
    "vacations",
    "vc",
    "ve",
    "vegas",
    "ventures",
    "versicherung",
    "vet",
    "vg",
    "vi",
    "viajes",
    "video",
    "villas",
    "vision",
    "vlaanderen",
    "vn",
    "vodka",
    "vote",
    "voting",
    "voto",
    "voyage",
    "vu",
    # w
    "wales",
    "wang",
    "watch",
    "webcam",
    "website",
    "wed",
    "wedding",
    "wf",
    "whoswho",
    "wien",
    "wiki",
    "williamhill",
    "wme",
    "work",
    "works",
    "world",
    "ws",
    "wtc",
    "wtf",
    # x
    "xn--1qqw23a",
    "xn--3bst00m",
    "xn--3ds443g",
    "xn--3e0b707e",
    "xn--45brj9c",
    "xn--45q11c",
    "xn--4gbrim",
    "xn--55qw42g",
    "xn--55qx5d",
    "xn--6frz82g",
    "xn--6qq986b3xl",
    "xn--80adxhks",
    "xn--80ao21a",
    "xn--80asehdb",
    "xn--80aswg",
    "xn--90a3ac",
    "xn--90ais",
    "xn--b4w605ferd",
    "xn--c1avg",
    "xn--cg4bki",
    "xn--clchc0ea0b2g2a9gcd",
    "xn--czr694b",
    "xn--czrs0t",
    "xn--czru2d",
    "xn--d1acj3b",
    "xn--d1alf",
    "xn--fiq228c5hs",
    "xn--fiq64b",
    "xn--fiqs8s",
    "xn--fiqz9s",
    "xn--flw351e",
    "xn--fpcrj9c3d",
    "xn--fzc2c9e2c",
    "xn--gecrj9c",
    "xn--h2brj9c",
    "xn--hxt814e",
    "xn--i1b6b1a6a2e",
    "xn--io0a7i",
    "xn--j1amh",
    "xn--j6w193g",
    "xn--kprw13d",
    "xn--kpry57d",
    "xn--kput3i",
    "xn--l1acc",
    "xn--lgbbat1ad8j",
    "xn--mgb9awbf",
    "xn--mgba3a4f16a",
    "xn--mgbaam7a8h",
    "xn--mgbab2bd",
    "xn--mgbayh7gpa",
    "xn--mgbbh1a71e",
    "xn--mgbc0a9azcg",
    "xn--mgberp4a5d4ar",
    "xn--mgbx4cd0ab",
    "xn--ngbc5azd",
    "xn--node",
    "xn--nqv7f",
    "xn--nqv7fs00ema",
    "xn--o3cw4h",
    "xn--ogbpf8fl",
    "xn--p1acf",
    "xn--p1ai",
    "xn--pgbs0dh",
    "xn--q9jyb4c",
    "xn--qcka1pmc",
    "xn--rhqv96g",
    "xn--s9brj9c",
    "xn--ses554g",
    "xn--unup4y",
    "xn--vermgensberater-ctb",
    "xn--vermgensberatung-pwb",
    "xn--vhquv",
    "xn--wgbh1c",
    "xn--wgbl6a",
    "xn--xhq521b",
    "xn--xkc2al3hye2a",
    "xn--xkc2dl3a5ee0h",
    "xn--yfro4i67o",
    "xn--ygbi2ammx",
    "xn--zfr164b",
    "xxx",
    "xyz",
    # y
    "yachts",
    "yandex",
    "ye",
    "yodobashi",
    "yoga",
    "yokohama",
    "youtube",
    "yt",
    # z
    "za",
    "zip",
    "zm",
    "zone",
    "zuerich",
    "zw",
]
