"""
Conversions between Python and OpenMath objects

This module implements conversions from Python objects to OpenMath and
back. All state is encapsulated in instances `c` of the class ``Converter``.
The two main methods are ``to_python()`` and ``to_openmath()``,
which do the conversion as the name suggests, or raise a ``ValueError``
if no conversion is known.

The class ``Converter`` implements no conversion by default. The class
``BasicPythonConverter`` implements conversions in both directions for
basic Python types. For convenience, a default instance
``DefaultConverter`` of the latter is provided.

Examples::

    sage: from openmath.convert import to_openmath, to_python, DefaultConverter

    >>> o = DefaultConverter.to_openmath(1); o
    OMInteger(integer=1, id=None)
    >>> DefaultConverter.to_python(o)
    1

For backward compatibility, one may use the following shorthands::

    >>> o = to_openmath(1); o
    OMInteger(integer=1, id=None)
    >>> to_python(o)
    1

The ``Converter`` class contains a mechanism for registering
additional conversions.

Python to OpenMath conversions
------------------------------

The method ``c.register_to_openmath(py_class, conv)`` registers a
conversion to OpenMath for all instances `x` of the class
``py_class``. If ``py_class`` is ``None``, the registration is for all
Python objects.

If ``conv`` is an OpenMath object, the result of the conversion is
that object. If ``conv`` is a callable, is it called as ``conv(x)``.
It must either return the result of the conversion as an OpenMath
object, or raise an exception. The special exception
``CannotConvertError`` can be used to signify that ``conv`` does not
know how to convert the current object, and that ``to_openmath`` shall
continue with the other converters. Any other exception stops
conversion immediately. Converters registered this way are called in
order from the most recent to the oldest.

Furthermore, any object ``x`` that defines an ``__openmath__``
method will be converted to ``x.__openmath__(converter)``. Inside
this method,``converter`` should be used for recursive conversions.

OpenMath to Python conversion
------------------------------

Four methods are provided for registering additional conversions of OM
objects to Python:

- ``c.register_to_python_class(cls, f)`` registers a conversion from
  any OpenMath object in the class ``cls`` (which should be a subclass
  of ``openmath.OMAny``). The result of the conversion is ``f(x)``.

- ``register_to_python_name(cdbase, cd, name, py)`` registers a
  conversion for the symbol with that name, content dictionary and cd
  base. The result of the conversion is the Python object ``py``;

- ``register_to_python_cd(cdbase, cd, py)`` registers a conversion for
  any symbol ``x`` with that content dictionary and cd base. The
  result of the conversion is ``py(x.name)``;

- ``register_to_python_cdbase(cdbase, py)`` registers a conversion for
  any symbol ``x`` with that cd base. The result of the conversion is
  ``py(x.cd, x.name)``.

"""

import six
from inspect import isclass
from . import openmath as om

class Converter(object):
    """
    A class implementing conversions between native Python and OpenMath objects
    """

    def __init__(self):
        # A list of converters from Python types to OM
        self._conv_to_om = []

        # a dictionary mapping OM classes to converters
        self._omclass_to_py = {}

        # a dictionary to convert OMS elements to Python objects:
        # _oms_to_py((cdbase,cd,name)) = lambda : ...
        # _oms_to_py((cdbase,cd,None)) = lambda name: ...
        # _oms_to_py((cdbase,None,None)) = lambda cd,name: ...
        # _oms_to_py((None,None,None)) = lambda cdbase,cd,name: ...
        self._oms_to_py = {}

    # use this to convert literals or to override the conversion implemented in _oms_to_py
    # any obj of class cls is converted to conv(obj)
    def register_to_python_class(self, cls, conv):
        self._omclass_to_py[cls] = conv

    # registration functions for symbols
    def register_to_python_name(self, base, cd, name, py):
        self._register_to_python(base,cd,name,lambda:py)
    def register_to_python_cd(self, base, cd, py):
        self._register_to_python(base,cd,None,py)
    def register_to_python_cdbase(self, base, py):
        self._register_to_python(base,None,None,py)
    # unifies the above
    def _register_to_python(self, base, cd, name, py):
        self._oms_to_py[(base,cd,name)] = py

    # lookup in _oms_to_py, trying from most to least specific entry
    def _lookup_to_python(self, cdbase, cd, name):
        r = self._oms_to_py.get((cdbase, cd, name))
        if r is not None:
            return r()
        r = self._oms_to_py.get((cdbase, cd, None))
        if r is not None:
            return r(name)
        r = self._oms_to_py.get((cdbase, None, None))
        if r is not None:
            return r(cd,name)
        r = self._oms_to_py.get((None, None, None))
        if r is not None:
            return r(cdbase,cd,name)
        raise ValueError("no entry found for " + cdbase + "?" + cd + "?" + name)

    def to_python(self, omobj):
        """ Convert OpenMath object to Python """
        # general overrides
        if omobj.__class__ in self._omclass_to_py:
            return self._omclass_to_py[omobj.__class__](omobj)
        # oms
        elif isinstance(omobj, om.OMSymbol):
            return self._lookup_to_python(omobj.cdbase, omobj.cd, omobj.name)
        # oma
        elif isinstance(omobj, om.OMApplication):
            elem = self.to_python(omobj.elem)
            arguments = [self.to_python(x) for x in omobj.arguments]
            return elem(*arguments)
        raise ValueError('Cannot convert object of class %s to Python.' % omobj.__class__.__name__)

    def to_openmath(self, obj):
        """ Convert Python object to OpenMath """
        for cl, conv in reversed(self._conv_to_om):
            if cl is None or isinstance(obj, cl):
                try:
                    return conv(obj)
                except CannotConvertError:
                    continue

        if hasattr(obj, '__openmath__'):
            return obj.__openmath__()

        raise ValueError('Cannot convert %r to OpenMath.' % obj)

    def register_to_openmath(self, py_class, converter):
        """Register a conversion from Python to OpenMath

        :param py_class: A Python class the conversion is attached to, or None
        :type py_class: None, type

        :param converter: A conversion function or an OpenMath object
        :type converter: Callable, OMAny

        :rtype: None

        ``converter`` will used to convert any object of type ``py_class``,
        or any object if ``py_class`` is ``None``. If ``converter`` is an
        OpenMath object, it is returned immediately. If it is a callable, it
        is called with the Python object as paramter; in this case, it must
        either return an OpenMath object, or raise an exception.  The
        special exception ``CannotConvertError`` can be used to signify that
        ``converter`` does not know how to convert the current object, and that
        ``to_openmath`` shall continue with the other converters. Any other
        exception stops conversion immediately.

        Converters registered by this function are called in order from the
        most recent to the oldest.
        """
        if py_class is not None and not isclass(py_class):
            raise TypeError('Expected class, found %r' % py_class)
        if not callable(converter) and not isinstance(converter, om.OMAny):
            raise TypeError('Expected callable or openmath.OMAny object, found %r' % converter)
        self._conv_to_om.append((py_class, converter))

    # deprecated, made private for now
    def _deprecated_register_to_python(self, cd, name, converter=None):
        """Register a conversion from OpenMath to Python

        This function has two forms. A three-arguments one:

        :param cd: A content dictionary name
        :type cd: str

        :param name: A symbol name
        :type name: str

        :param converter: A conversion function, or a Python object
        :type: Callable, Any

        Any object of type ``openmath.OMSymbol``, with content
        dictionary equal to ``cd`` and name equal to ``name`` will be converted
        using ``converter``. Also, any object of type ``openmath.OMApplication``
        whose first child is an ``openmath.OMSymbol`` as above will be converted
        using ``converter``. If ``converter`` is a callable, it will be called with the
        OpenMath object as parameter; otherwise ``converter`` will be returned.

        In the two-argument form

        :param cd: A subclass of ``OMAny``
        :type cd: type

        :param name: A conversion function
        :type name: Callable

        Any object of type ``cd`` will be passed to ``name()``, and the
        result will be returned. This forms is mainly to override default
        conversions for basic OpenMath tags (OMInteger, OMString, etc.). It
        is discouraged to use it for ``OMSymbol`` and ``OMApplication``.
        """
        if converter is None:
            if isclass(cd) and issubclass(cd, om.OMAny):
                self._conv_to_py[cd] = name
            else:
                raise TypeError('Two-arguments form expects subclass of openmath.OMAny, found %r' % cd)
        else:
            if isinstance(cd, str) and isinstance(name, str):
                self._conv_sym_to_py[(cd, name)] = converter
            else:
                raise TypeError('Three-arguments form expects string, found %r' % cd.__class__)

    # deprecated, made private for now
    def _deprecated_register(self, py_class, to_om, om_cd, om_name, to_py=None):
        """
        This is a shorthand for:

           ``self.register_to_python(om_cd, om_name, to_py)``
           ``self.register_to_openmath(py_class, to_om)``
        """
        self.register_to_python(om_cd, om_name, to_py)
        self.register_to_openmath(py_class, to_om)

class BasicPythonConverter(Converter):
    """
    adds conversions for basic Python types:
    - bools,
    - ints,
    - floats,
    - complex numbers (recursively),
    - strings,
    - bytes,
    - lists (recursively),
    - sets (recursively).
    """
    # base for OM standard CDs
    _omBase = 'http://www.openmath.org/cd'

    def __init__(self):
        super(BasicPythonConverter, self).__init__()
        # to Python

        # primitive operators
        r = lambda cd,name,py: self.register_to_python_name(self._omBase, cd, name, py)
        r('nums1', 'infinity', float('inf'))
        r('logic1', 'true', True)
        r('logic1', 'false', False)
        r('set1', 'emptyset', set())
        r('set1', 'set', lambda *args: set(args))
        r('list1', 'list', lambda *args: list(args))
        r('complex1', 'complex_cartesian', complex) # this does not work if the arguments are not numbers
        # literals
        s = self.register_to_python_class
        s(om.OMInteger, lambda o: o.integer)
        s(om.OMFloat,   lambda o: o.double)
        s(om.OMString,  lambda o: o.string)
        s(om.OMBytes,   lambda o: o.bytes)

        # to OpenMath
        t = self.register_to_openmath
        def oms(name, cd):
            return om.OMSymbol(name=name, cd=cd, cdbase=self._omBase)

        for int_type in six.integer_types:
            t(int_type, lambda i: om.OMInteger(i))
        for string_type in six.string_types:
            t(string_type, lambda s: om.OMString(s))
        t(bytes, lambda b: om.OMBytes(b))
        # bool should be registered after int: isinstance(True, int) holds!
        t(bool, lambda b: oms(str(b).lower(), 'logic1'))
        def do_float(f):
            if f == float('inf'):
                return oms('infinity', 'nums1')
            else:
                return om.OMFloat(f)
        t(float, do_float)
        t(complex, lambda c: om.OMApplication(oms('complex_cartesian', 'complex1'), map(self.to_openmath, [c.real, c.imag])))
        t(list, lambda l: om.OMApplication(oms('list','list1'), map(self.to_openmath, l)))
        def do_set(s):
            if s:
                return om.OMApplication(oms('set', 'set1'), map(self.to_openmath, s))
            else:
                return oms('emptyset', cd='set1')
        t(set, do_set)

# A default converter instance for convenience
DefaultConverter = BasicPythonConverter()

# Shorthands for backward compatibility (and convenience?)
to_python = DefaultConverter.to_python
to_openmath = DefaultConverter.to_openmath
#register = DefaultConverter.register # not used anymore
register_to_openmath = DefaultConverter.register_to_openmath
register_to_python_class = DefaultConverter.register_to_python_class

class CannotConvertError(RuntimeError):
    """
    Raise this exception if your converter to OpenMath is not able
    to handle the inputs.
    """
    pass


import redis
from flask import g

class Redis(object):

    def __init__(self, app=None):

        if app is not None:
            self.init_app(app)
        else:
            self.app = None

    def init_app(self, app):
        """
        Used to initialize redis with app object
        """

        app.config.setdefault('REDIS_URLS', {
            'main': 'redis://localhost:6379/0',
            'admin': 'redis://localhost:6379/1',
        })

        app.before_request(self.before_request)

        self.app = app

    _redis_connection = {}

    def connect(self, connection_type='main'):
        r_con = self._redis_connection.get(connection_type)
        if r_con:
            return r_con

        self._redis_connection[connection_type] = redis.Redis.from_url(self.app.config['REDIS_URLS'][connection_type])
        return self._redis_connection[connection_type]

    def before_request(self):
        g.r = self.connect()
        g.get_redis_connection = self.connect


#-*- coding: utf-8 -*-
"""
Additional functions for django-any
"""

def valid_choices(choices):
    """
    Return list of choices's keys
    """
    for key, value in choices:
        if isinstance(value, (list, tuple)):
            for key, _ in value:
                yield key
        else:
            yield key

def split_model_kwargs(kw):
    """
    django_any birds language parser
    """
    from collections import defaultdict

    model_fields = {}
    fields_agrs = defaultdict(lambda : {})

    for key in kw.keys():
        if '__' in key:
            field, _, subfield = key.partition('__')
            fields_agrs[field][subfield] = kw[key]
        else:
            model_fields[key] = kw[key]

    return model_fields, fields_agrs

class ExtensionMethod(object):
    """
    Works like one parameter multimethod
    """
    def __init__(self, by_instance=False):
        self.registry = {}
        self.by_instance = by_instance
        self.default = None

    def register(self, field_type, impl=None):
        """
        Register form field data function.

        Could be used as decorator
        """
        def _wrapper(func):
            self.registry[field_type] = func
            return func

        if impl:
            return _wrapper(impl)
        return _wrapper

    def register_default(self, func):
        self.default = func
        return func

    def decorator(self, impl):
        """
        Decorator for register decorators
        """
        self._create_value = impl(self._create_value)
        return impl

    def _create_value(self, *args, **kwargs):
        """
        Lowest value generator.

        Separated from __call__, because it seems that python
        cache __call__ reference on module import
        """
        if not len(args):
            raise TypeError('Object instance is not provided')

        if self.by_instance:
            field_type = args[0]
        else:
            field_type = args[0].__class__

        function = self.registry.get(field_type, self.default)

        if function is None:
            raise TypeError("no match %s" % field_type)

        return function(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        return self._create_value(*args, **kwargs)


# -*- coding: utf-8 -*-
# pylint: disable=W0613, C0103
"""
Django forms data generators

"""
import random
from datetime import date, datetime, time
from django import forms
from django.utils import formats
from django_any import xunit
from django_any.functions import valid_choices, split_model_kwargs, \
    ExtensionMethod

any_form = ExtensionMethod()
any_form_field = ExtensionMethod()

@any_form.register_default
def any_form_default(form_cls, **kwargs):
    """
    Returns tuple with form data and files
    """
    form_data = {}
    form_files = {}

    form_fields, fields_args = split_model_kwargs(kwargs)

    for name, field in form_cls.base_fields.iteritems():
        if name in form_fields:
            form_data[name] = kwargs[name]
        else:
            form_data[name] = any_form_field(field, **fields_args[name])

    return form_data, form_files

@any_form_field.decorator
def field_required_attribute(function):
    """
    Sometimes return None if field is not required

    >>> result = any_form_field(forms.BooleanField(required=False))
    >>> result in ['', 'True', 'False']
    True
    """
    def _wrapper(field, **kwargs):
        if not field.required and random.random < 0.1:
            return None
        return function(field, **kwargs)
    return _wrapper

@any_form_field.decorator
def field_choices_attibute(function):
    """
    Selection from field.choices
    """
    def _wrapper(field, **kwargs):
        if hasattr(field.widget, 'choices'):
            return random.choice(list(valid_choices(field.widget.choices)))
        return function(field, **kwargs)

    return _wrapper

@any_form_field.register(forms.BooleanField)
def boolean_field_data(field, **kwargs):
    """
    Return random value for BooleanField

    >>> result = any_form_field(forms.BooleanField())
    >>> type(result)
    <type 'str'>
    """
    return str(xunit.any_boolean())

@any_form_field.register(forms.CharField)
def char_field_data(field, **kwargs):
    """
    Return random value for CharField
    >>> result = any_form_field(forms.CharField(min_length=3, max_length=10))
    >>> type(result)
    <type 'str'>
    """
    min_length = kwargs.get('min_length', 1)
    max_length = kwargs.get('max_length', field.max_length or 255)
    return xunit.any_string(min_length=field.min_length or min_length,
                            max_length=field.max_length or max_length)

@any_form_field.register(forms.DecimalField)
def decimal_field_data(field, **kwargs):
    """
    Return random value for DecimalField

    >>> result = any_form_field(forms.DecimalField(max_value=100, min_value=11, max_digits=4, decimal_places = 2))
    >>> type(result)
    <type 'str'>
    >>> from decimal import Decimal
    >>> Decimal(result) >= 11, Decimal(result) <= Decimal('99.99')
    (True, True)
    """
    min_value = 0
    max_value = 10
    from django.core.validators import MinValueValidator, MaxValueValidator
    for elem in field.validators:
        if isinstance(elem, MinValueValidator):
            min_value = elem.limit_value
        if isinstance(elem, MaxValueValidator):
            max_value = elem.limit_value
    if (field.max_digits and field.decimal_places):
        from decimal import Decimal
        max_value = min(max_value,
                        Decimal('%s.%s' % ('9'*(field.max_digits-field.decimal_places),
                                           '9'*field.decimal_places)))

    min_value = kwargs.get('min_value') or min_value
    max_value = kwargs.get('max_value') or max_value

    return str(xunit.any_decimal(min_value=min_value,
                             max_value=max_value,
                             decimal_places = field.decimal_places or 2))

@any_form_field.register(forms.EmailField)
def email_field_data(field, **kwargs):
    """
    Return random value for EmailField

    >>> result = any_form_field(forms.EmailField(min_length=10, max_length=30))
    >>> type(result)
    <type 'str'>
    >>> len(result) <= 30, len(result) >= 10
    (True, True)
    """
    max_length = 10
    if field.max_length:
        max_length = (field.max_length -5) / 2
    min_length = 10
    if field.min_length:
        min_length = (field.min_length-4) / 2
    return "%s@%s.%s" % (
        xunit.any_string(min_length=min_length, max_length=max_length),
        xunit.any_string(min_length=min_length, max_length=max_length),
        xunit.any_string(min_length=2, max_length=3))

@any_form_field.register(forms.DateField)
def date_field_data(field, **kwargs):
    """
    Return random value for DateField

    >>> result = any_form_field(forms.DateField())
    >>> type(result)
    <type 'str'>
    """
    from_date = kwargs.get('from_date', date(1990, 1, 1))
    to_date = kwargs.get('to_date', date.today())

    date_format = random.choice(field.input_formats or formats.get_format('DATE_INPUT_FORMATS'))

    return xunit.any_date(from_date=from_date, to_date=to_date).strftime(date_format)

@any_form_field.register(forms.DateTimeField)
def datetime_field_data(field, **kwargs):
    """
    Return random value for DateTimeField

    >>> result = any_form_field(forms.DateTimeField())
    >>> type(result)
    <type 'str'>
    """
    from_date = kwargs.get('from_date', datetime(1990, 1, 1))
    to_date = kwargs.get('to_date', datetime.today())
    date_format = random.choice(field.input_formats or formats.get_format('DATETIME_INPUT_FORMATS'))
    return xunit.any_datetime(from_date=from_date, to_date=to_date).strftime(date_format)

@any_form_field.register(forms.FloatField)
def float_field_data(field, **kwargs):
    """
    Return random value for FloatField

    >>> result = any_form_field(forms.FloatField(max_value=200, min_value=100))
    >>> type(result)
    <type 'str'>
    >>> float(result) >=100, float(result) <=200
    (True, True)
    """
    min_value = 0
    max_value = 100
    from django.core.validators import MinValueValidator, MaxValueValidator
    for elem in field.validators:
        if isinstance(elem, MinValueValidator):
            min_value = elem.limit_value
        if isinstance(elem, MaxValueValidator):
            max_value = elem.limit_value

    min_value = kwargs.get('min_value', min_value)
    max_value = kwargs.get('max_value', max_value)
    precision = kwargs.get('precision', 3)

    return str(xunit.any_float(min_value=min_value, max_value=max_value, precision=precision))

@any_form_field.register(forms.IntegerField)
def integer_field_data(field, **kwargs):
    """
    Return random value for IntegerField

    >>> result = any_form_field(forms.IntegerField(max_value=200, min_value=100))
    >>> type(result)
    <type 'str'>
    >>> int(result) >=100, int(result) <=200
    (True, True)
    """
    min_value = 0
    max_value = 100
    from django.core.validators import MinValueValidator, MaxValueValidator
    for elem in field.validators:
        if isinstance(elem, MinValueValidator):
            min_value = elem.limit_value
        if isinstance(elem, MaxValueValidator):
            max_value = elem.limit_value

    min_value = kwargs.get('min_value', min_value)
    max_value = kwargs.get('max_value', max_value)

    return str(xunit.any_int(min_value=min_value, max_value=max_value))

@any_form_field.register(forms.IPAddressField)
def ipaddress_field_data(field, **kwargs):
    """
    Return random value for IPAddressField

    >>> result = any_form_field(forms.IPAddressField())
    >>> type(result)
    <type 'str'>
    >>> from django.core.validators import ipv4_re
    >>> import re
    >>> re.match(ipv4_re, result) is not None
    True
    """
    choices = kwargs.get('choices')
    if choices:
        return random.choice(choices)
    else:
        nums = [str(xunit.any_int(min_value=0, max_value=255)) for _ in xrange(0, 4)]
        return ".".join(nums)

@any_form_field.register(forms.NullBooleanField)
def null_boolean_field_data(field, **kwargs):
    """
    Return random value for NullBooleanField

    >>> result = any_form_field(forms.NullBooleanField())
    >>> type(result)
    <type 'unicode'>
    >>> result in [u'1', u'2', u'3']
    True
    """
    return random.choice(['None', 'True', 'False'])

@any_form_field.register(forms.SlugField)
def slug_field_data(field, **kwargs):
    """
    Return random value for SlugField

    >>> result = any_form_field(forms.SlugField())
    >>> type(result)
    <type 'str'>
    >>> from django.core.validators import slug_re
    >>> import re
    >>> re.match(slug_re, result) is not None
    True
    """
    min_length = kwargs.get('min_length', 1)
    max_length = kwargs.get('max_length', field.max_length or 20)

    from string import ascii_letters, digits
    letters = ascii_letters + digits + '_-'
    return xunit.any_string(letters = letters, min_length = min_length, max_length = max_length)

@any_form_field.register(forms.URLField)
def url_field_data(field, **kwargs):
    """
    Return random value for URLField

    >>> result = any_form_field(forms.URLField())
    >>> from django.core.validators import URLValidator
    >>> import re
    >>> re.match(URLValidator.regex, result) is not None
    True
    """
    urls = kwargs.get('choices',
                      ['http://news.yandex.ru/society.html',
                       'http://video.google.com/?hl=en&tab=wv',
                       'http://www.microsoft.com/en/us/default.aspx',
                       'http://habrahabr.ru/company/opera/',
                       'http://www.apple.com/support/hardware/',
                                        'http://localhost/',
                       'http://72.14.221.99',
                       'http://fr.wikipedia.org/wiki/France'])

    return random.choice(urls)

@any_form_field.register(forms.TimeField)
def time_field_data(field, **kwargs):
    """
    Return random value for TimeField

    >>> result = any_form_field(forms.TimeField())
    >>> type(result)
    <type 'str'>
    """
    time_format = random.choice(field.input_formats or formats.get_format('TIME_INPUT_FORMATS'))

    return time(xunit.any_int(min_value=0, max_value=23),
                xunit.any_int(min_value=0, max_value=59),
                xunit.any_int(min_value=0, max_value=59)).strftime(time_format)

@any_form_field.register(forms.TypedChoiceField)
@any_form_field.register(forms.ChoiceField)
def choice_field_data(field, **kwargs):
    """
    Return random value for ChoiceField

    >>> CHOICES = [('YNG', 'Child'), ('OLD', 'Parent')]
    >>> result = any_form_field(forms.ChoiceField(choices=CHOICES))
    >>> type(result)
    <type 'str'>
    >>> result in ['YNG', 'OLD']
    True
    >>> typed_result = any_form_field(forms.TypedChoiceField(choices=CHOICES))
    >>> typed_result in ['YNG', 'OLD']
    True
    """
    if field.choices:
        return str(random.choice(list(valid_choices(field.choices))))
    return 'None'

@any_form_field.register(forms.MultipleChoiceField)
def multiple_choice_field_data(field, **kwargs):
    """
    Return random value for MultipleChoiceField

    >>> CHOICES = [('YNG', 'Child'), ('MIDDLE', 'Parent') ,('OLD', 'GrandParent')]
    >>> result = any_form_field(forms.MultipleChoiceField(choices=CHOICES))
    >>> type(result)
    <type 'str'>
    """
    if field.choices:
        from django_any.functions import valid_choices
        l = list(valid_choices(field.choices))
        random.shuffle(l)
        choices = []
        count = xunit.any_int(min_value=1, max_value=len(field.choices))
        for i in xrange(0, count):
            choices.append(l[i])
        return ' '.join(choices)
    return 'None'

@any_form_field.register(forms.models.ModelChoiceField)
def model_choice_field_data(field, **kwargs):
    """
    Return one of first ten items for field queryset
    """
    data = list(field.queryset[:10])
    if data:
        return random.choice(data)
    else:
        raise TypeError('No %s available in queryset' % field.queryset.model)


""" Code used to encode an OpenMath object into XML. """
import base64

from lxml import etree
from lxml.builder import ElementMaker

from . import openmath as om
from . import xml

# Default element maker
default_E = ElementMaker(namespace=xml.openmath_ns,
                         nsmap={ None: xml.openmath_ns })

def encode_xml(obj, E=None):
    """ Encodes an OpenMath object as an XML node.

    :param obj: OpenMath object (or related item) to encode as XML.
    :type obj: OMAny

    :param ns: Namespace prefix to use for
        http://www.openmath.org/OpenMath", or None if default namespace.
    :type ns: str, None

    :return: The XML node representing the OpenMath data structure.
    :rtype: etree._Element
    """

    if E is None:
        E = default_E
    elif isinstance(E, str):
        E = ElementMaker(namespace=xml.openmath_ns,
                         nsmap={ E: xml.openmath_ns })

    name = ""
    attr = {}
    children = []

    if isinstance(obj, om.CDBaseAttribute) and obj.cdbase is not None:
        attr["cdbase"] = obj.cdbase

    if isinstance(obj, om.CommonAttributes) and obj.id is not None:
        attr["id"] = obj.id

    # Wrapper object
    if isinstance(obj, om.OMObject):
        children.append(encode_xml(obj.omel, E))
        attr["version"] = obj.version

    # Derived Objects
    elif isinstance(obj, om.OMReference):
        attr["href"] = obj.href

    # Basic Objects
    elif isinstance(obj, om.OMInteger):
        children.append(str(obj.integer))
    elif isinstance(obj, om.OMFloat):
        attr["dec"] = obj.double
    elif isinstance(obj, om.OMString):
        if obj.string is not None:
            children.append(str(obj.string))
    elif isinstance(obj, om.OMBytes):
        children.append(base64.b64encode(obj.bytes).decode('ascii'))
    elif isinstance(obj, om.OMSymbol):
        attr["name"] = obj.name
        attr["cd"] = obj.cd
    elif isinstance(obj, om.OMVariable):
        attr["name"] = obj.name

    # Derived Elements
    elif isinstance(obj, om.OMForeign):
        attr["encoding"] = obj.encoding
        children.append(str(obj.obj))

    # Compound Elements
    elif isinstance(obj, om.OMApplication):
        children = [encode_xml(obj.elem, E)]
        children.extend(encode_xml(x, E) for x in obj.arguments)
    elif isinstance(obj, om.OMAttribution):
        children = [encode_xml(obj.pairs, E), encode_xml(obj.obj, E)]

    elif isinstance(obj, om.OMAttributionPairs):
        for (k, v) in obj.pairs:
            children.append(encode_xml(k, E))
            children.append(encode_xml(v, E))

    elif isinstance(obj, om.OMBinding):
        children = [
            encode_xml(obj.binder, E),
            encode_xml(obj.vars, E),
            encode_xml(obj.obj, E)
        ]
    elif isinstance(obj, om.OMBindVariables):
        children = [encode_xml(x, E) for x in obj.vars]
    elif isinstance(obj, om.OMAttVar):
        children = [encode_xml(obj.pairs, E), encode_xml(obj.obj, E)]
    elif isinstance(obj, om.OMError):
        children = [encode_xml(obj.name, E)]
        children.extend(encode_xml(x, E) for x in obj.params)
    else:
        raise TypeError("Expected obj to be of type OMAny, found %s." % obj.__class__.__name__)

    attr = dict((k,str(v)) for k, v in attr.items() if v is not None)

    return E(xml.object_to_tag(obj), *children, **attr)

def encode_bytes(obj, nsprefix=None):
    """ Encodes an OpenMath element into a string.

    :param obj: Object to encode as string.
    :type obj: OMAny

    :rtype: bytes
    """

    node = encode_xml(obj, nsprefix)
    return etree.tostring(node)

__all__ = ["encode_xml"]


""" Code used for decoding XML into an OpenMath object. """

from lxml import etree
from pkg_resources import resource_filename
from . import openmath as om
from . import xml
import base64
import io

def decode_bytes(xml, validator=None, snippet=False):
    """ Decodes a stream into an OpenMath object.

    :param xml: XML to decode.
    :type xml: bytes

    :param validator: Validator to use.

    :param snippet: Is this an OpenMath snippet, or a full object?
    :type snippet: Bool

    :rtype: OMAny
    """
    return decode_stream(io.BytesIO(xml), validator, snippet)

def decode_stream(stream, validator=None, snippet=False):
    """ Decodes a stream into an OpenMath object.

    :param stream: Stream to decode.
    :type stream: Any

    :param validator: Validator to use.

    :param snippet: Is this an OpenMath snippet, or a full object?
    :type snippet: Bool

    :rtype: OMAny
    """

    # TODO: Complete the docstring above

    tree = etree.parse(stream)
    if validator is not None:
        validator.assertValid(tree)

    root = tree.getroot()
    v = root.get("version")
    if not snippet and (not v or v != "2.0"):
        raise ValueError("Only OpenMath 2.0 is supported")

    return decode_xml(root)

def decode_xml(elem, _in_bind = False):
    """ Decodes an XML element into an OpenMath object.

    :param elem: Element to decode.
    :type elem: etree._Element

    :param _in_bind: Internal flag used to indicate if we should decode within
    an OMBind.
    :type _in_bind: bool

    :rtype: OMAny
    """

    obj = xml.tag_to_object(elem)
    attrs = {}

    def a2d(*props):
        for p in props:
            attrs[p] = elem.get(p)

    if issubclass(obj, om.CommonAttributes):
        a2d("id")
    if issubclass(obj, om.CDBaseAttribute):
        a2d("cdbase")

    # Root Object
    if issubclass(obj, om.OMObject):
        a2d("version")
        attrs["omel"] = decode_xml(elem[0])

    # Reference Objects
    elif issubclass(obj, om.OMReference):
        a2d("href")

    # Basic Objects
    elif issubclass(obj, om.OMInteger):
        attrs["integer"] = int(elem.text)
    elif issubclass(obj, om.OMFloat):
        # TODO: Support Hex
        attrs["double"] = float(elem.get('dec'))
    elif issubclass(obj, om.OMString):
        attrs["string"] = elem.text
    elif issubclass(obj, om.OMBytes):
        try:
            attrs["bytes"] = base64.b64decode(elem.text)
        except TypeError:
            attrs["bytes"] = base64.b64decode(bytes(elem.text, "ascii"))
    elif issubclass(obj, om.OMSymbol):
        a2d("name", "cd")
    elif issubclass(obj, om.OMVariable):
        a2d("name")

    # Derived Elements
    elif issubclass(obj, om.OMForeign):
        attrs["obj"] = elem.text
        a2d("encoding")

    # Compound Elements
    elif issubclass(obj, om.OMApplication):
        attrs["elem"] = decode_xml(elem[0])
        attrs["arguments"] = list(map(decode_xml, elem[1:]))
    elif issubclass(obj, om.OMAttribution):
        attrs["pairs"] = decode_xml(elem[0])
        attrs["obj"] = decode_xml(elem[1])
    elif issubclass(obj, om.OMAttributionPairs):
        if not _in_bind:
            attrs["pairs"] = [(decode_xml(k), decode_xml(v)) for k, v in zip(elem[::2], elem[1::2])]
        else:
            obj = om.OMAttVar
            attrs["pairs"] = decode_xml(elem[0], True)
            attrs["obj"] = decode_xml(elem[1], True)
    elif issubclass(obj, om.OMBinding):
        attrs["binder"] = decode_xml(elem[0])
        attrs["vars"] = decode_xml(elem[1])
        attrs["obj"] = decode_xml(elem[2])
    elif issubclass(obj, om.OMBindVariables):
        attrs["vars"] = list(map(lambda x:decode_xml(x, True), elem[:]))
    elif issubclass(obj, om.OMError):
        attrs["name"] = decode_xml(elem[0])
        attrs["params"] = list(map(decode_xml, elem[1:]))

    else:
        raise TypeError("Expected OMAny, found %s." % obj.__name__)

    return obj(**attrs)

def decode_xml_binding():
    pass

def normative_validator():
    """ Load the normative validator provided by the OpenMath foundation. """

    relaxng_doc = etree.parse(resource_filename(__name__, 'openmath2.rng'))
    return etree.RelaxNG(relaxng_doc)


"""Summary."""

from fabric.api import local, task

from accounting import __version__

@task
def install(version=""):
    """Install project artifacts.

    Args:
        version (str, optional): Description
    """
    local("pip install -r requirements.txt")
    local("pip install -r test-requirements.txt")

@task
def clean():
    """Remove all the .pyc files."""
    local("find . -name '*.pyc' -print0|xargs -0 rm", capture=False)
    # Remove the dist folder
    local("rm -rf ./dist && rm -rf accounting.egg-info")
    local("rm -rf **__pycache__* && rm -rf coverage*")
    local("rm -rf ./temp && rm -rf ./build")

@task
def push(msg):
    """Push to github.

    Args:
        msg (str, required): Description
    """
    clean()
    local("git add . && git commit -m '{}'".format(msg))
    local("git push")

@task
def publish(msg="checkpoint: publish package"):
    """Deploy the app to PYPI.

    Args:
        msg (str, optional): Description
    """
    test = check()
    if test.succeeded:
        # clean()
        # push(msg)
        sdist = local("python setup.py sdist")
        if sdist.succeeded:
            build = local(
                'python setup.py build && python setup.py bdist_egg')
            if build.succeeded:
                upload = local("twine upload dist/*")
                if upload.succeeded:
                    tag()

@task
def check():
    """Test project."""
    test = local(
        "coverage erase && nosetests  --with-coverage "
        "--cover-package=accounting/"
    )

@task
def tag(version=__version__):
    """Deploy a version tag."""
    build = local("git tag {0}".format(version))
    if build.succeeded:
        local("git push --tags")


#-*- coding: utf-8 -*-
# pylint: disable=E0102, W0613
"""
Values generators for common Django Fields
"""
import re, os, random
from decimal import Decimal
from datetime import date, datetime, time
from string import ascii_letters, digits
from random import choice

from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models, IntegrityError
from django.db.models import Q
from django.db.models.fields.files import FieldFile
from django.contrib.webdesign.lorem_ipsum import paragraphs

from django_any import xunit
from django_any.functions import valid_choices, split_model_kwargs, \
    ExtensionMethod

any_field = ExtensionMethod()
any_model = ExtensionMethod(by_instance=True)

@any_field.decorator
def any_field_blank(function):
    """
    Sometimes return None if field could be blank
    """
    def wrapper(field, **kwargs):
        if kwargs.get('isnull', False):
            return None

        if field.blank and random.random < 0.1:
            return None
        return function(field, **kwargs)
    return wrapper

@any_field.decorator
def any_field_choices(function):
    """
    Selection from field.choices

    >>> CHOICES = [('YNG', 'Child'), ('OLD', 'Parent')]
    >>> result = any_field(models.CharField(max_length=3, choices=CHOICES))
    >>> result in ['YNG', 'OLD']
    True
    """
    def wrapper(field, **kwargs):
        if field.choices:
            return random.choice(list(valid_choices(field.choices)))
        return function(field, **kwargs)

    return wrapper

@any_field.register(models.BigIntegerField)
def any_biginteger_field(field, **kwargs):
    """
    Return random value for BigIntegerField

    >>> result = any_field(models.BigIntegerField())
    >>> type(result)
    <type 'long'>
    """
    min_value = kwargs.get('min_value', 1)
    max_value = kwargs.get('max_value', 10**10)
    return long(xunit.any_int(min_value=min_value, max_value=max_value))

@any_field.register(models.BooleanField)
def any_boolean_field(field, **kwargs):
    """
    Return random value for BooleanField

    >>> result = any_field(models.BooleanField())
    >>> type(result)
    <type 'bool'>
    """
    return xunit.any_boolean()

@any_field.register(models.PositiveIntegerField)
def any_positiveinteger_field(field, **kwargs):
    """
    An positive integer

    >>> result = any_field(models.PositiveIntegerField())
    >>> type(result)
    <type 'int'>
    >>> result > 0
    True
    """
    min_value = kwargs.get('min_value', 1)
    max_value = kwargs.get('max_value', 9999)
    return xunit.any_int(min_value=min_value, max_value=max_value)

@any_field.register(models.CharField)
def any_char_field(field, **kwargs):
    """
    Return random value for CharField

    >>> result = any_field(models.CharField(max_length=10))
    >>> type(result)
    <type 'str'>
    """
    min_length = kwargs.get('min_length', 1)
    max_length = kwargs.get('max_length', field.max_length)
    return xunit.any_string(min_length=min_length, max_length=max_length)

@any_field.register(models.CommaSeparatedIntegerField)
def any_commaseparatedinteger_field(field, **kwargs):
    """
    Return random value for CharField

    >>> result = any_field(models.CommaSeparatedIntegerField(max_length=10))
    >>> type(result)
    <type 'str'>
    >>> [int(num) for num in result.split(',')] and 'OK'
    'OK'
    """
    nums_count = field.max_length/2
    nums = [str(xunit.any_int(min_value=0, max_value=9)) for _ in xrange(0, nums_count)]
    return ",".join(nums)

@any_field.register(models.DateField)
def any_date_field(field, **kwargs):
    """
    Return random value for DateField,
    skips auto_now and auto_now_add fields

    >>> result = any_field(models.DateField())
    >>> type(result)
    <type 'datetime.date'>
    """
    if field.auto_now or field.auto_now_add:
        return None
    from_date = kwargs.get('from_date', date(1990, 1, 1))
    to_date = kwargs.get('to_date', date.today())
    return xunit.any_date(from_date=from_date, to_date=to_date)

@any_field.register(models.DateTimeField)
def any_datetime_field(field, **kwargs):
    """
    Return random value for DateTimeField,
    skips auto_now and auto_now_add fields

    >>> result = any_field(models.DateTimeField())
    >>> type(result)
    <type 'datetime.datetime'>
    """
    from_date = kwargs.get('from_date', datetime(1990, 1, 1))
    to_date = kwargs.get('to_date', datetime.today())
    return xunit.any_datetime(from_date=from_date, to_date=to_date)

@any_field.register(models.DecimalField)
def any_decimal_field(field, **kwargs):
    """
    Return random value for DecimalField

    >>> result = any_field(models.DecimalField(max_digits=5, decimal_places=2))
    >>> type(result)
    <class 'decimal.Decimal'>
    """
    min_value = kwargs.get('min_value', 0)
    max_value = kwargs.get('max_value',
                           Decimal('%s.%s' % ('9'*(field.max_digits-field.decimal_places),
                                              '9'*field.decimal_places)))
    decimal_places = kwargs.get('decimal_places', field.decimal_places)
    return xunit.any_decimal(min_value=min_value, max_value=max_value,
                             decimal_places = decimal_places)

@any_field.register(models.EmailField)
def any_email_field(field, **kwargs):
    """
    Return random value for EmailField

    >>> result = any_field(models.EmailField())
    >>> type(result)
    <type 'str'>
    >>> re.match(r"(?:^|\s)[-a-z0-9_.]+@(?:[-a-z0-9]+\.)+[a-z]{2,6}(?:\s|$)", result, re.IGNORECASE) is not None
    True
    """
    return "%s@%s.%s" % (xunit.any_string(max_length=10),
                         xunit.any_string(max_length=10),
                         xunit.any_string(min_length=2, max_length=3))

@any_field.register(models.FloatField)
def any_float_field(field, **kwargs):
    """
    Return random value for FloatField

    >>> result = any_field(models.FloatField())
    >>> type(result)
    <type 'float'>
    """
    min_value = kwargs.get('min_value', 1)
    max_value = kwargs.get('max_value', 100)
    precision = kwargs.get('precision', 3)
    return xunit.any_float(min_value=min_value, max_value=max_value, precision=precision)

@any_field.register(models.FileField)
def any_file_field(field, **kwargs):
    """
    Lookup for nearest existing file

    """
    def get_some_file(path):
        subdirs, files = field.storage.listdir(path)

        if files:
            result_file = random.choice(files)
            instance = field.storage.open("%s/%s" % (path, result_file)).file
            return FieldFile(instance, field, result_file)

        for subdir in subdirs:
            result = get_some_file("%s/%s" % (path, subdir))
            if result:
                return result

    result = get_some_file(field.upload_to)

    if result is None and not field.null:
        raise TypeError("Can't found file in %s for non nullable FileField" % field.upload_to)
    return result

@any_field.register(models.FilePathField)
def any_filepath_field(field, **kwargs):
    """
    Lookup for nearest existing file

    """
    def get_some_file(path):
        subdirs, files = [], []
        for entry in os.listdir(path):
            entry_path = os.path.join(path, entry)
            if os.path.isdir(entry_path):
                subdirs.append(entry_path)
            else:
                if not field.match or re.match(field.match,entry):
                    files.append(entry_path)

        if files:
            return random.choice(files)

        if field.recursive:
            for subdir in subdirs:
                result = get_some_file(subdir)
                if result:
                    return result

    result = get_some_file(field.path)
    if result is None and not field.null:
        raise TypeError("Can't found file in %s for non nullable FilePathField" % field.path)
    return result

@any_field.register(models.IPAddressField)
def any_ipaddress_field(field, **kwargs):
    """
    Return random value for IPAddressField
    >>> result = any_field(models.IPAddressField())
    >>> type(result)
    <type 'str'>
    >>> from django.core.validators import ipv4_re
    >>> re.match(ipv4_re, result) is not None
    True
    """
    nums = [str(xunit.any_int(min_value=0, max_value=255)) for _ in xrange(0, 4)]
    return ".".join(nums)

@any_field.register(models.NullBooleanField)
def any_nullboolean_field(field, **kwargs):
    """
    Return random value for NullBooleanField
    >>> result = any_field(models.NullBooleanField())
    >>> result in [None, True, False]
    True
    """
    return random.choice([None, True, False])

@any_field.register(models.PositiveSmallIntegerField)
def any_positivesmallinteger_field(field, **kwargs):
    """
    Return random value for PositiveSmallIntegerField
    >>> result = any_field(models.PositiveSmallIntegerField())
    >>> type(result)
    <type 'int'>
    >>> result < 256, result > 0
    (True, True)
    """
    min_value = kwargs.get('min_value', 1)
    max_value = kwargs.get('max_value', 255)
    return xunit.any_int(min_value=min_value, max_value=max_value)

@any_field.register(models.SlugField)
def any_slug_field(field, **kwargs):
    """
    Return random value for SlugField
    >>> result = any_field(models.SlugField())
    >>> type(result)
    <type 'str'>
    >>> from django.core.validators import slug_re
    >>> re.match(slug_re, result) is not None
    True
    """
    letters = ascii_letters + digits + '_-'
    return xunit.any_string(letters = letters, max_length = field.max_length)

@any_field.register(models.SmallIntegerField)
def any_smallinteger_field(field, **kwargs):
    """
    Return random value for SmallIntegerValue
    >>> result = any_field(models.SmallIntegerField())
    >>> type(result)
    <type 'int'>
    >>> result > -256, result < 256
    (True, True)
    """
    min_value = kwargs.get('min_value', -255)
    max_value = kwargs.get('max_value', 255)
    return xunit.any_int(min_value=min_value, max_value=max_value)

@any_field.register(models.IntegerField)
def any_integer_field(field, **kwargs):
    """
    Return random value for IntegerField
    >>> result = any_field(models.IntegerField())
    >>> type(result)
    <type 'int'>
    """
    min_value = kwargs.get('min_value', -10000)
    max_value = kwargs.get('max_value', 10000)
    return xunit.any_int(min_value=min_value, max_value=max_value)

@any_field.register(models.TextField)
def any_text_field(field, **kwargs):
    """
    Return random 'lorem ipsum' Latin text
    >>> result = any_field(models.TextField())
    >>> from django.contrib.webdesign.lorem_ipsum import COMMON_P
    >>> result[0] == COMMON_P
    True
    """
    return paragraphs(10)

@any_field.register(models.URLField)
def any_url_field(field, **kwargs):
    """
    Return random value for URLField
    >>> result = any_field(models.URLField())
    >>> from django.core.validators import URLValidator
    >>> re.match(URLValidator.regex, result) is not None
    True
    """
    url = kwargs.get('url')

    if not url:
        verified = [validator for validator in field.validators \
                    if isinstance(validator, validators.URLValidator) and \
                    validator.verify_exists == True]
        if verified:
            url = choice(['http://news.yandex.ru/society.html',
                          'http://video.google.com/?hl=en&tab=wv',
                          'http://www.microsoft.com/en/us/default.aspx',
                          'http://habrahabr.ru/company/opera/',
                          'http://www.apple.com/support/hardware/',
                          'http://ya.ru',
                          'http://google.com',
                          'http://fr.wikipedia.org/wiki/France'])
        else:
            url = "http://%s.%s/%s" % (
                xunit.any_string(max_length=10),
                xunit.any_string(min_length=2, max_length=3),
                xunit.any_string(max_length=20))

    return url

@any_field.register(models.TimeField)
def any_time_field(field, **kwargs):
    """
    Return random value for TimeField
    >>> result = any_field(models.TimeField())
    >>> type(result)
    <type 'datetime.time'>
    """
    return time(
        xunit.any_int(min_value=0, max_value=23),
        xunit.any_int(min_value=0, max_value=59),
        xunit.any_int(min_value=0, max_value=59))

@any_field.register(models.ForeignKey)
def any_foreignkey_field(field, **kwargs):
    return any_model(field.rel.to, **kwargs)

@any_field.register(models.OneToOneField)
def any_onetoone_field(field, **kwargs):
    return any_model(field.rel.to, **kwargs)

def _fill_model_fields(model, **kwargs):
    model_fields, fields_args = split_model_kwargs(kwargs)

    # fill local fields
    for field in model._meta.fields:
        if field.name in model_fields:
            if isinstance(kwargs[field.name], Q):
                """
                Lookup ForeingKey field in db
                """
                key_field = model._meta.get_field(field.name)
                value = key_field.rel.to.objects.get(kwargs[field.name])
                setattr(model, field.name, value)
            else:
                # TODO support any_model call
                setattr(model, field.name, kwargs[field.name])
        elif isinstance(field, models.OneToOneField) and field.rel.parent_link:
            """
            skip link to parent instance
            """
        elif isinstance(field, models.fields.AutoField):
            """
            skip primary key field
            """
        else:
            setattr(model, field.name, any_field(field, **fields_args[field.name]))

    # procceed reversed relations
    onetoone = [(relation.var_name, relation.field) \
                for relation in model._meta.get_all_related_objects() \
                if relation.field.unique] # TODO and not relation.field.rel.parent_link ??
    for field_name, field in onetoone:
        if field_name in model_fields:
            # TODO support any_model call
            setattr(model, field_name, kwargs[field_name])

@any_model.register_default
def any_model_default(model_cls, **kwargs):
    result = model_cls()

    attempts = 10
    while True:
        try:
            _fill_model_fields(result, **kwargs)
            result.full_clean()
            result.save()
            return result
        except (IntegrityError, ValidationError):
            attempts -= 1
            if not attempts:
                raise


# coding: utf-8
"""
A generic OpenMath exporter for Python based on the pickle protocol

Note: the current implementation works only with Python 2, due to
reliance on internals of Python's pickle that have changed in Python 3.

EXAMPLES::

    >>> from openmath.convert_pickle import to_python, to_openmath, test_openmath

Strings::

    >>> to_openmath('coucou')
    OMString(string='coucou', id=None)
    >>> to_python(_)
    'coucou'

Unicode strings are not yet supported

    > to_openmath(u'coucou')           # doctest: +SKIP
    OMString(string='coucou', id=None)
    > to_python(_)                     # doctest: +SKIP
    'coucou'

Integers::

    >>> to_openmath(3)
    OMInteger(integer=3, id=None)
    >>> to_python(_)
    3
    >>> test_openmath(3)

Sage integers::

    sage: to_openmath(3)
    OMApplication(elem=OMSymbol(name='make_integer', cd='sage.rings.integer', id=None, cdbase='http://python.org/'),
                  arguments=[OMString(string='3', id=None)], id=None, cdbase=None)
    sage: test_openmath(3)

Sage real numbers::

    sage: test_openmath(1.1+1.1)

But not quite yet real literals::

    sage: test_openmath(1.1)
    Traceback (most recent call last):
    ...
    AssertionError
    sage: l = 1.1
    sage: o = to_openmath(1.1)
    sage: l2 = to_python(o); l2
    1.10000000000000
    sage: l2 == l
    True
    sage: type(l2)
    <type 'sage.rings.real_mpfr.RealNumber'>
    sage: type(l)
    <type 'sage.rings.real_mpfr.RealLiteral'>

Lists of sets of Sage integers::

    sage: test_openmath([{1,3}, {2}])

Dictionaries::

    >>> test_openmath({})
    >>> test_openmath({1:3})

Class instances::
    >>> import openmath.convert_pickle
    >>> class A(object):
    ...     def __eq__(self, other):
    ...         return type(self) is type(other) and self.__dict__ == other.__dict__
    >>> import __main__; __main__.A = A     # Usual trick
    >>> openmath.convert_pickle.A = A       # Idem
    >>> a = A()
    >>> test_openmath(a)
    >>> a.foo = 1
    >>> a.bar = 4
    >>> test_openmath(a)

Functions::

    >>> from openmath import openmath as om
    >>> f = om.OMSymbol(cdbase="http://python.org/", cd='math', name='sin')
    >>> o = om.OMApplication(f, [om.OMFloat(3.14)])
    >>> to_python(o)
    0.0015926529164868282
    >>> import math
    >>> test_openmath(math.sin)

Sage parents::

    sage: test_openmath(Partitions(3))
    sage: test_openmath(QQ)
    sage: test_openmath(RR)
    sage: test_openmath(Algebras(QQ))
    sage: test_openmath(SymmetricFunctions(QQ))
    sage: test_openmath(SymmetricFunctions(QQ).s())
    sage: test_openmath(GF(3))

Sage objects::

    sage: to_openmath(Partition([2,1]))
    OMApplication(...)
    sage: test_openmath(Partition([2,1]))
"""

from __future__ import absolute_import
import importlib

import openmath.convert
from openmath import openmath as om

import zlib

import six
import six.moves

import pickle

##############################################################################
# PickleConverter -- main class
##############################################################################

class PickleConverter(object):
    importPythonBasics = {"list": lambda *args:list(args), "tuple": lambda *args:tuple(args), "none": None, "dict": lambda *args: dict(args),
                          "true":True, "false": False,
                          "getattr": lambda x,f: getattr(x,f)}

    def __init__(self):
        self._cdbase="http://python.org/"
        self._basic_converter = openmath.convert.BasicPythonConverter()
        self._basic_converter.register_to_python_cd(base=self._cdbase, cd="Python", py=lambda name: PickleConverter.importPythonBasics[name])
        self._basic_converter.register_to_python_cdbase(base=self._cdbase, py=load_python_global)

    def to_python(self, obj):
        return self._basic_converter.to_python(obj)

    def to_openmath(self, o=None, sobj=None):
        assert o is None or sobj is None

        if o is not None:
            try:
                return self._basic_converter.to_openmath(o)
            except openmath.convert.CannotConvertError:
                pass
            except ValueError:
                pass

        if sobj is None:
            sobj = pickle.dumps(o, protocol=2)
        file = six.BytesIO(sobj)

        return OMUnpickler(file, self).load()

    ##############################################################################
    # Some new OpenMath constructs
    # Those are mostly lazy versions of unpickling operations (TODO: update this comment)
    ##############################################################################

    def OMSymbol(self, module, name):
        r"""
        Helper function to build an OMS object

        EXAMPLES::

            >>> from openmath.convert_pickle import PickleConverter
            >>> converter = PickleConverter()
            >>> o = converter.OMSymbol(module="foo.bar", name="baz"); o
            OMSymbol(name='baz', cd='foo.bar', id=None, cdbase='http://python.org/')
        """
        return om.OMSymbol(cdbase=self._cdbase, cd=module, name=name)

    def OMNone(self):
        r"""
        Return an OM object for :obj:`None`

        EXAMPLES::

            >>> from openmath.convert_pickle  import PickleConverter
            >>> converter = PickleConverter()
            >>> o = converter.OMNone(); o
            OMSymbol(name='none', cd='Python', id=None, cdbase='http://python.org/')
            >>> converter.to_python(o)
        """
        return self.OMSymbol('Python', name='none')

    def OMBool(self, b):
        r"""
        Return an OM object for booleans

        EXAMPLES::

            >>> from openmath.convert_pickle  import PickleConverter
            >>> converter = PickleConverter()
            >>> o = converter.OMBool(True); o
            OMSymbol(name='true', cd='Python', id=None, cdbase='http://python.org/')
            >>> converter.to_python(o)
            True
            >>> o = converter.OMBool(False); o
            OMSymbol(name='false', cd='Python', id=None, cdbase='http://python.org/')
            >>> converter.to_python(o)
            False
        """
        if b:
            return self.OMSymbol("Python", "true")
        else:
            return self.OMSymbol("Python", "false")

    def OMList(self, l):
        """
        Convert a list of OM objects into an OM object

        EXAMPLES::

            >>> from openmath import openmath as om
            >>> from openmath.convert_pickle  import PickleConverter
            >>> converter = PickleConverter()
            >>> o = converter.OMList([om.OMInteger(2), om.OMInteger(2)]); o
            OMApplication(elem=OMSymbol(name='list', cd='Python', id=None, cdbase='http://python.org/'),
                     arguments=[OMInteger(integer=2, id=None),
                                OMInteger(integer=2, id=None)],
                     id=None, cdbase=None)
            >>> converter.to_python(o)
            [2, 2]
        """
        # Except for the conversion of operands, this duplicates the default
        # implementation of python's list conversion to openmath in py_openmath
        return om.OMApplication(elem=om.OMSymbol(cdbase=self._cdbase, cd='Python', name='list', ), arguments=l)

    def OMTuple(self, l):
        """
        Convert a tuple of OM objects into an OM object

        EXAMPLES::

            >>> from openmath import openmath as om
            >>> from openmath.convert_pickle  import PickleConverter
            >>> converter = PickleConverter()
            >>> o = converter.OMTuple([om.OMInteger(2), om.OMInteger(3)]); o
            OMApplication(elem=OMSymbol(name='tuple', cd='Python', id=None, cdbase='http://python.org/'),
                     arguments=[OMInteger(integer=2, id=None), OMInteger(integer=3, id=None)], id=None, cdbase=None)
            >>> converter.to_python(o)
            (2, 3)
        """
        return om.OMApplication(elem=self.OMSymbol(module='Python', name='tuple'),
                                arguments=l)

    def OMDict(self, items):
        """
        Convert a dictionary (or list of items thereof) of OM objects into an OM object

        EXAMPLES::

            >>> from openmath import openmath as om
            >>> from openmath.convert_pickle  import PickleConverter
            >>> converter = PickleConverter()
            >>> a = om.OMInteger(1)
            >>> b = om.OMInteger(3)
            >>> o = converter.OMDict([(a,b), (b,b)]); print(o)
            OMApplication(
              elem=OMSymbol(name='dict', cd='Python', cdbase='http://python.org/'),
              arguments=[
                OMApplication(
                  elem=OMSymbol(name='tuple', cd='Python', cdbase='http://python.org/'),
                  arguments=[
                    OMInteger(integer=1),
                    OMInteger(integer=3)]),
                OMApplication(
                  elem=OMSymbol(name='tuple', cd='Python', cdbase='http://python.org/'),
                  arguments=[
                    OMInteger(integer=3),
                    OMInteger(integer=3)])])
            >>> converter.to_python(o)
            {1: 3, 3: 3}
        """
        return om.OMApplication(elem=self.OMSymbol(module='Python', name='dict'),
                                arguments=[self.OMTuple(item) for item in items])

def load_python_global(module, name):
    """
    Evaluate an OpenMath symbol describing a global Python object

    EXAMPLES::

        >>> from openmath.convert_pickle import to_python
        >>> from openmath.convert_pickle  import load_python_global
        >>> load_python_global('math', 'sin')
        <built-in function sin>

        >>> from openmath import openmath as om
        >>> o = om.OMSymbol(cdbase="http://python.org/", cd='math', name='sin')
        >>> to_python(o)
        <built-in function sin>
    """

    # The builtin module has been renamed in python3
    if module == '__builtin__' and six.PY3:
        module = 'builtins'
    module = importlib.import_module(module)
    return getattr(module, name)

##############################################################################
# Generic Object Constructors for loading objects
##############################################################################

def tuple_from_sequence(*args):
    """
    Construct a tuple from a sequence (not list!) of objects

    EXAMPLES::

        >>> from openmath.convert_pickle import tuple_from_sequence
        >>> tuple_from_sequence(1, 2, 3)
        (1, 2, 3)
        >>> tuple([1, 2, 3])
        (1, 2, 3)
    """
    return tuple(args)

def cls_new(cls, *args):
    return cls.__new__(cls, *args)

def cls_build(inst, state):
    """
    Apply the setstate protocol to initialize `inst` from `state`.

    INPUT:

    - ``inst`` -- a raw instance of a class
    - ``state`` -- the state to restore; typically a dictionary mapping attribute names to their values

    EXAMPLES::

        >>> from openmath.convert_pickle import cls_build
        >>> class A(object): pass
        >>> inst = A.__new__(A)
        >>> state = {"foo": 1, "bar": 4}
        >>> inst2 = cls_build(inst,state)
        >>> inst is inst2
        True
        >>> inst.foo
        1
        >>> inst.bar
        4
    """
    # Copied from Pickler.load_build
    setstate = getattr(inst, "__setstate__", None)
    if setstate:
        setstate(state)
        return inst
    slotstate = None
    if isinstance(state, tuple) and len(state) == 2:
        state, slotstate = state

    if state:
        try:
            d = inst.__dict__
            try:
                for k, v in six.iteritems(state):
                    d[six.moves.intern(k)] = v
            # keys in state don't have to be strings
            # don't blow up, but don't go out of our way
            except TypeError:
                d.update(state)

        except RuntimeError:
            # XXX In restricted execution, the instance's __dict__
            # is not accessible.  Use the old way of unpickling
            # the instance variables.  This is a semantic
            # difference when unpickling in restricted
            # vs. unrestricted modes.
            # Note, however, that cPickle has never tried to do the
            # .update() business, and always uses
            #     PyObject_SetItem(inst.__dict__, key, value) in a
            # loop over state.items().
            for k, v in state.items():
                setattr(inst, k, v)
    if slotstate:
        for k, v in slotstate.items():
            setattr(inst, k, v)
    return inst

##############################################################################
# Unpickler -- turns a pickled object into OpenMath
##############################################################################

# check if we have the dispatch attribute on the Unpickler class
# if not, fall back to the _Unpickler version (python3)
if hasattr(pickle.Unpickler, 'dispatch'):
    _Unpickler = pickle.Unpickler
else:
    _Unpickler = pickle._Unpickler

class OMUnpickler(_Unpickler):
    """
    An unpickler that constructs an OpenMath object whose later
    conversion to evaluation will produce the desired Python object.

    This can be seen as a lazy unpickler that produces an OpenMath
    object as intermediate step.
    """
    def __init__(self, file, converter):
        _Unpickler.__init__(self, file)
        self._converter = converter

    # we need to do this twice to enable stuff
    dispatch = dict(_Unpickler.dispatch)

    # Finalization
    def load_stop(self):
        value = self.stack.pop()
        value = self.finalize(value)
        raise pickle._Stop(value)
    dispatch[pickle.STOP] = load_stop    # Python 2
    dispatch[pickle.STOP[0]] = load_stop # Python 3

    def finalize(self, value):
        converter = self._converter
        if isinstance(value, om.OMApplication):
            for i in range(len(value.arguments)):
                value.arguments[i] = self.finalize(value.arguments[i])
        if isinstance(value, om.OMAny):
            return value
        elif isinstance(value, bool):
            return OMBool(value)
        elif isinstance(value, list):
            return converter.OMList([self.finalize(arg) for arg in value])
        elif isinstance(value, tuple):
            return converter.OMTuple([self.finalize(arg) for arg in value])
        elif isinstance(value, dict):
            return converter.OMDict([(self.finalize(key), self.finalize(v))
                            for key, v in value.items()])
        elif isinstance(value, six.string_types):
            return om.OMString(string=value)
        elif value is None:
            return converter.OMNone()
        else:
            return converter._basic_converter.to_openmath(value)

    def load_global(self):
        module = self.readline()[:-1].decode("utf-8")
        name = self.readline()[:-1].decode("utf-8")
        #func_name = module+"."+name
        # Variant: om.OMSymbol(name='func_name', **cd_of(func_name))
        # where cd_of would return {'cdbase': 'sagemath.org', 'cd': 'sagemath'}
        #self.append(om.OMApplication(om.OMSymbol(name='load_global', cd='python'),
        #                             [om.OMString(func_name)]))
        #def func(*args):
        #    return om.OMApplication(om.OMSymbol(name='apply_function', cd='python'),
        #                            (om.OMString(func_name),)+args)
        #self.append(func)
        OMSymbol = self._converter.OMSymbol
        self.append(OMSymbol(module=module, name=name))
    dispatch[pickle.GLOBAL[0]] = load_global

    def load_reduce(self):
        stack = self.stack
        args = stack.pop()
        func = stack[-1]
        stack[-1] = om.OMApplication(func, args)
    dispatch[pickle.REDUCE[0]] = load_reduce

    def load_newobj(self):
        args = self.stack.pop()
        cls = self.stack.pop()
        OMSymbol = self._converter.OMSymbol
        obj =  om.OMApplication(OMSymbol(module="openmath.convert_pickle", name='cls_new'),
                                (cls,) + args)
        self.append(obj)
    dispatch[pickle.NEWOBJ[0]] = load_newobj

    def load_build(self):
        stack = self.stack
        state = stack.pop()
        inst = stack.pop()
        OMSymbol = self._converter.OMSymbol
        obj =  om.OMApplication(OMSymbol(module="openmath.convert_pickle", name='cls_build'),
                                (inst, state))
        self.append(obj)
    dispatch[pickle.BUILD[0]] = load_build

##############################################################################
# Shorthands
##############################################################################

pickle_converter=PickleConverter()
to_openmath = pickle_converter.to_openmath
to_python = pickle_converter.to_python

def OMloads(str):
    str = zlib.decompress(str)
    file = six.BytesIO(str)
    return OMUnpickler(file).load()

def test_openmath(l):
    """
    Test that we can convert an object to openmath and back

    EXAMPLES::

        >>> from openmath.convert_pickle import test_openmath
        >>> test_openmath([1, 2, 3])
    """
    o = to_openmath(l)
    l2 = to_python(o)
    assert l == l2
    assert type(l) is type(l2)

"""

# In[73]:

# In[74]:

# In[75]:

class A:
    pass
a = A()
a.x=3

# In[76]:

t = OMloads(dumps(a))
print(t)
a2 = to_python(t)
print(a2)

# In[70]:

a2.x

"""


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals, division, print_function

def decode(data):
    """
    Decodes a PackBit encoded data.
    """
    data = bytearray(data) # <- python 2/3 compatibility fix
    result = bytearray()
    pos = 0
    while pos < len(data):
        header_byte = data[pos]
        if header_byte > 127:
            header_byte -= 256
        pos += 1

        if 0 <= header_byte <= 127:
            result.extend(data[pos:pos+header_byte+1])
            pos += header_byte+1
        elif header_byte == -128:
            pass
        else:
            result.extend([data[pos]] * (1 - header_byte))
            pos += 1

    return bytes(result)

def encode(data):
    """
    Encodes data using PackBits encoding.
    """
    if len(data) == 0:
        return data

    if len(data) == 1:
        return b'\x00' + data

    data = bytearray(data)

    result = bytearray()
    buf = bytearray()
    pos = 0
    repeat_count = 0
    MAX_LENGTH = 127

    # we can safely start with RAW as empty RAW sequences
    # are handled by finish_raw()
    state = 'RAW'

    def finish_raw():
        if len(buf) == 0:
            return
        result.append(len(buf)-1)
        result.extend(buf)
        buf[:] = bytearray()

    def finish_rle():
        result.append(256-(repeat_count - 1))
        result.append(data[pos])

    while pos < len(data)-1:
        current_byte = data[pos]

        if data[pos] == data[pos+1]:
            if state == 'RAW':
                # end of RAW data
                finish_raw()
                state = 'RLE'
                repeat_count = 1
            elif state == 'RLE':
                if repeat_count == MAX_LENGTH:
                    # restart the encoding
                    finish_rle()
                    repeat_count = 0
                # move to next byte
                repeat_count += 1

        else:
            if state == 'RLE':
                repeat_count += 1
                finish_rle()
                state = 'RAW'
                repeat_count = 0
            elif state == 'RAW':
                if len(buf) == MAX_LENGTH:
                    # restart the encoding
                    finish_raw()

                buf.append(current_byte)

        pos += 1

    if state == 'RAW':
        buf.append(data[pos])
        finish_raw()
    else:
        repeat_count += 1
        finish_rle()

    return bytes(result)


"""Base Project Module.

The MIT License (MIT)

Copyright (c) 2016 Ojengwa Bernard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re

from .utils import is_str, check_type, is_num

class Accounting(object):
    """docstring for Accounting.

    Attributes:
        settings (dict): The library's settings configuration object. Contains
         default parameters for currency and number formatting
    """

    def __init__(self, options={}):
        """
        Summary.

        Args:
            options (dict, optional): settings configuration object.
        """
        settings = {
            'currency': {
                'symbol': "$",
                'format': "%s%v",
                'decimal': ".",
                'thousand': ",",
                'precision': 2,
                'grouping': 3
            },
            'number': {
                'precision': 0,
                'grouping': 3,
                'thousand': ",",
                'decimal': "."
            }
        }
        if options:
            settings.update(options)

        self.settings = settings

    def _check_currency_format(self, format=None):
        """
        Summary.

        Args:
            format (TYPE, optional): Description

        Returns:
            name (TYPE): Description
        """
        defaults = self.settings['currency']['format']
        if hasattr(format, '__call__'):
            format = format()
        if is_str(format) and re.match('%v', format):

            # Create and return positive, negative and zero formats:
            return {
                'pos': format,
                'neg': format.replace("-", "").replace("%v", "-%v"),
                'zero': format
            }
        elif not format or not format['por'] or not re.match('%v',
                                                             format['pos']):
            self.settings['currency']['format'] = {
                'pos': defaults,
                'neg': defaults.replace("%v", "-%v"),
                'zero': defaults
            }
            return self.settings

        return format

    def _change_precision(self, val, base=0):
        """
        Check and normalise the value of precision (must be positive integer).

        Args:
            val (INT): must be positive integer
            base (INT): Description

        Returns:
            VAL (INT): Description
        """
        if not isinstance(val, int):
            raise TypeError('The first argument must be an integer.')
        val = round(abs(val))
        val = (lambda num: base if is_num(num) else num)(val)
        return val

    def parse(self, value, decimal=None):
        """
        Summary.

         Takes a string/array of strings, removes all formatting/cruft and
         returns the raw float value

         Decimal must be included in the regular expression to match floats
          (defaults to Accounting.settings.number.decimal),
          so if the number uses a non-standard decimal
         separator, provide it as the second argument.
         *
         Also matches bracketed negatives (eg. "$ (1.99)" => -1.99)

         Doesn't throw any errors (`None`s become 0) but this may change

        Args:
            value (TYPE): Description
            decimal (TYPE): Description

        Returns:
            name (TYPE): Description
        """
        # Fails silently (need decent errors):
        value = value or 0

        # Recursively unformat arrays:
        if check_type(value, 'list'):
            return map(lambda val: self.parse(val, decimal))

        # Return the value as-is if it's already a number:
        if check_type(value, 'int') or check_type(value, 'float'):
            return value

        # Default decimal point comes from settings, but could be set to eg.","
        decimal = decimal or self.settings.number.decimal

        # Build regex to strip out everything except digits,
        # decimal point and minus sign
        regex = re.compile("[^0-9-" + decimal + "]")
        unformatted = str(value)
        unformatted = re.sub('/\((.*)\)/', "-$1", unformatted)
        unformatted = re.sub(regex, '', unformatted)
        unformatted = unformatted.replace('.', decimal)
        formatted = (lambda val: unformatted if val else 0)(
            is_num(unformatted))

        return formatted

    def to_fixed(self, value, precision):
        """Implementation that treats floats more like decimals.

        Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61")
        that present problems for accounting and finance-related software.

        """
        precision = self._change_precision(
            precision, self.settings['number']['precision'])

        power = pow(10, precision)
        # Multiply up by precision, round accurately, then divide
        power = round(self.parse(value) * power) / power
        return '{0} {1}.{2}f'.format(value, precision, precision)

    def format(self, number, **kwargs):
        """Format a given number.

        Format a number, with comma-separated thousands and
        custom precision/decimal places

        Localise by overriding the precision and thousand / decimal separators
        2nd parameter `precision` can be an object matching `settings.number`

        Args:
            number (TYPE): Description
            precision (TYPE): Description
            thousand (TYPE): Description
            decimal (TYPE): Description

        Returns:
            name (TYPE): Description
        """
        # Resursively format lists
        if check_type(number, 'list'):
            return map(lambda val: self.format(val, **kwargs))
        # Clean up number
        number = self.parse(number)

        # Build options object from second param (if object) or all params,
        # extending defaults
        if check_type(kwargs, 'dict'):
            options = (self.settings['number'].update(kwargs))

        # Clean up precision
        precision = self._change_precision(options['precision'])
        negative = (lambda num: "-" if num < 0 else "")(number)
        base = str(int(self.to_fixed(abs(number) or 0, precision)), 10)
        mod = (lambda num: len(num) % 3 if len(num) > 3 else 0)(base)

        # Format the number:
        num = negative + (lambda num: base[0:num] if num else '')(mod)

        num += re.sub('/(\d{3})(?=\d)/g', '$1' +
                      options['thousand'], base[mod:])
        num += (lambda val: options[
            'decimal'] + self.to_fixed(abs(number), precision)
            .split('.')[1] if val else '')(precision)

        return num

    def as_money(self, number, **options):
        """Format a number into currency.

        Usage: accounting.formatMoney(number, symbol, precision, thousandsSep,
                                      decimalSep, format)
        defaults: (0, "$", 2, ",", ".", "%s%v")
        Localise by overriding the symbol, precision,
        thousand / decimal separators and format
        Second param can be an object matching `settings.currency`
        which is the easiest way.

        Args:
            number (TYPE): Description
            precision (TYPE): Description
            thousand (TYPE): Description
            decimal (TYPE): Description

        Returns:
            name (TYPE): Description
        """
        # Resursively format arrays
        if isinstance(number, list):
            return map(lambda val: self.as_money(val, **options))

        # Clean up number
        decimal = options.get('decimal')
        number = self.parse(number, decimal)

        # Build options object from second param (if object) or all params,
        # extending defaults
        if check_type(options, 'dict'):
            options = (self.settings['currency'].update(options))

        # Check format (returns object with pos, neg and zero)
        formats = self._check_currency_format(options['format'])

        # Choose which format to use for this value
        use_format = (lambda num: formats['pos'] if num > 0 else formats[
                      'neg'] if num < 0 else formats['zero'])(number)
        precision = self._change_precision(number, options['precision'])
        thousands = options['thousand']
        decimal = options['decimal']
        formater = self.format(abs(number), precision, thousands, decimal)

        # Return with currency symbol added
        amount = use_format.replace(
            '%s', options['symbol']).replace('%v', formater)

        return amount


from __future__ import absolute_import
import blosc
import numpy

def to_array(data):
    """
    Import a blosc array into a numpy array.

    Arguments:
        data: A blosc packed numpy array

    Returns:
        A numpy array with data from a blosc compressed array
    """
    try:
        numpy_data = blosc.unpack_array(data)
    except Exception as e:
        raise ValueError("Could not load numpy data. {}".format(e))

    return numpy_data

def from_array(array):
    """
    Export a numpy array to a blosc array.

    Arguments:
        array: The numpy array to compress to blosc array

    Returns:
        Bytes/String. A blosc compressed array
    """
    try:
        raw_data = blosc.pack_array(array)
    except Exception as e:
        raise ValueError("Could not compress data from array. {}".format(e))

    return raw_data


# This source file is part of Yoda.
#
# Yoda is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Yoda is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Yoda. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.

import logging
import shutil

import os
from pycolorizer import Color
from yoda import Config
from yoda import Repository
from yoda.repository import clone
from yoda import RepositoryError
from yoda import yn_choice

class Workspace:

    config = None

    def __init__(self, config):
        """Workspace object initialization."""
        if not isinstance(config, Config):
            raise TypeError("Variable config is not an instance of Config")
        self.config = config

    def add(self, name, path):
        """Add a workspace entry in user config file."""
        if not (os.path.exists(path)):
            raise ValueError("Workspace path `%s` doesn't exists." % path)

        if (self.exists(name)):
            raise ValueError("Workspace `%s` already exists." % name)

        self.config["workspaces"][name] = {"path": path, "repositories": {}}
        self.config.write()

    def remove(self, name):
        """Remove workspace from config file."""
        if not (self.exists(name)):
            raise ValueError("Workspace `%s` doesn't exists." % name)

        self.config["workspaces"].pop(name, 0)
        self.config.write()

    def list(self):
        """List all available workspaces."""
        ws_list = {}

        for key, value in self.config["workspaces"].items():
            ws_list[key] = dict({"name": key}, **value)

        return ws_list

    def get(self, name):
        """
        Get workspace infos from name.
        Return None if workspace doesn't exists.
        """
        ws_list = self.list()
        return ws_list[name] if name in ws_list else None

    def exists(self, name):
        """Check if given workspace name exists."""
        return name in self.list().keys()

    def repository_exists(self, workspace, repo):
        """Return True if workspace contains repository name."""
        if not self.exists(workspace):
            return False

        workspaces = self.list()
        return repo in workspaces[workspace]["repositories"]

    def sync(self, ws_name):
        """Synchronise workspace's repositories."""
        path = self.config["workspaces"][ws_name]["path"]
        repositories = self.config["workspaces"][ws_name]["repositories"]

        logger = logging.getLogger(__name__)
        color = Color()

        for r in os.listdir(path):
            try:
                repo = Repository(os.path.join(path, r))
            except RepositoryError:
                continue
            else:
                repositories[r] = repo.path

        for repo_name, path in repositories.items():
            logger.info(color.colored(
                " - %s" % repo_name, "blue"))

        self.config["workspaces"][ws_name]["repositories"]
        self.config.write()

    def rm_repo(self, wname, rname):
        if (rname not in self.config["workspaces"][wname]["repositories"]):
            raise ValueError(
                "%s not found in %s workspace" % (rname, wname))

        repo_path = self.config["workspaces"][wname]["repositories"][rname]
        del self.config["workspaces"][wname]["repositories"][rname]

        self.config.write()

        if (yn_choice("Do you want to delete this repository?")):
            shutil.rmtree(repo_path)

    def add_repo(self, wname, rname, url=None, path=None):
        ws = self.config["workspaces"][wname]
        repo_path = ws["path"] + "/" + rname if path is None else path

        if ("repositories" not in ws):
            ws["repositories"] = {}

        if (rname in ws["repositories"]):
            raise ValueError("Repository %s already exists" % rname)

        result = None
        if url is not None:
            result = clone(url, repo_path)

        if result is not None and result.returncode != 0:
            raise RepositoryError("Can't clone this repository")

        if (os.path.exists(repo_path) is False):
            os.mkdir(repo_path)

        self.config["workspaces"][wname] = ws
        self.config["workspaces"][wname]["repositories"][rname] = repo_path
        self.config.write()


# This source file is part of Yoda.
#
# Yoda is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Yoda is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Yoda. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.

import os

from yoda.adapter import Bzr
from yoda.adapter import Git
from yoda.adapter import Hg
from yoda.adapter import Svn

class RepositoryError(Exception):
    """Generic repository error."""
    pass

class RepositoryPathInvalid(RepositoryError):
    """Repository path is invalid, doesn't exists or is not a directory."""
    pass

class RepositoryAdapterNotFound(RepositoryError):
    """Repository invalid because adapter not found."""
    pass

class Repository:
    path = None
    adapter = None

    scm_dirs = [".git", ".svn", ".bzr", ".hg"]

    def __init__(self, path):
        if not os.path.exists(path) or not os.path.isdir(path):
            raise RepositoryPathInvalid(
                "Path doesn't exists or isn't a directory (%s)\n" % path)

        try:
            scm = (set(self.scm_dirs) & set(os.listdir(path))).pop()
        except KeyError:
            raise RepositoryAdapterNotFound("Can't define repository type")
        else:
            self.path = path
            if scm == ".git":
                self.adapter = Git(path)
            if scm == ".svn":
                self.adapter = Svn(path)
            if scm == ".bzr":
                self.adapter = Bzr(path)
            if scm == ".hg":
                self.adapter = Hg(path)

    def get_scm(self):
        """Get scm used as string."""
        if self.adapter is None:
            return None
        return self.adapter.__class__.__name__

    def status(self):
        return self.adapter.status()

    def update(self):
        return self.adapter.update()

def clone(url, path):
    """Clone a repository."""
    adapter = None
    if url[:4] == "git@" or url[-4:] == ".git":
        adapter = Git(path)
    if url[:6] == "svn://":
        adapter = Svn(path)
    if url[:6] == "bzr://":
        adapter = Bzr(path)
    if url[:9] == "ssh://hg@":
        adapter = Hg(path)

    if adapter is None:
        raise RepositoryAdapterNotFound(
            "Can't find adapter for `%s` repository url" % url)

    return adapter.clone(url)


"""
A Python library for open neuroscience data access and manipulation.
"""

version = "1.1.6"

def check_version():
    """
    Tells you if you have an old version of ndio.
    """
    import requests
    r = requests.get('https://pypi.python.org/pypi/ndio/json').json()
    r = r['info']['version']
    if r != version:
        print("A newer version of ndio is available. " +
              "'pip install -U ndio' to update.")
    return r


from __future__ import absolute_import
import numpy

def to_voxels(array):
    """
    Converts an array to its voxel list.

    Arguments:
        array (numpy.ndarray): A numpy nd array. This must be boolean!

    Returns:
        A list of n-tuples
    """
    if type(array) is not numpy.ndarray:
        raise ValueError("array argument must be of type numpy.ndarray")
    return numpy.argwhere(array)

def from_voxels(voxels):
    """
    Converts a voxel list to an ndarray.

    Arguments:
        voxels (tuple[]): A list of coordinates indicating coordinates of
            populated voxels in an ndarray.

    Returns:
        numpy.ndarray The result of the transformation.
    """
    dimensions = len(voxels[0])

    for d in range(len(dimensions)):
        size.append(max([i[d] for i in voxels]))

    result = numpy.zeros(dimensions)

    for v in voxels:
        result[v] = 1

    return result


# This source file is part of Yoda.
#
# Yoda is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Yoda is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Yoda. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.

import logging

from pycolorizer import Color

from yoda import find_path
from yoda import Repository
from yoda import RepositoryError
from yoda.subcommands import Subcommand

class Update(Subcommand, object):

    def setup(self, name, config, subparser):
        self.subparser = subparser
        self.logger = logging.getLogger(__name__)
        super(Update, self).setup(name, config, subparser)

    def parse(self):
        """Parse update subcommand."""
        parser = self.subparser.add_parser(
            "update",
            help="Update repositories",
            description="Update one or more repositories.")

        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument('--all', action='store_true', help="All workspaces")
        group.add_argument('name', type=str, help="Repo name", nargs='?')

    def execute(self, args):
        """Execute update subcommand."""
        if args.name is not None:
            self.print_workspace(args.name)
        elif args.all is not None:
            self.print_all()

    def print_all(self):
        """Print all workspaces update."""
        for ws_name in self.config['workspaces']:
            self.print_workspace(ws_name)

    def print_workspace(self, name):
        """Print workspace update."""
        path_list = find_path(name, self.config)

        if len(path_list) == 0:
            self.logger.error("No matches for `%s`" % name)
            return False

        for name, path in path_list.items():
            self.print_update(name, path)

    def print_update(self, repo_name, repo_path):
        """Print repository update."""
        color = Color()
        self.logger.info(color.colored(
            "=> [%s] %s" % (repo_name, repo_path), "green"))
        try:
            repo = Repository(repo_path)
            repo.update()
        except RepositoryError as e:
            self.logger.error(e)
            pass
        print("\n")


# This source file is part of Yoda.
#
# Yoda is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Yoda is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Yoda. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.

import copy
import logging
import os
import traceback

from prettytable import PrettyTable

BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
COLORS = {
    'WARNING': YELLOW,
    'INFO': WHITE,
    'DEBUG': BLUE,
    'CRITICAL': RED,
    'ERROR': RED
}
FORMAT = """[%(asctime)s] [%(levelname)s] <%(name)s> - \
%(message)s (%(filename)s:%(lineno)d)"""
LFORMAT = "%(levelname)s: %(message)s"

class Formatter(logging.Formatter):
    def format(self, record):
        rec = copy.copy(record)

        if isinstance(rec.msg, Exception) and self._fmt == FORMAT:
            rec.msg = "%s\n%s" % (rec.msg, traceback.format_exc())

        if isinstance(rec.msg, PrettyTable):
            rec.msg = "\n%s" % rec.msg

        level = record.levelname

        if level in COLORS:
            lvlname = COLOR_SEQ % (30 + COLORS[level]) + level + RESET_SEQ
            rec.levelname = lvlname

        if level == "INFO" and self._fmt != FORMAT:
            formatter = Formatter("%(message)s")
            return formatter.format(rec)

        return logging.Formatter.format(self, rec)

class Logger(logging.Logger):
    def __init__(self, name):
        logging.Logger.__init__(self, name, logging.DEBUG)

    def set_file_handler(self, logfile):
        """Set FileHandler"""
        handler = logging.FileHandler(logfile)
        handler.setLevel(logging.NOTSET)
        handler.setFormatter(Formatter(FORMAT))

        self.addHandler(handler)

    def set_console_handler(self, debug=False):
        """Set Console handler."""
        console = logging.StreamHandler()
        console.setFormatter(Formatter(LFORMAT))
        if not debug:
            console.setLevel(logging.INFO)

        self.addHandler(console)


# This source file is part of Yoda.
#
# Yoda is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Yoda is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Yoda. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>.

from abc import ABCMeta
from abc import abstractmethod
from distutils.spawn import find_executable
import logging
import subprocess

class ExecutableNotFoundException(OSError):
    pass

class Abstract:
    """SCM Adapter interface."""
    __metaclass__ = ABCMeta

    path = None
    executable = None

    def __init__(self, path):
        self.path = path

    def execute(self, command, path=None):
        """Execute command with os.popen and return output."""
        logger = logging.getLogger(__name__)

        self.check_executable()
        logger.debug("Executing command `%s` (cwd: %s)" % (command, path))
        process = subprocess.Popen(
            command,
            shell=True,
            cwd=path,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        stdout, stderr = process.communicate()
        exit_code = process.wait()

        if stdout:
            logger.info(stdout.decode("utf-8"))

        if stderr:
            if exit_code != 0:
                logger.error(stderr.decode("utf-8"))
            else:
                logger.info(stderr.decode("utf-8"))

        return process

    def exec_on_path(self, command):
        """Execute command in repository path."""
        self.execute("%s" % command, self.path)

    def check_executable(self):
        """Check adapter executable exists."""
        if not find_executable(self.executable):
            raise ExecutableNotFoundException(
                "Executable %s not found" % self.executable
            )

    @abstractmethod
    def status(self):
        pass

    @abstractmethod
    def update(self):
        pass

    @abstractmethod
    def show(self):
        pass

    @abstractmethod
    def clone(self):
        pass