#!/usr/bin/env python
#
# Copyright 2018 Google LLC
#
# 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.

"""Generates Javascript to load compile-time localization data.

This reads localization data at compile-time in a flat JSON-formatted
dictionary.  The keys are message IDs, and the values are the translated
strings.  For example:
  {
    "BEST_WISHES": "Merin sa haryalye alasse"
  }

Each locale's translations are read from a separate file.  For example, the path
the Arabic data would be ui/locales/ar.json.
"""

import argparse
import contextlib
import json
import os
import sys
import io
import shakaBuildHelpers


_INDENTATION = '  '

# These are Google's "Tier 1" languages as of April 2019.
DEFAULT_LOCALES = [
    'ar',
    'de',
    'en',
    'en-GB',
    'es',
    'es-419',
    'fr',
    'it',
    'ja',
    'ko',
    'nl',
    'pl',
    'pt-BR',
    'ru',
    'th',
    'tr',
    'zh',
    'zh-TW',
]


class Doc(object):
  """A string builder class used to build out a tab-sensitive document."""

  def __init__(self):
    # All the lines that make-up this document.
    self._lines = []

    # Track each tab we need to insert ahead of the next line.
    self._tab_level = 0

  @contextlib.contextmanager
  def Block(self):
    """Starts a new tabbed block.

    This should be used with |with| to ensure that the block closes.
    """
    self._tab_level += 1
    yield
    self._tab_level -= 1

  def Code(self, block):
    """Insert a block of code with the current tab level.

    This will add the required leading white space to the line.
    """
    # Break the code block into each line of code
    lines = block.split('\n')

    for line in lines:
      # Right-strip the line to avoid trailing white space. We do this on the
      # full string so that tabbing will be removed if a blank line was added.
      new_line = (_INDENTATION * self._tab_level) + line
      self._lines.append(new_line.rstrip())

  def ToString(self):
    return '\n'.join(self._lines) + '\n'


def AsQuotedString(input_string):
  """Convert |input_string| into a quoted string."""
  subs = [
      ('\n', '\\n'),
      ('\t', '\\t'),
      ("'", "\\'")
  ]

  # Go through each substitution and replace any occurrences.
  output_string = input_string
  for before, after in subs:
    output_string = output_string.replace(before, after)

  # Lastly wrap the string in quotes.
  return "'%s'" % output_string


def GenerateLocalizations(localizations, class_name):
  """Generates JavaScript code to insert the localization data.

  This creates a function called "addTo" in the class called |class_name| that,
  when called, will insert the data from |localizations|.

  Args:
    localizations: A map of string locale name to a map of string tag to the
      string localization.
    class_name: A string name of the class to put generated code into.

  Returns:
    A string containing the generated code.
  """
  doc = Doc()

  doc.Code("""
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This file is auto-generated. DO NOT EDIT THIS FILE. If you need to:
//   - change which locales are in this file, use the --locales option in
//     "build/all.py" or "build/build.py"
//   - change an entry for a specific locale, update "ui/locales/"
//   - change anything else, update "build/generateLocalizations.py".
//
// To regenerate this file, run "build/generateLocalizations.py".
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
""")

  # Insert a comment that the build scripts will read to determine the freshness
  # of this output.  This will be compared against a list of locales in the
  # current build to decide if the output needs to be regenerated.
  # DO NOT change the formatting here without also updating the "compiler.py"
  # module and the "GenerateLocalizations" class's "_locales_changed" method.
  locale_list_string = ', '.join(sorted(localizations.keys()))
  doc.Code('// LOCALES: %s' % locale_list_string)

  doc.Code("goog.provide('%s');" % class_name)
  doc.Code("goog.require('shaka.ui.Localization');")

  doc.Code("""
/**
 * Insert all localization data for the UI into |localization|. This should be
 * done BEFORE any listeners are added to the localization system (to avoid
 * callbacks for each insert) and should be done BEFORE changing to the initial
 * preferred locale (reduces the work needed to update the internal state after
 * each insert).
 *
 * @param {!shaka.ui.Localization} localization
 */""")

  doc.Code('%s.addTo = function(localization) {' % class_name)

  message_ids = set()

  # Go through the locales in sorted order so that we will be consistent between
  # runs.
  for locale in sorted(localizations.keys()):
    localization = localizations[locale]

    with doc.Block():
      quoted_locale = AsQuotedString(locale)
      doc.Code('localization.insert(%s, new Map([' % quoted_locale)

      with doc.Block():
        # Make sure that we sort by the localization keys so that they will
        # always be in the same order.
        for key, value in sorted(localization.items()):
          message_ids.add(key)
          quoted_key = AsQuotedString(key)
          quoted_value = AsQuotedString(value)
          doc.Code('[%s, %s],' % (quoted_key, quoted_value))

      doc.Code(']));')  # Close the call to insert.

  doc.Code('};')  # Close the function.

  doc.Code("""
/**
 * @enum {string}
 * @const
 */
%s.Ids = {""" % class_name)
  for message_id in message_ids:
    doc.Code('  %s: %s,' % (message_id, AsQuotedString(message_id)))
  doc.Code('};')

  return doc.ToString()


def CreateParser():
  """Create the argument parser for this application."""
  base = shakaBuildHelpers.get_source_base()

  parser = argparse.ArgumentParser(
      description=__doc__,
      formatter_class=argparse.RawDescriptionHelpFormatter)

  parser.add_argument(
      '--locales',
      type=str,
      nargs='+',
      default=DEFAULT_LOCALES,
      help='The list of locales to compile in (default %(default)r)')

  parser.add_argument(
      '--source',
      type=str,
      default=os.path.join(base, 'ui', 'locales'),
      help='The folder path for JSON inputs')

  parser.add_argument(
      '--output',
      type=str,
      default=os.path.join(base, 'dist', 'locales.js'),
      help='The file path for JavaScript output')

  parser.add_argument(
      '--class-name',
      type=str,
      default='shaka.ui.Locales',
      help='The fully qualified class name for the JavaScript output')

  return parser


def main(args):
  parser = CreateParser()
  args = parser.parse_args(args)

  combined_localizations = {}
  for locale in args.locales:
    path = os.path.join(args.source, locale + '.json')
    with io.open(path, 'r', encoding='utf8') as f:
      combined_localizations[locale] = json.load(f)

  doc = GenerateLocalizations(combined_localizations, args.class_name)
  with open(args.output, 'wb') as f:
    f.write(doc.encode('utf-8'))

  return args.output


if __name__ == '__main__':
  main(sys.argv[1:])
