# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa.  All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc.  All Rights Reserved.

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA

from __future__ import division, print_function, unicode_literals

from collections import OrderedDict

from . import lang_EU

GENERIC_CENTS = ('sentti', 'senttiä')
GENERIC_CENTAVOS = ('centavo', 'centavoa')

# grammatical cases
NOM = 10  # nominative: the dictionary form
GEN = 11  # genitive: ~of/'s
ACC = 12  # accusative: not used; either nominative or genitive
PTV = 13  # partitive: as an object
# locative cases (internal)
INE = 14  # inessive: in
ELA = 15  # elative: from/out of
ILL = 16  # illative: into
# locative cases (external)
ADE = 17  # adessive: at/on
ABL = 18  # ablative: from (after being at/on, not in)
ALL = 19  # allative: to
# essive
ESS = 20     # essive: as (in the role of)
TRANSL = 21  # translative: to (the role of; being sth)
# rare
INSTRUC = 22  # instructive: with (plural is the same as singular)
ABE = 23      # abessive: without
COM = 24      # comitative: together with (plural = singular)

NAME_TO_CASE = {
    'nominative': NOM,
    'genitive': GEN,
    'accusative': ACC,
    'partitive': PTV,
    'inessive': INE,
    'elative': ELA,
    'illative': ILL,
    'adessive': ADE,
    'ablative': ABL,
    'allative': ALL,
    'essive': ESS,
    'translative': TRANSL,
    'instructive': INSTRUC,
    'abessive': ABE,
    'comitative': COM,
}

# https://en.wikibooks.org/wiki/Finnish/Grammar-Vowel_harmony
BACK_TO_FRONT = {
    'a': 'ä',
    'o': 'ö',
    'u': 'y',
}

# https://en.wiktionary.org/wiki/Appendix:Finnish_nominal_inflection
# CASE: (SINGULAR_SUFFIX+, PLURAL_SUFFIX+)
KOTUS_TYPE = {

    # Kotus type 5/risti, no gradation
    5: {
        # grammatical
        NOM: ('i', 'it'),
        GEN: ('in', 'ien'),
        PTV: ('ia', 'eja'),
        # locative, internal
        INE: ('issa', 'eissa'),
        ELA: ('ista', 'eista'),
        ILL: ('iin', 'eihin'),
        # locative, external
        ADE: ('illa', 'eilla'),
        ABL: ('ilta', 'eilta'),
        ALL: ('ille', 'eille'),
        # essive
        ESS: ('ina', 'eina'),
        TRANSL: ('iksi', 'eiksi'),
        # rare
        INSTRUC: ('ein', 'ein'),
        ABE: ('itta', 'eitta'),
        COM: ('eine', 'eine'),  # works better
    },

    # Kotus type 7/ovi, no gradation
    7: {
        # grammatical
        NOM: ('i', 'et'),
        GEN: ('en', 'ien'),
        PTV: ('ea', 'ia'),
        # locative, internal
        INE: ('essa', 'issa'),
        ELA: ('esta', 'ista'),
        ILL: ('een', 'iin'),
        # locative, external
        ADE: ('ella', 'illa'),
        ABL: ('elta', 'ilta'),
        ALL: ('elle', 'ille'),
        # essive
        ESS: ('ena', 'ina'),
        TRANSL: ('eksi', 'iksi'),
        # rare
        INSTRUC: ('in', 'in'),
        ABE: ('etta', 'itta'),
        COM: ('ine', 'ine'),  # works better
    },

    # Kotus type 8/nalle, no gradation
    8: {
        # grammatical
        NOM: ('e', 'et'),
        GEN: ('en', ('ejen', 'ein')),
        PTV: ('ea', 'eja'),
        # locative, internal
        INE: ('essa', 'eissa'),
        ELA: ('esta', 'eista'),
        ILL: ('een', 'eihin'),
        # locative, external
        ADE: ('ella', 'eilla'),
        ABL: ('elta', 'eilta'),
        ALL: ('elle', 'eille'),
        # essive
        ESS: ('ena', 'eina'),
        TRANSL: ('eksi', 'eiksi'),
        # rare
        INSTRUC: ('ein', 'ein'),
        ABE: ('etta', 'eitta'),
        COM: ('eine', 'eine'),  # works better
    },

    # Kotus type 9/kala, t-d gradation (sata)
    109: {
        # grammatical
        NOM: ('ta', 'dat'),
        GEN: ('dan', ('tojen', 'tain')),
        PTV: ('taa', 'toja'),
        # locative, internal
        INE: ('dassa', 'doissa'),
        ELA: ('dasta', 'doista'),
        ILL: ('taan', 'toihin'),
        # locative, external
        ADE: ('dalla', 'doilla'),
        ABL: ('dalta', 'doilta'),
        ALL: ('dalle', 'doille'),
        # essive
        ESS: ('tana', 'toina'),
        TRANSL: ('daksi', 'doiksi'),
        # rare
        INSTRUC: ('doin', 'doin'),
        ABE: ('datta', 'doitta'),
        COM: ('toine', 'toine'),  # works better
    },

    # Kotus type 10/koira, no gradation
    10: {
        # grammatical
        NOM: ('a', 'at'),
        GEN: ('an', ('ien', 'ain')),
        PTV: ('aa', 'ia'),
        # locative, internal
        INE: ('assa', 'issa'),
        ELA: ('asta', 'ista'),
        ILL: ('aan', 'iin'),
        # locative, external
        ADE: ('alla', 'illa'),
        ABL: ('alta', 'ilta'),
        ALL: ('alle', 'ille'),
        # essive
        ESS: ('ana', 'ina'),
        TRANSL: ('aksi', 'iksi'),
        # rare
        INSTRUC: ('in', 'in'),
        ABE: ('atta', 'itta'),
        COM: ('ine', 'ine'),  # works better
    },

    # Kotus type 27/käsi, t-d gradation
    27: {
        # grammatical
        NOM: ('si', 'det'),
        GEN: ('den', ('sien', 'tten')),
        PTV: ('tta', 'sia'),
        # locative, internal
        INE: ('dessa', 'sissa'),
        ELA: ('desta', 'sista'),
        ILL: ('teen', 'siin'),
        # locative, external
        ADE: ('della', 'silla'),
        ABL: ('delta', 'silta'),
        ALL: ('delle', 'sille'),
        # essive
        ESS: ('tena', 'sina'),
        TRANSL: ('deksi', 'siksi'),
        # rare
        INSTRUC: ('sin', 'sin'),
        ABE: ('detta', 'sitta'),
        COM: ('sine', 'sine'),  # works better
    },

    # Kotus type 31/kaksi, t-d gradation
    31: {
        # grammatical
        NOM: ('ksi', 'hdet'),
        GEN: ('hden', 'ksien'),
        PTV: ('hta', 'ksia'),
        # locative, internal
        INE: ('hdessa', 'ksissa'),
        ELA: ('hdesta', 'ksista'),
        ILL: ('hteen', 'ksiin'),
        # locative, external
        ADE: ('hdella', 'ksilla'),
        ABL: ('hdelta', 'ksilta'),
        ALL: ('hdelle', 'ksille'),
        # essive
        ESS: ('htena', 'ksina'),
        TRANSL: ('hdeksi', 'ksiksi'),
        # rare
        INSTRUC: ('ksin', 'ksin'),
        ABE: ('hdetta', 'ksitta'),
        COM: ('ksine', 'ksine'),  # works better
    },

    # Kotus type 32/sisar, no gradation
    32: {
        # grammatical
        NOM: ('', 'et'),
        GEN: ('en', ('ien', 'ten')),
        PTV: ('ta', 'ia'),
        # locative, internal
        INE: ('essa', 'issa'),
        ELA: ('esta', 'ista'),
        ILL: ('een', 'iin'),
        # locative, external
        ADE: ('ella', 'illa'),
        ABL: ('elta', 'ilta'),
        ALL: ('elle', 'ille'),
        # essive
        ESS: ('ena', 'ina'),
        TRANSL: ('eksi', 'iksi'),
        # rare
        INSTRUC: ('in', 'in'),
        ABE: ('etta', 'itta'),
        COM: ('ine', 'ine'),  # works better
    },

    # Kotus type 38/nainen, no gradation
    38: {
        # grammatical
        NOM: ('nen', 'set'),
        GEN: ('sen', ('sten', 'sien')),
        PTV: ('sta', 'sia'),
        # locative, internal
        INE: ('sessa', 'sissa'),
        ELA: ('sesta', 'sista'),
        ILL: ('seen', 'siin'),
        # locative, external
        ADE: ('sella', 'silla'),
        ABL: ('selta', 'silta'),
        ALL: ('selle', 'sille'),
        # essive
        ESS: ('sena', 'sina'),
        TRANSL: ('seksi', 'siksi'),
        # rare
        INSTRUC: ('sin', 'sin'),
        ABE: ('setta', 'sitta'),
        COM: ('sine', 'sine'),  # works better
    },

    # Kotus type 45/kahdeksas, nt-nn gradation
    45: {
        # grammatical
        NOM: ('s', 'nnet'),
        GEN: ('nnen', 'nsien'),
        PTV: ('tta', 'nsia'),
        # locative, internal
        INE: ('nnessa', 'nsissa'),
        ELA: ('nnesta', 'nsista'),
        ILL: ('nteen', 'nsiin'),
        # locative, external
        ADE: ('nnella', 'nsilla'),
        ABL: ('nnelta', 'nsilta'),
        ALL: ('nnelle', 'nsille'),
        # essive
        ESS: ('ntena', 'nsina'),
        TRANSL: ('nneksi', 'nsiksi'),
        # rare
        INSTRUC: ('nsin', 'nsin'),
        ABE: ('nnetta', 'nsitta'),
        COM: ('nsine', 'nsine'),  # works better
    },

    # Kotus type 46/tuhat, nt-nn gradation
    46: {
        # grammatical
        NOM: ('t', 'nnet'),
        GEN: ('nnen', ('nsien', 'nten')),
        PTV: ('tta', 'nsia'),
        # locative, internal
        INE: ('nnessa', 'nsissa'),
        ELA: ('nnesta', 'nsista'),
        ILL: ('nteen', 'nsiin'),
        # locative, external
        ADE: ('nnella', 'nsilla'),
        ABL: ('nnelta', 'nsilta'),
        ALL: ('nnelle', 'nsille'),
        # essive
        ESS: ('ntena', 'nsina'),
        TRANSL: ('nneksi', 'nsiksi'),
        # rare
        INSTRUC: ('nsin', 'nsin'),
        ABE: ('nnetta', 'nsitta'),
        COM: ('nsine', 'nsine'),  # works better
    },
}

# kolme
KOTUS_TYPE[108] = {
    c: (KOTUS_TYPE[8][c][0], KOTUS_TYPE[7][c][1])
    for c in KOTUS_TYPE[8]
}
KOTUS_TYPE[108][INSTRUC] = ('en', 'in')
KOTUS_TYPE[108][ABE] = ('etta', 'itta')
KOTUS_TYPE[108][COM] = ('ine', 'ine')

# seitsemän, kahdeksan, yhdeksän
KOTUS_TYPE[110] = KOTUS_TYPE[10].copy()
KOTUS_TYPE[110][NOM] = ('an', 'at')

# kymmenen
KOTUS_TYPE[132] = KOTUS_TYPE[32].copy()
KOTUS_TYPE[132][NOM] = ('en', 'et')


def inflect(parts, options):
    if not isinstance(parts, list):
        parts = [parts]

    out = ''
    for part in parts:
        # part is plain text, concat and continue
        if not isinstance(part, tuple):
            out += part
            continue
        # predefined case (kaksikymmentä, ...)
        tmp_case = options.case
        if len(part) == 3:
            # override singular nominative only
            if options.case == NOM and not options.plural:
                tmp_case = part[2]
            part = part[:2]
        # stem and suffix
        stem, kotus_type = part
        suffix = KOTUS_TYPE[kotus_type][tmp_case][options.plural]
        # many choices, choose preferred or first
        if isinstance(suffix, tuple):
            common = set(suffix) & set(options.prefer or set())
            if len(common) == 1:
                suffix = common.pop()
            else:
                suffix = suffix[0]
        # apply vowel harmony
        if not set(BACK_TO_FRONT) & set(stem):
            for back, front in BACK_TO_FRONT.items():
                suffix = suffix.replace(back, front)
        # concat
        out += stem + suffix

    return out


class Options(object):
    def __init__(self, ordinal, case, plural, prefer):
        self.ordinal = ordinal
        self.case = case
        self.plural = plural
        self.prefer = prefer

    def variation(self, ordinal=None, case=None, plural=None, prefer=None):
        return Options(
            ordinal if ordinal is not None else self.ordinal,
            case if case is not None else self.case,
            plural if plural is not None else self.plural,
            prefer if prefer is not None else self.prefer,
        )


class Num2Word_FI(lang_EU.Num2Word_EU):
    CURRENCY_FORMS = {
        'BRL': (('real', 'realia'), GENERIC_CENTAVOS),
        'CHF': (('frangi', 'frangia'), ('rappen', 'rappenia')),
        'CNY': (('juan', 'juania'), ('fen', 'feniä')),
        'EUR': (('euro', 'euroa'), GENERIC_CENTS),
        'FIM': (('markka', 'markkaa'), ('penni', 'penniä')),  # historical
        'INR': (('rupia', 'rupiaa'), ('paisa', 'paisaa')),
        'JPY': (('jeni', 'jeniä'), ('sen', 'seniä')),  # rare subunit
        'KRW': (('won', 'wonia'), ('jeon', 'jeonia')),  # rare subunit
        'KPW': (('won', 'wonia'), ('chon', 'chonia')),  # rare subunit
        'MXN': (('peso', 'pesoa'), GENERIC_CENTAVOS),
        'RUB': (('rupla', 'ruplaa'), ('kopeekka', 'kopeekkaa')),
        'TRY': (('liira', 'liiraa'), ('kuruş', 'kuruşia')),
        'ZAR': (('randi', 'randia'), GENERIC_CENTS),
    }

    # crowns
    for curr_code in 'DKK', 'ISK', 'NOK', 'SEK':
        CURRENCY_FORMS[curr_code] = (('kruunu', 'kruunua'), ('äyri', 'äyriä'))

    # dollars
    for curr_code in 'AUD', 'CAD', 'HKD', 'NZD', 'SGD', 'USD':
        CURRENCY_FORMS[curr_code] = (
            ('dollari', 'dollaria'), GENERIC_CENTS)

    # pounds
    for curr_code in ('GBP',):
        CURRENCY_FORMS[curr_code] = (('punta', 'puntaa'), ('penny', 'pennyä'))

    CURRENCY_ADJECTIVES = {
        'AUD': 'Australian',
        'BRL': 'Brasilian',
        'CAD': 'Kanadan',
        'CHF': 'Sveitsin',
        'DKK': 'Tanskan',
        'FIM': 'Suomen',  # historical
        'GBP': 'Englannin',
        'HKD': 'Hongkongin',
        'INR': 'Intian',
        'ISK': 'Islannin',
        'KRW': 'Etelä-Korean',
        'KPW': 'Pohjois-Korean',
        'MXN': 'Meksikon',
        'NOK': 'Norjan',
        'NZD': 'Uuden-Seelannin',
        'RUB': 'Venäjän',
        'SEK': 'Ruotsin',
        'SGD': 'Singaporen',
        'TRY': 'Turkin',
        'USD': 'Yhdysvaltain',
        'ZAR': 'Etelä-Afrikan',
    }

    def __init__(self):
        self.ords = OrderedDict()
        super(Num2Word_FI, self).__init__()

    def set_numwords(self):
        self.set_high_numwords(self.high_numwords)
        self.set_mid_numwords(self.mid_numwords, self.mid_ords)
        self.set_low_numwords(self.low_numwords, self.low_ords)

    def set_high_numwords(self, high):
        # references:
        # https://fi.wikipedia.org/wiki/Suurten_lukujen_nimet
        # https://en.wikipedia.org/wiki/Names_of_large_numbers#Standard_dictionary_numbers

        # translate to Finnish
        replacements = [
            ("qu", "kv"),
            ("x", "ks"),
            ("c", "k"),
            ("kent", "sent"),  # applied after c -> k to cent
        ]
        translated = []
        for i, numword in enumerate(high):
            # notes:
            # - 1e6**9 can be either noviljoona or noniljoona
            # - 1e6**38 and above are untested

            # 1e6**6 is sekstiljoona but 1e6**16 is sedekiljoona
            if numword.startswith("sex") and numword != "sext":
                numword = numword.replace("sex", "se")
            # 1e6**7 is septiljoona but 1e6**17 is septendekiljoona
            elif numword.startswith("sept") and numword != "sept":
                numword = "septen" + numword[len("sept"):]
            # 1e6**8 is oktiljoona but 1e6**18 is duodevigintiljoona
            # (2 from 20)
            elif numword.startswith("octo"):
                numword = high[i + -10]
                numword = "duode" + numword[len("octo"):]
            # 1e6**9 is noniljoona but 1e6**19 is undevigintiljoona (1 from 20)
            elif numword.startswith("nove"):
                numword = high[i + -10]
                numword = "unde" + numword[len("nove") + 1:]

            # apply general replacements to all numwords
            for repl in replacements:
                numword = numword.replace(repl[0], repl[1])
            translated.append(numword)

        max = 6 * len(translated)
        for word, n in zip(translated, range(max, 0, -6)):
            if n == 6:
                # irregularity considering short scale and long scale
                self.cards[10 ** 9] = ("miljard", 5)
                self.ords[10 ** 9] = ("miljardi", 45)
            self.cards[10 ** n] = (word + "iljoon", 10)
            self.ords[10 ** n] = (word + "iljoona", 45)

    def set_mid_numwords(self, cards, ords):
        for key, val in cards:
            self.cards[key] = val
        for key, val in ords:
            self.ords[key] = val

    def set_low_numwords(self, cards, ords):
        for key, val in cards:
            self.cards[key] = val
        for key, val in ords:
            self.ords[key] = val

    def setup(self):
        super(Num2Word_FI, self).setup()

        self.negword = "miinus "
        self.pointword = "pilkku"
        self.exclude_title = ["pilkku", "miinus"]

        self.mid_numwords = [
            (1000, ("tuha", 46)),
            (100, ("sa", 109)),
            (90, [("yhdeks", 110), ("kymmen", 132, PTV)]),
            (80, [("kahdeks", 110), ("kymmen", 132, PTV)]),
            (70, [("seitsem", 110), ("kymmen", 132, PTV)]),
            (60, [("kuu", 27), ("kymmen", 132, PTV)]),
            (50, [("vii", 27), ("kymmen", 132, PTV)]),
            (40, [("nelj", 10), ("kymmen", 132, PTV)]),
            (30, [("kolm", 108), ("kymmen", 132, PTV)]),
        ]

        self.mid_ords = [
            (1000, ("tuhanne", 45)),
            (100, ("sada", 45)),
            (90, [("yhdeksä", 45), ("kymmene", 45)]),
            (80, [("kahdeksa", 45), ("kymmene", 45)]),
            (70, [("seitsemä", 45), ("kymmene", 45)]),
            (60, [("kuude", 45), ("kymmene", 45)]),
            (50, [("viide", 45), ("kymmene", 45)]),
            (40, [("neljä", 45), ("kymmene", 45)]),
            (30, [("kolma", 45), ("kymmene", 45)]),
        ]

        self.low_numwords = [
            (20, [("ka", 31), ("kymmen", 132, PTV)]),
            (19, [("yhdeks", 110), "toista"]),
            (18, [("kahdeks", 110), "toista"]),
            (17, [("seitsem", 110), "toista"]),
            (16, [("kuu", 27), "toista"]),
            (15, [("vii", 27), "toista"]),
            (14, [("nelj", 10), "toista"]),
            (13, [("kolm", 108), "toista"]),
            (12, [("ka", 31), "toista"]),
            (11, [("y", 31), "toista"]),
            (10, ("kymmen", 132)),
            (9, ("yhdeks", 110)),
            (8, ("kahdeks", 110)),
            (7, ("seitsem", 110)),
            (6, ("kuu", 27)),
            (5, ("vii", 27)),
            (4, ("nelj", 10)),
            (3, ("kolm", 108)),
            (2, ("ka", 31)),
            (1, ("y", 31)),
            (0, ("noll", 10)),
        ]

        self.low_ords = [
            (20, [("kahde", 45), ("kymmene", 45)]),
            (19, [("yhdeksä", 45), "toista"]),
            (18, [("kahdeksa", 45), "toista"]),
            (17, [("seitsemä", 45), "toista"]),
            (16, [("kuude", 45), "toista"]),
            (15, [("viide", 45), "toista"]),
            (14, [("neljä", 45), "toista"]),
            (13, [("kolma", 45), "toista"]),
            (12, [("kahde", 45), "toista"]),
            (11, [("yhde", 45), "toista"]),
            (10, ("kymmene", 45)),
            (9, ("yhdeksä", 45)),
            (8, ("kahdeksa", 45)),
            (7, ("seitsemä", 45)),
            (6, ("kuude", 45)),
            (5, ("viide", 45)),
            (4, ("neljä", 45)),
            (3, ("kolma", 45)),
            (2, ("toi", 38)),
            (1, ("ensimmäi", 38)),
            (0, ("nolla", 45)),
        ]

    def merge(self, lpair, rpair, options):
        ltext, lnum = lpair
        rtext, rnum = rpair

        # http://www.kielitoimistonohjepankki.fi/ohje/49
        fmt = "%s%s"
        # ignore lpair if lnum is 1
        if lnum == 1:
            rtext = inflect(rtext, options)
            return (rtext, rnum)
        # rnum is added to lnum
        elif lnum > rnum:
            ltext = inflect(ltext, options)
            rtext = inflect(rtext, options)
            # separate groups with space
            if lnum >= 1000:
                fmt = "%s %s"
            return (fmt % (ltext, rtext), lnum + rnum)
        # rnum is multiplied by lnum
        elif lnum < rnum:
            if options.ordinal:
                # kahdessadas, not toinensadas
                if lnum == 2:
                    ltext = ("kahde", 45)
                rtext = inflect(rtext, options)
            else:
                # kaksituhatta but kahdettuhannet
                rcase = options.case
                if options.case == NOM and not options.plural:
                    rcase = PTV
                rtext = inflect(rtext, options.variation(case=rcase))
            ltext = inflect(ltext, options)
            return (fmt % (ltext, rtext), lnum * rnum)

    def to_cardinal(self, value, case='nominative', plural=False, prefer=None):
        case = NAME_TO_CASE[case]
        options = Options(False, case, plural, prefer)
        try:
            assert int(value) == value
        except (ValueError, TypeError, AssertionError):
            if case != NOM:
                raise NotImplementedError(
                    "Cases other than nominative are not implemented for "
                    "cardinal floating point numbers.")
            return self.to_cardinal_float(value)

        out = ""
        if value < 0:
            value = abs(value)
            out = self.negword

        if value >= self.MAXVAL:
            raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))

        val = self.splitnum(value, options)
        words, num = self.clean(val, options)
        return self.title(out + words)

    def to_ordinal(self, value, case='nominative', plural=False, prefer=None):
        case = NAME_TO_CASE[case]
        options = Options(True, case, plural, prefer)

        self.verify_ordinal(value)
        if value >= self.MAXVAL:
            raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))

        val = self.splitnum(value, options)
        words, num = self.clean(val, options)
        return self.title(words)

    def to_ordinal_num(self, value, case='nominative', plural=False):
        case = NAME_TO_CASE[case]
        raise NotImplementedError

    def to_year(self, val, suffix=None, longval=True):
        suffix = suffix or ""
        if val < 0:
            val = abs(val)
            suffix = suffix or " ennen ajanlaskun alkua"
        return self.to_cardinal(val).replace(" ", "") + suffix

    def to_currency(self, val, currency="EUR", cents=True, separator=" ja",
                    adjective=False):
        return super(Num2Word_FI, self).to_currency(
            val, currency=currency, cents=cents, separator=separator,
            adjective=adjective)

    def splitnum(self, value, options):
        elems = self.ords if options.ordinal else self.cards
        for elem in elems:
            if elem > value:
                continue

            out = []
            if value == 0:
                div, mod = 1, 0
            else:
                div, mod = divmod(value, elem)

            if div == 1:
                out.append((elems[1], 1))
            else:
                if div == value:  # The system tallies, eg Roman Numerals
                    return [(div * elems[elem], div*elem)]
                out.append(self.splitnum(div, options))

            out.append((elems[elem], elem))

            if mod:
                out.append(self.splitnum(mod, options))

            return out

    def clean(self, val, options):
        out = val
        while len(val) != 1:
            out = []
            left, right = val[:2]
            if isinstance(left, tuple) and isinstance(right, tuple):
                out.append(self.merge(left, right, options))
                if val[2:]:
                    out.append(val[2:])
            else:
                for elem in val:
                    if isinstance(elem, list):
                        if len(elem) == 1:
                            out.append(elem[0])
                        else:
                            out.append(self.clean(elem, options))
                    else:
                        out.append(elem)
            val = out
        return out[0]
