import re

number_system = {
    'zero': 0,          'one': 1,           'two': 2,           'three': 3,
    'four': 4,          'five': 5,          'six': 6,           'seven': 7,
    'eight': 8,         'nine': 9,          'ten': 10,          'eleven': 11,
    'twelve': 12,       'thirteen': 13,     'fourteen': 14,     'fifteen': 15,
    'sixteen': 16,      'seventeen': 17,    'eighteen': 18,     'nineteen': 19,
    'twenty': 20,       'thirty': 30,       'forty': 40,        'fifty': 50,
    'sixty': 60,        'seventy': 70,      'eighty': 80,       'ninety': 90,
    'hundred': 100,     'thousand': 1000,   'million': 1000000, 'point': '.',
    'billion': 1000000000
}

decimals = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']

def number_formation(number_words):
    """
    function to form numeric multipliers for million, billion, thousand etc.

    input: list of strings
    return value: integer
    """
    numbers = []
    for number_word in number_words:
        numbers.append(number_system[number_word])
    if len(numbers) == 4:
        return (numbers[0] * numbers[1]) + numbers[2] + numbers[3]
    elif len(numbers) == 3:
        return numbers[0] * numbers[1] + numbers[2]
    elif len(numbers) == 2:
        if 100 in numbers:
            return numbers[0] * numbers[1]
        else: return numbers[0] + numbers[1]
    else: return numbers[0]

def get_decimal_sum(decimal_digit_words):
    """
    function to convert post decimal digit words to numerial digits
    input: list of strings
    output: double
    """
    decimal_string = []
    for dec_word in decimal_digit_words:
        if(dec_word not in decimals): return 0
        else: decimal_string.append(number_system[dec_word])
    return float('0.' + ''.join(map(str,decimal_string)))


def to_digits(number_sentence):
    """
    function to return integer for an input `number_sentence` string
    input: string
    output: int or double or None
    """
    if not isinstance(number_sentence, str):
        raise ValueError("Type of input is not string! Please enter a valid number word (eg. \'two million twenty three thousand and forty nine\')")

    number_sentence = re.sub(r'\W', ' ', number_sentence).lower()

    if number_sentence.isdecimal(): return int(number_sentence)

    split_words = number_sentence.split()  # strip extra spaces and split sentence into words

    clean_decimal_numbers = []

    clean_numbers = [word for word in split_words if word in number_system]

    # Error message if the user enters invalid input!
    if not clean_numbers:
        raise ValueError("No valid number words found! Please enter a valid number word (eg. two million twenty three thousand and forty nine)") 

    # Error if user enters million,billion, thousand or decimal point twice
    if clean_numbers.count('thousand') > 1 or clean_numbers.count('million') > 1 or clean_numbers.count('billion') > 1 or clean_numbers.count('point')> 1:
        raise ValueError("Redundant number word! Please enter a valid number word (eg. two million twenty three thousand and forty nine)")

    # separate decimal part of number (if exists)
    if clean_numbers.count('point') == 1:
        clean_decimal_numbers = clean_numbers[clean_numbers.index('point')+1:]
        clean_numbers = clean_numbers[:clean_numbers.index('point')]

    billion_index = clean_numbers.index('billion') if 'billion' in clean_numbers else -1
    million_index = clean_numbers.index('million') if 'million' in clean_numbers else -1
    thousand_index = clean_numbers.index('thousand') if 'thousand' in clean_numbers else -1

    if (thousand_index > -1 and (thousand_index < million_index or thousand_index < billion_index)) or (million_index>-1 and million_index < billion_index):
        raise ValueError("Malformed number! Please enter a valid number word (eg. two million twenty three thousand and forty nine)")

    total_sum = 0  # storing the number to be returned

    if clean_numbers:
        # hack for now, better way TODO
        if len(clean_numbers) == 1:
                total_sum += number_system[clean_numbers[0]]
        else:
            if billion_index > -1:
                billion_multiplier = number_formation(clean_numbers[0:billion_index])
                total_sum += billion_multiplier * 1000000000

            if million_index > -1:
                if billion_index > -1:
                    million_multiplier = number_formation(clean_numbers[billion_index+1:million_index])
                else:
                    million_multiplier = number_formation(clean_numbers[0:million_index])
                total_sum += million_multiplier * 1000000

            if thousand_index > -1:
                if million_index > -1:
                    thousand_multiplier = number_formation(clean_numbers[million_index+1:thousand_index])
                elif billion_index > -1 and million_index == -1:
                    thousand_multiplier = number_formation(clean_numbers[billion_index+1:thousand_index])
                else:
                    thousand_multiplier = number_formation(clean_numbers[0:thousand_index])
                total_sum += thousand_multiplier * 1000

            if thousand_index > -1 and thousand_index != len(clean_numbers)-1:
                hundreds = number_formation(clean_numbers[thousand_index+1:])
            elif million_index > -1 and million_index != len(clean_numbers)-1:
                hundreds = number_formation(clean_numbers[million_index+1:])
            elif billion_index > -1 and billion_index != len(clean_numbers)-1:
                hundreds = number_formation(clean_numbers[billion_index+1:])
            elif thousand_index == -1 and million_index == -1 and billion_index == -1:
                hundreds = number_formation(clean_numbers)
            else:
                hundreds = 0
            total_sum += hundreds

    # adding decimal part to total_sum (if exists)
    if clean_decimal_numbers: total_sum += get_decimal_sum(clean_decimal_numbers)

    return total_sum
