class Minimum(Constraint):
    type = '<'

    def check(self, entry, value, context):
        expected = self.limit.evaluate(context)
        if isinstance(value, basestring):
            # It is useful to check the bounds of a text character...
            value = ord(value)
        if int(value) < expected:
            raise ConstraintError(entry, int(value), '>=', expected)

    def range(self):
        return Range(_limit(self.limit), None) class Maximum(Constraint):
    type = '>'

    def check(self, entry, value, context):
        expected = self.limit.evaluate(context)
        if isinstance(value, basestring):
            # It is useful to check the bounds of a text character...
            value = ord(value)
        if int(value) > expected:
            raise ConstraintError(entry, int(value), '<=', expected)

    def range(self):
        return Range(None, _limit(self.limit)) class Choice(bdec.entry.Entry):
    """
    An entry that can be one of many entries.

    The first entry to decode correctly will be used.
    """

    def __init__(self, name, children, length=None):
        bdec.entry.Entry.__init__(self, name, length, children)

    def _range(self, ignore_entries):
        minimum = min(child.entry.range(ignore_entries).min for child in self.children)
        maximum = max(child.entry.range(ignore_entries).max for child in self.children)
        return bdec.entry.Range(minimum, maximum) class Equals(Constraint):
    type = '!='

    def check(self, entry, value, context):
        expected = self.limit.evaluate(context)
        if isinstance(expected, int):
            if int(value) !=  expected:
                raise ConstraintError(entry, int(value), '==', expected)
        elif isinstance(expected, Data):
            # We aren't checking the length here, only the value (for example,
            # the expected value for a variable length binary field will have
            # a different length than the actual). Only compare the bits that
            # are valid.
            length_diff = len(value) - len(expected)
            if length_diff > 0:
                # The expected value is shorter than the actual, so grow it.
                expected = Data('\x00' * (length_diff / 8 + 1), 0, length_diff) + expected
            elif length_diff < 0:
                # The expected value is longer than the actual, so shrink it.
                shorter = expected.copy()
                leading = shorter.pop(-length_diff)
                if not int(leading):
                    # We can safely pop the leading bits.
                    expected = shorter
            if value !=  expected:
                raise ConstraintError(entry, value, '==', expected)
        else:
            if value !=  expected:
                raise ConstraintError(entry, value, '==', expected)

    def range(self):
        limit = _limit(self.limit)
        return Range(limit, limit) class Child(object):
    """ A child is embedded in another entry."""
    def __init__(self, name, entry):
        self.name = name
        self.entry = entry

    def __repr__(self):
        return "'%s' %s" % (self.entry, self.name) def parse(text):
    """
    Compile a length expression into an integer convertible object.

    :param name_lookup: A object to call to query a named integer 
        convertible object.
    return -- An Expression instance
    """
    from pyparsing import StringEnd, ParseException
    complete = _int_expression() + StringEnd()
    try:
        return complete.parseString(text)[0]
    except ParseException, ex:
        raise ExpressionError(ex) class Field(bdec.entry.Entry):
    """A field represents data found in the binary file.

    A field can be of variable length, be non-byte aligned, be in one of many
    formats and encodings, and can reference previously decoded fields.
    """

    # Field format types
    TEXT = "text"
    INTEGER = "integer"
    HEX = "hex"
    BINARY = "binary"
    FLOAT = "float"

    _formats = [TEXT, INTEGER, HEX, BINARY, FLOAT]

    # Field 'encoding' types
    LITTLE_ENDIAN = "little endian"
    BIG_ENDIAN = "big endian"

    def __init__(self, name, length, format=BINARY, encoding=None, constraints=[]):
        bdec.entry.Entry.__init__(self, name, length, [], constraints)
        assert format in self._formats

        if encoding is None:
            if format == self.TEXT:
                encoding = "ascii"
            else:
                # We default to big endian for non text types, as little
                # endian requires data with a length of a multiple of 8
                encoding = self.BIG_ENDIAN

        self.format = format
        self.encoding = encoding

    def _get_context(self, query, context):
        try:
            result = query(context, self)
        except bdec.entry.MissingInstanceError:
            if not self.is_hidden():
                raise
            expected = self._get_expected()
            if expected is None:
                raise
            result = self.decode_value(expected)
        return result

    def __str__(self):
        result = "%s '%s'" % (self.format, self.name)
        if self.format not in [self.HEX, self.BINARY]:
            result += ' (%s)' % self.encoding
        return result

    def _decode_int(self, data):
        if self.encoding == self.LITTLE_ENDIAN:
            result = data.get_little_endian_integer()
        else:
            # If we aren't explicitly little endian, we become big endian.
            # This is because we can sometimes convert a non-integer to
            # an integer (eg: we may want to treat a string character as an
            # integer, to ensure we are in a given character range).
            result = int(data)
        return result

    def decode_value(self, data):
        """
        Get a python instance from a data object.
        """
        if self.format == self.BINARY:
            return data.copy(klass=_BinaryData)
        elif self.format == self.HEX:
            return data.copy(klass=_HexData)
        elif self.format == self.TEXT:
            result = data.text(self.encoding)
        elif self.format == self.INTEGER:
            result = self._decode_int(data)
        elif self.format == self.FLOAT:
            if self.encoding == self.LITTLE_ENDIAN:
                result = data.get_litten_endian_float()
            else:
                result = float(data)
        else:
            raise Exception("Unknown field format of '%s'!" % self.format)
        return result class ExpressionParameters(_Parameters):
    """
    A class to calculate parameters passed between entries.

    All entries that have expressions can reference other entries; this class
    is responsible for detecting the parameters that must be passed between
    those entries to supply the data the expression requires.
    """
    def __init__(self, entries):
        # A map of entry -> list of _VariableParam instances
        self._params = {}

        # A lookup of [entry][child][param.name] to get the 'local' name of the
        # child parameter.
        self._local_child_param_name = {}
        self._referenced_values = set()
        self._referenced_lengths = set()
        unreferenced_entries = {}
        entry_stack = []
        entries_visited = set()
        recursive_entries = set()
        param_source = []
        for entry in entries:
            self._populate_references(entry, unreferenced_entries,
                    entries_visited, entry_stack, recursive_entries,
                    param_source)

        # We need to redetect parameters for recursive entries, as we there may
        # be input parameters that weren't detected on the first pass.
        parents = {}
        visited = set()
        for entry in entries:
            parents.setdefault(entry, set())
            self._update_parent_tree(entry, parents, visited)
        for entry in recursive_entries:
            self._update_recursive_input_params(entry, unreferenced_entries, param_source, set(), parents)

        # Walk through all entries with output parameters, and use the
        # type of those entries to populate the input variables.
        for entry, name, param_type in param_source:
            visited = set()
            self._populate_child_input_parameter_type(entry, name, param_type, visited)

        should_have_failed = False
        for entry, references in unreferenced_entries.iteritems():
            for param in self._params[entry]:
                if not param.types:
                    should_have_failed = True
                    if entry not in entries_visited:
                        # We found a top-level entry with an unknown parameter.
                        # We only want top-level entries as this provides the
                        # most context to the user (and if a child entry is
                        # failing, so to will the top level entry).
                        name = iter(references).next().name
                        stack = self._find_child_using_param(entry, name)
                        raise UnknownReferenceError(stack[0], name, stack[1:])
        assert not should_have_failed, 'Found a parameter with an unknown type, ' \
            "but didn't fail on a parent entry! Something went wrong."

    def _update_parent_tree(self, entry, parents, visited):
        if entry in visited:
            return
        visited.add(entry)
        for child in entry.children:
            parents.setdefault(child.entry, set()).add(entry)
            self._update_parent_tree(child.entry, parents, visited)

    def _update_recursive_input_params(self, entry, unreferenced_entries,
            param_source, visited, parents):
        self._populate_parameter_source(entry, unreferenced_entries,
                param_source)
        if entry in visited:
            return
        visited.add(entry)
        for parent in parents[entry]:
            self._update_recursive_input_params(parent, unreferenced_entries,
                    param_source, visited, parents)

    def _find_child_using_param(self, entry, name):
        """Find the child entry using the given parameter name.

        Returns a list of entries from entry down to the child."""
        for child in entry.children:
            for param in self._params[child.entry]:
                if param.reference.name == name:
                    return self._find_child_using_param(child.entry, name) + [entry]
        # None of the child entries use the parameter; it must be us.
        return [entry]

    def _collect_references(self, expression):
        """
        Walk an expression object, collecting all named references.
        
        Returns a list of ValueResult / LengthResult instances.
        """
        result = []
        if isinstance(expression, expr.Constant):
            pass
        elif isinstance(expression, expr.ValueResult):
            result.append(expression)
        elif isinstance(expression, expr.LengthResult):
            result.append(expression)
        elif isinstance(expression, expr.ArithmeticExpression):
            result = self._collect_references(expression.left) + \
                     self._collect_references(expression.right)
        else:
            raise Exception("Unable to collect references from unhandled expression type '%s'!" % expression)
        return result

    def _populate_references(self, entry, unreferenced_entries, entries_visited,
            entry_stack, recursive_entries, param_source):
        """
        Walk down the tree, populating the '_params', '_referenced_XXX' sets.

        Handles recursive elements.
        """
        if entry in entry_stack:
            # We've found a cyclic dependency. We will not have populated the
            # input / output parameters yet, so we'll have to delay it until
            # later.
            recursive_entries.add(entry)
            return

        if entry in self._params:
            # We have already detected parameters for this entry.
            return
        self._params[entry] = set()
        unreferenced_entries[entry] = set()

        entry_stack.append(entry)
        for child in entry.children:
            entries_visited.add(child.entry)
            self._populate_references(child.entry, unreferenced_entries,
                    entries_visited, entry_stack, recursive_entries,
                    param_source)
        assert entry == entry_stack.pop()

        # An entries unknown references are those referenced in any 
        # expressions, and those that are unknown in all of its children.
        if entry.length is not None:
            unreferenced_entries[entry].update(self._collect_references(entry.length))
        if isinstance(entry, sof.SequenceOf) and entry.count is not None:
            unreferenced_entries[entry].update(self._collect_references(entry.count))

        self._populate_parameter_source(entry, unreferenced_entries, param_source)

    def _populate_parameter_source(self, entry, unreferenced_entries,
            param_source):
        """Populate the source of parameters values for unknown values."""
        # Store the names the child doesn't know about (ie: names that must be
        # resolved for this entry to decode).
        child_unknowns = []
        if isinstance(entry, seq.Sequence) and entry.value is not None:
            child_unknowns.extend((None, ref) for ref in self._collect_references(entry.value))
        for constraint in entry.constraints:
            child_unknowns.extend((None, ref) for ref in self._collect_references(constraint.limit))
        for i, child in enumerate(entry.children):
            unknowns = unreferenced_entries[child.entry]
            self._local_child_param_name.setdefault(entry, {}).setdefault(child, {}).update(
                    (ref.name, ref.name) for ref in unknowns)
            child_unknowns.extend((i, ref) for ref in unknowns)

        # Our unknown list is all the unknowns in our children that aren't
        # present in our known references.
        for i, unknown in child_unknowns:
            name = unknown.name.split('.')[0]
            for j, child in enumerate(entry.children):
                if child.name == name and (i is None or i > j):
                    # This value comes from one of our child entries, so drill down
                    # into it.
                    param_type = self._add_out_params(entry, unknown)
                    param_source.append((entry, unknown.name, param_type))
                    break
            else:
                # This value is 'unknown' to the entry, and must be passed in.
                unreferenced_entries[entry].add(unknown)

        for reference in unreferenced_entries[entry]:
            self._params[entry].add(_VariableParam(reference, Param.IN, None))

    def _populate_child_input_parameter_type(self, entry, name, param_type, visited):
        """ Set the input parameter for children that use the named
        parameter."""
        if entry in visited:
            # A recursive entry; we've already populated the input parameter.
            return
        visited.add(entry)

        for child in entry.children:
            for param in self._params[child.entry]:
                if param.reference.name == name and param.direction == Param.IN \
                        and param_type.is_reference_match(param.reference):
                    param.types.add(param_type)
                    self._populate_child_input_parameter_type(child.entry,
                            name, param_type, visited)

    def is_output_param_used(self, entry, child, param):
        assert param.direction == Param.OUT
        return param.name in self._local_child_param_name[entry][child].values()

    def _get_local_reference(self, entry, child, param):
        """Get the local of a parameter used by a child entry.

        Return either a bdec.expression.ValueResult or a
        bdec.expression.LengthResult.
        """
        try:
            name = self._local_child_param_name[entry][child][param.reference.name]
        except KeyError:
            # If an output parameter isn't in the lookup, it it's because
            # the child is a common entry that has been referenced
            # elsewhere (but isn't used in this context).
            assert param.direction != Param.IN, 'Found input parameter ' \
                    "%s being passed from %s to %s, but %s doesn't have a " \
                    'local name for the parameter!' % (param, entry, child,
                            entry)
            name = 'unused %s' % param.reference.name

        # Create a new instance of the expression reference, using the new name
        return type(param.reference)(name)

    def _add_reference(self, entry, reference):
        param_type = EntryValueType(entry)
        self._params[entry].add(_VariableParam(reference, Param.OUT, param_type))
        if isinstance(entry, fld.Field):
            if entry.format not in [fld.Field.INTEGER, fld.Field.BINARY]:
                # We currently only allow integers and binary
                # strings in integer expressions.
                raise BadReferenceTypeError(entry)
            self._referenced_values.add(entry)
        elif isinstance(entry, seq.Sequence):
            if entry.value is not None:
                self._referenced_values.add(entry)
            else:
                # We can only reference sequences with a value
                raise BadReferenceError(entry)
        elif isinstance(entry, chc.Choice):
            # When referencing a choice, we want to attempt to reference each
            # of its children.
            child_params = self._local_child_param_name.setdefault(entry, {})
            for child in entry.children:
                child_reference = type(reference)(child.entry.name)
                self._add_reference(child.entry, child_reference)

                local_names = child_params.setdefault(child, {})
                local_names[child_reference.name] = reference.name
        else:
            raise BadReferenceError(entry)
        return param_type

    def _add_out_params(self, entry, reference):
        """
        Drill down into the children of 'entry', adding output params to 'name'.

        entry -- An instance of bdec.entry.Entry
        reference -- An instance of either bdec.expression.ValueResult or
            bdec.expression.LengthResult.
        return -- A VariableType instance representing the type being referenced.
        """
        if isinstance(entry, chc.Choice):
            # The option names aren't specified in a choice expression
            option_types = []
            for child in entry.children:
                child_type = self._add_out_params(child.entry, reference)
                self._params[child.entry].add(_VariableParam(reference, Param.OUT, child_type))
                option_types.append(child_type)

                sub_name = '.'.join(reference.name.split('.')[1:])
                name = child.name
                if sub_name:
                    name = '%s.%s' % (name, sub_name)
                self._local_child_param_name.setdefault(entry, {}).setdefault(child, {})[reference.name] = reference.name
            result =  MultiSourceType(option_types)
        elif isinstance(entry, seq.Sequence):
            child_params = self._local_child_param_name.setdefault(entry, {})

            # Detect the child name, and the name of the reference under that child.
            names = reference.name.split('.')
            child_name = names[0]
            sub_name = ".".join(names[1:])

            for child in entry.children:
                local_names = child_params.setdefault(child, {})
                if child.name == reference.name:
                    # This parameter references the value / length of the child
                    # item. As the name might be different between parent and
                    # child, use the childs name.
                    child_reference = type(reference)(child.entry.name)
                    local_names[child.entry.name] = reference.name
                    if isinstance(child_reference, expr.ValueResult):
                        result = self._add_reference(child.entry, child_reference)
                    else:
                        result = EntryLengthType(child.entry)
                        self._params[child.entry].add(_VariableParam(child_reference, Param.OUT, result))
                        self._referenced_lengths.add(child.entry)
                    break
                elif child.name == child_name:
                    # We found a child item that knows about this parameter
                    local_names[sub_name] = reference.name
                    child_reference = type(reference)(sub_name)

                    result = self._add_out_params(child.entry, child_reference)
                    self._params[child.entry].add(_VariableParam(child_reference, Param.OUT, result))
                    break
            else:
                # None of the children implement this name.
                raise MissingExpressionReferenceError(entry, reference.name)
        else:
            # We don't know how to resolve a reference to this type of entry!
            raise BadReferenceError(entry)
        return result

    def get_locals(self, entry):
        """
        Get the names of local variables used when decoding an entry.

        Local variables are all child outputs that aren't an output of the
        entry.
        """
        params = self._params[entry]
        locals = {}
        for child in entry.children:
            for child_param in self._params[child.entry]:
                if child_param.direction == Param.OUT:
                    local = self._get_local_reference(entry, child, child_param)
                    if _VariableParam(local, child_param.direction, None) not in params:
                        locals.setdefault(local.param_name(), set()).add(child_param.get_type())
        result = []
        for name, types in locals.items():
            if len(types) == 1:
                type = types.pop()
            else:
                type = MultiSourceType(types)
            result.append(Local(name, type))
        result.sort(key=lambda a:a.name)
        return result

    def _get_params(self, entry):
        assert isinstance(entry, bdec.entry.Entry)
        params = list(self._params[entry])

        def compare_references(left, right):
            # It doesn't really matter what order we use for parameters, but it
            # has to be consistent. As such we order by name, and put value types
            # before length types.
            result = cmp(left.reference.name, right.reference.name)
            if result:
                return result
            if isinstance(left.reference, expr.ValueResult) and \
                    not isinstance(right.reference, expr.ValueResult):
                return -1
            if not isinstance(left.reference, expr.ValueResult) and \
                    isinstance(right.reference, expr.ValueResult):
                return 1
            return 0

        params.sort(cmp=compare_references)
        return params

    def get_params(self, entry):
        """
        Get an iterator to all parameters passed to an entry due to value references.
        """
        try:
            return [param.get_param() for param in self._get_params(entry)]
        except _FailedToResolveError, ex:
            raise UnknownReferenceError(entry, ex.name)

    def get_passed_variables(self, entry, child):
        """
        Get an iterator to parameters passed from a parent to child entry.

        entry -- A bdec.entry.Entry instance
        child -- A bdec.entry.Child instance
        return -- An iterator to Param instances for variables passed from
            entry to child entry.
        """
        assert isinstance(entry, bdec.entry.Entry)
        assert isinstance(child, bdec.entry.Child)
        for param in self._get_params(child.entry):
            local = self._get_local_reference(entry, child, param)
            yield Param(local.param_name(), param.direction, param.get_type())

    def is_value_referenced(self, entry):
        """ Is the decoded value of an entry used elsewhere. """
        return entry in self._referenced_values

    def is_length_referenced(self, entry):
        """ Is the decoded length of an entry used elsewhere. """
        return entry in self._referenced_lengths def solve(expression, entry, params, context, value):
    """Solve an expression given the result and the input parameters.

    expression -- A bdec.expression.Expression instance to solve.
    params -- A bdec.param.ExpressionParameters instance used to query all
        values passed into the expression.
    value -- The integer value to use when solving the expression.
    context -- A dict of (name:value) representing all known parameter
        values that can be used for solving.
    result -- Returns a dict of {ReferenceExpression: value} """

    # Figure out the components by working out each item independantly,
    # starting with the most significant. We create a reference to a
    # 'solve result' variable which we will reference in the inverted
    # expressions.
    solve_result = ValueResult('solve result')
    constant, variables = solve_expression(solve_result, expression, entry, params, context.keys())
    result = {}
    original_value = value
    value -= constant.evaluate(context)
    for ref, expr, inverted_expr in variables:
        # Work out a value for this variable
        c = context.copy()
        c[solve_result.name] = value
        result[ref] = inverted_expr.evaluate(c)

        # Remove it's impact from the overall value so we can work out the next
        # variable
        c = context.copy()
        c[ref.param_name()] = result[ref]
        value -= expr.evaluate(c)
    if value != 0:
        raise UnsolvableExpressionError(entry, expression, original_value)
    return result class SolverError(Exception):
    """ This isn't an encoding error, but rather a specification, or internal error.

    Treating it as an encoding error could cause encoding to continue, but
    with a different option in a choice (which is wrong)."""
    def __init__(self, entry, expr, reason):
        self.entry = entry
        self.expr = expr
        self.reason = reason

    def __str__(self):
        return "%s: %s" % (self.reason, self.expr) class EntryValueType(IntegerType):
    """Parameter value whose source is the integer value of another entry."""
    def __init__(self, entry):
        self.entry = entry

    def __hash__(self):
        return hash(self.entry)

    def __eq__(self, other):
        return isinstance(other, EntryValueType) and self.entry is other.entry

    def range(self, parameters, entries_stack=None):
        if entries_stack is None:
            entries_stack = set()
        if self.entry in entries_stack:
            # We can't keep recursing...
            return Range()
        entries_stack.add(self.entry)
        if isinstance(self.entry, fld.Field):
            length_range = expression_range(self.entry.length, self.entry, parameters, entries_stack)
            # If our length is of a variable range, it can be very large.
            # Attempting to take a power of a large number takes a very long
            # time, is is quite meaningless; limit it to 64 bits.
            max_length = min(length_range.max, 64)
            result = Range(0, pow(2, max_length) - 1)
        elif isinstance(self.entry, seq.Sequence):
            result = expression_range(self.entry.value, self.entry, parameters, entries_stack)
        elif isinstance(self.entry, chc.Choice):
            ranges = [EntryValueType(child.entry).range(parameters, entries_stack) for child in self.entry.children]
            result = reduce(Range.union, ranges)
        else:
            raise NotImplementedError("Don't know how to query the value range of entry '%s'!'" % self.entry)
        entries_stack.remove(self.entry)

        for constraint in self.entry.constraints:
            result = result.intersect(constraint.range())
        return result

    def _is_value_known(self, entry):
        for constraint in entry.constraints:
            if isinstance(constraint, Equals):
                return True

        if isinstance(entry, seq.Sequence):
            if entry.value:
                # Check to see if the entry value is constant
                # TODO: We should examine the source parameters of this
                # value, instead of trying to execute it...
                try:
                    entry.value.evaluate({})
                    return True
                except UndecodedReferenceError:
                    pass
            return False
        elif isinstance(entry, chc.Choice):
            for child in entry.children:
                if not self._is_value_known(child.entry):
                    return False
            return True
        return False

    def has_expected_value(self):
        return self._is_value_known(self.entry)

    def is_reference_match(self, expression):
        assert isinstance(expression, ReferenceExpression), "expected ReferenceExpression, got %s" % expression
        return isinstance(expression, ValueResult)

    def __repr__(self):
        return '${%s}' % self.entry class Sequence(bdec.entry.Entry):
    """
    A sequence type protocol entry.

    A sequence protocol entry is made up of multiple other
    entry types, and they are decoded one after the other.
    All of the child protocol entries must be decoded for
    the sequence to successfully decode.

    A sequence object may be assigned a value, derived from
    the child elements. This allows techniques such as
    lookup tables, and alternate integer encoding methods.
    """

    def __init__(self, name, children, value=None, length=None, constraints=[]):
        bdec.entry.Entry.__init__(self, name, length, children, constraints)
        self.value = value

    def _range(self, ignore_entries):
        return sum((child.entry.range(ignore_entries) for child in self.children), bdec.entry.Range(0, 0))
import unittest
from bdec.constraints import Minimum, Maximum
from bdec.choice import Choice
from bdec.constraints import Equals
from bdec.entry import Child
from bdec.expression import parse
from bdec.field import Field
from bdec.inspect.param import ExpressionParameters
from bdec.inspect.solver import solve, SolverError
from bdec.inspect.type import EntryValueType
from bdec.sequence import Sequence
# Copyright (c) 2010, PRESENSE Technologies GmbH
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the PRESENSE Technologies GmbH nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



def _solve(entry, child, value, context=None):
    if context is None:
        context = {}
    ent = entry.children[child].entry if child is not None else entry
    result = solve(ent.value, ent,
            ExpressionParameters([entry]), context, value)
    return dict((str(c), v) for c,v in result.items())


class TestSolver (unittest.TestCase):
    def test_single_value(self):
        # Test when the expression is the value of an entry
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('${b}')),
            ])
        self.assertEqual({'${b}':5}, _solve(a, 1, 5))

    def test_single_value_addition(self):
        # Test that we correctly resolve the correct value when there is an
        # addition involved
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('${b} + 3')),
            ])
        self.assertEqual({'${b}':7}, _solve(a, 1, 10))

    def test_single_value_multiply(self):
        # Test when the expression is the value of an entry multipled by something
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('${b} * 2')),
            ])
        self.assertEqual({'${b}':4}, _solve(a, 1, 8))

    def test_single_value_bimdas(self):
        # Test that we correctly resolve the value when there are addition
        # and multiplication involved.
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('(${b} + 3) * 8')),
            ])
        self.assertEqual({'${b}':5}, _solve(a, 1, 64))

    def test_big_endian(self):
        # Test that we can break apart a big endian style number
        a = Sequence('a', [
            Field('b1', length=8),
            Field('b2', length=8),
            Sequence('c', [], value=parse('(${b1} << 8) + ${b2} ')),
            ])
        self.assertEqual({'${b1}':0x12, '${b2}':0x34}, _solve(a, 2, 0x1234))

    def test_little_endian(self):
        # Test that we can break apart a little endian style number
        a = Sequence('a', [
            Field('b1', length=8),
            Field('b2', length=8),
            Sequence('c', [], value=parse('(${b2} << 8) + ${b1} ')),
            ])
        self.assertEqual({'${b1}':0x34, '${b2}':0x12}, _solve(a, 2, 0x1234))

    def test_same_reference_multiple_times(self):
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('${b} + ${b}')),
            ])
        # TODO: We should correctly be able to invert 'c = b + b'. See issue245.
        #self.assertEqual({'${b}':7}, _solve(a, 1, 14))
        self.assertRaises(SolverError, _solve, a, 1, 14)

    def test_divide(self):
        a = Sequence('a', [
            Field('b', length=8),
            Sequence('c', [], value=parse('${b} / 2')),
            ])
        # TODO: What should we do in this case? This is a lossy conversion... See issue246.
        #self.assertEqual({'${b}':20}, _solve(a, 1, 10))
        self.assertRaises(SolverError, _solve, a, 1, 10)

    def test_digit(self):
        # Tests a common case of representing text digits
        digit = Sequence('digit', [Field('char:', length=8, constraints=[Minimum(48), Maximum(57)])],
                value = parse('${char:} - 48'))
        self.assertEqual({'${char:}':54}, _solve(digit, None, 6))

    def test_two_digits(self):
        # Tests a common case of representing text digits

        digit = Sequence('digit', [Field('char:', length=8, constraints=[Minimum(48), Maximum(57)])],