#!/usr/bin/env python
#
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Extract what lint expects for cmap from our data."""

import argparse
import sys

from nototools import lint_config
from nototools import noto_data
from nototools import opentype_data
from nototools import unicode_data
from nototools import cmap_data


_PHASE_TWO_SCRIPTS = """
  Arab, Aran, Armi, Armn, Avst, Bali, Bamu, Batk, Beng, Brah, Bugi, Buhd, Cans,
  Cari, Cham, Cher, Copt, Cprt, Deva, Dsrt, Egyp, Ethi, Geor, Glag, Goth, Gujr,
  Guru, Hano, Hans, Hant, Hebr, Ital, Java, Jpan, Kali, Khar, Khmr, Knda, Kore,
  Kthi, LGC, Lana, Laoo, Lepc, Limb, Linb, Lisu, Lyci, Lydi, Mand, Mlym, Mong,
  Mtei, Mymr, Nkoo, Ogam, Olck, Orkh, Orya, Osma, Phag, Phli, Phnx, Prti, Qaae,
  Rjng, Runr, Samr, Sarb, Saur, Shaw, Sinh, Sund, Sylo, Syrc, Tagb, Tale, Talu,
  Taml, Tavt, Telu, Tfng, Tglg, Thaa, Thai, Tibt, Ugar, Vaii, Xpeo, Xsux, Yiii,
  Zsym
"""


def _code_range_to_set(code_range):
    """Converts a code range output by _parse_code_ranges to a set."""
    characters = set()
    for first, last, _ in code_range:
        characters.update(range(first, last + 1))
    return characters


def _symbol_set():
    """Returns set of characters that should be supported in Noto Symbols."""
    ranges = unicode_data._parse_code_ranges(noto_data.SYMBOL_RANGES_TXT)
    return _code_range_to_set(ranges)


def _math_set():
    """Returns set of characters that should be supported in Noto Math."""
    ranges = unicode_data._parse_code_ranges(noto_data.MATH_RANGES_TXT)
    return _code_range_to_set(ranges)


def _cjk_set():
    """Returns set of characters that will be provided in CJK fonts."""
    ranges = unicode_data._parse_code_ranges(noto_data.CJK_RANGES_TXT)
    return _code_range_to_set(ranges)


def _emoji_pua_set():
    """Returns the legacy PUA characters required for Android emoji."""
    return lint_config.parse_int_ranges("FE4E5-FE4EE FE82C FE82E-FE837")


def _get_script_required(
    script, unicode_version, noto_phase, unicode_only=False, verbose=False
):
    needed_chars = set()
    if script == "Zsye":  # Emoji
        # TODO: Check emoji coverage
        if not unicode_only:
            needed_chars = _emoji_pua_set()  # legacy PUA for android emoji
    elif script == "Zmth":  # Math
        if not unicode_only:
            needed_chars = _math_set()
    elif script == "Zsym":  # Symbols
        if not unicode_only:
            needed_chars = _symbol_set()
    elif script == "LGC":
        needed_chars = (
            unicode_data.defined_characters(scr="Latn", version=unicode_version)
            | unicode_data.defined_characters(scr="Grek", version=unicode_version)
            | unicode_data.defined_characters(scr="Cyrl", version=unicode_version)
        )
        if not unicode_only:
            needed_chars -= _symbol_set()
            needed_chars -= _cjk_set()
    elif script == "Aran":
        if unicode_only:
            needed_chars = unicode_data.defined_characters(
                scr="Arab", version=unicode_version
            )
        else:
            needed_chars = noto_data.urdu_set()
    elif script in ["Hans", "Hant", "Jpan", "Kore"]:
        needed_chars = _cjk_set()
    else:
        needed_chars = unicode_data.defined_characters(
            scr=script, version=unicode_version
        )
        if not unicode_only:
            needed_chars -= _symbol_set()

    if not unicode_only:
        needed_chars |= noto_data.get_extra_characters_needed(script, noto_phase)
        try:
            needed_chars |= set(opentype_data.SPECIAL_CHARACTERS_NEEDED[script])
        except KeyError:
            pass
        needed_chars -= noto_data.get_characters_not_needed(script, noto_phase)

    if not unicode_only:
        needed_chars |= {0, 0xD, 0x20}

    if verbose:
        sys.stderr.write(script + "\n")

    needed_chars &= unicode_data.defined_characters(version=unicode_version)

    return needed_chars


def _required_unicode_version(noto_font, noto_phase):
    if noto_font.family != "Noto":  # e.g. Arimo, Cousine, Tinos
        return 8.0
    if noto_phase == 2:
        return 6.0
    return 9.0


def _compute_required_chars(noto_font, noto_phase):
    unicode_version = _required_unicode_version(noto_font, noto_phase)
    needed_chars = _get_script_required(noto_font.script, unicode_version, noto_phase)
    return frozenset(needed_chars)


_REQUIRED_CACHE = {}


def get_required_chars(noto_font, phase):
    # Required characters must only depend on family, script, variant, and phase
    key = "_".join(
        filter(
            None, [noto_font.family, noto_font.script, noto_font.variant, str(phase)]
        )
    )
    result = _REQUIRED_CACHE.get(key, None)
    if not result:
        result = _compute_required_chars(noto_font, phase)
        _REQUIRED_CACHE[key] = result
    return result


def _check_scripts(scripts):
    # TODO(dougfelt): something realer
    bad_scripts = []
    for script in scripts:
        if script[0] < "A" or script[0] > "Z":
            bad_scripts.append(script)
    if bad_scripts:
        print("bad scripts: %s" % ", ".join(bad_scripts))
        raise ValueError("bad scripts")

    return set(scripts)


def get_cmap_data(scripts, unicode_version, noto_phase, unicode_only, verbose):
    metadata = cmap_data.create_metadata(
        "lint_cmap_reqs",
        [
            ("unicode_version", unicode_version),
            ("phase", noto_phase),
            ("unicode_only", unicode_only),
        ],
    )
    tabledata = cmap_data.create_table_from_map(
        {
            script: _get_script_required(
                script, unicode_version, noto_phase, unicode_only, verbose
            )
            for script in sorted(scripts)
        }
    )
    return cmap_data.CmapData(metadata, tabledata)


def main():
    DEFAULT_UNICODE_VERSION = 9.0

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--scripts",
        help="list of pseudo-script codes, empty for all " "phase 2 scripts",
        metavar="code",
        nargs="*",
    )
    parser.add_argument(
        "--unicode_version",
        help="version of unicode to use (default %s)" % DEFAULT_UNICODE_VERSION,
        metavar="version",
        type=float,
        default=DEFAULT_UNICODE_VERSION,
    )
    parser.add_argument(
        "--unicode_only",
        help="only use unicode data, not noto-specific data",
        action="store_true",
    )
    parser.add_argument(
        "-p",
        "--phase",
        help="noto phase (default 3)",
        metavar="phase",
        type=int,
        default=3,
    )
    parser.add_argument(
        "--outfile",
        help="write to output file, otherwise to stdout",
        metavar="fname",
        nargs="?",
        const="-default-",
    )
    parser.add_argument(
        "--verbose",
        help="log to stderr as each script is complete",
        action="store_true",
    )
    args = parser.parse_args()

    if not args.scripts:
        scripts = set(s.strip() for s in _PHASE_TWO_SCRIPTS.split(","))
    else:
        scripts = _check_scripts(args.scripts)

    cmapdata = get_cmap_data(
        scripts, args.unicode_version, args.phase, args.unicode_only, args.verbose
    )
    if args.outfile:
        if args.outfile == "-default-":
            args.outfile = "lint_cmap_%s.xml" % args.unicode_version
        sys.stderr.write("writing %s\n" % args.outfile)
        cmap_data.write_cmap_data_file(cmapdata, args.outfile, pretty=True)
    else:
        print(cmap_data.write_cmap_data(cmapdata, pretty=True).decode("utf-8"))


if __name__ == "__main__":
    main()
