# Currency.py
"""
Currency = Decimal + Babel

>>> Currency()
Decimal("0.00")
"""

from decimal import *
from babel.numbers import format_decimal, format_currency, parse_decimal, parse_number, get_decimal_symbol, get_group_symbol, get_currency_symbol, NumberFormatError
from django.conf import settings
from django.utils.translation import ugettext_lazy as _

default_error_messages = {
    'decimal_symbol': _(u'Ensure that there is only one decimal symbol (%s).'),
    'invalid_format': _(u'Invalid currency format. Please use the format 9%s999%s00')
}

TWOPLACES = Decimal(10) ** -2

def _getSymbols(value):
    retVal = ''
    for x in value:
        if x < u'0' or x > u'9':
            retVal += x
    return retVal

def _getCodes():
    l_currency_language_code = 'en_US'
    l_currency_code = 'USD'
    try:
        l_currency_language_code = settings.CURRENCY_LANGUAGE_CODE
        l_currency_code = ''
    except AttributeError:
        pass
    try:
        l_currency_code = settings.CURRENCY_CODE
    except AttributeError:
        pass

    return (l_currency_language_code, l_currency_code)


def parse_value(value):
    """
    Accepts a string value and attempts to parce it as a currency value.

    Returns the extracted numeric value converted to a string
    """
    l_currency_language_code, l_currency_code = _getCodes()

    curSym = get_currency_symbol(l_currency_code, l_currency_language_code)
    grpSym = get_group_symbol(locale=l_currency_language_code.lower())
    decSym = get_decimal_symbol(locale=l_currency_language_code.lower())

    # Convert the Official characters into what comes from the keyboard.
    #   This section may need to grow over time.
    #   - Character 160 is a non-breaking space, which is different from a typed space
    if ord(grpSym) == 160:
        value = value.replace(u' ', grpSym)

    allSym = _getSymbols(value)
    invalidSym = allSym.replace(curSym, '').replace(grpSym, '').replace(decSym, '').replace(u'-', '')

    value = value.replace(curSym, '')

    if allSym.count(decSym) > 1:
        raise NumberFormatError(default_error_messages['decimal_symbol'] % decSym)
    elif (allSym.count(decSym) == 1 and allSym[-1] != decSym) or len(invalidSym) > 0:
        raise NumberFormatError(default_error_messages['invalid_format'] % (grpSym, decSym))
    elif value.count(decSym) == 1:
        value = parse_decimal(value, locale=l_currency_language_code.lower())
    else:
        value = parse_number(value, locale=l_currency_language_code.lower())

    # The value is converted into a string because the parse functions return floats
    return str(value)

class Currency(Decimal):
    """
    A Currency data type that extends the Decimal type and integrates the Bable libraries.

    Accepts any numeric value or formated currency string as input.


    Testing different numeric inputs and rounding
    >>> Currency(.1)
    Decimal("0.10")
    >>> Currency(1)
    Decimal("1.00")
    >>> Currency(.015)
    Decimal("0.02")
    >>> Currency(.014)
    Decimal("0.01")


    Testing string input and format validation using en_US currency format
    >>> import os
    >>> os.environ['DJANGO_SETTINGS_MODULE'] = 'django.conf.global_settings'
    >>> Currency("1")
    Decimal("1.00")
    >>> Currency("1,234.00")
    Decimal("1234.00")
    >>> Currency("1,234.0.0")
    Traceback (most recent call last):
      ...
    NumberFormatError: Ensure that there is only one decimal symbol (.).
    >>> Currency("1,2,34.0")
    Decimal("1234.00")
    >>> Currency("1,234.00").format()
    u'1,234.00'
    >>> Currency("1,234.00").format_pretty()
    u'$1,234.00'
    >>> Currency("-1,234.00").format_pretty()
    u'$-1,234.00'
    >>> Currency("1 234.00")
    Traceback (most recent call last):
      ...
    NumberFormatError: Invalid currency format. Please use the format 9,999.00
    >>> Currency("1.234,00")
    Traceback (most recent call last):
      ...
    NumberFormatError: Invalid currency format. Please use the format 9,999.00
    >>> Currency("1.234")
    Decimal("1.23")
    >>> Currency("$1,234.00")
    Decimal("1234.00")
    >>> Currency("$1,234.00", format="#,##0").format()
    u'1,234'
    >>> Currency("$-1,234.00", format_pretty=u"#,##0 \xa4").format_pretty()
    u'-1,234 $'


    Testing string input and format validation using pt_BR currency format
    >>> from django.conf import settings
    >>> settings.CURRENCY_LANGUAGE_CODE = 'pt_BR'
    >>> Currency("1 234.00")
    Traceback (most recent call last):
      ...
    NumberFormatError: Invalid currency format. Please use the format 9.999,00
    >>> Currency("1.234")
    Decimal("1234.00")
    >>> Currency("1.234").format()
    u'1.234,00'
    """

def __new__(cls, value="0", format=None, format_pretty=None, parse_string=False, context=None):
        """
        Create a new Currency object
            
        value: Can be any number (integer, decimal, or float) or a properly formated string
        format: The format to use in the format() method
        format_pretty: The format used in the format_pretty() method
        parse_string: *IMPORTANT* Set this to True if you are passing a string formatted in a currency 
                      other than the standard decimal format of #,###.##
        context: How to handle a malformed string value
        """
        if value != "0" and isinstance(value, basestring) and parse_string:
            value = parse_value(value)
        elif isinstance(value, float):
            value = str(value)

        if format:
            cls._format = format
        else:
            cls._format = '#,##0.00;-#'

        if format_pretty:
            cls._formatPretty = format_pretty
        else:
            cls._formatPretty = u'\xa4#,##0.00;\xa4-#'

        ld_rounded = Decimal(value).quantize(TWOPLACES, ROUND_HALF_UP)

        return super(Currency, cls).__new__(cls, value=ld_rounded, context=context)

    def format(self):
        l_currency_language_code, l_currency_code = _getCodes()
        return format_decimal(self, format=self._format, locale=l_currency_language_code)

    def format_pretty(self):
        l_currency_language_code, l_currency_code = _getCodes()
        return format_currency(self, l_currency_code, format=self._formatPretty, locale=l_currency_language_code)

def _test():
    import doctest
    doctest.testmod()

if __name__ == "__main__":
    _test()


# Additions to Setting.py
#CURRENCY_LANGUAGE_CODE = 'pt_BR'
#CURRENCY_CODE = '' # If one exists like 'USD', 'EUR'