from typing import Callable, Iterable, List, Optional, Type

from .. import optics
from ..typevars import S, T, A, B, X, Y

from .base import BaseUiLens

class UnboundLens(BaseUiLens[S, T, A, B]):
    'An unbound lens is a lens that has not been bound to any state.'

    __slots__ = ('_optic',)

    def __init__(self, optic):
        self._optic = optic

    def __repr__(self):
        # type: () -> str
        return 'UnboundLens({!r})'.format(self._optic)

    def get(self):
        # type: () -> Callable[[S], B]
        '''Get the first value focused by the lens.

            >>> from lenses import lens
            >>> getter = lens.get()
            >>> getter([1, 2, 3])
            [1, 2, 3]
            >>> zero_item_getter = lens[0].get()
            >>> zero_item_getter([1, 2, 3])
            1
        '''

        def getter(state):
            return self._optic.to_list_of(state)[0]

        return getter

    def collect(self):
        # type: () -> Callable[[S], List[B]]
        '''Get multiple values focused by the lens. Returns them as
        a list.

            >>> from lenses import lens
            >>> collect_each_first = lens.Each()[0].collect()
            >>> collect_each_first([(1, 2), (3, 4), (5, 6)])
            [1, 3, 5]
        '''

        def getter(state):
            return self._optic.to_list_of(state)

        return getter

    def get_monoid(self):
        # type: () -> Callable[[S], B]
        '''Get the values focused by the lens, merging them together by
        treating them as a monoid. See `lenses.typeclass.mappend`.

            >>> from lenses import lens
            >>> get_each_monoidally = lens.Each().get_monoid()
            >>> get_each_monoidally([[], [1], [2, 3]])
            [1, 2, 3]
        '''

        def getter(state):
            return self._optic.view(state)

        return getter

    def set(self, newvalue):
        # type: (B) -> Callable[[S], T]
        '''Set the focus to `newvalue`.

            >>> from lenses import lens
            >>> set_item_one_to_four = lens[1].set(4)
            >>> set_item_one_to_four([1, 2, 3])
            [1, 4, 3]
        '''

        def setter(state):
            return self._optic.set(state, newvalue)

        return setter

    def set_many(self, new_values):
        # type: (Iterable[B]) -> Callable[[S], T]
        '''Set many foci to values taken by iterating over `new_values`.

            >>> from lenses import lens
            >>> lens.Each().set_many(range(4, 7))([0, 1, 2])
            [4, 5, 6]
        '''

        def setter_many(state):
            return self._optic.iterate(state, new_values)

        return setter_many

    def modify(self, func):
        # type: (Callable[[A], B]) -> Callable[[S], T]
        '''Apply a function to the focus.

            >>> from lenses import lens
            >>> convert_item_one_to_string = lens[1].modify(str)
            >>> convert_item_one_to_string([1, 2, 3])
            [1, '2', 3]
            >>> add_ten_to_item_one = lens[1].modify(lambda n: n + 10)
            >>> add_ten_to_item_one([1, 2, 3])
            [1, 12, 3]
        '''

        def modifier(state):
            return self._optic.over(state, func)

        return modifier

    def construct(self, focus):
        # type: (A) -> S
        '''Construct a state given a focus.'''
        return self._optic.re().view(focus)

    def flip(self):
        # type: () -> UnboundLens[A, B, S, T]
        '''Flips the direction of the lens. The lens must be unbound
        and all the underlying operations must be isomorphisms.

            >>> from lenses import lens
            >>> json_encoder = lens.Decode().Json().flip()
            >>> json_encode = json_encoder.get()
            >>> json_encode(['hello', 'world'])  # doctest: +SKIP
            b'["hello", "world"]'
        '''
        return UnboundLens(self._optic.re())

    def __and__(self, other):
        # type: (UnboundLens[A, B, X, Y]) -> UnboundLens[S, T, X, Y]
        '''Refine the current focus of this lens by composing it with
        another lens object. The other lens must be unbound.

            >>> from lenses import lens
            >>> first = lens[0]
            >>> second = lens[1]
            >>> second_first = second & first
            >>> get_second_then_first = second_first.get()
            >>> get_second_then_first([[0, 1], [2, 3]])
            2
        '''
        if not isinstance(other, UnboundLens):
            message = 'Cannot compose lens of type {!r}.'
            raise TypeError(message.format(type(other)))
        return self._compose_optic(other._optic)

    def __get__(self, instance, owner):
        # type: (Optional[S], Type) -> BaseUiLens[S, T, A, B]
        if instance is None:
            return self
        return BoundLens(instance, self._optic)

    def _compose_optic(self, optic):
        # type: (optics.LensLike) -> UnboundLens[S, T, X, Y]
        return UnboundLens(self._optic.compose(optic))

    def kind(self):
        # type: () -> str
        'Returns the "kind" of the lens.'
        return self._optic.kind().__name__

    add_lens = __and__

class BoundLens(BaseUiLens[S, T, A, B]):
    'A bound lens is a lens that has been bound to a specific state.'

    __slots__ = ('_state', '_optic')

    def __init__(self, state, optic):
        # type: (S, optics.LensLike) -> None
        self._state = state
        self._optic = optic

    def __repr__(self):
        # type: () -> str
        return 'BoundLens({!r}, {!r})'.format(self._state, self._optic)

    def get(self):
        # type: () -> B
        '''Get the first value focused by the lens.

            >>> from lenses import bind
            >>> bind([1, 2, 3]).get()
            [1, 2, 3]
            >>> bind([1, 2, 3])[0].get()
            1
        '''
        return self._optic.to_list_of(self._state)[0]

    def collect(self):
        # type: () -> List[B]
        '''Get multiple values focused by the lens. Returns them as
        a list.

            >>> from lenses import bind
            >>> bind([(1, 2), (3, 4), (5, 6)]).Each()[0].collect()
            [1, 3, 5]
        '''
        return self._optic.to_list_of(self._state)

    def get_monoid(self):
        # type: () -> B
        '''Get the values focused by the lens, merging them together by
        treating them as a monoid. See `lenses.typeclass.mappend`.

            >>> from lenses import bind
            >>> bind([[], [1], [2, 3]]).Each().get_monoid()
            [1, 2, 3]
        '''
        return self._optic.view(self._state)

    def set(self, newvalue):
        # type: (B) -> T
        '''Set the focus to `newvalue`.

            >>> from lenses import bind
            >>> bind([1, 2, 3])[1].set(4)
            [1, 4, 3]
        '''
        return self._optic.set(self._state, newvalue)

    def set_many(self, new_values):
        # type: (Iterable[B]) -> T
        '''Set many foci to values taken by iterating over `new_values`.

            >>> from lenses import bind
            >>> bind([0, 1, 2]).Each().set_many(range(4, 7))
            [4, 5, 6]
        '''

        return self._optic.iterate(self._state, new_values)

    def modify(self, func):
        # type: (Callable[[A], B]) -> T
        '''Apply a function to the focus.

            >>> from lenses import bind
            >>> bind([1, 2, 3])[1].modify(str)
            [1, '2', 3]
            >>> bind([1, 2, 3])[1].modify(lambda n: n + 10)
            [1, 12, 3]
        '''
        return self._optic.over(self._state, func)

    def __and__(self, other):
        # type: (UnboundLens[A, B, X, Y]) -> BoundLens[S, T, X, Y]
        '''Refine the current focus of this lens by composing it with
        another lens object. The other lens must be unbound.

            >>> from lenses import lens, bind
            >>> first = lens[0]
            >>> second = bind([[0, 1], [2, 3]])[1]
            >>> (second & first).get()
            2
        '''
        if not isinstance(other, UnboundLens):
            message = 'Cannot compose lens of type {!r}.'
            raise TypeError(message.format(type(other)))
        return self._compose_optic(other._optic)

    def _compose_optic(self, optic):
        # type: (optics.LensLike) -> BoundLens[S, T, X, Y]
        return BoundLens(self._state, self._optic.compose(optic))

    def kind(self):
        # type: () -> str
        'Returns the "kind" of the lens.'
        return self._optic.kind().__name__

    add_lens = __and__

__all__ = ['UnboundLens', 'BoundLens']


from typing import Callable, Generic, Iterable, List, Optional, cast

from ..const import Const
from ..functorisor import Functorisor
from ..identity import Identity
from .. import typeclass
from ..maybe import Just, Nothing
from ..typevars import S, T, A, B

def multiap(func, *args):
    '''Applies `func` to the data inside the `args` functors
    incrementally. `func` must be a curried function that takes
    `len(args)` arguments.

        >>> func = lambda a: lambda b: a + b
        >>> multiap(func, [1, 10], [100])
        [101, 110]
    '''
    functor = typeclass.fmap(args[0], func)
    for arg in args[1:]:
        functor = typeclass.apply(arg, functor)
    return functor

def collect_args(n):
    '''Returns a function that can be called `n` times with a single
    argument before returning all the args that have been passed to it
    in a tuple. Useful as a substitute for functions that can't easily be
    curried.

        >>> collect_args(3)(1)(2)(3)
        (1, 2, 3)
    '''
    args = []

    def arg_collector(arg):
        args.append(arg)
        if len(args) == n:
            return tuple(args)
        else:
            return arg_collector

    return arg_collector

class LensLike(object):
    '''A LensLike. Serves as the backbone of the lenses library. Acts as an
    object-oriented wrapper around a function (`LensLike.func`) that
    does all the hard work. This function is an uncurried form of the
    van Laarhoven lens and has the following type (in ML-style
    notation):

    func :: (value -> functor value), state -> functor state

    A LensLike has a kind that determines what operations are valid on
    that LensLike. Valid kinds are Equality, Isomorphism, Prism, Review,
    Lens, Traversal, Getter, Setter, Fold, and None.

    Fold

    : A Fold is an optic capable of getting, but not necessarily setting,
    multiple foci at once. You can think of a fold as kind of like an
    iterator; it allows you to view many subparts of a larger structure.

    Setter

    : A Setter is an optic that is capable of setting foci in a state
    but not necessarily getting them.

    Getter

    : A Getter is a Fold that is restricted to getting a single focus at
    a time. It can not necessarily set any foci.

    Traversal

    : A Traversal is both a Fold and a Setter. It is capable of both
    setting and getting multiple foci at once.

    Lens

    : A Lens is both a Getter and a Traversal. It is capable of getting
    and setting a single focus at a time.

    Review

    : A Review is an optic that is capable of being constructed
    from. Constructing allows you to supply a focus and get back a
    complete state. You cannot neccessarily use reviews to get or set
    any values.

    Prism

    : A Prism is both a Traversal and a Review. It is capable of getting
    and setting a single focus that may or may not exist. You can also
    use it to construct.

    Isomorphism

    : An Isomorphism is both a Lens and a Prism. They can be used to
    get, set, and construct. Isomorphisms have the property that they
    are reversable; You can take an isomorphism and flip it around
    so that getting the focus becomes setting the focus and setting
    becomes getting.

    Equality

    : An Equality is an Isomorphism. Currently unused.

    None

    : Here "None" is referring to the built-in python `None` object and
    not a custom class like the other kinds. An optic of kind None is
    an invalid optic. Optics of this kind may exist internally, but if
    you manage to create a None optic through normal means then this
    represents a bug in the library.
    '''

    __slots__ = ()

    def func(self, f, state):
        '''Intended to be overridden by subclasses. Raises
        NotImplementedError.'''
        message = 'Tried to use unimplemented lens {}.'
        raise NotImplementedError(message.format(type(self)))

    def apply(self, f, pure, state):
        '''Runs the lens over the `state` applying `f` to all the foci
        collecting the results together using the applicative functor
        functions defined in `lenses.typeclass`. `f` must return an
        applicative functor. For the case when no focus exists you must
        also provide a `pure` which should take a focus and return the
        pure form of the functor returned by `f`.
        '''
        return self.func(Functorisor(pure, f), state)

    def preview(self, state):
        # type: (S) -> Just[B]
        '''Previews a potentially non-existant focus within
        `state`. Returns `Just(focus)` if it exists, Nothing otherwise.

        Requires kind Fold.
        '''
        if not self._is_kind(Fold):
            raise TypeError('Must be an instance of Fold to .preview()')

        pure = lambda a: Const(Nothing())
        func = lambda a: Const(Just(a))
        return self.apply(func, pure, state).unwrap()

    def view(self, state):
        # type: (S) -> B
        '''Returns the focus within `state`. If multiple items are
        focused then it will attempt to join them together as a monoid.
        See `lenses.typeclass.mappend`.

        Requires kind Fold. This method will raise TypeError if the
        optic has no way to get any foci.

        For technical reasons, this method requires there to be at least
        one foci at the end of the view. It will raise ValueError when
        there is none.
        '''
        if not self._is_kind(Fold):
            raise TypeError('Must be an instance of Fold to .view()')

        guard = object()
        result = self.preview(state).maybe(guard)
        if result is guard:
            raise ValueError('No focus to view')
        return cast(B, result)

    def to_list_of(self, state):
        # type: (S) -> List[B]
        '''Returns a list of all the foci within `state`.

        Requires kind Fold. This method will raise TypeError if the
        optic has no way to get any foci.
        '''
        if not self._is_kind(Fold):
            raise TypeError('Must be an instance of Fold to .to_list_of()')

        pure = lambda a: Const([])
        func = lambda a: Const([a])
        return self.apply(func, pure, state).unwrap()

    def over(self, state, fn):
        # type: (S, Callable[[A], B]) -> T
        '''Applies a function `fn` to all the foci within `state`.

        Requires kind Setter. This method will raise TypeError when the
        optic has no way to set foci.
        '''
        if not self._is_kind(Setter):
            raise TypeError('Must be an instance of Setter to .over()')

        pure = lambda a: Identity(a)
        func = lambda a: Identity(fn(a))
        return self.apply(func, pure, state).unwrap()

    def set(self, state, value):
        # type: (S, B) -> T
        '''Sets all the foci within `state` to `value`.

        Requires kind Setter. This method will raise TypeError when the
        optic has no way to set foci.
        '''
        if not self._is_kind(Setter):
            raise TypeError('Must be an instance of Setter to .set()')

        pure = lambda a: Identity(a)
        func = lambda a: Identity(value)
        return self.apply(func, pure, state).unwrap()

    def iterate(self, state, iterable):
        # type: (S, Iterable[B]) -> T
        '''Sets all the foci within `state` to values taken from `iterable`.

        Requires kind Setter. This method will raise TypeError when the
        optic has no way to set foci.
        '''
        if not self._is_kind(Setter):
            raise TypeError('Must be an instance of Setter to .iterate()')

        i = iter(iterable)
        pure = lambda a: Identity(a)
        func = lambda a: Identity(next(i))
        return self.apply(func, pure, state).unwrap()

    def compose(self, other):
        # type: (LensLike) -> LensLike
        '''Composes another lens with this one. The result is a lens
        that feeds the foci of `self` into the state of `other`.
        '''
        return ComposedLens([self]).compose(other)

    def re(self):
        # type: () -> LensLike
        raise TypeError('Must be an instance of Review to .re()')

    def kind(self):
        '''Returns a class representing the 'kind' of optic.'''
        optics = [
            Equality,
            Isomorphism,
            Prism,
            Review,
            Lens,
            Traversal,
            Getter,
            Setter,
            Fold,
        ]
        for optic in optics:
            if self._is_kind(optic):
                return optic

    def _is_kind(self, cls):
        return isinstance(self, cls)

    __and__ = compose

class Fold(LensLike):
    '''An optic that wraps a folder function. A folder function is a
    function that takes a single argument - the state - and returns
    an iterable containing all the foci that can be found in that
    state. Generator functions work particularly well here.

        >>> def iterate_2d_list(rows):
        ...     for row in rows:
        ...         for cell in row:
        ...             yield cell
        >>> nested_fold = Fold(iterate_2d_list)
        >>> nested_fold
        Fold(<function iterate_2d_list at ...>)
        >>> state = [[1], [2, 3], [4, 5, 6]]
        >>> nested_fold.to_list_of(state)
        [1, 2, 3, 4, 5, 6]

    Folds are incapable of setting anything.
    '''

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

    def func(self, f, state):
        foci = list(self.folder(state))
        if foci == []:
            return f.pure(state)
        collector = collect_args(len(foci))
        applied = multiap(collector, *map(f, foci))
        return applied

    def __repr__(self):
        return 'Fold({!r})'.format(self.folder)

class Setter(LensLike):
    pass

class Getter(Fold):
    '''An optic that wraps a getter function. A getter function is one
    that takes a state and returns a value derived from that state. The
    function is called on the focus before it is returned.

        >>> Getter(abs)
        Getter(<built-in function abs>)
        >>> Getter(abs).view(-1)
        1
    '''

    def __init__(self, getter):
        # type: (Callable[[S], A]) -> None
        self.getter = getter

    def func(self, f, state):
        return f(self.getter(state))

    def folder(self, state):
        yield self.getter(state)

    def __repr__(self):
        return 'Getter({!r})'.format(self.getter)

class Traversal(Fold, Setter):
    '''An optic that wraps folder and builder functions. The folder
    function is a function that takes a single argument - the state -
    and returns an iterable containing all the foci that exist in that
    state. Generators are a good option for writing folder functions.

    A builder function takes the old state and an list of values and
    constructs a new state with the old state's values swapped out. The
    number of values passed to builder for any given state should always
    be the same as the number of values that the folder function would
    have returned for that same state.

        >>> def folder(state):
        ...     'Yields the first and last elements of a list'
        ...     yield state[0]
        ...     yield state[-1]
        >>> def builder(state, values):
        ...     'Sets the first and last elements of a list'
        ...     result = list(state)
        ...     result[0] = values[0]
        ...     result[-1] = values[1]
        ...     return result
        >>> both_ends = Traversal(folder, builder)
        >>> both_ends
        Traversal(<function folder at ...>, <function builder at ...>)
        >>> both_ends.to_list_of([1, 2, 3, 4])
        [1, 4]
        >>> both_ends.set([1, 2, 3, 4], 5)
        [5, 2, 3, 5]
    '''

    def __init__(self, folder, builder):
        self.folder = folder
        self.builder = builder

    def func(self, f, state):
        foci = list(self.folder(state))
        if foci == []:
            return f.pure(state)
        collector = collect_args(len(foci))
        applied = multiap(collector, *map(f, foci))
        apbuilder = lambda values: self.builder(state, values)
        return typeclass.fmap(applied, apbuilder)

    def __repr__(self):
        return 'Traversal({!r}, {!r})'.format(self.folder, self.builder)

class Lens(Getter, Traversal):
    '''An optic that wraps a pair of getter and setter functions. A getter
    function is one that takes a state and returns a value derived from
    that state. A setter function takes an old state and a new value
    and uses them to construct a new state.

        >>> def getter(state):
        ...     'Get the average of a list'
        ...     return sum(state) // len(state)
        ...
        >>> def setter(old_state, value):
        ...     'Set the average of a list by changing the final value'
        ...     target_sum = value * len(old_state)
        ...     prefix = old_state[:-1]
        ...     return prefix + [target_sum - sum(prefix)]
        ...
        >>> average = Lens(getter, setter)
        >>> average
        Lens(<function getter...>, <function setter...>)
        >>> average.view([1, 2, 4, 5])
        3
        >>> average.set([1, 2, 3], 4)
        [1, 2, 9]
    '''

    def __init__(self, getter, setter):
        # type: (Callable[[S], A], Callable[[S, B], T]) -> None
        self.getter = getter
        self.setter = setter

    def func(self, f, state):
        old_value = self.getter(state)
        fa = f(old_value)
        return typeclass.fmap(fa, lambda a: self.setter(state, a))

    def __repr__(self):
        return 'Lens({!r}, {!r})'.format(self.getter, self.setter)

class Review(LensLike):
    '''A review is an optic that is capable of constructing states from
    a focus.

        >>> Review(abs)
        Review(<built-in function abs>)
        >>> Review(abs).re().view(-1)
        1
    '''

    def __init__(self, pack):
        # type: (Callable[[B], T]) -> None
        self.pack = pack

    def re(self):
        return Getter(self.pack)

    def __repr__(self):
        return 'Review({!r})'.format(self.pack)

class Prism(Traversal, Review):
    '''A prism is an optic made from a pair of functions that pack and
    unpack a state where the unpacking process can potentially fail.

    `pack` is a function that takes a focus and returns that focus
    wrapped up in a new state. `unpack` is a function that takes a state
    and unpacks it to get a focus. The unpack function must return an
    instance of `lenses.maybe.Maybe`; `Just` if the unpacking succeeded
    and `Nothing` if the unpacking failed.

    Parsing strings is a common situation when prisms are useful:

        >>> from lenses.maybe import Nothing, Just
        >>> def pack(focus):
        ...     return str(focus)
        >>> def unpack(state):
        ...     try:
        ...         return Just(int(state))
        ...     except ValueError:
        ...         return Nothing()
        >>> Prism(unpack, pack)
        Prism(<function unpack ...>, <function pack ...>)
        >>> Prism(unpack, pack).preview('42')
        Just(42)
        >>> Prism(unpack, pack).preview('fourty two')
        Nothing()

    All prisms are also traversals that have exactly zero or one foci.
    '''

    def __init__(self, unpack, pack):
        self.unpack = unpack
        self.pack = pack

    def folder(self, state):
        result = self.unpack(state)
        if not result.is_nothing():
            yield result.unwrap()

    def func(self, f, state):
        result = self.unpack(state)
        if result.is_nothing():
            return f.pure(state)
        return typeclass.fmap(f(result.unwrap()), self.pack)

    def has(self, state):
        '''Returns `True` when the state would be successfully focused
        by this prism, otherwise `False`.

            >>> from lenses.maybe import Nothing, Just
            >>> def pack(focus):
            ...     return focus
            >>> def unpack(state):
            ...     if state > 0:
            ...         return Just(state)
            ...     return Nothing()
            >>> positive = Prism(unpack, pack)
            >>> positive.has(-1)
            False
            >>> positive.has(1)
            True
        '''
        return not self.unpack(state).is_nothing()

    def __repr__(self):
        return 'Prism({!r}, {!r})'.format(self.unpack, self.pack)

class Isomorphism(Lens, Prism):
    '''A lens based on an isomorphism. An isomorphism can be formed by
    two functions that mirror each other; they can convert forwards
    and backwards between a state and a focus without losing
    information. The difference between this and a regular Lens is
    that here the backwards functions don't need to know anything about
    the original state in order to produce a new state.

    These equalities should hold for the functions you supply (given
    a reasonable definition for __eq__):

        backwards(forwards(state)) == state
        forwards(backwards(focus)) == focus

    These kinds of conversion functions are very common across the
    python ecosystem. For example, NumPy has `np.array` and
    `np.ndarray.tolist` for converting between python lists and its own
    arrays. Isomorphism makes it easy to store data in one form, but
    interact with it in a more convenient form.

        >>> Isomorphism(chr, ord)
        Isomorphism(<built-in function chr>, <built-in function ord>)
        >>> Isomorphism(chr, ord).view(65)
        'A'
        >>> Isomorphism(chr, ord).set(65, 'B')
        66

    Due to their symmetry, isomorphisms can be flipped, thereby swapping
    thier forwards and backwards functions:

        >>> flipped = Isomorphism(chr, ord).re()
        >>> flipped
        Isomorphism(<built-in function ord>, <built-in function chr>)
        >>> flipped.view('A')
        65
    '''

    def __init__(self, forwards, backwards):
        # type: (Callable[[S], A], Callable[[B], T]) -> None
        self.forwards = forwards
        self.backwards = backwards

    def getter(self, state):
        return self.forwards(state)

    def setter(self, old_state, focus):
        return self.backwards(focus)

    def unpack(self, state):
        return Just(self.forwards(state))

    def pack(self, focus):
        return self.backwards(focus)

    def re(self):
        return Isomorphism(self.backwards, self.forwards)

    def func(self, f, state):
        return typeclass.fmap(f(self.forwards(state)), self.backwards)

    def __repr__(self):
        return 'Isomorphism({!r}, {!r})'.format(self.forwards, self.backwards)

class Equality(Isomorphism):
    pass

class ComposedLens(LensLike):
    '''A lenses representing the composition of several sub-lenses. This
    class tries to just pass operations down to the sublenses without
    imposing any constraints on what can happen. The sublenses are in
    charge of what capabilities they support.

        >>> import lenses
        >>> gi = lenses.optics.GetitemLens
        >>> ComposedLens((gi(0), gi(1)))
        GetitemLens(0) & GetitemLens(1)

    (The ComposedLens is represented above by the `&` symbol)
    '''

    __slots__ = ('lenses',)

    def __init__(self, lenses=()):
        # type: (Iterable[LensLike]) -> None
        self.lenses = list(self._filter_lenses(lenses))

    @staticmethod
    def _filter_lenses(lenses):
        for lens in lenses:
            lenstype = type(lens)
            if lenstype is TrivialIso:
                continue
            elif lenstype is ComposedLens:
                for lens in lens.lenses:
                    yield lens
            else:
                yield lens

    def func(self, f, state):
        if not self.lenses:
            return TrivialIso().func(f, state)

        res = f
        for lens in reversed(self.lenses):
            res = res.update(lens.func)

        return res(state)

    def re(self):
        return ComposedLens([l.re() for l in reversed(self.lenses)])

    def compose(self, other):
        result = ComposedLens(self.lenses + [other])
        if len(result.lenses) == 0:
            return TrivialIso()
        elif len(result.lenses) == 1:
            return result.lenses[0]
        if result.kind() is None:
            raise RuntimeError('Optic has no valid type')
        return result

    def __repr__(self):
        return ' & '.join(str(l) for l in self.lenses)

    def _is_kind(self, cls):
        return all(lens._is_kind(cls) for lens in self.lenses)

class ErrorIso(Isomorphism):
    '''An optic that raises an exception whenever it tries to focus
    something. If `message is None` then the exception will be raised
    unmodified. If `message is not None` then when the lens is asked
    to focus something it will run `message.format(state)` and the
    exception will be called with the resulting formatted message as
    it's only argument. Useful for debugging.

        >>> ErrorIso(Exception())
        ErrorIso(Exception())
        >>> ErrorIso(Exception, '{}')  # doctest: +SKIP
        ErrorLens(<class 'Exception'>, '{}')
        >>> ErrorIso(Exception).view(True)
        Traceback (most recent call last):
          File "<stdin>", line 1, in ?
        Exception
        >>> ErrorIso(Exception('An error occurred')).set(True, False)
        Traceback (most recent call last):
          File "<stdin>", line 1, in ?
        Exception: An error occurred
        >>> ErrorIso(ValueError, 'applied to {}').view(True)
        Traceback (most recent call last):
          File "<stdin>", line 1, in ?
        ValueError: applied to True
    '''

    def __init__(self, exception, message=None):
        # type: (Exception, Optional[str]) -> None
        self.exception = exception
        self.message = message

    def func(self, f, state):
        if self.message is None:
            raise self.exception
        raise self.exception(self.message.format(state))

    def __repr__(self):
        if self.message is None:
            return 'ErrorIso({!r})'.format(self.exception)
        return 'ErrorIso({!r}, {!r})'.format(self.exception, self.message)

class TrivialIso(Isomorphism):
    '''A trivial isomorphism that focuses the whole state. It doesn't
    manipulate the state in any way. Mostly used as a "null" lens.
    Analogous to `lambda a: a`.

        >>> TrivialIso()
        TrivialIso()
        >>> TrivialIso().view(True)
        True
        >>> TrivialIso().set(True, False)
        False
    '''

    def __init__(self):
        # type: () -> None
        pass

    def forwards(self, state):
        return state

    def backwards(self, focus):
        return focus

    def __repr__(self):
        return 'TrivialIso()'


#!/usr/bin/env python3
'''
robots.py

You (`@`) are a person surrounded by killer robots (`O`). Dodge the
robots with the vi-keys (`hjkl`, `.` to wait, and `yubn` for diagonals)
and try to make them crash into each other. You win if all robots crash.
You lose if a robot lands on your square. If you find yourself stuck you
can press `t` to teleport to a new position. If you teleport next to a
robot then they will kill you, so watch out.
'''

import sys
import tty
import termios
from random import randint

from lenses import lens

MAXX = 40
MAXY = 20
ROBOTS = 12

def get_single_char():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        return sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def duplicates(items):
    seen_items = set()
    for item in items:
        if item in seen_items:
            yield item
        seen_items.add(item)

class Vector(tuple):
    @classmethod
    def random(cls):
        'produces a random vector inside the play area.'

        return cls((randint(0, MAXX), randint(0, MAXY)))

    def __add__(self, other):
        return Vector((self[0] + other[0], self[1] + other[1]))

    def inside(self):
        'checks if the vector is inside the play area.'

        return all([0 <= self[0] <= MAXX, 0 <= self[1] <= MAXY])

    def step_towards(self, other):
        '''returns the vector moved one step in the direction of the
        other, potentially diagonally.'''

        return self + Vector(
            (
                (self[0] < other[0]) - (self[0] > other[0]),
                (self[1] < other[1]) - (self[1] > other[1]),
            )
        )

class GameState:
    def __init__(self):
        self.robots = list()
        self.crashes = set()
        self.player = Vector((MAXX // 2, MAXY // 2))
        self.running = True
        self.message = None

        for _ in range(ROBOTS):
            self.robots.append(Vector.random())

    def handle_input(self, input):
        '''Takes a single character string as input and alters the game
        state according to that input. Mostly, this means moving the
        player around. Returns a new game state and boolean indicating
        whether the input had an effect on the state.'''

        dirs = {
            'h': (-1, 0),
            'j': (0, 1),
            'k': (0, -1),
            'l': (1, 0),
            'y': (-1, -1),
            'u': (1, -1),
            'n': (1, 1),
            'b': (-1, 1),
        }

        if input in dirs:
            new_self = (lens.player + dirs[input])(self)
            if not new_self.player.inside():
                return self, False
            return new_self, True
        elif input == '.':
            return self, True
        elif input == 'q':
            return self.end_game(), False
        elif input == 't':
            self = lens.player.set(Vector.random())(self)
            return self, True
        else:
            return self, False

    def advance_robots(self):
        '''Produces a new game state in which the robots have advanced
        towards the player by one step. Handles the robots crashing into
        one another too.'''

        # move the robots towards the player
        self = lens.robots.Each().call_step_towards(self.player)(self)
        # robots in the same place are crashes
        self = lens.crashes.call_union(duplicates(self.robots))(self)
        # remove crashed robots
        self = lens.robots.modify(lambda r: list(set(r) - self.crashes))(self)

        return self

    def check_game_end(self):
        '''Checks for the game's win/lose conditions and 'alters' the
        game state to reflect the condition found. If the game has not
        been won or lost then it just returns the game state
        unaltered.'''

        if self.player in self.crashes.union(self.robots):
            return self.end_game('You Died!')
        elif not self.robots:
            return self.end_game('You Win!')
        else:
            return self

    def end_game(self, message=''):
        '''Returns a completed game state object, setting an optional
        message to display after the game is over.'''

        return lens.running.set(False)(lens.message.set(message)(self))

    def __str__(self):
        rows = []
        for y in range(0, MAXY + 1):
            chars = []
            for x in range(0, MAXX + 1):
                coord = Vector((x, y))
                if coord == self.player:
                    ch = '@'
                elif coord in self.crashes:
                    ch = '#'
                elif coord in self.robots:
                    ch = 'O'
                else:
                    ch = '.'
                chars.append(ch)
            rows.append(''.join(chars))
        return '\n'.join(rows) + '\n'

def main():
    '''The main function. Instantiates a GameState object and then
    enters a REPL-like main loop, waiting for input, updating the state
    based on the input, then outputting the new state.'''

    state = GameState()
    print(state)
    while state.running:
        input = get_single_char()

        state, should_advance = state.handle_input(input)
        if should_advance:
            state = state.advance_robots()
            state = state.check_game_end()

        print(state)

    print(state.message)

if __name__ == '__main__':
    main()


#!/usr/bin/env python3
'''
naughts_and_crosses.py

Play a game of naughts and crosses (also known as tic-tac-toe) against
the computer.  To make a move choose a cell and type the number and
letter of that cell. Your input must be exactly two characters long;
do not include any spacing or punctionation.
'''

import random
import enum

from lenses import lens

class Outcome(enum.Enum):
    'The possible outcomes of a game of naughts and crosses.'
    win_for_crosses = enum.auto()
    win_for_naughts = enum.auto()
    draw = enum.auto()
    ongoing = enum.auto()

    def __bool__(self):
        'Returns whether the game has concluded.'
        return self is not Outcome.ongoing

    def __str__(self):
        return {
            Outcome.win_for_crosses: 'X is the winner!',
            Outcome.win_for_naughts: 'O is the winner!',
            Outcome.draw: 'The game is a draw!',
        }.get(self, 'The winner is unknown.')

class Board:
    'A noughts and crosses board.'

    def __init__(self):
        self.board = ((' ',) * 3,) * 3

    def make_move(self, x, y):
        '''Return a board with a cell filled in by the current player. If
        the cell is already occupied then return the board unchanged.'''
        if self.board[y][x] == ' ':
            return lens.board[y][x].set(self.player)(self)
        return self

    @property
    def player(self):
        'The player whose turn it currently is.'
        return 'X' if self._count('X') <= self._count('O') else 'O'

    @property
    def winner(self):
        'The winner of this board if one exists.'
        for potential_win in self._potential_wins():
            if potential_win == tuple('XXX'):
                return Outcome.win_for_crosses
            elif potential_win == tuple('OOO'):
                return Outcome.win_for_naughts
        if self._count(' ') == 0:
            return Outcome.draw
        return Outcome.ongoing

    def _count(self, character):
        '''Counts the number of cells in the board that contain a
        particular character.'''
        return sum(cell == character for cell in self._all_cells())

    def _potential_wins(self):
        '''Generates all the combinations of board positions that need
        to be checked for a win.'''
        yield from self.board
        yield from zip(*self.board)
        yield self.board[0][0], self.board[1][1], self.board[2][2]
        yield self.board[0][2], self.board[1][1], self.board[2][0]

    def __str__(self):
        result = []
        for letter, row in zip('abc', self.board):
            result.append(letter + '  ' + (' │ '.join(row)))
        return '   1   2   3\n' + ('\n  ───┼───┼───\n'.join(result))

    _all_cells = lens.board.Each().Each().collect()

def player_move(board):
    '''Shows the board to the player on the console and asks them to
    make a move.'''
    print(board, end='\n\n')
    x, y = input('Enter move (e.g. 2b): ')
    print()
    return int(x) - 1, ord(y) - ord('a')

def random_move(board):
    'Makes a random move on the board.'
    return random.choice(range(3)), random.choice(range(3))

def play():
    'Play a game of naughts and crosses against the computer.'
    ai = {'X': player_move, 'O': random_move}
    board = Board()
    while not board.winner:
        x, y = ai[board.player](board)
        board = board.make_move(x, y)
    print(board, end='\n\n')
    print(board.winner)

if __name__ == '__main__':
    play()


from io import BytesIO
from urllib.parse import urlparse
from datetime import datetime
import gzip

import boto3
from botocore.exceptions import ClientError

from scrapy.exporters import JsonLinesItemExporter

class S3Pipeline:
    """
    Scrapy pipeline to store items into S3 bucket with JSONLines format.
    Unlike FeedExporter, the pipeline has the following features:

    * The pipeline stores items by chunk.
    * Support GZip compression.
    """

    def __init__(self, settings, stats):
        self.stats = stats

        url = settings['S3PIPELINE_URL']
        o = urlparse(url)
        self.bucket_name = o.hostname
        self.object_key_template = o.path[1:]  # Remove the first '/'

        self.max_chunk_size = settings.getint('S3PIPELINE_MAX_CHUNK_SIZE', 100)
        self.use_gzip = settings.getbool('S3PIPELINE_GZIP', url.endswith('.gz'))

        self.s3 = boto3.client(
            's3',
            region_name=settings['AWS_REGION_NAME'], use_ssl=settings['AWS_USE_SSL'],
            verify=settings['AWS_VERIFY'], endpoint_url=settings['AWS_ENDPOINT_URL'],
            aws_access_key_id=settings['AWS_ACCESS_KEY_ID'],
            aws_secret_access_key=settings['AWS_SECRET_ACCESS_KEY'])
        self.items = []
        self.chunk_number = 0

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings, crawler.stats)

    def process_item(self, item, spider):
        """
        Process single item. Add item to items and then upload to S3 if size of items
        >= max_chunk_size.
        """
        self.items.append(item)
        if len(self.items) >= self.max_chunk_size:
            self._upload_chunk(spider)

        return item

    def open_spider(self, spider):
        """
        Callback function when spider is open.
        """
        # Store timestamp to replace {time} in S3PIPELINE_URL
        self.ts = datetime.utcnow().replace(microsecond=0).isoformat().replace(':', '-')

    def close_spider(self, spider):
        """
        Callback function when spider is closed.
        """
        # Upload remained items to S3.
        self._upload_chunk(spider)

    def _upload_chunk(self, spider):
        """
        Do upload items to S3.
        """

        if not self.items:
            return  # Do nothing when items is empty.

        f = self._make_fileobj()

        # Build object key by replacing variables in object key template.
        object_key = self.object_key_template.format(**self._get_uri_params(spider))

        try:
            self.s3.upload_fileobj(f, self.bucket_name, object_key)
        except ClientError:
            self.stats.inc_value('pipeline/s3/fail')
            raise
        else:
            self.stats.inc_value('pipeline/s3/success')
        finally:
            # Prepare for the next chunk
            self.chunk_number += len(self.items)
            self.items = []

    def _get_uri_params(self, spider):
        params = {}
        for key in dir(spider):
            params[key] = getattr(spider, key)

        params['chunk'] = self.chunk_number
        params['time'] = self.ts
        return params

    def _make_fileobj(self):
        """
        Build file object from items.
        """

        bio = BytesIO()
        f = gzip.GzipFile(mode='wb', fileobj=bio) if self.use_gzip else bio

        # Build file object using ItemExporter
        exporter = JsonLinesItemExporter(f)
        exporter.start_exporting()
        for item in self.items:
            exporter.export_item(item)
        exporter.finish_exporting()

        if f is not bio:
            f.close()  # Close the file if GzipFile

        # Seek to the top of file to be read later
        bio.seek(0)

        return bio


def update_wrapper(wrapper, wrapped):
    """ To be used under python2.4 because functools.update_wrapper() is available only from python2.5+ """
    for attr_name in ('__module__', '__name__', '__doc__'):
        attr_value = getattr(wrapped, attr_name, None)
        if attr_value is not None:
            setattr(wrapper, attr_name, attr_value)
    wrapper.__dict__.update(getattr(wrapped, '__dict__', {}))
    return wrapper


"""
    NEO JSON-RPC client
    ===================

    This module defines the ``Client`` class allowing to interact with the JSON-RPC endpoints.

"""

import binascii
import json

import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import HTTPError

from .constants import JSONRPCMethods
from .exceptions import ProtocolError, TransportError
from .utils import decode_invocation_result, encode_invocation_params

class Client:
    """ The NEO JSON-RPC client class. """

    def __init__(self, host=None, port=None, tls=False, http_max_retries=None):
        # Initializes attributes related to the client settings (host, port, etc).
        self.host = host or 'localhost'
        self.port = port or 30333
        self.tls = tls
        self.session = requests.Session()
        self.session.mount(self.host, HTTPAdapter(max_retries=http_max_retries or 3))

        # Initializes an "ID counter" that'll be used to forge each request to the JSON-RPC
        # endpoint. The "id" parameter is "required" in order to help clients sort responses out.
        # In the case of the current client, we'll just ensure that this value gets incremented
        # after each request made to the JSON-RPC endpoint.
        self._id_counter = 0

    @classmethod
    def for_mainnet(cls):
        """ Creates a ``Client`` instance for use with the NEO Main Net. """
        return cls(host='seed1.cityofzion.io', port=8080)

    @classmethod
    def for_testnet(cls):
        """ Creates a ``Client`` instance for use with the NEO Test Net. """
        return cls(host='test1.cityofzion.io', port=8880)

    def contract(self, script_hash):
        """ Returns a ``ContractWrapper`` instance allowing to easily invoke contract functions.

        This method allows to invoke smart contract functions as if they were Python class instance
        methods. For example:

        .. code-block:: python

            >>> contract = client.contract('34af1b6634fcd7cfcff0158965b18601d3837e32')
            >>> contract.symbol()
            {...}
            >>> contract.getBalance('<address>')
            {...}

        :param script_hash: contract script hash
        :type script_hash: str
        :return: :class:`ContractWrapper <ContractWrapper>` object
        :rtype: neojsonrpc.client.ContractWrapper

        """
        return ContractWrapper(self, script_hash)

    ####################
    # JSON-RPC METHODS #
    ####################

    def get_account_state(self, address, **kwargs):
        """ Returns the account state information associated with a specific address.

        :param address: a 34-bit length address (eg. AJBENSwajTzQtwyJFkiJSv7MAaaMc7DsRz)
        :type address: str
        :return: dictionary containing the account state information
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_ACCOUNT_STATE.value, params=[address, ], **kwargs)

    def get_asset_state(self, asset_id, **kwargs):
        """ Returns the asset information associated with a specific asset ID.

        :param asset_id:
            an asset identifier (the transaction ID of the RegistTransaction when the asset is
            registered)
        :type asset_id: str
        :return: dictionary containing the asset state information
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_ASSET_STATE.value, params=[asset_id, ], **kwargs)

    def get_best_block_hash(self, **kwargs):
        """ Returns the hash of the tallest block in the main chain.

        :return: hash of the tallest block in the chain
        :rtype: str

        """
        return self._call(JSONRPCMethods.GET_BEST_BLOCK_HASH.value, **kwargs)

    def get_block(self, block_hash, verbose=True, **kwargs):
        """ Returns the block information associated with a specific hash value or block index.

        :param block_hash: a block hash value or a block index (block height)
        :param verbose:
            a boolean indicating whether the detailed block information should be returned in JSON
            format (otherwise the block information is returned as an hexadecimal string by the
            JSON-RPC endpoint)
        :type block_hash: str or int
        :type verbose: bool
        :return:
            dictionary containing the block information (or an hexadecimal string if verbose is set
            to False)
        :rtype: dict or str

        """
        return self._call(
            JSONRPCMethods.GET_BLOCK.value, params=[block_hash, int(verbose), ], **kwargs)

    def get_block_count(self, **kwargs):
        """ Returns the number of blocks in the chain.

        :return: number of blocks in the chain
        :rtype: int

        """
        return self._call(JSONRPCMethods.GET_BLOCK_COUNT.value, **kwargs)

    def get_block_hash(self, block_index, **kwargs):
        """ Returns the hash value associated with a specific block index.

        :param block_index: a block index (block height)
        :type block_index: int
        :return: hash of the block associated with the considered index
        :rtype: str

        """
        return self._call(JSONRPCMethods.GET_BLOCK_HASH.value, [block_index, ], **kwargs)

    def get_block_sys_fee(self, block_index, **kwargs):
        """ Returns the system fees associated with a specific block index.

        :param block_index: a block index (block height)
        :type block_index: int
        :return: system fees of the block, expressed in NeoGas units
        :rtype: str

        """
        return self._call(JSONRPCMethods.GET_BLOCK_SYS_FEE.value, [block_index, ], **kwargs)

    def get_connection_count(self, **kwargs):
        """ Returns the current number of connections for the considered node.

        :return: number of connections for the node
        :rtype: int

        """
        return self._call(JSONRPCMethods.GET_CONNECTION_COUNT.value, **kwargs)

    def get_contract_state(self, script_hash, **kwargs):
        """ Returns the contract information associated with a specific script hash.

        :param script_hash: contract script hash
        :type script_hash: str
        :return: dictionary containing the contract information
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_CONTRACT_STATE.value, [script_hash, ], **kwargs)

    def get_raw_mem_pool(self, **kwargs):
        """ Returns a list of unconfirmed transactions in memory associated with the node.

        :return: list of unconfirmed transaction hashes
        :rtype: list

        """
        return self._call(JSONRPCMethods.GET_RAW_MEM_POOL.value, **kwargs)

    def get_raw_transaction(self, tx_hash, verbose=True, **kwargs):
        """ Returns detailed information associated with a specific transaction hash.

        :param tx_hash: transaction hash
        :param verbose:
            a boolean indicating whether the detailed transaction information should be returned in
            JSON format (otherwise the transaction information is returned as an hexadecimal string
            by the JSON-RPC endpoint)
        :type tx_hash: str
        :type verbose: bool
        :return:
            dictionary containing the transaction information (or an hexadecimal string if verbose
            is set to False)
        :rtype: dict or str

        """
        return self._call(
            JSONRPCMethods.GET_RAW_TRANSACTION.value, params=[tx_hash, int(verbose), ], **kwargs)

    def get_storage(self, script_hash, key, **kwargs):
        """ Returns the value stored in the storage of a contract script hash for a given key.

        :param script_hash: contract script hash
        :param key: key to look up in the storage
        :type script_hash: str
        :type key: str
        :return: value associated with the storage key
        :rtype: bytearray

        """
        hexkey = binascii.hexlify(key.encode('utf-8')).decode('utf-8')
        hexresult = self._call(
            JSONRPCMethods.GET_STORAGE.value, params=[script_hash, hexkey, ], **kwargs)
        try:
            assert hexresult
            result = bytearray(binascii.unhexlify(hexresult.encode('utf-8')))
        except AssertionError:
            result = hexresult
        return result

    def get_tx_out(self, tx_hash, index, **kwargs):
        """ Returns the transaction output information corresponding to a hash and index.

        :param tx_hash: transaction hash
        :param index:
            index of the transaction output to be obtained in the transaction (starts from 0)
        :type tx_hash: str
        :type index: int
        :return: dictionary containing the transaction output
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_TX_OUT.value, params=[tx_hash, index, ], **kwargs)

    def get_peers(self, **kwargs):
        """ Returns a list of nodes that the node is currently connected/disconnected from.

        :return: dictionary containing the nodes the current node is connected/disconnected from
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_PEERS.value, **kwargs)

    def get_version(self, **kwargs):
        """ Returns version information about the current node.

        :return: dictionary containing version information about the current node
        :rtype: dict

        """
        return self._call(JSONRPCMethods.GET_VERSION.value, **kwargs)

    def invoke(self, script_hash, params, **kwargs):
        """ Invokes a contract with given parameters and returns the result.

        It should be noted that the name of the function invoked in the contract should be part of
        paramaters.

        :param script_hash: contract script hash
        :param params: list of paramaters to be passed in to the smart contract
        :type script_hash: str
        :type params: list
        :return: result of the invocation
        :rtype: dictionary

        """
        contract_params = encode_invocation_params(params)
        raw_result = self._call(
            JSONRPCMethods.INVOKE.value, [script_hash, contract_params, ], **kwargs)
        return decode_invocation_result(raw_result)

    def invoke_function(self, script_hash, operation, params, **kwargs):
        """ Invokes a contract's function with given parameters and returns the result.

        :param script_hash: contract script hash
        :param operation: name of the operation to invoke
        :param params: list of paramaters to be passed in to the smart contract
        :type script_hash: str
        :type operation: str
        :type params: list
        :return: result of the invocation
        :rtype: dictionary

        """
        contract_params = encode_invocation_params(params)
        raw_result = self._call(
            JSONRPCMethods.INVOKE_FUNCTION.value, [script_hash, operation, contract_params, ],
            **kwargs)
        return decode_invocation_result(raw_result)

    def invoke_script(self, script, **kwargs):
        """ Invokes a script on the VM and returns the result.

        :param script: script runnable by the VM
        :type script: str
        :return: result of the invocation
        :rtype: dictionary

        """
        raw_result = self._call(JSONRPCMethods.INVOKE_SCRIPT.value, [script, ], **kwargs)
        return decode_invocation_result(raw_result)

    def send_raw_transaction(self, hextx, **kwargs):
        """ Broadcasts a transaction over the NEO network and returns the result.

        :param hextx: hexadecimal string that has been serialized
        :type hextx: str
        :return: result of the transaction
        :rtype: bool

        """
        return self._call(JSONRPCMethods.SEND_RAW_TRANSACTION.value, [hextx, ], **kwargs)

    def validate_address(self, addr, **kwargs):
        """ Validates if the considered string is a valid NEO address.

        :param hex: string containing a potential NEO address
        :type hex: str
        :return: dictionary containing the result of the verification
        :rtype: dictionary

        """
        return self._call(JSONRPCMethods.VALIDATE_ADDRESS.value, [addr, ], **kwargs)

    ##################################
    # PRIVATE METHODS AND PROPERTIES #
    ##################################

    def _call(self, method, params=None, request_id=None):
        """ Calls the JSON-RPC endpoint. """
        params = params or []

        # Determines which 'id' value to use and increment the counter associated with the current
        # client instance if applicable.
        rid = request_id or self._id_counter
        if request_id is None:
            self._id_counter += 1

        # Prepares the payload and the headers that will be used to forge the request.
        payload = {'jsonrpc': '2.0', 'method': method, 'params': params, 'id': rid}
        headers = {'Content-Type': 'application/json'}
        scheme = 'https' if self.tls else 'http'
        url = '{}://{}:{}'.format(scheme, self.host, self.port)

        # Calls the JSON-RPC endpoint!
        try:
            response = self.session.post(url, headers=headers, data=json.dumps(payload))
            response.raise_for_status()
        except HTTPError:
            raise TransportError(
                'Got unsuccessful response from server (status code: {})'.format(
                    response.status_code),
                response=response)

        # Ensures the response body can be deserialized to JSON.
        try:
            response_data = response.json()
        except ValueError as e:
            raise ProtocolError(
                'Unable to deserialize response body: {}'.format(e), response=response)

        # Properly handles potential errors.
        if response_data.get('error'):
            code = response_data['error'].get('code', '')
            message = response_data['error'].get('message', '')
            raise ProtocolError(
                'Error[{}] {}'.format(code, message), response=response, data=response_data)
        elif 'result' not in response_data:
            raise ProtocolError(
                'Response is empty (result field is missing)', response=response,
                data=response_data)

        return response_data['result']

class ContractWrapper:
    """ Strategy class allowing to provide a high-level interface for invoking smart contracts. """

    def __init__(self, client, script_hash):
        self.client = client
        self.script_hash = script_hash

    def __getattribute__(self, attr):
        try:
            return super(ContractWrapper, self).__getattribute__(attr)
        except AttributeError:
            client = super(ContractWrapper, self).__getattribute__('client')
            script_hash = super(ContractWrapper, self).__getattribute__('script_hash')
            return ContractFunctionWrapper(client, script_hash, attr)

class ContractFunctionWrapper:
    """ Strategy class allowing to easily invoke smart contract functions. """

    def __init__(self, client, script_hash, funcname):
        self.client = client
        self.script_hash = script_hash
        self.funcname = funcname

    def __call__(self, *args):
        return self.client.invoke_function(self.script_hash, self.funcname, args)


"""
    NEO JSON-RPC client utilities
    =============================

    This module defines utilities and shortcuts used by the NEO JSON-RPC client.

"""

import binascii
import copy
import re

from .constants import ContractParameterTypes

def is_hash256(s):
    """ Returns True if the considered string is a valid SHA256 hash. """
    if not s or not isinstance(s, str):
        return False
    return re.match('^[0-9A-F]{64}$', s.strip(), re.IGNORECASE)

def is_hash160(s):
    """ Returns True if the considered string is a valid RIPEMD160 hash. """
    if not s or not isinstance(s, str):
        return False
    if not len(s) == 40:
        return False
    for c in s:
        if (c < '0' or c > '9') and (c < 'A' or c > 'F') and (c < 'a' or c > 'f'):
            return False
    return True

def encode_invocation_params(params):
    """ Returns a list of paramaters meant to be passed to JSON-RPC endpoints. """
    final_params = []
    for p in params:
        if isinstance(p, bool):
            final_params.append({'type': ContractParameterTypes.BOOLEAN.value, 'value': p})
        elif isinstance(p, int):
            final_params.append({'type': ContractParameterTypes.INTEGER.value, 'value': p})
        elif is_hash256(p):
            final_params.append({'type': ContractParameterTypes.HASH256.value, 'value': p})
        elif is_hash160(p):
            final_params.append({'type': ContractParameterTypes.HASH160.value, 'value': p})
        elif isinstance(p, bytearray):
            final_params.append({'type': ContractParameterTypes.BYTE_ARRAY.value, 'value': p})
        elif isinstance(p, str):
            final_params.append({'type': ContractParameterTypes.STRING.value, 'value': p})
        elif isinstance(p, list):
            innerp = encode_invocation_params(p)
            final_params.append({'type': ContractParameterTypes.ARRAY.value, 'value': innerp})
    return final_params

def decode_invocation_result(result):
    """ Tries to decode the values embedded in an invocation result dictionary. """
    if 'stack' not in result:
        return result
    result = copy.deepcopy(result)
    result['stack'] = _decode_invocation_result_stack(result['stack'])
    return result

def _decode_invocation_result_stack(stack):
    stack = copy.deepcopy(stack)
    for value_dict in stack:
        if value_dict['type'] == 'Array':
            value_dict['value'] = _decode_invocation_result_stack(value_dict['value'])
        elif value_dict['type'] == 'ByteArray':
            value_dict['value'] = bytearray(binascii.unhexlify(value_dict['value'].encode('utf-8')))
    return stack


# -*- coding: utf-8 -*-

import inspect
import sys

# The @decorator syntax is available since python2.4 and we support even this old version. Unfortunately functools
# has been introduced only in python2.5 so we have to emulate functools.update_wrapper() under python2.4.
try:
    from functools import update_wrapper
except ImportError:
    from kwonly_args.utils import update_wrapper

__all__ = ['first_kwonly_arg', 'KWONLY_REQUIRED', 'FIRST_DEFAULT_ARG', 'kwonly_defaults']

# version_info[0]: Increase in case of large milestones/releases.
# version_info[1]: Increase this and zero out version_info[2] if you have explicitly modified
#                  a previously existing behavior/interface.
#                  If the behavior of an existing feature changes as a result of a bugfix
#                  and the new (bugfixed) behavior is that meets the expectations of the
#                  previous interface documentation then you shouldn't increase this, in that
#                  case increase only version_info[2].
# version_info[2]: Increase in case of bugfixes. Also use this if you added new features
#                  without modifying the behavior of the previously existing ones.
version_info = (1, 0, 10)
__version__ = '.'.join(str(n) for n in version_info)
__author__ = 'István Pásztor'
__license__ = 'MIT'

KWONLY_REQUIRED = ('KWONLY_REQUIRED',)
FIRST_DEFAULT_ARG = ('FIRST_DEFAULT_ARG',)

def first_kwonly_arg(name):
    """ Emulates keyword-only arguments under python2. Works with both python2 and python3.
    With this decorator you can convert all or some of the default arguments of your function
    into kwonly arguments. Use ``KWONLY_REQUIRED`` as the default value of required kwonly args.

    :param name: The name of the first default argument to be treated as a keyword-only argument. This default
    argument along with all default arguments that follow this one will be treated as keyword only arguments.

    You can also pass here the ``FIRST_DEFAULT_ARG`` constant in order to select the first default argument. This
    way you turn all default arguments into keyword-only arguments. As a shortcut you can use the
    ``@kwonly_defaults`` decorator (without any parameters) instead of ``@first_kwonly_arg(FIRST_DEFAULT_ARG)``.

        >>> from kwonly_args import first_kwonly_arg, KWONLY_REQUIRED, FIRST_DEFAULT_ARG, kwonly_defaults
        >>>
        >>> # this decoration converts the ``d1`` and ``d2`` default args into kwonly args
        >>> @first_kwonly_arg('d1')
        >>> def func(a0, a1, d0='d0', d1='d1', d2='d2', *args, **kwargs):
        >>>     print(a0, a1, d0, d1, d2, args, kwargs)
        >>>
        >>> func(0, 1, 2, 3, 4)
        0 1 2 d1 d2 (3, 4) {}
        >>>
        >>> func(0, 1, 2, 3, 4, d2='my_param')
        0 1 2 d1 my_param (3, 4) {}
        >>>
        >>> # d0 is an optional deyword argument, d1 is required
        >>> def func(d0='d0', d1=KWONLY_REQUIRED):
        >>>     print(d0, d1)
        >>>
        >>> # The ``FIRST_DEFAULT_ARG`` constant automatically selects the first default argument so it
        >>> # turns all default arguments into keyword-only ones. Both d0 and d1 are keyword-only arguments.
        >>> @first_kwonly_arg(FIRST_DEFAULT_ARG)
        >>> def func(a0, a1, d0='d0', d1='d1'):
        >>>     print(a0, a1, d0, d1)
        >>>
        >>> # ``@kwonly_defaults`` is a shortcut for the ``@first_kwonly_arg(FIRST_DEFAULT_ARG)``
        >>> # in the previous example. This example has the same effect as the previous one.
        >>> @kwonly_defaults
        >>> def func(a0, a1, d0='d0', d1='d1'):
        >>>     print(a0, a1, d0, d1)
    """
    def decorate(wrapped):
        if sys.version_info[0] == 2:
            arg_names, varargs, _, defaults = inspect.getargspec(wrapped)
        else:
            arg_names, varargs, _, defaults = inspect.getfullargspec(wrapped)[:4]

        if not defaults:
            raise TypeError("You can't use @first_kwonly_arg on a function that doesn't have default arguments!")
        first_default_index = len(arg_names) - len(defaults)

        if name is FIRST_DEFAULT_ARG:
            first_kwonly_index = first_default_index
        else:
            try:
                first_kwonly_index = arg_names.index(name)
            except ValueError:
                raise ValueError("%s() doesn't have an argument with the specified first_kwonly_arg=%r name" % (
                                 getattr(wrapped, '__name__', '?'), name))

        if first_kwonly_index < first_default_index:
            raise ValueError("The specified first_kwonly_arg=%r must have a default value!" % (name,))

        kwonly_defaults = defaults[-(len(arg_names)-first_kwonly_index):]
        kwonly_args = tuple(zip(arg_names[first_kwonly_index:], kwonly_defaults))
        required_kwonly_args = frozenset(arg for arg, default in kwonly_args if default is KWONLY_REQUIRED)

        def wrapper(*args, **kwargs):
            if required_kwonly_args:
                missing_kwonly_args = required_kwonly_args.difference(kwargs.keys())
                if missing_kwonly_args:
                    raise TypeError("%s() missing %s keyword-only argument(s): %s" % (
                                    getattr(wrapped, '__name__', '?'), len(missing_kwonly_args),
                                    ', '.join(sorted(missing_kwonly_args))))
            if len(args) > first_kwonly_index:
                if varargs is None:
                    raise TypeError("%s() takes exactly %s arguments (%s given)" % (
                                    getattr(wrapped, '__name__', '?'), first_kwonly_index, len(args)))
                kwonly_args_from_kwargs = tuple(kwargs.pop(arg, default) for arg, default in kwonly_args)
                args = args[:first_kwonly_index] + kwonly_args_from_kwargs + args[first_kwonly_index:]

            return wrapped(*args, **kwargs)

        return update_wrapper(wrapper, wrapped)
    return decorate

kwonly_defaults = first_kwonly_arg(FIRST_DEFAULT_ARG)


import json
import os
import zipfile

from packaging.version import parse as parse_version

from devpi_server.log import threadlog

def extract_metadata_from_wheel_file(wheel_filename):
    with zipfile.ZipFile(wheel_filename) as zf:
        for meta in zf.namelist():
            if meta.endswith('.dist-info/metadata.json'):
                return json.loads(zf.open(meta).read().decode('utf8'))

def devpiserver_on_upload(stage, project, version, link):
    """ called when a file is uploaded to a private stage for
    a projectname/version.  link.entry.file_exists() may be false because
    a more recent revision deleted the file (and files are not revisioned).
    NOTE that this hook is currently NOT called for the implicit "caching"
    uploads to the pypi mirror.

    If the uploaded file is a wheel and is the latest version on this index,
    store its metadata in json file at the root of index/+f/ directory.
    With the standard config with nginx, nginx will directly serve this file.
    """
    if link.entry and link.entry.file_exists() and link.entry.basename.endswith('.whl'):
        threadlog.info("Wheel detected: %s", link.entry.basename)
        new_version = parse_version(version)
        latest_version = parse_version(stage.get_latest_version_perstage(project))
        if latest_version > new_version:
            threadlog.debug("A newer release has already been uploaded: %s - nothing to do", latest_version)
            return
        metadata = extract_metadata_from_wheel_file(link.entry.file_os_path())
        linkstore = stage.get_linkstore_perstage(link.project, link.version)
        project_dir = '%s/%s/+f/%s' % (linkstore.filestore.storedir, stage.name, project)

        if not os.path.exists(project_dir):
            os.mkdir(project_dir)

        json_path = '%s/%s-%s.json' % (project_dir, project, new_version)
        with open(json_path, 'w') as fd:
            fd.write(json.dumps(metadata))

        threadlog.info("Stored %s to: %s", metadata, json_path)

        # We symlink the latest version
        symlink_path = '%s.json' % project_dir
        if os.path.exists(symlink_path):
            os.unlink(symlink_path)
        os.symlink(json_path, symlink_path)


import re
from functools import reduce
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import pytz

UTC = pytz.utc

class SnapParseError(Exception):
    pass

class SnapUnitError(Exception):
    pass

SECONDS = "seconds"
MINUTES = "minutes"
HOURS = "hours"
DAYS = "days"
WEEKS = "weeks"
MONTHS = "months"
YEARS = "years"

# see also
# http://docs.splunk.com/Documentation/Splunk/latest/SearchReference/SearchTimeModifiers#How_to_specify_relative_time_modifiers
UNIT_LISTS = {
    SECONDS: ["s", "sec", "secs", "second", "seconds"],
    MINUTES: ["m", "min", "minute", "minutes"],
    HOURS: ["h", "hr", "hrs", "hour", "hours"],
    DAYS: ["d", "day", "days"],
    WEEKS: ["w", "week", "weeks"],
    MONTHS: ["mon", "month", "months"],
    # "quarters": ["q", "qtr", "qtrs", "quarter", "quarters"],  # not supported by relativedelta
    YEARS: ["y", "yr", "yrs", "year", "years"]
}

def get_unit(string):

    for unit, variants in UNIT_LISTS.items():
        if string in variants:
            return unit

    raise SnapUnitError("Unknown unit string '%s'" % string)

def get_weekday(string):
    result = get_num(string, default=None)
    if result and not (result >= 0 and result <= 7):
        raise SnapParseError("Bad weekday '%s'" % str(result))
    return result

def get_num(string, default=1):
    if string is None or string == "":
        return default

    return int(string)

def get_mult(string):
    return -1 if string == "-" else 1

def truncate(datetime_, unit):
    if unit == "seconds":
        result = datetime_.replace(microsecond=0)
    elif unit == "minutes":
        result = datetime_.replace(second=0, microsecond=0)
    elif unit == "hours":
        result = datetime_.replace(minute=0, second=0, microsecond=0)
    elif unit == "days":
        result = datetime_.replace(hour=0, minute=0, second=0, microsecond=0)
    elif unit == "months":
        result = datetime_.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    elif unit == "years":
        result = datetime_.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)

    return result

D_GENERAL = r"[-+]?\d+[a-zA-Z]+"
D_DETAILS = r"(?P<sign>[-+]?)(?P<num>\d+)(?P<unit_string>[a-zA-Z]+)"
D_PATTERN = re.compile(D_DETAILS)

S_GENERAL = r"@[a-zA-Z]+\d*"
S_DETAILS = r"@(?P<unit_string>[a-zA-Z]+)(?P<weekday>\d*)"
S_PATTERN = re.compile(S_DETAILS)

HEAD_PATTERN = re.compile(r"^({snap}|{delta})(.*)".format(snap=S_GENERAL, delta=D_GENERAL))

class SnapTransformation(object):
    def __init__(self, group):
        matchdict = S_PATTERN.match(group).groupdict()
        assert matchdict
        self.unit = get_unit(matchdict.get("unit_string"))
        self.weekday = get_weekday(matchdict.get("weekday"))

    def apply_to(self, dttm):
        result = dttm

        if self.unit == "weeks" and self.weekday:
            result = result - timedelta((result.isoweekday() - self.weekday) % 7)
            result = truncate(result, "days")
        elif self.unit == "weeks":
            result = result - timedelta((dttm.isoweekday() - 0) % 7)
            result = truncate(result, "days")
        else:
            # normal case
            result = truncate(result, self.unit)
        return result

    def apply_to_with_tz(self, dttm, timezone):
        """We make sure that after truncating we use the correct timezone,
        even if we 'jump' over a daylight saving time switch.

        I.e. if we apply "@d" to `Sun Oct 30 04:30:00 CET 2016` (1477798200)
        we want to have `Sun Oct 30 00:00:00 CEST 2016` (1477778400)
        but not `Sun Oct 30 00:00:00 CET 2016` (1477782000)
        """
        result = self.apply_to(dttm)
        if self.unit in [DAYS, WEEKS, MONTHS, YEARS]:
            naive_dttm = datetime(result.year, result.month, result.day)
            result = timezone.localize(naive_dttm)
        return result

class DeltaTransformation(object):
    def __init__(self, group):
        matchdict = D_PATTERN.match(group).groupdict()
        self.mult = get_mult(matchdict.get("sign"))
        self.num = get_num(matchdict.get("num"))
        self.unit = get_unit(matchdict.get("unit_string"))

    def apply_to(self, dttm):
        return dttm + relativedelta(**{self.unit: self.mult * self.num})

    def apply_to_with_tz(self, dttm, timezone):
        return timezone.normalize(self.apply_to(dttm))

def parse(instruction):
    instr = instruction
    result = []

    while instr:
        match = HEAD_PATTERN.match(instr)
        if not match:
            raise SnapParseError("Cannot parse instruction '%s'. There is an error at '%s'" % (instruction, instr))
        group = match.group(1)

        if S_PATTERN.match(group):
            transformation = SnapTransformation(group)
        else:
            transformation = DeltaTransformation(group)

        result.append(transformation)
        instr = match.group(2)

    return result

def snap(dttm, instruction):
    """
    Args:
        instruction (string): a string that encodes 0 to n transformations of a time, i.e. "-1h@h", "@mon+2d+4h", ...
        dttm (datetime):
    Returns:
        datetime: The datetime resulting from applying all transformations to the input datetime.

    Example:
        >>> snap(datetime(2016, 1, 1, 15, 30), "-1h@h")
        datetime(2016, 1, 1, 14)
    """
    transformations = parse(instruction)
    return reduce(lambda dt, transformation: transformation.apply_to(dt), transformations, dttm)

def snap_tz(dttm, instruction, timezone):
    """This function handles timezone aware datetimes.
    Sometimes it is necessary to keep daylight saving time switches in mind.

    Args:
        instruction (string): a string that encodes 0 to n transformations of a time, i.e. "-1h@h", "@mon+2d+4h", ...
        dttm (datetime): a datetime with timezone
        timezone: a pytz timezone
    Returns:
        datetime: The datetime resulting from applying all transformations to the input datetime.

    Example:
        >>> import pytz
        >>> CET = pytz.timezone("Europe/Berlin")
        >>> dttm = CET.localize(datetime(2017, 3, 26, 3, 44)
        >>> dttm
        datetime.datetime(2017, 3, 26, 3, 44, tzinfo=<DstTzInfo 'Europe/Berlin' CEST+2:00:00 DST>)

        >>> snap_tz(dttm, "-2h@h", CET)
        datetime.datetime(2017, 3, 26, 0, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
        >>> # switch from winter to summer time!
    """
    transformations = parse(instruction)
    return reduce(lambda dt, transformation: transformation.apply_to_with_tz(dt, timezone), transformations, dttm)


# -*- coding: utf-8 -*-
"""barcode.base

"""

from __future__ import unicode_literals

from barcode.writer import SVGWriter

class Barcode(object):

    name = ''

    raw = None

    digits = 0

    default_writer = SVGWriter

    default_writer_options = {
        'module_width': 0.2,
        'module_height': 15.0,
        'quiet_zone': 6.5,
        'font_size': 10,
        'text_distance': 5.0,
        'background': 'white',
        'foreground': 'black',
        'write_text': True,
        'text': '',
    }

    def to_ascii(self):
        code = self.build()
        for i, line in enumerate(code):
            code[i] = line.replace('1', 'X').replace('0', ' ')
        return '\n'.join(code)

    def __repr__(self):
        return '<{0}({1!r})>'.format(self.__class__.__name__,
                                     self.get_fullcode())

    def build(self):
        raise NotImplementedError

    def get_fullcode(self):
        """Returns the full code, encoded in the barcode.

        :returns: Full human readable code.
        :rtype: String
        """
        raise NotImplementedError

    def save(self, filename, options=None):
        """Renders the barcode and saves it in `filename`.

        :parameters:
            filename : String
                Filename to save the barcode in (without filename
                extension).
            options : Dict
                The same as in `self.render`.

        :returns: The full filename with extension.
        :rtype: String
        """
        output = self.render(options)
        _filename = self.writer.save(filename, output)
        return _filename

    def write(self, fp, options=None):
        """Renders the barcode and writes it to the file like object
        `fp`.

        :parameters:
            fp : File like object
                Object to write the raw data in.
            options : Dict
                The same as in `self.render`.
        """
        output = self.render(options)
        if hasattr(output, 'tostring'):
            output.save(fp, format=self.writer.format)
        else:
            fp.write(output)

    def render(self, writer_options=None):
        """Renders the barcode using `self.writer`.

        :parameters:
            writer_options : Dict
                Options for `self.writer`, see writer docs for details.

        :returns: Output of the writers render method.
        """
        options = Barcode.default_writer_options.copy()
        options.update(writer_options or {})
        if options['write_text']:
            options['text'] = self.get_fullcode()
        self.writer.set_options(options)
        code = self.build()
        raw = Barcode.raw = self.writer.render(code)
        return raw


# -*- coding: utf-8 -*-
"""Module: barcode.ean

:Provided barcodes: EAN-13, EAN-8, JAN
"""

from __future__ import unicode_literals

from barcode.base import Barcode
from barcode.charsets import ean as _ean
from barcode.errors import *

# Python 3
try:
    reduce
except NameError:
    from functools import reduce

# EAN13 Specs (all sizes in mm)
SIZES = dict(SC0=0.27, SC1=0.297, SC2=0.33, SC3=0.363, SC4=0.396, SC5=0.445,
             SC6=0.495, SC7=0.544, SC8=0.61, SC9=0.66)

class EuropeanArticleNumber13(Barcode):
    """Initializes EAN13 object.

    :parameters:
        ean : String
            The ean number as string.
        writer : barcode.writer Instance
            The writer to render the barcode (default: SVGWriter).
    """

    name = 'EAN-13'

    digits = 12

    def __init__(self, ean, writer=None):
        ean = ean[:self.digits]
        if not ean.isdigit():
            raise IllegalCharacterError('EAN code can only contain numbers.')
        self.ean = ean
        self.ean = '{0}{1}'.format(ean, self.calculate_checksum())
        self.writer = writer or Barcode.default_writer()

    def __unicode__(self):
        return self.ean

    __str__ = __unicode__

    def get_fullcode(self):
        return self.ean

    def calculate_checksum(self):
        """Calculates the checksum for EAN13-Code.

        :returns: The checksum for `self.ean`.
        :rtype: Integer
        """
        def sum_(x, y):
            return int(x) + int(y)

        evensum = reduce(sum_, self.ean[::2])
        oddsum = reduce(sum_, self.ean[1::2])
        return (10 - ((evensum + oddsum * 3) % 10)) % 10

    def build(self):
        """Builds the barcode pattern from `self.ean`.

        :returns: The pattern as string
        :rtype: String
        """
        code = _ean.EDGE[:]
        pattern = _ean.LEFT_PATTERN[int(self.ean[0])]
        for i, number in enumerate(self.ean[1:7]):
            code += _ean.CODES[pattern[i]][int(number)]
        code += _ean.MIDDLE
        for number in self.ean[7:]:
            code += _ean.CODES['C'][int(number)]
        code += _ean.EDGE
        return [code]

    def to_ascii(self):
        """Returns an ascii representation of the barcode.

        :rtype: String
        """
        code = self.build()
        for i, line in enumerate(code):
            code[i] = line.replace('1', '|').replace('0', ' ')
        return '\n'.join(code)

    def render(self, writer_options=None):
        options = dict(module_width=SIZES['SC2'])
        options.update(writer_options or {})
        return Barcode.render(self, options)

class JapanArticleNumber(EuropeanArticleNumber13):
    """Initializes JAN barcode.

    :parameters:
        jan : String
            The jan number as string.
        writer : barcode.writer Instance
            The writer to render the barcode (default: SVGWriter).
    """

    name = 'JAN'

    valid_country_codes = list(range(450, 460)) + list(range(490, 500))

    def __init__(self, jan, writer=None):
        if int(jan[:3]) not in JapanArticleNumber.valid_country_codes:
            raise WrongCountryCodeError("Country code isn't between 450-460 "
                                        "or 490-500.")
        super(JapanArticleNumber, self).__init__(jan, writer)

class EuropeanArticleNumber8(EuropeanArticleNumber13):
    """Represents an EAN-8 barcode. See EAN13's __init__ for details.

    :parameters:
        ean : String
            The ean number as string.
        writer : barcode.writer Instance
            The writer to render the barcode (default: SVGWriter).
    """

    name = 'EAN-8'

    digits = 7

    def calculate_checksum(self):
        """Calculates the checksum for EAN8-Code.

        :returns: The checksum for `self.ean`.
        :rtype: Integer
        """
        def sum_(x, y):
            return int(x) + int(y)

        evensum = reduce(sum_, self.ean[::2])
        oddsum = reduce(sum_, self.ean[1::2])
        return (10 - ((evensum * 3 + oddsum) % 10)) % 10

    def build(self):
        """Builds the barcode pattern from `self.ean`.

        :returns: The pattern as string
        :rtype: String
        """
        code = _ean.EDGE[:]
        for number in self.ean[:4]:
            code += _ean.CODES['A'][int(number)]
        code += _ean.MIDDLE
        for number in self.ean[4:]:
            code += _ean.CODES['C'][int(number)]
        code += _ean.EDGE
        return [code]

class EuropeanArticleNumber14(EuropeanArticleNumber13):
        """Represents an EAN-14 barcode. See EAN13's __init__ for details.

        :parameters:
            ean : String
                The ean number as string.
            writer : barcode.writer Instance
                The writer to render the barcode (default: SVGWriter).
        """

        name = 'EAN-14'
        digits = 13

        def calculate_checksum(self):
            """Calculates the checksum for EAN13-Code.

            :returns: The checksum for `self.ean`.
            :rtype: Integer
            """

            def sum_(x, y):
                return int(x) + int(y)

            evensum = reduce(sum_, self.ean[::2])
            oddsum = reduce(sum_, self.ean[1::2])
            return (10 - (((evensum * 3) + oddsum) % 10)) % 10

# Shortcuts
EAN13 = EuropeanArticleNumber13
EAN14 = EuropeanArticleNumber14
EAN8 = EuropeanArticleNumber8
JAN = JapanArticleNumber


# -*- coding: utf-8 -*-

from __future__ import unicode_literals

import gzip
import os
import xml.dom

from barcode import __release__

try:
    import Image
    import ImageDraw
    import ImageFont
except ImportError:
    try:
        from PIL import Image, ImageDraw, ImageFont  # lint:ok
    except ImportError:
        import logging
        log = logging.getLogger('barcode')
        log.info('PIL not found. Image output disabled.')
        Image = ImageDraw = ImageFont = None  # lint:ok

def mm2px(mm, dpi=300):
    return (mm * dpi) / 25.4

def pt2mm(pt):
    return pt * 0.352777778

def _set_attributes(element, **attributes):
    for key, value in attributes.items():
        element.setAttribute(key, value)

def create_svg_object():
    imp = xml.dom.getDOMImplementation()
    doctype = imp.createDocumentType(
        'svg',
        '-//W3C//DTD SVG 1.1//EN',
        'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
    )
    document = imp.createDocument(None, 'svg', doctype)
    _set_attributes(document.documentElement, version='1.1',
                    xmlns='http://www.w3.org/2000/svg')
    return document

SIZE = '{0:.3f}mm'
COMMENT = 'Autogenerated with viivakoodi {0}'.format(__release__)
PATH = os.path.dirname(os.path.abspath(__file__))
FONT = os.path.join(PATH, 'DejaVuSansMono.ttf')

class BaseWriter(object):
    """Baseclass for all writers.

    Initializes the basic writer options. Childclasses can add more
    attributes and can set them directly or using
    `self.set_options(option=value)`.

    :parameters:
        initialize : Function
            Callback for initializing the inheriting writer.
            Is called: `callback_initialize(raw_code)`
        paint_module : Function
            Callback for painting one barcode module.
            Is called: `callback_paint_module(xpos, ypos, width, color)`
        paint_text : Function
            Callback for painting the text under the barcode.
            Is called: `callback_paint_text(xpos, ypos)` using `self.text`
            as text.
        finish : Function
            Callback for doing something with the completely rendered
            output.
            Is called: `return callback_finish()` and must return the
            rendered output.
    """

    def __init__(self, initialize=None, paint_module=None, paint_text=None,
                 finish=None):
        self._callbacks = dict(initialize=initialize, paint_module=paint_module,
                               paint_text=paint_text, finish=finish)
        self.module_width = 10
        self.module_height = 10
        self.font_size = 10
        self.quiet_zone = 6.5
        self.background = 'white'
        self.foreground = 'black'
        self.text = ''
        self.text_distance = 5
        self.center_text = True

    def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
        """Calculates the size of the barcode in pixel.

        :parameters:
            modules_per_line : Integer
                Number of modules in one line.
            number_of_lines : Integer
                Number of lines of the barcode.
            dpi : Integer
                DPI to calculate.

        :returns: Width and height of the barcode in pixel.
        :rtype: Tuple
        """
        width = 2 * self.quiet_zone + modules_per_line * self.module_width
        height = 2.0 + self.module_height * number_of_lines
        if self.font_size and self.text:
            height += pt2mm(self.font_size) / 2 + self.text_distance
        return int(mm2px(width, dpi)), int(mm2px(height, dpi))

    def save(self, filename, output):
        """Saves the rendered output to `filename`.

        :parameters:
            filename : String
                Filename without extension.
            output : String
                The rendered output.

        :returns: The full filename with extension.
        :rtype: String
        """
        raise NotImplementedError

    def register_callback(self, action, callback):
        """Register one of the three callbacks if not given at instance
        creation.

        :parameters:
            action : String
                One of 'initialize', 'paint_module', 'paint_text', 'finish'.
            callback : Function
                The callback function for the given action.
        """
        self._callbacks[action] = callback

    def set_options(self, options):
        """Sets the given options as instance attributes (only
        if they are known).

        :parameters:
            options : Dict
                All known instance attributes and more if the childclass
                has defined them before this call.

        :rtype: None
        """
        for key, val in options.items():
            key = key.lstrip('_')
            if hasattr(self, key):
                setattr(self, key, val)

    def render(self, code):
        """Renders the barcode to whatever the inheriting writer provides,
        using the registered callbacks.

        :parameters:
            code : List
                List of strings matching the writer spec
                (only contain 0 or 1).
        """
        if self._callbacks['initialize'] is not None:
            self._callbacks['initialize'](code)
        ypos = 1.0
        for line in code:
            # Left quiet zone is x startposition
            xpos = self.quiet_zone
            for mod in line:
                if mod == '0':
                    color = self.background
                else:
                    color = self.foreground
                self._callbacks['paint_module'](xpos, ypos, self.module_width,
                                                color)
                xpos += self.module_width
            # Add right quiet zone to every line
            self._callbacks['paint_module'](xpos, ypos, self.quiet_zone,
                                            self.background)
            ypos += self.module_height
        if self.text and self._callbacks['paint_text'] is not None:
            ypos += self.text_distance
            if self.center_text:
                xpos = xpos / 2.0
            else:
                xpos = self.quiet_zone + 4.0
            self._callbacks['paint_text'](xpos, ypos)
        return self._callbacks['finish']()

class SVGWriter(BaseWriter):

    def __init__(self):
        super(SVGWriter, self).__init__(
            self._init, self._create_module, self._create_text, self._finish,
        )
        self.compress = False
        self.dpi = 25.4
        self._document = None
        self._root = None

    def _init(self, code):
        width, height = self.calculate_size(len(code[0]), len(code), self.dpi)
        self._document = create_svg_object()
        self._root = self._document.documentElement
        attributes = dict(width=SIZE.format(width), height=SIZE.format(height))
        _set_attributes(self._root, **attributes)
        self._root.appendChild(self._document.createComment(COMMENT))
        background = self._document.createElement('rect')
        attributes = dict(width='100%', height='100%',
                          style='fill:{0}'.format(self.background))
        _set_attributes(background, **attributes)
        self._root.appendChild(background)

    def _create_module(self, xpos, ypos, width, color):
        element = self._document.createElement('rect')
        attributes = dict(x=SIZE.format(xpos), y=SIZE.format(ypos),
                          width=SIZE.format(width),
                          height=SIZE.format(self.module_height),
                          style='fill:{0};'.format(color))
        _set_attributes(element, **attributes)
        self._root.appendChild(element)

    def _create_text(self, xpos, ypos):
        element = self._document.createElement('text')
        attributes = dict(x=SIZE.format(xpos), y=SIZE.format(ypos),
                          style='fill:{0};font-size:{1}pt;text-anchor:'
                                'middle;'.format(self.foreground,
                                                 self.font_size))
        _set_attributes(element, **attributes)
        text_element = self._document.createTextNode(self.text)
        element.appendChild(text_element)
        self._root.appendChild(element)

    def _finish(self):
        if self.compress:
            return self._document.toxml(encoding='UTF-8')
        else:
            return self._document.toprettyxml(indent=4 * ' ', newl=os.linesep,
                                              encoding='UTF-8')

    def save(self, filename, output):
        if self.compress:
            _filename = '{0}.svgz'.format(filename)
            f = gzip.open(_filename, 'wb')
            f.write(output)
            f.close()
        else:
            _filename = '{0}.svg'.format(filename)
            with open(_filename, 'wb') as f:
                f.write(output)
        return _filename

if Image is None:
    ImageWriter = None
else:
    class ImageWriter(BaseWriter):

        def __init__(self):
            super(ImageWriter, self).__init__(
                self._init, self._paint_module, self._paint_text,
                self._finish,
            )
            self.format = 'PNG'
            self.dpi = 300
            self._image = None
            self._draw = None

        def _init(self, code):
            size = self.calculate_size(len(code[0]), len(code), self.dpi)
            self._image = Image.new('RGB', size, self.background)
            self._draw = ImageDraw.Draw(self._image)

        def _paint_module(self, xpos, ypos, width, color):
            size = [(mm2px(xpos, self.dpi), mm2px(ypos, self.dpi)),
                    (mm2px(xpos + width, self.dpi),
                     mm2px(ypos + self.module_height, self.dpi))]
            self._draw.rectangle(size, outline=color, fill=color)

        def _paint_text(self, xpos, ypos):
            font = ImageFont.truetype(FONT, self.font_size * 2)
            width, height = font.getsize(self.text)
            pos = (mm2px(xpos, self.dpi) - width // 2,
                   mm2px(ypos, self.dpi) - height // 4)
            self._draw.text(pos, self.text, font=font, fill=self.foreground)

        def _finish(self):
            return self._image

        def save(self, filename, output):
            filename = '{0}.{1}'.format(filename, self.format.lower())
            output.save(filename, self.format.upper())
            return filename


from sqlalchemy.orm import Query
import pandas as pd

class SangrealQuery(Query):
    def to_df(self, **kwargs):
        """[pandas.read_sql]

        Arguments:
            Query {[type]} -- [description]

        Returns:
            [pd.DataFrame or generate] -- [description]
        """

        return pd.read_sql(sql=self.statement, con=self.session.bind, **kwargs)


import logging

from .serializer import serializer
from .kvs import KVS

log = logging.getLogger(__name__)

class PerlSession(object):
    """
    Read only session from a perl storable.
    Use the "connect method" during the configuration to initialize it
    """
    cookie_name = None
    client = None

    def __init__(self, request):
        self._session_key = request.cookies.get(self.cookie_name)

        self.request = request
        self._session_data = self.client.get(self._session_key)
        if self._session_data is None:
            log.warn('session %s not deserialized' % self._session_key)
            self._session_data = {}

    def __call__(self, request):
        return self

    @classmethod
    def connect(cls, settings):
        """ Call that method in the pyramid configuration phase.
        """
        server = serializer('json').loads(settings['kvs.perlsess'])
        server.setdefault('key_prefix', 'perlsess::')
        server.setdefault('codec', 'storable')
        cls.cookie_name = server.pop('cookie_name', 'session_id')
        cls.client = KVS(**server)

    def __getitem__(self, key):
        return self._session_data[key]

    def __len__(self):
        return len(self._session_data)

    def __contains__(self, key):
        return key in self._session_data

    def keys(self):
        return self._session_data.keys()

    def items(self):
        return self._session_data.items()

    def get(self, key, default=None):
        return self._session_data.get(key, default)

    def __iter__(self):
        return self._session_data.__iter__()

    def has_key(self, key):
        return key in self._session_data

    def values(self):
        return self._session_data.values()

    def itervalues(self):
        return self._session_data.itervalues()

    def iteritems(self):
        return self._session_data.iteritems()

    def iterkeys(self):
        return self._session_data.iterkeys()

    def __repr__(self):
        return self._session_data.__repr__()