from __future__ import unicode_literals
from builtins import str
import re

import pytest

from context import unit as u
from context import template as t
from context import exceptions as e
from helpers import make_obj_factory, generate_params


# SimpleTemplate ******************************************************

# Fixtures, factories, and test data

st_kwargs = ['min_length', 'max_length', 'base_pattern', 'pre_pattern',
             'post_pattern']
make_simple_template = make_obj_factory(t.SimpleTemplate, kwarg_list=st_kwargs)

SIMPLE_TEMPLATE_DATA = {
    # min, max, base_pattern, pre_pattern, post_pattern
    (0, 1, r'(?:[A-Z][0-9])', '', ''): {
        'valid': ['', 'A1', 'B2'],
        'invalid': ['A', '0', '.', 'AA', '0A', 'AA0', 'A1B2'],
    },
    (1, 1, r'(?:[A-Z][0-9])', '', ''): {
        'valid': ['A1', 'B2'],
        'invalid': ['', 'A1B2'],
    },
    (2, 2, r'(?:[A-Z][0-9])', '', ''): {
        'valid': ['A1B2'],
        'invalid': ['', 'A1', 'A1B2C3'],
    },
    (1, 2, r'(?:[A-Z][0-9])', '', ''): {
        'valid': ['A1', 'A1B2'],
        'invalid': ['', 'A1B2C3'],
    },
    (1, None, r'(?:[A-Z][0-9])', '', ''): {
        'valid': ['A1', 'A1B2', 'A1B2C3', 'A1B2C3D4'],
        'invalid': [''],
    },
    (0, 2, r'(?:[A-Z][0-9])', r'[0-9]', r'\.'): {
        'valid': ['0.', '0A0.', '0A1B2.'],
        'invalid': ['A0', 'A0.', '0A0', '0A1B2C3.', '0A0.1B1.'],
    },
}

SIMPLE_VALID_PARAMS = generate_params(SIMPLE_TEMPLATE_DATA, 'valid')
SIMPLE_INVALID_PARAMS = generate_params(SIMPLE_TEMPLATE_DATA, 'invalid')


# Tests

def test_simple_template_validate_invalid_min_max():
    """Instantiating a SimpleTemplate should raise a SettingsError if
    ``min_length`` is greater than ``max_length``.
    """
    with pytest.raises(e.SettingsError):
        t.SimpleTemplate(min_length=1, max_length=0)


@pytest.mark.parametrize('params, tstr', SIMPLE_VALID_PARAMS)
def test_simple_template_validate_true(params, tstr):
    """The given test string, when passed to a SimpleTemplate object
    created with the given params' ``validate`` method, should return
    True.
    """
    template = make_simple_template(*params)
    assert template.validate(tstr) is True


@pytest.mark.parametrize('params, tstr', SIMPLE_INVALID_PARAMS)
def test_simple_template_validate_error(params, tstr):
    """The given test string, when passed to a SimpleTemplate object
    created with the given params' ``validate`` method, should raise an
    InvalidCallNumberStringError.
    """
    template = make_simple_template(*params)
    with pytest.raises(e.InvalidCallNumberStringError):
        template.validate(tstr)


def test_simple_template_describe_short_provided():
    """Passing a ``short_description`` parameter to SimpleTemplate
    should mean calls to ``describe_short`` use that exact string.
    """
    short_desc = 'short'
    template = t.SimpleTemplate(short_description=short_desc)
    assert template.describe_short() == short_desc


def test_simple_template_describe_long_provided():
    """Passing a ``long_description`` parameter to SimpleTemplate
    should mean calls to ``describe_long`` use that exact string.
    """
    long_desc = 'long'
    template = t.SimpleTemplate(long_description=long_desc)
    assert template.describe_long() == long_desc


def test_simple_template_describe_short_generated_qualities():
    """A description generated by SimpleTemplate.describe_short should
    start with 'a string' and not end with a period.
    """
    short_desc = t.SimpleTemplate().describe_short()
    assert short_desc.startswith('a string') and not short_desc.endswith('.')


def test_simple_template_describe_long_generated_qualities():
    """A description generated by SimpleTemplate.describe_long should
    start with 'A SimpleTemplate' and end with a period. It should
    contain the full text of SimpleTemplate.describe_short.
    """
    long_desc = t.SimpleTemplate().describe_long()
    short_desc = t.SimpleTemplate().describe_short()
    assert (long_desc.startswith('A SimpleTemplate') and
            long_desc.endswith('.') and
            short_desc in long_desc)


@pytest.mark.parametrize('dtype', ['base', 'pre', 'post'])
def test_simple_template_describe_generated_base_pre_post(dtype):
    """Descriptions generated by the SimpleTemplate.describe methods
    should include a provided ``base_description``. But, they should
     only include a ``pre_description`` and ``post_description`` if the
     ``pre_pattern`` and `post_pattern`` are also provided.
    """
    desc = 'test_{}'.format(dtype)
    kwargs_no_pattern = {
        '{}_description'.format(dtype): desc
    }
    kwargs_with_pattern = {
        '{}_description'.format(dtype): desc,
        '{}_pattern'.format(dtype): desc
    }
    template_no_pattern = t.SimpleTemplate(**kwargs_no_pattern)
    template_with_pattern = t.SimpleTemplate(**kwargs_with_pattern)
    if dtype == 'base':
        assertion_no_pat = desc in template_no_pattern.describe_short()
    else:
        assertion_no_pat = desc not in template_no_pattern.describe_short()

    assert desc in template_with_pattern.describe_short() and assertion_no_pat


@pytest.mark.parametrize('min_, max_, pattern', [
    (0, 1, r'0 to 1'),
    (1, 1, r'(?<!0 to )1'),
    (2, 2, r'(?<!0 to )2'),
    (1, 2, r'1 to 2'),
    (0, None, r'0 or more')
])
def test_simple_template_describe_generated_base_min_max(min_, max_, pattern):
    """Descriptions generated by the SimpleTemplate.describe methods
    should match the appropriate regular expression pattern based on
    the min_length and max_length values used to initialize the
    SimpleTemplate, plus a textual description provided for
    ``base_description``.
    """
    base_description = 'base'
    pattern = r'{} {}'.format(pattern, base_description)
    template = t.SimpleTemplate(min_length=min_, max_length=max_,
                                base_description=base_description)
    assert re.search(pattern, template.describe_short()) is not None


# Grouping ************************************************************

# Fixtures, factories, and test data

gr_kwargs = ['min', 'max', 'types', 'name', 'inner_sep_type',
             'outer_sep_group', 'outer_sep_placement']
make_grouping = make_obj_factory(t.Grouping, kwarg_list=gr_kwargs)


def make_simple_unit_type(min_length, max_length, base_pattern, classname):
    return u.SimpleUnit.derive(min_length=min_length, max_length=max_length,
                               base_pattern=base_pattern, classname=classname)


alpha_utype = make_simple_unit_type(1, 1, r'[A-Za-z]', 'Alpha')
alpha_opt_utype = make_simple_unit_type(0, 1, r'[A-Za-z]', 'AlphaOpt')
olap_utype = make_simple_unit_type(1, 1, r'[T-Zt-z]', 'Overlap')
digit_utype = make_simple_unit_type(1, 1, r'[0-9]', 'Digit')
pipe_utype = make_simple_unit_type(1, 1, r'\|', 'Pipe')
dot_utype = make_simple_unit_type(1, 1, r'\.', 'Dot')

outer_sep_gr = make_grouping(1, 1, [pipe_utype], 'sep0', None, None, None)
outer_sep_gr_opt = make_grouping(0, 1, [pipe_utype], 'sep0', None, None, None)

GROUPING_DATA = {
    # min, max, types, name, inner_sep_type, outer_sep_group,
    # outer_sep_placement
    ('0 to 1, alpha_utype, no separators',
     0, 1, (alpha_utype,), 'letter', None, None, None): {
        'invalid': ['aa', '0', ' '],
        'valid_parse': [
            ('a', ((alpha_utype, 'a'),)),
            ('', (None,))
        ]
    },
    ('1 to 1, alpha_utype, no separators',
     1, 1, (alpha_utype,), 'letter', None, None, None): {
        'invalid': [''],
        'valid_parse': []
    },
    ('1 to 1, alpha_utype, inner separator (pipe)',
     1, 1, (alpha_utype,), 'letter', pipe_utype, None, None): {
        'invalid': [''],
        'valid_parse': []
    },
    ('2 to 2, alpha_utype, no separators',
     2, 2, (alpha_utype,), 'letter', None, None, None): {
        'invalid': ['a', 'abc'],
        'valid_parse': [
            ('ab', ([(alpha_utype, 'a'), (alpha_utype, 'b')],)),
        ]
    },
    ('1 to N, alpha_utype, no separators',
     1, None, (alpha_utype,), 'letter', None, None, None): {
        'invalid': [],
        'valid_parse': [
            ('abc', ([(alpha_utype, 'a'), (alpha_utype, 'b'),
                      (alpha_utype, 'c')],)),
        ]
    },
    ('1 to 3, alpha_utype, inner separator (pipe)',
     1, 3, (alpha_utype,), 'letter', pipe_utype, None, None): {
        'invalid': ['', '|', 'a|', '|a', 'a|b|', 'a|b|c|d', 'abc'],
        'valid_parse': [
            ('a', ([(alpha_utype, 'a')],)),
            ('a|b', ([(alpha_utype, 'a'), (pipe_utype, '|'),
                      (alpha_utype, 'b')],)),
        ]
    },
    ('1 to 3, alpha_opt_utype, inner separator (pipe)',
     1, 3, (alpha_opt_utype,), 'letter', pipe_utype, None, None): {
        'invalid': ['|', 'a|', '|a', 'a|b|', 'a|b|c|d', 'abc'],
        'valid_parse': [
            ('a', ([(alpha_opt_utype, 'a')],)),
            ('a|b', ([(alpha_opt_utype, 'a'), (pipe_utype, '|'),
                      (alpha_opt_utype, 'b')],)),
        ]
    },
    ('1 to 3, alpha_utype or digit_utype, inner separator (pipe)',
     1, 3, (alpha_utype, digit_utype), 'chr', pipe_utype, None, None): {
        'invalid': ['0a1', '0|a|1|2'],
        'valid_parse': [
            ('0|1|a', ([(digit_utype, '0'), (pipe_utype, '|'),
                        (digit_utype, '1'), (pipe_utype, '|'),
                        (alpha_utype, 'a')],)),
        ]
    },
    ('1 to 3, alpha_utype or overlap_utype, inner separator (pipe)',
     1, 3, (alpha_utype, olap_utype), 'chr', pipe_utype, None, None): {
        'invalid': [],
        'valid_parse': [
            ('a|b', ([(alpha_utype, 'a'), (pipe_utype, '|'),
                      (alpha_utype, 'b')],)),
            ('a|t|z', ([(alpha_utype, 'a'), (pipe_utype, '|'),
                        (alpha_utype, 't'), (pipe_utype, '|'),
                        (alpha_utype, 'z')],)),
        ]
    },
    ('1 to 3, overlap_utype or alpha_utype, inner separator (pipe)',
     1, 3, (olap_utype, alpha_utype), 'chr', pipe_utype, None, None): {
        'invalid': [],
        'valid_parse': [
            ('a|b', ([(alpha_utype, 'a'), (pipe_utype, '|'),
                      (alpha_utype, 'b')],)),
            ('a|t|z', ([(alpha_utype, 'a'), (pipe_utype, '|'),
                        (olap_utype, 't'), (pipe_utype, '|'),
                        (olap_utype, 'z')],)),
        ]
    },
    ('0 to 1, alpha_utype, required outer separator (pipe) before',
     0, 1, (alpha_utype,), 'letter', None, outer_sep_gr, 'before'): {
        'invalid': [],
        'valid_parse': [
            ('|a', ((pipe_utype, '|'), (alpha_utype, 'a'))),
        ]
    },
    ('0 to 1, alpha_utype, required outer separator (pipe) after',
     0, 1, (alpha_utype,), 'letter', None, outer_sep_gr, 'after'): {
        'invalid': [],
        'valid_parse': [
            ('a|', ((alpha_utype, 'a'), (pipe_utype, '|'))),
        ]
    },
    ('0 to 1, alpha_utype, optional outer separator (pipe) before',
     0, 1, (alpha_utype,), 'letter', None, outer_sep_gr_opt, 'before'): {
        'invalid': [],
        'valid_parse': [
            ('', (None, None)),
            ('a', (None, (alpha_utype, 'a'))),
        ]
    },
    ('0 to 1, alpha_utype, optional outer separator (pipe) after',
     0, 1, (alpha_utype,), 'letter', None, outer_sep_gr_opt, 'after'): {
        'invalid': [],
        'valid_parse': [
            ('a', ((alpha_utype, 'a'), None)),
        ]
    },
    (('0 to 2, alpha_utype, inner separator (dot) and required outer '
      'separator (pipe) before'),
     0, 2, (alpha_utype,), 'letter', dot_utype, outer_sep_gr, 'before'): {
        'invalid': [],
        'valid_parse': [
            ('|a.a', ((pipe_utype, '|'), [(alpha_utype, 'a'), (dot_utype, '.'),
                                          (alpha_utype, 'a')])),
        ]
    },
}

GROUPING_INVALID_PARAMS = generate_params(GROUPING_DATA, 'invalid')
GROUPING_VALID_PARSE_PARAMS = generate_params(GROUPING_DATA, 'valid_parse')


# Tests

def test_grouping_init_fails_without_types():
    """Trying to initialize a Grouping object without specifying the
    ``types`` kwarg raises a SettingsError.
    """
    with pytest.raises(e.SettingsError):
        t.Grouping(name='one')


def test_grouping_init_fails_without_name():
    """Trying to initialize a Grouping object without specifying the
    ``name`` kwarg raises a SettingsError.
    """
    with pytest.raises(e.SettingsError):
        t.Grouping(types=[alpha_utype])


def test_grouping_is_optional():
    """If the min value of the grouping is 0, then accessing the
    ``is_optional`` property should return True.
    """
    grouping = t.Grouping(name='one', types=[alpha_utype], min=0)
    assert grouping.is_optional


def test_grouping_is_not_optional():
    """If the min value of the grouping is >0, then accessing the
    ``is_optional`` property should return False.
    """
    grouping = t.Grouping(name='one', types=[alpha_utype], min=1)
    assert not grouping.is_optional


@pytest.mark.parametrize('params, tstr, expected', GROUPING_VALID_PARSE_PARAMS)
def test_grouping_cnstr_to_units(params, tstr, expected):
    """Calling the ``cnstr_to_units`` method of a Grouping created
    using the given params on the provided test string (tstr) should
    produce the list of expected results. Each value of ``expected``
    should be a tuple, where the first value is a Unit type, and the
    second value is the portion of the test string that goes with that
    Unit type.
    """
    grouping = make_grouping(*params[1:])
    split = grouping.cnstr_to_units(tstr, {})
    for i, test_part in enumerate(split):
        expected_part = expected[i]
        if test_part is None:
            assert expected_part is None
        elif isinstance(test_part, list):
            assert isinstance(expected_part, list)
            for j, test_sub_part in enumerate(test_part):
                ex_type, ex_value = expected_part[j]
                assert type(test_sub_part) == ex_type
                assert str(test_sub_part) == ex_value
        else:
            ex_type, ex_value = expected_part
            assert type(test_part) == ex_type
            assert str(test_part) == ex_value


@pytest.mark.parametrize('params, tstr', GROUPING_INVALID_PARAMS)
def test_grouping_cnstr_to_units_error(params, tstr):
    """Calling the ``cnstr_to_units`` method of a Grouping created
    using the given params on the provided test string (tstr) should
    either raise an InvalidCallNumberStringError, assuming the test
    string matches the pattern produced by the ``get_full_regex``
    method.
    """
    grouping = make_grouping(*params[1:])
    p = r'^{}$'.format(grouping.get_full_regex().pattern)
    if re.search(p, tstr):
        with pytest.raises(e.InvalidCallNumberStringError):
            grouping.cnstr_to_units(tstr, {})


# CompoundTemplate ****************************************************

# Fixtures, factories, and test data
#
# Unit Types
# (utypes matching exactly 1 character)
alpha_utype = make_simple_unit_type(1, 1, r'[A-Za-z]', 'Alpha')
digit_utype = make_simple_unit_type(1, 1, r'[0-9]', 'Digit')
pipe_utype = make_simple_unit_type(1, 1, r'\|', 'Pipe')
dot_utype = make_simple_unit_type(1, 1, r'\.', 'Dot')

# (opt utypes, matching 0 or 1 characters)
alpha_opt_utype = make_simple_unit_type(0, 1, r'[A-Za-z]', 'AlphaOpt')
digit_opt_utype = make_simple_unit_type(0, 1, r'[0-9]', 'DigitOpt')
pipe_opt_utype = make_simple_unit_type(0, 1, r'\|', 'PipeOpt')
dot_opt_utype = make_simple_unit_type(0, 1, r'\.', 'DotOpt')

# (multi utypes, matching 1 or 2 characters)
alpha_multi_utype = make_simple_unit_type(1, 2, r'[A-Za-z]', 'AlphaMulti')
digit_multi_utype = make_simple_unit_type(1, 2, r'[0-9]', 'DigitMulti')
pipe_multi_utype = make_simple_unit_type(1, 2, r'\|', 'PipeMulti')
dot_multi_utype = make_simple_unit_type(1, 2, r'\.', 'DotMulti')

# (max utypes, matching 1 or more characters)
alpha_max_utype = make_simple_unit_type(1, None, r'[A-Za-z]', 'AlphaMax')

# Grouping Parameters, or gp
# (gps matching exactly 1 grouping)
alpha_gp = (1, 1, 'letter1', (alpha_utype,))
beta_gp = (1, 1, 'letter2', (alpha_utype,))
digit_gp = (1, 1, 'digit', (digit_utype,))
sep_pipe_gp = (1, 1, 'sep_pipe', (pipe_utype,), True)
sep_dot_gp = (1, 1, 'sep_dot', (dot_utype,), True)
mixed_gp = (1, 1, 'mixed', (alpha_utype, digit_utype))

# (opt gps, matching 0 or 1 groupings)
alpha_gp_opt = (0, 1, 'letter1', (alpha_utype,))
alpha_opt_gp = (1, 1, 'letter1', (alpha_opt_utype,))
alpha_opt_gp_opt = (0, 1, 'letter1', (alpha_opt_utype,))
digit_gp_opt = (0, 1, 'digit', (digit_utype,))
digit_opt_gp = (1, 1, 'digit', (digit_opt_utype,))
digit_opt_gp_opt = (0, 1, 'digit', (digit_opt_utype,))
sep_pipe_gp_opt = (0, 1, 'sep_pipe', (pipe_utype,), True)
sep_pipe_opt_gp = (1, 1, 'sep_pipe', (pipe_opt_utype,), True)
sep_pipe_opt_gp_opt = (0, 1, 'sep_pipe', (pipe_opt_utype,), True)
mixed_gp_opt = (0, 1, 'mixed', (alpha_utype, digit_utype))
mixed_opt_gp = (1, 1, 'mixed', (alpha_opt_utype, digit_opt_utype))
mixed_mixed_opt_gp1 = (1, 1, 'mixed', (alpha_opt_utype, digit_utype))
mixed_mixed_opt_gp2 = (1, 1, 'mixed', (alpha_utype, digit_opt_utype))
mixed_opt_gp_opt = (0, 1, 'mixed', (alpha_opt_utype, digit_opt_utype))
mixed_mixed_opt_gp_opt1 = (0, 1, 'mixed', (alpha_opt_utype, digit_utype))
mixed_mixed_opt_gp_opt2 = (0, 1, 'mixed', (alpha_utype, digit_opt_utype))

# (multi gps, matching 1 or 2 groupings)
alpha_gp_multi = (1, 2, 'letter1', (alpha_utype,))
alpha_gp_multi_sep = (1, 2, 'letter1', (alpha_utype,), False, dot_utype)
alpha_gp_multi_sep_opt = (1, 2, 'letter1', (alpha_utype,), False,
                          dot_opt_utype)
alpha_gp_multi_sep_multi = (1, 2, 'letter1', (alpha_utype,), False,
                            dot_multi_utype)
alpha_multi_gp = (1, 1, 'letter1', (alpha_multi_utype,))
alpha_multi_gp_multi = (1, 2, 'letter1', (alpha_multi_utype,))

# (optmulti_gps, matching 0 or 2 groupings)
alpha_gp_optmulti_sep = (0, 2, 'letter1', (alpha_utype,), False, dot_utype)
alpha_gp_optmulti_sep_opt = (0, 2, 'letter1', (alpha_utype,), False,
                             dot_opt_utype)

# (max gps, matching 1 or more groupings)
alpha_gp_max = (1, None, 'letter1', (alpha_utype,))
alpha_gp_max_sep = (1, None, 'letter1', (alpha_utype,), False, dot_utype)
alpha_max_gp = (1, 1, 'letter1', (alpha_max_utype,))
alpha_max_gp_max = (1, None, 'letter1', (alpha_max_utype,))
alpha_max_gp_max_sep = (1, None, 'letter1', (alpha_max_utype,), False,
                        dot_utype)
alpha_max_gp_max_sep_opt = (1, None, 'letter1', (alpha_max_utype,), False,
                            dot_opt_utype)


def gp_tuple_to_dict(gp_tuple):
    """Convert a groupings parameters (gp) tuple into a dict suitable
    to pass to the ``grouping_parameters`` CompoundTemplate.__init__
    kwarg.
    """
    params = [{'min': 1}, {'max': 1}, {'name': None}, {'possible_types': None},
              {'is_separator': False}, {'inner_sep_type': None}]
    d = {}
    for i, param in enumerate(params):
        if i < len(gp_tuple):
            d[list(param.keys())[0]] = gp_tuple[i]
        else:
            d[list(param.keys())[0]] = list(param.values())[0]
    return d


COMPOUND_TEMPLATE_DATA = {
    # dict key tuple format is:
    # (test description, separator_type, grouping_parameters)
    #
    # GENERAL tests with single groups that match one unit type
    ('No separators, exact character match groupings',
     None, (alpha_gp, beta_gp)): {
        'valid': ['aa', 'AA', 'aA', 'Aa', 'az', 'za'],
        'invalid': ['', 'aaa', 'a', 'aa1', 'a1', '1a', 'a ', 'a a', ' a'],
        'parts': [
            ('az', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'z')])
        ]
    },
    ('Default separator, exact character match groupings',
     pipe_utype, (alpha_gp, beta_gp)): {
        'valid': ['a|a'],
        'invalid': ['aa', 'a.a', 'a a', '|a', 'a|'],
        'parts': [
            ('a|z', [(alpha_utype, 'letter1', 'a'), (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'z')])
        ]
    },
    ('Explicit separator, exact character match groupings',
     None, (alpha_gp, sep_dot_gp, beta_gp)): {
        'valid': ['a.a'],
        'invalid': ['aa', 'a|a', 'a a'],
        'parts': [
            ('a.z', [(alpha_utype, 'letter1', 'a'),
                     (dot_utype, 'sep_dot', '.'),
                     (alpha_utype, 'letter2', 'z')])
        ]
    },
    ('Default and explicit separators, exact character match groupings',
     pipe_utype, (alpha_gp, sep_dot_gp, beta_gp, digit_gp)): {
        'valid': ['a.a|1'],
        'invalid': ['aa1', 'a.a1', 'aa|1', 'a a 1', 'a.a.1', 'a|a|1', 'a|.a|1',
                    'a.|a|1'],
        'parts': [
            ('a.z|1', [(alpha_utype, 'letter1', 'a'),
                       (dot_utype, 'sep_dot', '.'),
                       (alpha_utype, 'letter2', 'z'), (pipe_utype, 0, '|'),
                       (digit_utype, 'digit', '1')])
        ]
    },

    # OPTIONAL Groupings and Unit Types, no separator
    ('No separators, first grouping is optional',
     None, (alpha_gp_opt, beta_gp)): {
        'valid': ['a', 'aa'],
        'invalid': ['', '1a', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, first grouping uses optional utype',
     None, (alpha_opt_gp, beta_gp)): {
        'valid': ['a', 'aa'],
        'invalid': ['', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, first grouping is optional and uses optional utype',
     None, (alpha_opt_gp_opt, beta_gp)): {
        'valid': ['a', 'aa'],
        'invalid': ['', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, last grouping is optional',
     None, (beta_gp, alpha_gp_opt)): {
        'valid': ['a', 'aa'],
        'invalid': ['', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_utype, 'letter1', 'a')])
        ]
    },
    ('No separators, last grouping uses optional utype',
     None, (beta_gp, alpha_opt_gp)): {
        'valid': ['a', 'aa'],
        'invalid': ['', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_opt_utype, 'letter1', 'a')])
        ]
    },
    ('No separators, last grouping is optional and uses optional utype',
     None, (beta_gp, alpha_opt_gp_opt)): {
        'valid': ['a', 'aa'],
        'invalid': ['', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_opt_utype, 'letter1', 'a')])
        ]
    },

    # OPTIONAL Groupings and Unit Types, explicit separator
    ('Explicit optional separator grouping',
     None, (alpha_gp, sep_pipe_gp_opt, beta_gp)): {
        'valid': ['aa', 'a|a'],
        'invalid': ['', 'a', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 'sep_pipe', '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Explicit separator grouping that uses optional utype',
     None, (alpha_gp, sep_pipe_opt_gp, beta_gp)): {
        'valid': ['aa', 'a|a'],
        'invalid': ['', 'a', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 'sep_pipe', '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Explicit optional separator grouping that uses optional utype',
     None, (alpha_gp, sep_pipe_opt_gp_opt, beta_gp)): {
        'valid': ['aa', 'a|a'],
        'invalid': ['', 'a', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 'sep_pipe', '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },

    ('Different explicit separators surrounding an optional grouping',
     None, (beta_gp, sep_dot_gp, alpha_gp_opt, sep_pipe_gp, digit_gp)): {
        'valid': ['a.a|1', 'a|1'],
        'invalid': ['a.1', 'a.|1'],
        'parts': [
            ('a.a|1', [(alpha_utype, 'letter2', 'a'),
                       (dot_utype, 'sep_dot', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 'sep_pipe', '|'),
                       (digit_utype, 'digit', '1')]),
            ('a|1', [(alpha_utype, 'letter2', 'a'),
                     (pipe_utype, 'sep_pipe', '|'),
                     (digit_utype, 'digit', '1')])
        ]
    },

    # OPTIONAL Groupings and Unit Types, default optional separator
    ('Default optional separator, exact character match groupings',
     pipe_opt_utype, (alpha_gp, beta_gp)): {
        'valid': ['aa', 'a|a'],
        'invalid': ['', 'a', '|', 'a|', '|a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Default optional separator, first grouping is optional',
     pipe_opt_utype, (alpha_gp_opt, beta_gp)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Default optional separator, first grouping uses optional utype',
     pipe_opt_utype, (alpha_opt_gp, beta_gp)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_opt_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default optional separator, first grouping is optional and uses '
      'optional utype'),
     # NOTE Here: '|a' is invalid because the first grouping is
     # optional; it uses min=0. A blank value means there is no first
     # grouping, and so the separator (pipe) is not allowed, as it
     # makes no sense to have a separator with a single valid
     # grouping.
     pipe_opt_utype, (alpha_opt_gp_opt, beta_gp)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', 'a|', '|a', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_opt_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Default optional separator, last grouping is optional',
     pipe_opt_utype, (beta_gp, alpha_gp_opt)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', 'a|', '|a', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_utype, 'letter1', 'a')]),
            ('a|a', [(alpha_utype, 'letter2', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter1', 'a')])
        ]
    },
    ('Default optional separator, last grouping uses optional utype',
     pipe_opt_utype, (beta_gp, alpha_opt_gp)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', '|a', 'a|', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_opt_utype, 'letter1', 'a')]),
            ('a|a', [(alpha_utype, 'letter2', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_opt_utype, 'letter1', 'a')])
        ]
    },
    (('Default optional separator, last grouping is optional and uses '
      'optional utype'),
     pipe_opt_utype, (beta_gp, alpha_opt_gp_opt)): {
        'valid': ['a', 'aa', 'a|a'],
        'invalid': ['', '|', 'a|', '|a', 'aaa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_opt_utype, 'letter1', 'a')]),
            ('a|a', [(alpha_utype, 'letter2', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_opt_utype, 'letter1', 'a')])
        ]
    },
    (('Default optional separator, both groupings are optional and use '
      'optional utype'),
     pipe_opt_utype, (alpha_opt_gp_opt, digit_opt_gp_opt)): {
        'valid': ['', 'a', '1', 'a1', 'a|1'],
        'invalid': ['|', 'a|', '|1'],
        'parts': [
            ('a', [(alpha_opt_utype, 'letter1', 'a')]),
            ('1', [(digit_opt_utype, 'digit', '1')]),
            ('a1', [(alpha_opt_utype, 'letter1', 'a'),
                    (digit_opt_utype, 'digit', '1')]),
            ('a|1', [(alpha_opt_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (digit_opt_utype, 'digit', '1')])
        ]
    },

    # OPTIONAL Groupings and Unit Types, default required separator
    ('Default required separator, first grouping is optional',
     pipe_utype, (alpha_gp_opt, beta_gp)): {
        'valid': ['a', 'a|a'],
        'invalid': ['', 'aa', 'aaa', '|', 'a|', '|a'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Default required separator, first grouping uses optional utype',
     pipe_utype, (alpha_opt_gp, beta_gp)): {
        'valid': ['a|a'],
        'invalid': ['', 'a', 'aa', 'aaa', '|', 'a|', '|a'],
        'parts': [
            ('a|a', [(alpha_opt_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default required separator, first grouping is optional and uses '
      'optional utype'),
     pipe_utype, (alpha_opt_gp_opt, beta_gp)): {
        'valid': ['a', 'a|a'],
        'invalid': ['', 'aa', '|', 'aaa', 'a|', '|a'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_opt_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },


    # MULTIPLE Instances of a Unit Type and/or Grouping, no separators
    ('No separators, first grouping allows multiple',
     None, (alpha_gp_multi, beta_gp)): {
        'valid': ['aa', 'aaa'],
        'invalid': ['', 'a', 'aaaa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, first grouping uses multiple utype',
     None, (alpha_multi_gp, beta_gp)): {
        'valid': ['aa', 'aaa'],
        'invalid': ['', 'a', 'aaaa'],
        'parts': [
            ('aa', [(alpha_multi_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_multi_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, first grouping allows multiple and uses multiple utype',
     None, (alpha_multi_gp_multi, beta_gp)): {
        'valid': ['aa', 'aaa', 'aaaaa'],
        'invalid': ['', 'a', 'aaaaaa'],
        'parts': [
            ('aa', [(alpha_multi_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_multi_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aaaa', [(alpha_multi_utype, 'letter1', 'aa'),
                      (alpha_multi_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaaaa', [(alpha_multi_utype, 'letter1', 'aa'),
                       (alpha_multi_utype, 'letter1', 'aa'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('No separators, last grouping allows multiple',
     None, (beta_gp, alpha_gp_multi)): {
        'valid': ['aa', 'aaa'],
        'invalid': ['', 'a', 'aaaa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_utype, 'letter1', 'a')]),
            ('aaa', [(alpha_utype, 'letter2', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a')])
        ]
    },
    ('No separators, last grouping uses multiple utype',
     None, (beta_gp, alpha_multi_gp)): {
        'valid': ['aa', 'aaa'],
        'invalid': ['', 'a', 'aaaa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_multi_utype, 'letter1', 'a')]),
            ('aaa', [(alpha_utype, 'letter2', 'a'),
                     (alpha_multi_utype, 'letter1', 'aa')])
        ]
    },
    ('No separators, last grouping allows multiple and uses multiple utype',
     None, (beta_gp, alpha_multi_gp_multi)): {
        'valid': ['aa', 'aaa', 'aaaaa'],
        'invalid': ['', 'a', 'aaaaaa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_multi_utype, 'letter1', 'a')]),
            ('aaa', [(alpha_utype, 'letter2', 'a'),
                     (alpha_multi_utype, 'letter1', 'aa')]),
            ('aaaa', [(alpha_utype, 'letter2', 'a'),
                      (alpha_multi_utype, 'letter1', 'aa'),
                      (alpha_multi_utype, 'letter1', 'a')]),
            ('aaaaa', [(alpha_utype, 'letter2', 'a'),
                       (alpha_multi_utype, 'letter1', 'aa'),
                       (alpha_multi_utype, 'letter1', 'aa')])
        ]
    },

    # MULTIPLE Unit Types/Groupings, inner separators only
    ('Inner required separator, first grouping allows multiple',
     None, (alpha_gp_multi_sep, beta_gp)): {
        'valid': ['aa', 'a.aa'],
        'invalid': ['', 'a', 'aaa', 'aa.a', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'a.a.aa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Inner optional separator, first grouping allows multiple',
     None, (alpha_gp_multi_sep_opt, beta_gp)): {
        'valid': ['aa', 'a.aa', 'aaa'],
        'invalid': ['', 'a', 'aaaa', 'aa.a', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'a.a.aa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_opt_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Inner multi-separator, first grouping allows multiple',
     None, (alpha_gp_multi_sep_multi, beta_gp)): {
        'valid': ['aa', 'a.aa', 'a..aa'],
        'invalid': ['', 'a', 'aaa', 'aa.a', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'a.a.aa', 'a...aa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_multi_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a..aa', [(alpha_utype, 'letter1', 'a'),
                       (dot_multi_utype, '', '..'),
                       (alpha_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Inner required separator, last grouping allows multiple',
     None, (beta_gp, alpha_gp_multi_sep)): {
        'valid': ['aa', 'aa.a'],
        'invalid': ['', 'a', 'aaa', 'a.aa', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'aa.a.a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_utype, 'letter1', 'a')]),
            ('aa.a', [(alpha_utype, 'letter2', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a')])
        ]
    },

    # MULTIPLE Unit Types/Groupings, outer separators only
    ('Default required separator, first grouping allows multiple',
     pipe_utype, (alpha_gp_multi, beta_gp)): {
        'valid': ['a|a', 'aa|a'],
        'invalid': ['', 'a', 'aaaa', 'a|', '|a', 'aaa|a', 'a|aa', 'a|a|a'],
        'parts': [
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (pipe_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')])
        ]
    },
    ('Default required separator, first grouping uses multiple utype',
     pipe_utype, (alpha_multi_gp, beta_gp)): {
        'valid': ['a|a', 'aa|a'],
        'invalid': ['', 'a', 'aaaa', 'a|', '|a', 'aaa|a', 'a|aa', 'a|a|a'],
        'parts': [
            ('a|a', [(alpha_multi_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_multi_utype, 'letter1', 'aa'),
                      (pipe_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default required separator, first grouping allows multiple and uses '
      'multiple utype'),
     pipe_utype, (alpha_multi_gp_multi, beta_gp)): {
        'valid': ['a|a', 'aa|a', 'aaa|a', 'aaaa|a'],
        'invalid': ['', 'a', 'aa', 'aaa', 'aaaa', 'aaaaa', 'aaaaaa', 'a|aa',
                    'aaaaa|a', 'a|a|a'],
        'parts': [
            ('a|a', [(alpha_multi_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_multi_utype, 'letter1', 'aa'),
                      (pipe_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaa|a', [(alpha_multi_utype, 'letter1', 'aa'),
                       (alpha_multi_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')]),
            ('aaaa|a', [(alpha_multi_utype, 'letter1', 'aa'),
                        (alpha_multi_utype, 'letter1', 'aa'),
                        (pipe_utype, 0, '|'),
                        (alpha_utype, 'letter2', 'a')])
        ]
    },

    # MULTIPLE Unit Types/Groupings, outer+inner separators
    (('Default required separator and inner required separator, first '
      'grouping allows multiple'),
     pipe_utype, (alpha_gp_multi_sep, beta_gp)): {
        'valid': ['a|a', 'a.a|a'],
        'invalid': ['', 'a', 'aa', 'a|', '|a', 'aa|a', 'a.|a', 'a.a.|a',
                    'a.a', 'a.a.a', 'aa.a', 'a.aa'],
        'parts': [
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default optional separator and inner required separator, first '
      'grouping allows multiple'),
     pipe_opt_utype, (alpha_gp_multi_sep, beta_gp)): {
        'valid': ['aa', 'a|a', 'a.aa', 'a.a|a'],
        'invalid': ['', 'a', 'a|', '|a', 'aa|a', 'a.|a', 'a.a.|a', 'a.a',
                    'a.a.a', 'aa.a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_opt_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default required separator and inner optional separator, first '
      'grouping allows multiple'),
     pipe_utype, (alpha_gp_multi_sep_opt, beta_gp)): {
        'valid': ['a|a', 'a.a|a', 'aa|a'],
        'invalid': ['', 'a', 'aa', 'a|', '|a', 'a.|a', 'a.a.|a', 'a.a',
                    'a.a.a', 'aa.a', 'a.aa'],
        'parts': [
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_opt_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (pipe_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Default optional separator and inner optional separator, first '
      'grouping allows multiple'),
     pipe_opt_utype, (alpha_gp_multi_sep_opt, beta_gp)): {
        'valid': ['aa', 'aaa', 'a.aa', 'a|a', 'aa|a', 'a.a|a'],
        'invalid': ['', 'a', 'aaaa', 'a|', '|a', 'a.|a', 'a.a.|a', 'a.a',
                    'a.a.a', 'aa.a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_opt_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (pipe_opt_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_opt_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_opt_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },

    # OPTIONAL/MULTIPLE Unit Types/Groupings, inner separators only
    (('Inner required separator, first grouping is optional and allows '
      'multiple'),
     None, (alpha_gp_optmulti_sep, beta_gp)): {
        'valid': ['a', 'aa', 'a.aa'],
        'invalid': ['', 'aaa', 'aa.a', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'a.a.aa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')])
        ]
    },
    (('Inner optional separator, first grouping is optional and allows '
      'multiple'),
     None, (alpha_gp_optmulti_sep_opt, beta_gp)): {
        'valid': ['a', 'aa', 'a.aa', 'aaa'],
        'invalid': ['', 'aa.a', 'a.a', 'a.', 'a.a.', 'a.aa.',
                    '.a', '.a.a', 'a.a.aa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_opt_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')])
        ]
    },

    # OPTIONAL/MULTIPLE Unit Types/Groupings, outer+inner separators
    (('Default required separator and inner required separator, first '
      'grouping is optional and allows multiple'),
     pipe_utype, (alpha_gp_optmulti_sep, beta_gp)): {
        'valid': ['a', 'a|a', 'a.a|a'],
        'invalid': ['', '.', '.|', '.|a' 'aa', 'a|', '|a', 'aa|a', 'a.|a',
                    'a.a.|a', 'a.a', 'a.a.a', 'aa.a', 'a.aa'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')])
        ]
    },

    # OPTIONAL/MIXED Unit Types/Groupings
    (('No separators; first grouping is optional and has mixed Unit types'),
     None, (mixed_gp_opt, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping has mixed Unit types, both types are '
      'optional'),
     None, (mixed_opt_gp, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_opt_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping has mixed Unit types, first type is '
      'optional'),
     None, (mixed_mixed_opt_gp1, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping has mixed Unit types, second type is '
      'optional'),
     None, (mixed_mixed_opt_gp2, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_opt_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping is optional and has mixed Unit types, '
      'both types are optional'),
     None, (mixed_opt_gp_opt, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_opt_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping is optional and has mixed Unit types, '
      'first type is optional'),
     None, (mixed_mixed_opt_gp_opt1, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_opt_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; first grouping is optional and has mixed Unit types, '
      'second type is optional'),
     None, (mixed_mixed_opt_gp_opt2, beta_gp)): {
        'valid': ['a', 'aa', '1a'],
        'invalid': ['', '1', 'a1'],
        'parts': [
            ('a', [(alpha_utype, 'letter2', 'a')]),
            ('aa', [(alpha_utype, 'mixed', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('1a', [(digit_opt_utype, 'mixed', '1'),
                    (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators; second grouping is optional and has mixed Unit types'),
     None, (alpha_gp, mixed_gp_opt)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping has mixed Unit types, both types are '
      'optional'),
     None, (alpha_gp, mixed_opt_gp)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_opt_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_opt_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping has mixed Unit types, first type is '
      'optional'),
     None, (alpha_gp, mixed_mixed_opt_gp1)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_opt_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping has mixed Unit types, second type is '
      'optional'),
     None, (alpha_gp, mixed_mixed_opt_gp2)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_opt_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping is optional and has mixed Unit types, '
      'both types are optional'),
     None, (alpha_gp, mixed_opt_gp_opt)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_opt_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_opt_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping is optional and has mixed Unit types, '
      'first type is optional'),
     None, (alpha_gp, mixed_mixed_opt_gp_opt1)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_opt_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_utype, 'mixed', '1')]),
        ]
    },
    (('No separators; second grouping is optional and has mixed Unit types, '
      'second type is optional'),
     None, (alpha_gp, mixed_mixed_opt_gp_opt2)): {
        'valid': ['a', 'aa', 'a1'],
        'invalid': ['', '1', '1a'],
        'parts': [
            ('a', [(alpha_utype, 'letter1', 'a')]),
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'mixed', 'a')]),
            ('a1', [(alpha_utype, 'letter1', 'a'),
                    (digit_opt_utype, 'mixed', '1')]),
        ]
    },

    # MAX Unit Types/Groupings
    ('No separators, first grouping has no maximum limit',
     None, (alpha_gp_max, beta_gp)): {
        'valid': ['aa', 'aaa', 'aaaa'],
        'invalid': ['', 'a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aaaa', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
        ]
    },
    ('No separators, first grouping has utype with no maximum limit',
     None, (alpha_max_gp, beta_gp)): {
        'valid': ['aa', 'aaa', 'aaaa'],
        'invalid': ['', 'a'],
        'parts': [
            ('aa', [(alpha_max_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_max_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aaaa', [(alpha_max_utype, 'letter1', 'aaa'),
                      (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('No separators, first grouping has no maximum limit and a utype with no '
      'maximum limit'),
     None, (alpha_max_gp_max, beta_gp)): {
        'valid': ['aa', 'aaa', 'aaaa'],
        'invalid': ['', 'a'],
        'parts': [
            ('aa', [(alpha_max_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_max_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aaaa', [(alpha_max_utype, 'letter1', 'aaa'),
                      (alpha_utype, 'letter2', 'a')]),
        ]
    },
    ('No separators, last grouping has no maximum limit',
     None, (beta_gp, alpha_gp_max)): {
        'valid': ['aa', 'aaa', 'aaaa'],
        'invalid': ['', 'a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter2', 'a'),
                    (alpha_utype, 'letter1', 'a')]),
            ('aaa', [(alpha_utype, 'letter2', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a')]),
            ('aaaa', [(alpha_utype, 'letter2', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a')]),
        ]
    },

    ('Inner required separator, first grouping has no maximum limit',
     None, (alpha_gp_max_sep, beta_gp)): {
        'valid': ['aa', 'a.aa', 'a.a.aa'],
        'invalid': ['', 'a', 'aaa', 'aa.aa'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a.a.aa', [(alpha_utype, 'letter1', 'a'),
                        (dot_utype, '', '.'),
                        (alpha_utype, 'letter1', 'a'),
                        (dot_utype, '', '.'),
                        (alpha_utype, 'letter1', 'a'),
                        (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('Inner required separator, first grouping has no maximum limit and a '
      'utype with no maximum limit'),
     None, (alpha_max_gp_max_sep, beta_gp)): {
        'valid': ['aa', 'aaa', 'a.aa', 'aa.aa', 'aa.aaa', 'a.a.aa',
                  'aa.aaaa.aaa'],
        'invalid': ['', 'a', 'a.a.a'],
        'parts': [
            ('aa', [(alpha_max_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_max_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_max_utype, 'letter1', 'a'),
                      (dot_utype, '', '.'),
                      (alpha_max_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aa.aa', [(alpha_max_utype, 'letter1', 'aa'),
                       (dot_utype, '', '.'),
                       (alpha_max_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter2', 'a')]),
            ('aa.aaa', [(alpha_max_utype, 'letter1', 'aa'),
                        (dot_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'aa'),
                        (alpha_utype, 'letter2', 'a')]),
            ('a.a.aa', [(alpha_max_utype, 'letter1', 'a'),
                        (dot_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'a'),
                        (dot_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'a'),
                        (alpha_utype, 'letter2', 'a')]),
            ('aa.aaaa.aa', [(alpha_max_utype, 'letter1', 'aa'),
                            (dot_utype, '', '.'),
                            (alpha_max_utype, 'letter1', 'aaaa'),
                            (dot_utype, '', '.'),
                            (alpha_max_utype, 'letter1', 'a'),
                            (alpha_utype, 'letter2', 'a')]),
        ]
    },
    (('Inner optional separator, first grouping has no maximum limit and a '
      'utype with no maximum limit'),
     None, (alpha_max_gp_max_sep_opt, beta_gp)): {
        'valid': ['aa', 'aaa', 'a.aa', 'aa.aa', 'aa.aaa', 'a.a.aa',
                  'aa.aaaa.aaa'],
        'invalid': ['', 'a', 'a.a.a'],
        'parts': [
            ('aa', [(alpha_max_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_max_utype, 'letter1', 'aa'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.aa', [(alpha_max_utype, 'letter1', 'a'),
                      (dot_opt_utype, '', '.'),
                      (alpha_max_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aa.aa', [(alpha_max_utype, 'letter1', 'aa'),
                       (dot_opt_utype, '', '.'),
                       (alpha_max_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter2', 'a')]),
            ('aa.aaa', [(alpha_max_utype, 'letter1', 'aa'),
                        (dot_opt_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'aa'),
                        (alpha_utype, 'letter2', 'a')]),
            ('a.a.aa', [(alpha_max_utype, 'letter1', 'a'),
                        (dot_opt_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'a'),
                        (dot_opt_utype, '', '.'),
                        (alpha_max_utype, 'letter1', 'a'),
                        (alpha_utype, 'letter2', 'a')]),
            ('aa.aaaa.aa', [(alpha_max_utype, 'letter1', 'aa'),
                            (dot_opt_utype, '', '.'),
                            (alpha_max_utype, 'letter1', 'aaaa'),
                            (dot_opt_utype, '', '.'),
                            (alpha_max_utype, 'letter1', 'a'),
                            (alpha_utype, 'letter2', 'a')]),
        ]
    },

    # MAX Unit Types/Groupings, outer separators only
    ('Default required separator, first grouping has no maximum limit',
     pipe_utype, (alpha_gp_max, beta_gp)): {
        'valid': ['a|a', 'aa|a', 'aaa|a'],
        'invalid': ['', 'a', '|', 'a|aa', 'a|a|a'],
        'parts': [
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (pipe_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaa|a', [(alpha_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')]),
        ]
    },
    ('Default optional separator, first grouping has no maximum limit',
     pipe_opt_utype, (alpha_gp_max, beta_gp)): {
        'valid': ['aa', 'aaa', 'aaaa', 'a|a', 'aa|a', 'aaa|a'],
        'invalid': ['', 'a', '|', 'a|aa', 'a|a|a'],
        'parts': [
            ('aa', [(alpha_utype, 'letter1', 'a'),
                    (alpha_utype, 'letter2', 'a')]),
            ('aaa', [(alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter1', 'a'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aaaa', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter2', 'a')]),
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_opt_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('aa|a', [(alpha_utype, 'letter1', 'a'),
                      (alpha_utype, 'letter1', 'a'),
                      (pipe_opt_utype, 0, '|'),
                      (alpha_utype, 'letter2', 'a')]),
            ('aaa|a', [(alpha_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter1', 'a'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_opt_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')]),
        ]
    },

    # MAX Unit Types/Groupings, inner+outer separators
    (('Default required separator and inner required separator, first '
      'grouping has no maximum limit'),
     pipe_utype, (alpha_gp_max_sep, beta_gp)): {
        'valid': ['a|a', 'a.a|a', 'a.a.a|a'],
        'invalid': ['', 'a', 'aa', 'aa|a', 'a|aa'],
        'parts': [
            ('a|a', [(alpha_utype, 'letter1', 'a'),
                     (pipe_utype, 0, '|'),
                     (alpha_utype, 'letter2', 'a')]),
            ('a.a|a', [(alpha_utype, 'letter1', 'a'),
                       (dot_utype, '', '.'),
                       (alpha_utype, 'letter1', 'a'),
                       (pipe_utype, 0, '|'),
                       (alpha_utype, 'letter2', 'a')]),
            ('a.a.a|a', [(alpha_utype, 'letter1', 'a'),
                         (dot_utype, '', '.'),
                         (alpha_utype, 'letter1', 'a'),
                         (dot_utype, '', '.'),
                         (alpha_utype, 'letter1', 'a'),
                         (pipe_utype, 0, '|'),
                         (alpha_utype, 'letter2', 'a')]),
        ]
    },
}

COMPOUND_VALID_PARAMS = generate_params(COMPOUND_TEMPLATE_DATA, 'valid')
COMPOUND_INVALID_PARAMS = generate_params(COMPOUND_TEMPLATE_DATA, 'invalid')
COMPOUND_PARTS_PARAMS = generate_params(COMPOUND_TEMPLATE_DATA, 'parts')


# Tests

@pytest.mark.parametrize('separator_type, gr_params, fails', [
    (None, [alpha_gp], False),
    (pipe_utype, [alpha_gp], False),
    (pipe_utype, [alpha_gp, beta_gp], False),
    (pipe_utype, [alpha_gp, sep_pipe_gp, beta_gp], False),
    (None, [alpha_gp, alpha_gp], '``name``.* must be unique'),
    (pipe_utype, None, 'groups.* must be a list'),
    ('a', [alpha_gp], 'separator_type.* must be None or a Unit'),
    (None, [sep_pipe_gp, alpha_gp], 'cannot begin or end with a separator'),
    (None, [alpha_gp, sep_pipe_gp], 'cannot begin or end with a separator'),
    (None, [alpha_gp, sep_pipe_gp, sep_dot_gp, beta_gp],
     'cannot have multiple separator groupings in a row')
])
def test_compound_template_initialization(separator_type, gr_params, fails):
    """Passing the given ``separator_type`` and ``groupings`` as kwarg
    parameters to initialize a CompoundTemplate object should cause it
    to pass (if ``fails`` is False) or raise a SettingsError that
    contains the regex given in ``fails``.
    """
    if gr_params is not None:
        gr_params = [gp_tuple_to_dict(gp_tuple) for gp_tuple in gr_params]
    if fails:
        with pytest.raises(e.SettingsError) as excinfo:
            t.CompoundTemplate(separator_type=separator_type, groups=gr_params)
        assert excinfo.match(fails)
    else:
        assert t.CompoundTemplate(separator_type=separator_type,
                                  groups=gr_params)


@pytest.mark.parametrize('params, tstr', COMPOUND_VALID_PARAMS)
def test_compound_template_validate_true(params, tstr):
    """Given a set of parameters for initializing a CompoundTemplate
    and a test string, the test string should return True when passed
    to the CompoundTemplate object's ``validate`` method.
    """
    separator_type = params[1]
    gr_params = [gp_tuple_to_dict(gp_tuple) for gp_tuple in params[2]]
    template = t.CompoundTemplate(separator_type=separator_type,
                                  groups=gr_params)
    assert bool(template.validate(tstr)) is True


@pytest.mark.parametrize('params, tstr', COMPOUND_INVALID_PARAMS)
def test_compound_template_validate_error(params, tstr):
    """Given a set of paremeters for initializing a CompoundTemplate
    and a test string, the test string should raise an
    InvalidCallNumberStringError when passed to the CompoundTemplate
    object's ``validate`` method.
    """
    separator_type = params[1]
    gr_params = [gp_tuple_to_dict(gp_tuple) for gp_tuple in params[2]]
    template = t.CompoundTemplate(separator_type=separator_type,
                                  groups=gr_params)
    with pytest.raises(e.InvalidCallNumberStringError):
        template.validate(tstr)


@pytest.mark.parametrize('params, tstr, expected_parts', COMPOUND_PARTS_PARAMS)
def test_compound_template_parts(params, tstr, expected_parts):
    """Given a set of parameters for initializing a CompoundTemplate,
    a test string, and a list of expected parts, passing the test
    string to the CompoundTemplate object's ``cnstr_to_parts`` method
    should result in the expected parts list.
    """
    separator_type = params[1]
    gr_params = [gp_tuple_to_dict(gp_tuple) for gp_tuple in params[2]]
    template = t.CompoundTemplate(separator_type=separator_type,
                                  groups=gr_params)
    results = template.cnstr_to_parts(tstr, {})
    flattened_results = []
    for result in results:
        if isinstance(result, list):
            flattened_results.extend(result)
        elif result is not None:
            flattened_results.append(result)

    for i, expected in enumerate(expected_parts):
        expected_type, expected_name, expected_value = expected
        if isinstance(expected_name, int):
            default_name = t.CompoundTemplate.default_separator_name
            expected_name = '{}{}'.format(default_name, expected_name)
        assert type(flattened_results[i]) == expected_type
        assert flattened_results[i].name == expected_name
        assert str(flattened_results[i]) == expected_value
