"""Base Compiler class used by all codecs.

"""

import binascii
import sys
from operator import attrgetter
import bitstruct

from copy import copy
from copy import deepcopy
from ..errors import CompileError
from ..parser import EXTENSION_MARKER


def flatten(dlist):
    flist = []

    for item in dlist:
        if isinstance(item, list):
            flist.extend(item)
        else:
            flist.append(item)

    return flist


def is_object_class_type_name(type_name):
    return '&' in type_name


def is_open_type(type_name):
    index = type_name.find('&')

    if index == -1:
        return False

    return type_name[index + 1].isupper()


def is_type_name(type_name):
    """Does not handle keywords.

    """

    return type_name[0].isupper()


def lowest_set_bit(value):
    offset = (value & -value).bit_length() - 1

    if offset < 0:
        offset = 0

    return offset


def rstrip_bit_string_zeros(data):
    data = data.rstrip(b'\x00')

    if len(data) == 0:
        number_of_bits = 0
    else:
        number_of_bits = 8 * len(data) - lowest_set_bit(data[-1])

    return (data, number_of_bits)


def clean_bit_string_value(value, has_named_bits):
    data = bytearray(value[0])
    number_of_bits = value[1]
    number_of_bytes, number_of_rest_bits = divmod(number_of_bits, 8)

    if number_of_rest_bits == 0:
        data = data[:number_of_bytes]
    else:
        data = data[:number_of_bytes + 1]
        data[number_of_bytes] &= ((0xff >> number_of_rest_bits) ^ 0xff)

    if has_named_bits:
        return rstrip_bit_string_zeros(data)
    else:
        return (data, number_of_bits)


class CompiledType(object):

    def __init__(self, type_):
        self.constraints_checker = None
        self.type_checker = None
        self._type = type_

    @property
    def type(self):
        return self._type

    @property
    def name(self):
        return self._type.name

    def check_types(self, data):
        return self.type_checker.encode(data)

    def check_constraints(self, data):
        return self.constraints_checker.encode(data)

    def encode(self, data):
        raise NotImplementedError('This codec does not support encode().')

    def decode(self, data):
        raise NotImplementedError('This codec does not support decode().')

    def decode_with_length(self, data):
        raise NotImplementedError('This codec does not support decode_with_length().')

    def __repr__(self):
        return repr(self._type)


class Recursive(object):
    """
    Mixin used to identify Recursive types
    """
    def __repr__(self):
        return '{}({})'.format(type(self).__name__,
                               self.type_name)


class OpenType(object):

    def __init__(self, name, table):
        self._name = name
        self._object_set_name = table[0]
        self._table = []

        for item in table[1]:
            offset = item.count('.')
            offset *= -1
            self._table.append((offset, item.lstrip('.')))

    def __repr__(self):
        return 'OpenType({}, {}, {})'.format(self._name,
                                             self._object_set_name,
                                             self._table)


class OpenTypeSequence(object):

    def __init__(self, name, members):
        self._name = name
        self._members = members

    def __repr__(self):
        return 'OpenTypeSequence({}, {})'.format(self._name, self._members)


class OpenTypeSequenceOf(object):

    def __init__(self, name, element_type):
        self._name = name
        self._element_type = element_type

    def __repr__(self):
        return 'OpenTypeSequenceOf({}, {})'.format(self._name,
                                                   self._element_type)


class CompiledOpenTypes(CompiledType):

    def __init__(self, compiled_open_types, compiled_type):
        super(CompiledOpenTypes, self).__init__(compiled_type)
        self._compiled_open_types = compiled_open_types

    @property
    def type(self):
        return self._type.type

    def encode(self, data, **kwargs):
        # print()
        # print('data:', data)
        # print(self._compiled_open_types)

        return self._type.encode(data, **kwargs)

    def decode(self, data):
        return self._type.decode(data)


class Compiler(object):

    def __init__(self, specification, numeric_enums=False):
        self._specification = specification
        self._numeric_enums = numeric_enums
        self._types_backtrace = []
        self.recursive_types = []
        self.compiled = {}
        self.current_type_descriptor = None

    def types_backtrace_push(self, type_name):
        self._types_backtrace.append(type_name)

    def types_backtrace_pop(self):
        self._types_backtrace.pop()

    @property
    def types_backtrace(self):
        return self._types_backtrace

    def process(self):
        self.pre_process()

        compiled = {}

        for module_name in self._specification:
            items = self._specification[module_name]['types'].items()

            for type_name, type_descriptor in items:
                self.types_backtrace_push(type_name)
                compiled_type = self.process_type(type_name,
                                                  type_descriptor,
                                                  module_name)
                compiled_type.type_name = type_name
                compiled_type.module_name = module_name
                compiled_open_types = self.compile_open_types(type_name,
                                                              type_descriptor,
                                                              module_name)

                if compiled_open_types:
                    compiled_type = CompiledOpenTypes(compiled_open_types,
                                                      compiled_type)

                self.types_backtrace_pop()

                if module_name not in compiled:
                    compiled[module_name] = {}

                compiled[module_name][type_name] = compiled_type

        for recursive_type in self.recursive_types:
            compiled_module = compiled[recursive_type.module_name]
            inner_type = compiled_module[recursive_type.type_name].type
            recursive_type.set_inner_type(inner_type)

        return compiled

    def pre_process(self):
        for module_name, module in self._specification.items():
            types = module['types']
            type_descriptors = types.values()

            self.pre_process_components_of(type_descriptors, module_name)
            self.pre_process_extensibility_implied(module, type_descriptors)
            self.pre_process_tags(module, module_name)
            self.pre_process_default_value(type_descriptors, module_name)

        for module_name, module in self._specification.items():
            self.pre_process_parameterization_step_1(module['types'],
                                                     module_name)

        for module_name, module in self._specification.items():
            types = module['types']
            types = self.pre_process_parameterization_step_2(types)
            module['types'] = types

        return self._specification

    def pre_process_components_of(self, type_descriptors, module_name):
        """COMPONENTS OF expansion.

        """

        for type_descriptor in type_descriptors:
            self.pre_process_components_of_type(type_descriptor,
                                                module_name)

    def pre_process_components_of_type(self, type_descriptor, module_name):
        if 'members' not in type_descriptor:
            return

        type_descriptor['members'] = self.pre_process_components_of_expand_members(
            type_descriptor['members'],
            module_name)

    def pre_process_components_of_expand_members(self, members, module_name):
        expanded_members = []

        for member in members:
            if member != EXTENSION_MARKER and 'components-of' in member:
                type_descriptor, inner_module_name = self.lookup_type_descriptor(
                    member['components-of'],
                    module_name)
                inner_members = self.pre_process_components_of_expand_members(
                    type_descriptor['members'],
                    inner_module_name)

                for inner_member in inner_members:
                    if inner_member == EXTENSION_MARKER:
                        break

                    expanded_members.append(deepcopy(inner_member))
            else:
                expanded_members.append(member)

        return expanded_members

    def pre_process_extensibility_implied(self, module, type_descriptors):
        """Make all types extensible.

        """

        if not module['extensibility-implied']:
            return

        for type_descriptor in type_descriptors:
            self.pre_process_extensibility_implied_type(type_descriptor)

    def pre_process_extensibility_implied_type(self, type_descriptor):
        if 'members' not in type_descriptor:
            return

        members = type_descriptor['members']

        for member in members:
            if member == EXTENSION_MARKER:
                continue

            if isinstance(member, list):
                for type_descriptor in member:
                    self.pre_process_extensibility_implied_type(type_descriptor)
            else:
                self.pre_process_extensibility_implied_type(member)

        if EXTENSION_MARKER not in members:
            members.append(EXTENSION_MARKER)

    def pre_process_tags(self, module, module_name):
        """Add tags where missing.

        """

        module_tags = module.get('tags', 'EXPLICIT')

        for type_descriptor in module['types'].values():
            self.current_type_descriptor = type_descriptor
            self.pre_process_tags_type(type_descriptor,
                                       module_tags,
                                       module_name)

    def is_dummy_reference(self, type_name):
        if 'parameters' not in self.current_type_descriptor:
            return False

        if type_name in self.current_type_descriptor['parameters']:
            return True

        return False

    def pre_process_tags_type(self,
                              type_descriptor,
                              module_tags,
                              module_name):
        type_name = type_descriptor['type']

        if 'tag' in type_descriptor:
            tag = type_descriptor['tag']
            resolved_type_name = self.resolve_type_name(type_name, module_name)

            if 'kind' not in tag:
                if resolved_type_name == 'CHOICE':
                    tag['kind'] = 'EXPLICIT'
                elif self.is_dummy_reference(type_name):
                    tag['kind'] = 'EXPLICIT'
                elif module_tags in ['IMPLICIT', 'EXPLICIT']:
                    tag['kind'] = module_tags
                else:
                    tag['kind'] = 'IMPLICIT'

        # SEQUENCE, SET and CHOICE.
        if 'members' in type_descriptor:
            self.pre_process_tags_type_members(type_descriptor,
                                               module_tags,
                                               module_name)

        # SEQUENCE OF and SET OF.
        if 'element' in type_descriptor:
            self.pre_process_tags_type(type_descriptor['element'],
                                       module_tags,
                                       module_name)

    def pre_process_tags_type_members(self,
                                      type_descriptor,
                                      module_tags,
                                      module_name):
        def is_any_member_tagged(members):
            for member in members:
                if member == EXTENSION_MARKER:
                    continue

                if 'tag' in member:
                    return True

            return False

        number = None
        members = flatten(type_descriptor['members'])

        # Add tag number to all members if AUTOMATIC TAGS are
        # selected and no member is tagged.
        if module_tags == 'AUTOMATIC' and not is_any_member_tagged(members):
            number = 0

        for member in members:
            if member == EXTENSION_MARKER:
                continue

            if number is not None:
                if 'tag' not in member:
                    member['tag'] = {}

                member['tag']['number'] = number
                number += 1

            self.pre_process_tags_type(member,
                                       module_tags,
                                       module_name)

    def pre_process_default_value(self, type_descriptors, module_name):
        """SEQUENCE and SET default member value cleanup.

        """

        sequences_and_sets = self.get_type_descriptors(
            type_descriptors,
            ['SEQUENCE', 'SET'])

        for type_descriptor in sequences_and_sets:
            for member in type_descriptor['members']:
                if member == EXTENSION_MARKER:
                    continue

                if 'default' not in member:
                    continue

                resolved_member = self.resolve_type_descriptor(member,
                                                               module_name)

                if resolved_member['type'] == 'BIT STRING':
                    self.pre_process_default_value_bit_string(member,
                                                              resolved_member)

                if resolved_member['type'] == 'OCTET STRING':
                    self.pre_process_default_value_octet_string(member)

                if resolved_member['type'] == 'ENUMERATED' and self._numeric_enums:
                    for key, value in resolved_member['values']:
                        if key == member['default']:
                            member['default'] = value
                            break

    def pre_process_default_value_bit_string(self, member, resolved_member):
        default = member['default']

        if isinstance(default, tuple):
            # Already pre-processed.
            return

        if isinstance(default, list):
            named_bits = dict(resolved_member['named-bits'])
            reversed_mask = 0

            for name in default:
                reversed_mask |= (1 << int(named_bits[name]))

            mask = int(bin(reversed_mask)[2:][::-1], 2)
            number_of_bits = reversed_mask.bit_length()
        elif default.startswith('0x'):
            if len(default) % 2 == 1:
                default += '0'

            default = '01' + default[2:]
            mask = int(default, 16)
            mask >>= lowest_set_bit(mask)
            number_of_bits = mask.bit_length() - 1
            mask ^= (1 << number_of_bits)
        elif default == '0b':
            number_of_bits = 0
        else:
            mask = int(default, 2)
            mask >>= lowest_set_bit(mask)
            number_of_bits = len(default) - 2

        if number_of_bits > 0:
            mask = bitstruct.pack('u{}'.format(number_of_bits), mask)
        else:
            mask = b''

        member['default'] = (mask, number_of_bits)

    def pre_process_default_value_octet_string(self, member):
        default = member['default']

        if sys.version_info[0] > 2 and isinstance(default, bytes):
            # Already pre-processed.
            return

        if default.startswith('0b'):
            default = default[2:]
            if len(default) % 8 != 0:
                default += '0' * (-len(default) % 8)
            member['default'] = binascii.unhexlify(
                hex(int('11111111' + default, 2))[4:]
            )
        elif default.startswith('0x'):
            default = default[2:]
            if len(default) % 2 == 1:
                default += '0'
            member['default'] = binascii.unhexlify(default)

    def pre_process_parameterization_step_1(self, types, module_name):
        """X.683 parameterization pre processing - step 1.

        """

        for type_name, type_descriptor in types.items():
            # Skip if the type is parameterized.
            if 'parameters' in type_descriptor:
                continue

            self.pre_process_parameterization_step_1_type(type_descriptor,
                                                          type_name,
                                                          module_name)

    def pre_process_parameterization_step_1_type(self,
                                                 type_descriptor,
                                                 type_name,
                                                 module_name):
        """Recursively find and replace all parameterized types with their
        actual types.

        """

        # SEQUENCE, SET and CHOICE.
        if 'members' in type_descriptor:
            for member in type_descriptor['members']:
                if member == EXTENSION_MARKER:
                    continue

                self.pre_process_parameterization_step_1_type(member,
                                                              type_name,
                                                              module_name)

        # SEQUENCE OF and SET OF.
        if 'element' in type_descriptor:
            self.pre_process_parameterization_step_1_type(
                type_descriptor['element'],
                type_name,
                module_name)

        # Just return if the type is not using a parameterized type.
        if 'actual-parameters' not in type_descriptor:
            return

        (parameterized_type_descriptor,
         parameterized_module_name) = self.lookup_type_descriptor(
            type_descriptor['type'],
            module_name)

        if 'parameters' not in parameterized_type_descriptor:
            raise CompileError(
                "Type '{}' in module '{}' is not parameterized.".format(
                    type_descriptor['type'],
                    parameterized_module_name))

        dummy_parameters = parameterized_type_descriptor['parameters']
        actual_parameters = type_descriptor['actual-parameters']

        if len(dummy_parameters) != len(actual_parameters):
            raise CompileError(
                "Parameterized type '{}' in module '{}' takes {} "
                "parameters, but {} are given in type '{}' in "
                "module '{}'.".format(type_descriptor['type'],
                                      parameterized_module_name,
                                      len(dummy_parameters),
                                      len(actual_parameters),
                                      type_name,
                                      module_name))

        parameterized_type_descriptor = deepcopy(parameterized_type_descriptor)

        self.pre_process_parameterization_step_1_dummy_to_actual_type(
            parameterized_type_descriptor,
            dummy_parameters,
            actual_parameters,
            parameterized_module_name)

        self.pre_process_parameterization_step_1_type(
            parameterized_type_descriptor,
            type_name,
            parameterized_module_name)

        type_descriptor.update(parameterized_type_descriptor)

        if 'module-name' not in type_descriptor:
            if module_name != parameterized_module_name:
                type_descriptor['module-name'] = parameterized_module_name

        if 'parameters' in type_descriptor:
            del type_descriptor['parameters']

        del type_descriptor['actual-parameters']

    def pre_process_parameterization_step_1_dummy_to_actual_type(
            self,
            type_descriptor,
            dummy_parameters,
            actual_parameters,
            module_name):
        if 'members' in type_descriptor:
            for member in type_descriptor['members']:
                if member == EXTENSION_MARKER:
                    continue

                self.pre_process_parameterization_step_1_dummy_to_actual_type(
                    member,
                    dummy_parameters,
                    actual_parameters,
                    module_name)
        elif 'element' in type_descriptor:
            self.pre_process_parameterization_step_1_dummy_to_actual_type(
                type_descriptor['element'],
                dummy_parameters,
                actual_parameters,
                module_name)

        # Replace dummy with actual in current type descriptor.
        for dummy_parameter, actual_parameter in zip(dummy_parameters,
                                                     actual_parameters):
            if type_descriptor['type'] == dummy_parameter:
                type_descriptor.update(actual_parameter)

            if 'actual-parameters' in type_descriptor:
                for i, parameter in enumerate(type_descriptor['actual-parameters']):
                    if parameter['type'] == dummy_parameter:
                        type_descriptor['actual-parameters'][i] = actual_parameter

            if 'size' in type_descriptor:
                actual_size = []

                for item in type_descriptor['size']:
                    if isinstance(item, tuple):
                        minimum, maximum = item

                        if minimum == dummy_parameter:
                            minimum = actual_parameter

                        if maximum == dummy_parameter:
                            maximum = actual_parameter

                        item = (minimum, maximum)
                    elif item == dummy_parameter:
                        item = actual_parameter

                    actual_size.append(item)

                type_descriptor['size'] = actual_size

            if 'restricted-to' in type_descriptor:
                actual_restricted_to = []

                for minimum, maximum in type_descriptor['restricted-to']:
                    if minimum == dummy_parameter:
                        minimum = actual_parameter

                    if maximum == dummy_parameter:
                        maximum = actual_parameter

                    actual_restricted_to.append((minimum, maximum))

                type_descriptor['restricted-to'] = actual_restricted_to

    def pre_process_parameterization_step_2(self, types):
        """X.683 parameterization pre processing - step 2.

        """

        # Remove parameterized types as they are no longer needed.
        return {
            type_name: type_descriptor
            for type_name, type_descriptor in types.items()
            if 'parameters' not in type_descriptor
        }

    def resolve_type_name(self, type_name, module_name):
        """Returns the ASN.1 type name of given type.

        """

        try:
            while True:
                if is_object_class_type_name(type_name):
                    type_name, module_name = self.lookup_object_class_type_name(
                        type_name,
                        module_name)
                else:
                    type_descriptor, module_name = self.lookup_type_descriptor(
                        type_name,
                        module_name)
                    type_name = type_descriptor['type']
        except CompileError:
            pass

        return type_name

    def resolve_type_descriptor(self, type_descriptor, module_name):
        type_name = type_descriptor['type']

        try:
            while True:
                if is_object_class_type_name(type_name):
                    type_name, module_name = self.lookup_object_class_type_name(
                        type_name,
                        module_name)
                else:
                    type_descriptor, module_name = self.lookup_type_descriptor(
                        type_name,
                        module_name)
                    type_name = type_descriptor['type']
        except CompileError:
            pass

        return type_descriptor

    def get_type_descriptors(self, type_descriptors, type_names):
        result = []

        for type_descriptor in type_descriptors:
            result += self.get_type_descriptors_type(type_descriptor,
                                                     type_names)

        return result

    def get_type_descriptors_type(self, type_descriptor, type_names):
        type_descriptors = []
        type_name = type_descriptor['type']

        if type_name in type_names:
            type_descriptors.append(type_descriptor)

        if 'members' in type_descriptor:
            for member in type_descriptor['members']:
                if member == EXTENSION_MARKER:
                    continue

                if isinstance(member, list):
                    type_descriptors.extend(self.get_type_descriptors(member,
                                                                      type_names))
                else:
                    type_descriptors += self.get_type_descriptors_type(member,
                                                                       type_names)

        if 'element' in type_descriptor:
            type_descriptors += self.get_type_descriptors_type(
                type_descriptor['element'],
                type_names)

        return type_descriptors

    def process_type(self, type_name, type_descriptor, module_name):
        return NotImplementedError('To be implemented by subclasses.')

    def compile_type(self, name, type_descriptor, module_name):
        return NotImplementedError('To be implemented by subclasses.')

    def compile_open_types(self, name, type_descriptor, module_name):
        """Compile the open types wrapper for given type. Returns ``None`` if
        given type does not have any open types.

        """

        compiled = None
        type_name = type_descriptor['type']

        if type_name in ['SEQUENCE', 'SET']:
            compiled_members = []

            for member in type_descriptor['members']:
                if member == EXTENSION_MARKER:
                    continue

                if isinstance(member, list):
                    # ToDo: Handle groups.
                    continue

                if is_open_type(member['type']):
                    if 'table' in member:
                        table = member['table']

                        if isinstance(table, list):
                            compiled_members.append(OpenType(member['name'],
                                                             table))
                else:
                    compiled_member = self.compile_open_types(member['name'],
                                                              member,
                                                              module_name)

                    if compiled_member is not None:
                        compiled_members.append(compiled_member)

            if compiled_members:
                compiled = OpenTypeSequence(name, compiled_members)
        elif type_name in ['SEQUENCE OF', 'SET OF']:
            compiled_element = self.compile_open_types('',
                                                       type_descriptor['element'],
                                                       module_name)

            if compiled_element:
                compiled = OpenTypeSequenceOf(name, compiled_element)
        elif type_name == 'CHOICE':
            # ToDo: Handle CHOICE.
            pass
        else:
            pass

        return compiled

    def compile_user_type(self, name, type_name, module_name):
        compiled = self.get_compiled_type(name,
                                          type_name,
                                          module_name)

        if compiled is None:
            self.types_backtrace_push(type_name)
            compiled = self.compile_type(
                name,
                *self.lookup_type_descriptor(
                    type_name,
                    module_name))
            compiled.type_name = type_name
            compiled.module_name = module_name
            self.types_backtrace_pop()
            self.set_compiled_type(name,
                                   type_name,
                                   module_name,
                                   compiled)

        return compiled

    def compile_members(self,
                        members,
                        module_name,
                        sort_by_tag=False):
        compiled_members = []
        has_extension_marker = False

        for member in members:
            if member == EXTENSION_MARKER:
                has_extension_marker = True
                continue

            if isinstance(member, list):
                group_members, _ = self.compile_members(member,
                                                        module_name)
                compiled_members.extend(group_members)
                continue

            compiled_member = self.compile_member(member, module_name)
            compiled_members.append(compiled_member)

        if sort_by_tag:
            compiled_members = sorted(compiled_members, key=attrgetter('tag'))

        return compiled_members, has_extension_marker

    def compile_root_member(self, member, module_name, compiled_members):
        compiled_member = self.compile_member(member,
                                              module_name)
        compiled_members.append(compiled_member)

    def compile_member(self, member, module_name):
        if is_object_class_type_name(member['type']):
            member, class_module_name = self.convert_object_class_type_descriptor(
                member,
                module_name)
            compiled_member = self.compile_type(member['name'],
                                                member,
                                                class_module_name)
        else:
            compiled_member = self.compile_type(member['name'],
                                                member,
                                                module_name)

        if 'optional' in member:
            compiled_member = self.copy(compiled_member)
            compiled_member.optional = member['optional']

        if 'default' in member:
            compiled_member = self.copy(compiled_member)
            compiled_member.set_default(member['default'])

        if 'size' in member:
            compiled_member = self.copy(compiled_member)
            compiled_member.set_size_range(*self.get_size_range(member,
                                                                module_name))

        return compiled_member

    def get_size_range(self, type_descriptor, module_name):
        """Returns a tuple of the minimum and maximum values allowed according
        the the ASN.1 specification SIZE parameter. Returns (None,
        None, None) if the type does not have a SIZE parameter.

        """

        size = type_descriptor.get('size', None)

        if size is None:
            minimum = None
            maximum = None
            has_extension_marker = None
        else:
            if isinstance(size[0], tuple):
                minimum, maximum = size[0]
            else:
                minimum = size[0]
                maximum = size[0]

            has_extension_marker = (EXTENSION_MARKER in size)

        if isinstance(minimum, str):
            if minimum != 'MIN':
                minimum = self.lookup_value(minimum, module_name)[0]['value']

        if isinstance(maximum, str):
            if maximum != 'MAX':
                maximum = self.lookup_value(maximum, module_name)[0]['value']

        return minimum, maximum, has_extension_marker

    def get_enum_values(self, type_descriptor, module_name):
        """Converts the enum values to ints if they are value references.

        """

        enum_values = []

        for value in type_descriptor['values']:
            if value != EXTENSION_MARKER and not isinstance(value[1], int):
                lookup = self.lookup_value(value[1], module_name)
                enum_values.append((value[0], lookup[0]['value']))
            else:
                enum_values.append(value)

        return enum_values

    def get_restricted_to_range(self, type_descriptor, module_name):
        def convert_value(value):
            try:
                value = float(value)
            except ValueError:
                if not is_type_name(value):
                    try:
                        resolved_type_descriptor = self.resolve_type_descriptor(
                            type_descriptor,
                            module_name)
                        value = resolved_type_descriptor['named-numbers'][value]
                    except KeyError:
                        value = self.lookup_value(value,
                                                  module_name)[0]['value']

            return value

        restricted_to = type_descriptor['restricted-to']

        if isinstance(restricted_to[0], tuple):
            minimum, maximum = restricted_to[0]
        else:
            minimum = restricted_to[0]
            maximum = restricted_to[0]

        if isinstance(minimum, str):
            minimum = convert_value(minimum)

        if isinstance(maximum, str):
            maximum = convert_value(maximum)

        has_extension_marker = (EXTENSION_MARKER in restricted_to)

        return minimum, maximum, has_extension_marker

    def get_with_components(self, type_descriptor):
        return type_descriptor.get('with-components', None)

    def get_named_bits(self, type_descriptor, module_name):
        named_bits = type_descriptor.get('named-bits', None)
        if named_bits is not None:
            named_bit_values = []

            for value in named_bits:
                if value != EXTENSION_MARKER and not value[1].isdigit():
                    lookup = self.lookup_value(value[1], module_name)
                    named_bit_values.append((value[0], lookup[0]['value']))
                else:
                    named_bit_values.append((value[0], int(value[1])))

            return named_bit_values

    def is_explicit_tag(self, type_descriptor):
        try:
            return type_descriptor['tag']['kind'] == 'EXPLICIT'
        except KeyError:
            pass

        return False

    def get_module_name(self, type_descriptor, module_name):
        module_name = type_descriptor.get('module-name', module_name)
        try:
            _, module_name = self.lookup_type_descriptor(type_descriptor['type'],
                                                         module_name)
        except CompileError:
            pass
        return module_name

    def lookup_in_modules(self, section, debug_string, name, module_name):
        begin_debug_string = debug_string[:1].upper() + debug_string[1:]
        module = self._specification[module_name]

        if name in module[section]:
            return module[section][name], module_name
        else:
            for from_module_name, imports in module['imports'].items():
                if name not in imports:
                    continue

                if from_module_name not in self._specification:
                    raise CompileError(
                        "Module '{}' cannot import {} '{}' from missing "
                        "module '{}'.".format(module_name,
                                              debug_string,
                                              name,
                                              from_module_name))

                try:
                    return self.lookup_in_modules(section,
                                                  debug_string,
                                                  name,
                                                  from_module_name)
                except CompileError:
                    raise CompileError(
                        "{} '{}' imported by module '{}' not found in "
                        "module '{}'.".format(
                            begin_debug_string,
                            name,
                            module_name,
                            from_module_name))

        raise CompileError("{} '{}' not found in module '{}'.".format(
            begin_debug_string,
            name,
            module_name))

    def lookup_type_descriptor(self, type_name, module_name):
        return self.lookup_in_modules('types', 'type', type_name, module_name)

    def lookup_value(self, value_name, module_name):
        return self.lookup_in_modules('values', 'value', value_name, module_name)

    def lookup_object_class_descriptor(self, object_class_name, module_name):
        return self.lookup_in_modules('object-classes',
                                      'object class',
                                      object_class_name,
                                      module_name)

    def lookup_object_class_type_name(self, type_name, module_name):
        class_name, member_name = type_name.split('.')
        result = self.lookup_object_class_descriptor(class_name,
                                                     module_name)
        object_class_descriptor, module_name = result

        for member in object_class_descriptor['members']:
            if member['name'] == member_name:
                return member['type'], module_name

    def get_compiled_type(self, name, type_name, module_name):
        try:
            return self.compiled[module_name][type_name][name]
        except KeyError:
            return None

    def set_compiled_type(self, name, type_name, module_name, compiled):
        if module_name not in self.compiled:
            self.compiled[module_name] = {}

        if type_name not in self.compiled[module_name]:
            self.compiled[module_name][type_name] = {}

        self.compiled[module_name][type_name][name] = compiled

    def convert_object_class_type_descriptor(self, type_descriptor, module_name):
        type_name, module_name = self.lookup_object_class_type_name(
            type_descriptor['type'],
            module_name)
        type_descriptor = deepcopy(type_descriptor)
        type_descriptor['type'] = type_name

        return type_descriptor, module_name

    def copy(self, compiled_type):
        if not isinstance(compiled_type, Recursive):
            compiled_type = copy(compiled_type)

        return compiled_type

    def set_compiled_restricted_to(self, compiled, type_descriptor, module_name):
        compiled = self.copy(compiled)
        compiled.set_restricted_to_range(
            *self.get_restricted_to_range(type_descriptor,
                                          module_name))

        return compiled

    def external_type_descriptor(self):
        return {
            'type': 'SEQUENCE',
            'tag': {
                'class': 'UNIVERSAL',
                'number': 8,
                'kind': 'IMPLICIT'
            },
            'members': [
                {
                    'name': 'direct-reference',
                    'type': 'OBJECT IDENTIFIER',
                    'optional': True
                },
                {
                    'name': 'indirect-reference',
                    'type': 'INTEGER',
                    'optional': True
                },
                {
                    'name': 'data-value-descriptor',
                    'type': 'ObjectDescriptor',
                    'optional': True
                },
                {
                    'name': 'encoding',
                    'type': 'CHOICE',
                    'members': [
                        {
                            'name': 'single-ASN1-type',
                            'type': 'NULL',  # ToDo: Should be ABSTRACT-SYNTAX.&Type
                            'tag': {
                                'number': 0
                            }
                        },
                        {
                            'name': 'octet-aligned',
                            'type': 'OCTET STRING',
                            'tag': {
                                'number': 1,
                                'kind': 'IMPLICIT'
                            }
                        },
                        {
                            'name': 'arbitrary',
                            'type': 'BIT STRING',
                            'tag': {
                                'number': 2,
                                'kind': 'IMPLICIT'
                            }
                        }
                    ]
                }
            ]
        }


def enum_values_as_dict(values):
    return {
        value[1]: value[0]
        for value in values
        if value != EXTENSION_MARKER
    }


def enum_values_split(values):
    if EXTENSION_MARKER in values:
        index = values.index(EXTENSION_MARKER)

        return values[:index], values[index + 1:]
    else:
        return values, None


def pre_process(specification):
    return Compiler(specification).pre_process()
