# Copyright 2018 DeepMind Technologies Limited.
#
# 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.

"""Functionality for displaying expressions.

SymPy provides a lot of functionality for displaying expressions, but it's
slightly too centered on being a symbolic maths engine to provides all our
needs. For example, it's impossible to display an unsimplified fraction like
3/6, or a decimal that isn't internally represented as a float and thus subject
to rounding.

Also provides some other convenience such as converting numbers to words, and
displaying percentages (properly formatted).
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import decimal

# Dependency imports
import sympy


# For converting integers to words:
_INTEGER_LOW = [
    'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
    'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteeen', 'fifteen',
    'sixteen', 'seventeen', 'eighteen', 'nineteen'
]
_INTEGER_MID = [
    '', '', 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty',
    'ninety'
]
_INTEGER_HIGH = [
    (int(1e12), 'trillion'), (int(1e9), 'billion'), (int(1e6), 'million'),
    (int(1e3), 'thousand'), (100, 'hundred')
]


# For converting rationals to words:
_SINGULAR_DENOMINATORS = [
    '', '', 'half', 'third', 'quarter', 'fifth', 'sixth', 'seventh', 'eighth',
    'ninth', 'tenth', 'eleventh', 'twelth', 'thirteenth', 'fourteenth',
    'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth',
    'twentieth'
]
_PLURAL_DENOMINATORS = [
    '', '', 'halves', 'thirds', 'quarters', 'fifths', 'sixths', 'sevenths',
    'eighths', 'ninths', 'tenths', 'elevenths', 'twelths', 'thirteenths',
    'fourteenths', 'fifteenths', 'sixteenths', 'seventeenths', 'eighteenths',
    'nineteenths', 'twentieths'
]


# For converting ordinals to words:
_ORDINALS = [
    'zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh',
    'eighth', 'ninth', 'tenth', 'eleventh', 'twelth', 'thirteenth',
    'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth',
    'nineteenth', 'twentieth'
]


class Decimal(object):
  """Display a value as a decimal."""

  def __init__(self, value):
    """Initializes a `Decimal`.

    Args:
      value: (Sympy) value to display as a decimal.

    Raises:
      ValueError: If `value` cannot be represented as a non-terminating decimal.
    """
    self._value = sympy.Rational(value)

    numer = int(sympy.numer(self._value))
    denom = int(sympy.denom(self._value))

    denom_factors = list(sympy.factorint(denom).keys())
    for factor in denom_factors:
      if factor not in [2, 5]:
        raise ValueError('Cannot represent {} as a non-recurring decimal.'
                         .format(value))
    self._decimal = decimal.Decimal(numer) / decimal.Decimal(denom)

  @property
  def value(self):
    """Returns the value as a `sympy.Rational` object."""
    return self._value

  def _sympy_(self):
    return self._value

  def decimal_places(self):
    """Returns the number of decimal places, e.g., 32 has 0 and 1.43 has 2."""
    if isinstance(self._decimal, int):
      return 0
    elif isinstance(self._decimal, decimal.Decimal):
      return -self._decimal.as_tuple().exponent

  def __str__(self):
    sign, digits, exponent = self._decimal.as_tuple()
    sign = '' if sign == 0 else '-'

    num_left_digits = len(digits) + exponent  # number digits "before" point

    if num_left_digits > 0:
      int_part = ''.join(str(digit) for digit in digits[:num_left_digits])
    else:
      int_part = '0'

    if exponent < 0:
      frac_part = '.'
      if num_left_digits < 0: