import functools
import json
from django.utils.translation import ugettext as _
from jsonobject.exceptions import BadValueError
from corehq.apps.userreports.exceptions import BadSpecError
from corehq.apps.userreports.expressions.specs import PropertyNameGetterSpec, PropertyPathGetterSpec, \
    ConditionalExpressionSpec, ConstantGetterSpec, RootDocExpressionSpec, RelatedDocExpressionSpec


def _simple_expression_generator(wrapper_class, spec, context):
    return wrapper_class.wrap(spec)

_constant_expression = functools.partial(_simple_expression_generator, ConstantGetterSpec)
_property_name_expression = functools.partial(_simple_expression_generator, PropertyNameGetterSpec)
_property_path_expression = functools.partial(_simple_expression_generator, PropertyPathGetterSpec)

def _conditional_expression(spec, context):
    # no way around this since the two factories inherently depend on each other
    from corehq.apps.userreports.filters.factory import FilterFactory
    wrapped = ConditionalExpressionSpec.wrap(spec)
    wrapped.configure(
        FilterFactory.from_spec(wrapped.test, context),
        ExpressionFactory.from_spec(wrapped.expression_if_true, context),
        ExpressionFactory.from_spec(wrapped.expression_if_false, context),
    )
    return wrapped


def _root_doc_expression(spec, context):
    wrapped = RootDocExpressionSpec.wrap(spec)
    wrapped.configure(ExpressionFactory.from_spec(wrapped.expression, context))
    return wrapped


def _related_doc_expression(spec, context):
    wrapped = RelatedDocExpressionSpec.wrap(spec)
    wrapped.configure(
        related_doc_type=wrapped.related_doc_type,
        doc_id_expression=ExpressionFactory.from_spec(wrapped.doc_id_expression, context),
        value_expression=ExpressionFactory.from_spec(wrapped.value_expression, context),
    )
    return wrapped


class ExpressionFactory(object):
    spec_map = {
        'constant': _constant_expression,
        'property_name': _property_name_expression,
        'property_path': _property_path_expression,
        'conditional': _conditional_expression,
        'root_doc': _root_doc_expression,
        'related_doc': _related_doc_expression,
    }

    @classmethod
    def from_spec(cls, spec, context=None):
        try:
            return cls.spec_map[spec['type']](spec, context)
        except KeyError:
            raise BadSpecError(_('Invalid or missing getter type: {}. Valid options are: {}').format(
                spec.get('type', '[missing]'),
                ', '.join(cls.spec_map.keys()),
            ))
        except BadValueError as e:
            raise BadSpecError(_('Problem creating getter: {}. Message is: {}').format(
                json.dumps(spec, indent=2),
                str(e),
            ))

@@ -386,16 +386,937 @@
 nSpec%0A%0A%0A
+class ExpressionFactory(object):%0A    spec_map = %7B%0A        'constant': _constant_expression,%0A        'property_name': _property_name_expression,%0A        'property_path': _property_path_expression,%0A        'conditional': _conditional_expression,%0A        'root_doc': _root_doc_expression,%0A        'related_doc': _related_doc_expression,%0A    %7D%0A%0A    @classmethod%0A    def from_spec(cls, spec, context=None):%0A        try:%0A            return cls.spec_map%5Bspec%5B'type'%5D%5D(spec, context)%0A        except KeyError:%0A            raise BadSpecError(_('Invalid or missing getter type: %7B%7D. Valid options are: %7B%7D').format(%0A                spec.get('type', '%5Bmissing%5D'),%0A                ', '.join(cls.spec_map.keys()),%0A            ))%0A        except BadValueError as e:%0A            raise BadSpecError(_('Problem creating getter: %7B%7D. Message is: %7B%7D').format(%0A                json.dumps(spec, indent=2),%0A                str(e),%0A            ))%0A%0A%0A
 def _sim
@@ -2767,925 +2767,4 @@
 ped%0A
-%0A%0Aclass ExpressionFactory(object):%0A    spec_map = %7B%0A        'constant': _constant_expression,%0A        'property_name': _property_name_expression,%0A        'property_path': _property_path_expression,%0A        'conditional': _conditional_expression,%0A        'root_doc': _root_doc_expression,%0A        'related_doc': _related_doc_expression,%0A    %7D%0A%0A    @classmethod%0A    def from_spec(cls, spec, context=None):%0A        try:%0A            return cls.spec_map%5Bspec%5B'type'%5D%5D(spec, context)%0A        except KeyError:%0A            raise BadSpecError(_('Invalid or missing getter type: %7B%7D. Valid options are: %7B%7D').format(%0A                spec.get('type', '%5Bmissing%5D'),%0A                ', '.join(cls.spec_map.keys()),%0A            ))%0A        except BadValueError as e:%0A            raise BadSpecError(_('Problem creating getter: %7B%7D. Message is: %7B%7D').format(%0A                json.dumps(spec, indent=2),%0A                str(e),%0A            ))%0A

move ExpressionFactory definition to begining of file