from __future__ import unicode_literals

from django.core import checks
from django.db import IntegrityError, router
from django.db.models import CASCADE
from django.db.models.fields.related import ForeignKey, ManyToManyField
from django.utils.functional import cached_property

from django.db.models.fields.related import ReverseManyToOneDescriptor, ManyToManyDescriptor

from modelcluster.utils import sort_by_fields

from modelcluster.queryset import FakeQuerySet

def create_deferring_foreign_related_manager(related, original_manager_cls):
    """
    Create a DeferringRelatedManager class that wraps an ordinary RelatedManager
    with 'deferring' behaviour: any updates to the object set (via e.g. add() or clear())
    are written to a holding area rather than committed to the database immediately.
    Writing to the database is deferred until the model is saved.
    """

    relation_name = related.get_accessor_name()
    rel_field = related.field
    rel_model = related.related_model
    superclass = rel_model._default_manager.__class__

    class DeferringRelatedManager(superclass):
        def __init__(self, instance):
            super(DeferringRelatedManager, self).__init__()
            self.model = rel_model
            self.instance = instance

        def _get_cluster_related_objects(self):
            # Helper to retrieve the instance's _cluster_related_objects dict,
            # creating it if it does not already exist
            try:
                return self.instance._cluster_related_objects
            except AttributeError:
                cluster_related_objects = {}
                self.instance._cluster_related_objects = cluster_related_objects
                return cluster_related_objects

        def get_live_query_set(self):
            # deprecated; renamed to get_live_queryset to match the move from
            # get_query_set to get_queryset in Django 1.6
            return self.get_live_queryset()

        def get_live_queryset(self):
            """
            return the original manager's queryset, which reflects the live database
            """
            return original_manager_cls(self.instance).get_queryset()

        def get_queryset(self):
            """
            return the current object set with any updates applied,
            wrapped up in a FakeQuerySet if it doesn't match the database state
            """
            try:
                results = self.instance._cluster_related_objects[relation_name]
            except (AttributeError, KeyError):
                return self.get_live_queryset()

            return FakeQuerySet(related.related_model, results)

        def _apply_rel_filters(self, queryset):
            # Implemented as empty for compatibility sake
            # But there is probably a better implementation of this function
            return queryset._next_is_sticky()

        def get_prefetch_queryset(self, instances, queryset=None):
            if queryset is None:
                db = self._db or router.db_for_read(self.model, instance=instances[0])
                queryset = super(DeferringRelatedManager, self).get_queryset().using(db)

            rel_obj_attr = rel_field.get_local_related_value
            instance_attr = rel_field.get_foreign_related_value
            instances_dict = dict((instance_attr(inst), inst) for inst in instances)

            query = {'%s__in' % rel_field.name: instances}
            qs = queryset.filter(**query)
            # Since we just bypassed this class' get_queryset(), we must manage
            # the reverse relation manually.
            for rel_obj in qs:
                instance = instances_dict[rel_obj_attr(rel_obj)]
                setattr(rel_obj, rel_field.name, instance)
            cache_name = rel_field.related_query_name()
            return qs, rel_obj_attr, instance_attr, False, cache_name, False

        def get_object_list(self):
            """
            return the mutable list that forms the current in-memory state of
            this relation. If there is no such list (i.e. the manager is returning
            querysets from the live database instead), one is created, populating it
            with the live database state
            """
            cluster_related_objects = self._get_cluster_related_objects()

            try:
                object_list = cluster_related_objects[relation_name]
            except KeyError:
                object_list = list(self.get_live_queryset())
                cluster_related_objects[relation_name] = object_list

            return object_list

        def add(self, *new_items):
            """
            Add the passed items to the stored object set, but do not commit them
            to the database
            """
            items = self.get_object_list()

            for target in new_items:
                item_matched = False
                for i, item in enumerate(items):
                    if item == target:
                        # Replace the matched item with the new one. This ensures that any
                        # modifications to that item's fields take effect within the recordset -
                        # i.e. we can perform a virtual UPDATE to an object in the list
                        # by calling add(updated_object). Which is semantically a bit dubious,
                        # but it does the job...
                        items[i] = target
                        item_matched = True
                        break
                if not item_matched:
                    items.append(target)

                # update the foreign key on the added item to point back to the parent instance
                setattr(target, related.field.name, self.instance)

            # Sort list
            if rel_model._meta.ordering and len(items) > 1:
                sort_by_fields(items, rel_model._meta.ordering)

        def remove(self, *items_to_remove):
            """
            Remove the passed items from the stored object set, but do not commit the change
            to the database
            """
            items = self.get_object_list()

            # filter items list in place: see http://stackoverflow.com/a/1208792/1853523
            items[:] = [item for item in items if item not in items_to_remove]

        def create(self, **kwargs):
            items = self.get_object_list()
            new_item = related.related_model(**kwargs)
            items.append(new_item)
            return new_item

        def clear(self):
            """
            Clear the stored object set, without affecting the database
            """
            self.set([])

        def set(self, objs, bulk=True, clear=False):
            # cast objs to a list so that:
            # 1) we can call len() on it (which we can't do on, say, a queryset)
            # 2) if we need to sort it, we can do so without mutating the original
            objs = list(objs)

            cluster_related_objects = self._get_cluster_related_objects()

            for obj in objs:
                # update the foreign key on the added item to point back to the parent instance
                setattr(obj, related.field.name, self.instance)

            # Clone and sort the 'objs' list, if necessary
            if rel_model._meta.ordering and len(objs) > 1:
                sort_by_fields(objs, rel_model._meta.ordering)

            cluster_related_objects[relation_name] = objs

        def commit(self):
            """
            Apply any changes made to the stored object set to the database.
            Any objects removed from the initial set will be deleted entirely
            from the database.
            """
            if self.instance.pk is None:
                raise IntegrityError("Cannot commit relation %r on an unsaved model" % relation_name)

            try:
                final_items = self.instance._cluster_related_objects[relation_name]
            except (AttributeError, KeyError):
                # _cluster_related_objects entry never created => no changes to make
                return

            original_manager = original_manager_cls(self.instance)

            live_items = list(original_manager.get_queryset())
            for item in live_items:
                if item not in final_items:
                    item.delete()

            for item in final_items:
                # Django 1.9+ bulk updates items by default which assumes
                # that they have already been saved to the database.
                # Disable this behaviour.
                # https://code.djangoproject.com/ticket/18556
                # https://github.com/django/django/commit/adc0c4fbac98f9cb975e8fa8220323b2de638b46
                original_manager.add(item, bulk=False)

            # purge the _cluster_related_objects entry, so we switch back to live SQL
            del self.instance._cluster_related_objects[relation_name]

    return DeferringRelatedManager

class ChildObjectsDescriptor(ReverseManyToOneDescriptor):
    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        return self.child_object_manager_cls(instance)

    def __set__(self, instance, value):
        manager = self.__get__(instance)
        manager.set(value)

    @cached_property
    def child_object_manager_cls(self):
        return create_deferring_foreign_related_manager(self.rel, self.related_manager_cls)

class ParentalKey(ForeignKey):
    related_accessor_class = ChildObjectsDescriptor

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('on_delete', CASCADE)
        super(ParentalKey, self).__init__(*args, **kwargs)

    def check(self, **kwargs):
        from modelcluster.models import ClusterableModel

        errors = super(ParentalKey, self).check(**kwargs)

        # Check that the destination model is a subclass of ClusterableModel.
        # If self.rel.to is a string at this point, it means that Django has been unable
        # to resolve it as a model name; if so, skip this test so that Django's own
        # system checks can report the appropriate error
        if isinstance(self.remote_field.model, type) and not issubclass(self.remote_field.model, ClusterableModel):
            errors.append(
                checks.Error(
                    'ParentalKey must point to a subclass of ClusterableModel.',
                    hint='Change {model_name} into a ClusterableModel or use a ForeignKey instead.'.format(
                        model_name=self.remote_field.model._meta.app_label + '.' + self.remote_field.model.__name__,
                    ),
                    obj=self,
                    id='modelcluster.E001',
                )
            )

        # ParentalKeys must have an accessor name (#49)
        if self.remote_field.get_accessor_name() == '+':
            errors.append(
                checks.Error(
                    "related_name='+' is not allowed on ParentalKey fields",
                    hint="Either change it to a valid name or remove it",
                    obj=self,
                    id='modelcluster.E002',
                )
            )

        return errors

def create_deferring_forward_many_to_many_manager(rel, original_manager_cls):
    relation_name = rel.field.name
    rel_model = rel.model
    superclass = rel_model._default_manager.__class__

    class DeferringManyRelatedManager(superclass):
        def __init__(self, instance=None):
            super(DeferringManyRelatedManager, self).__init__()
            self.model = rel_model
            self.instance = instance

        def get_original_manager(self):
            return original_manager_cls(self.instance)

        def get_live_queryset(self):
            """
            return the original manager's queryset, which reflects the live database
            """
            return self.get_original_manager().get_queryset()

        def _get_cluster_related_objects(self):
            # Helper to retrieve the instance's _cluster_related_objects dict,
            # creating it if it does not already exist
            try:
                return self.instance._cluster_related_objects
            except AttributeError:
                cluster_related_objects = {}
                self.instance._cluster_related_objects = cluster_related_objects
                return cluster_related_objects

        def get_queryset(self):
            """
            return the current object set with any updates applied,
            wrapped up in a FakeQuerySet if it doesn't match the database state
            """
            try:
                results = self.instance._cluster_related_objects[relation_name]
            except (AttributeError, KeyError):
                if self.instance.pk:
                    return self.get_live_queryset()
                else:
                    # the standard M2M manager fails on unsaved instances,
                    # so bypass it and return an empty queryset
                    return rel_model.objects.none()

            return FakeQuerySet(rel_model, results)

        def get_object_list(self):
            """
            return the mutable list that forms the current in-memory state of
            this relation. If there is no such list (i.e. the manager is returning
            querysets from the live database instead), one is created, populating it
            with the live database state
            """
            cluster_related_objects = self._get_cluster_related_objects()

            try:
                object_list = cluster_related_objects[relation_name]
            except KeyError:
                object_list = list(self.get_live_queryset())
                cluster_related_objects[relation_name] = object_list

            return object_list

        def add(self, *new_items):
            """
            Add the passed items to the stored object set, but do not commit them
            to the database
            """
            items = self.get_object_list()

            for target in new_items:
                if target.pk is None:
                    raise ValueError('"%r" needs to have a primary key value before '
                        'it can be added to a parental many-to-many relation.' % target)
                item_matched = False
                for i, item in enumerate(items):
                    if item == target:
                        # Replace the matched item with the new one. This ensures that any
                        # modifications to that item's fields take effect within the recordset -
                        # i.e. we can perform a virtual UPDATE to an object in the list
                        # by calling add(updated_object). Which is semantically a bit dubious,
                        # but it does the job...
                        items[i] = target
                        item_matched = True
                        break
                if not item_matched:
                    items.append(target)

            # Sort list
            if rel_model._meta.ordering and len(items) > 1:
                sort_by_fields(items, rel_model._meta.ordering)

        def clear(self):
            """
            Clear the stored object set, without affecting the database
            """
            self.set([])

        def set(self, objs, bulk=True, clear=False):
            # cast objs to a list so that:
            # 1) we can call len() on it (which we can't do on, say, a queryset)
            # 2) if we need to sort it, we can do so without mutating the original
            objs = list(objs)

            cluster_related_objects = self._get_cluster_related_objects()

            # Clone and sort the 'objs' list, if necessary
            if rel_model._meta.ordering and len(objs) > 1:
                sort_by_fields(objs, rel_model._meta.ordering)

            cluster_related_objects[relation_name] = objs

        def remove(self, *items_to_remove):
            """
            Remove the passed items from the stored object set, but do not commit the change
            to the database
            """
            items = self.get_object_list()

            # filter items list in place: see http://stackoverflow.com/a/1208792/1853523
            items[:] = [item for item in items if item not in items_to_remove]

        def commit(self):
            """
            Apply any changes made to the stored object set to the database.
            """
            if not self.instance.pk:
                raise IntegrityError("Cannot commit relation %r on an unsaved model" % relation_name)

            try:
                final_items = self.instance._cluster_related_objects[relation_name]
            except (AttributeError, KeyError):
                # _cluster_related_objects entry never created => no changes to make
                return

            original_manager = self.get_original_manager()
            live_items = list(original_manager.get_queryset())

            items_to_remove = [item for item in live_items if item not in final_items]
            items_to_add = [item for item in final_items if item not in live_items]

            if items_to_remove:
                original_manager.remove(*items_to_remove)
            if items_to_add:
                original_manager.add(*items_to_add)

            # purge the _cluster_related_objects entry, so we switch back to live SQL
            del self.instance._cluster_related_objects[relation_name]

    return DeferringManyRelatedManager

class ParentalManyToManyDescriptor(ManyToManyDescriptor):
    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        return self.child_object_manager_cls(instance)

    def __set__(self, instance, value):
        manager = self.__get__(instance)
        manager.set(value)

    @cached_property
    def child_object_manager_cls(self):
        rel = self.rel

        return create_deferring_forward_many_to_many_manager(rel, self.related_manager_cls)

class ParentalManyToManyField(ManyToManyField):
    related_accessor_class = ParentalManyToManyDescriptor
    _need_commit_after_assignment = True

    def contribute_to_class(self, cls, name, **kwargs):
        # ManyToManyField does not (as of Django 1.10) respect related_accessor_class,
        # but hard-codes ManyToManyDescriptor instead:
        # https://github.com/django/django/blob/6157cd6da1b27716e8f3d1ed692a6e33d970ae46/django/db/models/fields/related.py#L1538
        # So, we'll let the original contribute_to_class do its thing, and then overwrite
        # the accessor...
        super(ParentalManyToManyField, self).contribute_to_class(cls, name, **kwargs)
        setattr(cls, self.name, self.related_accessor_class(self.remote_field))

    def value_from_object(self, obj):
        # In Django >=1.10, ManyToManyField.value_from_object special-cases objects with no PK,
        # returning an empty list on the basis that unsaved objects can't have related objects.
        # Remove that special case.
        return getattr(obj, self.attname).all()


def sort_by_fields(items, fields):
    """
    Sort a list of objects on the given fields. The field list works analogously to
    queryset.order_by(*fields): each field is either a property of the object,
    or is prefixed by '-' (e.g. '-name') to indicate reverse ordering.
    """
    # To get the desired behaviour, we need to order by keys in reverse order
    # See: https://docs.python.org/2/howto/sorting.html#sort-stability-and-complex-sorts
    for key in reversed(fields):
        # Check if this key has been reversed
        reverse = False
        if key[0] == '-':
            reverse = True
            key = key[1:]

        # Sort
        # Use a tuple of (v is not None, v) as the key, to ensure that None sorts before other values,
        # as comparing directly with None breaks on python3
        items.sort(key=lambda x: (getattr(x, key) is not None, getattr(x, key)), reverse=reverse)


# coding=utf-8
from __future__ import absolute_import, division, print_function, \
    unicode_literals

from codecs import decode, encode
from itertools import chain
from math import ceil
from random import SystemRandom
from typing import Any, AnyStr, Generator, Iterable, Iterator, List, \
    MutableSequence, Optional, Text, Type, TypeVar, Union
from warnings import warn

from six import PY2, binary_type, itervalues, python_2_unicode_compatible, \
    text_type

from iota import AsciiTrytesCodec, TRITS_PER_TRYTE
from iota.crypto import HASH_LENGTH
from iota.crypto.kerl import Kerl
from iota.exceptions import with_context
from iota.json import JsonSerializable
from iota.trits import int_from_trits, trits_from_int

__all__ = [
    'Address',
    'AddressChecksum',
    'Hash',
    'Tag',
    'TrytesCompatible',
    'TryteString',
]

# Custom types for type hints and docstrings.
TrytesCompatible = Union[AnyStr, bytearray, 'TryteString']

T = TypeVar('T', bound='TryteString')

@python_2_unicode_compatible
class TryteString(JsonSerializable):
    """
    A string representation of a sequence of trytes.

    A tryte string is similar in concept to Python's byte string, except
    it has a more limited alphabet.  Byte strings are limited to ASCII
    (256 possible values), while the tryte string alphabet only has 27
    characters (one for each possible tryte configuration).

    .. important::
        A TryteString does not represent a numeric value!
    """

    @classmethod
    def random(cls, length):
        # type: (int) -> TryteString
        """
        Generates a random sequence of trytes.

        :param length:
            Number of trytes to generate.
        """
        alphabet = list(itervalues(AsciiTrytesCodec.alphabet))
        generator = SystemRandom()

        # :py:meth:`SystemRandom.choices` wasn't added until Python 3.6;
        # for compatibility, we will continue to use ``choice`` in a
        # loop.
        # https://docs.python.org/3/library/random.html#random.choices
        return cls(
            ''.join(
                chr(generator.choice(alphabet)) for _ in range(length)
            ).encode('ascii')
        )

    @classmethod
    def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs):
        # type: (Type[T], Union[binary_type, bytearray], Text, *Any, **Any) -> T
        """
        Creates a TryteString from a sequence of bytes.

        :param bytes_:
            Source bytes.

        :param codec:
            Reserved for future use.

            See https://github.com/iotaledger/iota.lib.py/issues/62 for
            more information.

        :param args:
            Additional positional arguments to pass to the initializer.

        :param kwargs:
            Additional keyword arguments to pass to the initializer.
        """
        return cls(encode(bytes_, codec), *args, **kwargs)

    @classmethod
    def from_unicode(cls, string, *args, **kwargs):
        # type: (Type[T], Text, *Any, **Any) -> T
        """
        Creates a TryteString from a Unicode string.

        :param string:
            Source string.

        :param args:
            Additional positional arguments to pass to the initializer.

        :param kwargs:
            Additional keyword arguments to pass to the initializer.
        """
        return cls.from_bytes(
            bytes_=string.encode('utf-8'),
            codec=AsciiTrytesCodec.name,
            *args,
            **kwargs
        )

    @classmethod
    def from_string(cls, *args, **kwargs):
        """
        Deprecated; use :py:meth:`from_unicode` instead.

        https://github.com/iotaledger/iota.lib.py/issues/90
        """
        warn(
            category=DeprecationWarning,

            message=(
                '`from_string()` is deprecated; use `from_unicode()` instead.'
            ),
        )
        return cls.from_unicode(*args, **kwargs)

    @classmethod
    def from_trytes(cls, trytes, *args, **kwargs):
        # type: (Type[T], Iterable[Iterable[int]], *Any, **Any) -> T
        """
        Creates a TryteString from a sequence of trytes.

        :param trytes:
            Iterable of tryte values.

            In this context, a tryte is defined as a list containing 3
            trits.

        :param args:
            Additional positional arguments to pass to the initializer.

        :param kwargs:
            Additional keyword arguments to pass to the initializer.

        References:

        - :py:meth:`as_trytes`
        """
        chars = bytearray()

        for t in trytes:
            converted = int_from_trits(t)

            # :py:meth:`_tryte_from_int`
            if converted < 0:
                converted += 27

            chars.append(AsciiTrytesCodec.alphabet[converted])

        return cls(chars, *args, **kwargs)

    @classmethod
    def from_trits(cls, trits, *args, **kwargs):
        # type: (Type[T], Iterable[int], *Any, **Any) -> T
        """
        Creates a TryteString from a sequence of trits.

        :param trits:
            Iterable of trit values (-1, 0, 1).

        :param args:
            Additional positional arguments to pass to the initializer.

        :param kwargs:
            Additional keyword arguments to pass to the initializer.

        References:

        - :py:func:`int_from_trits`
        - :py:meth:`as_trits`
        """
        # Allow passing a generator or other non-Sized value to this
        # method.
        trits = list(trits)

        if len(trits) % 3:
            # Pad the trits so that it is cleanly divisible into trytes.
            trits += [0] * (3 - (len(trits) % 3))

        return cls.from_trytes(
            # http://stackoverflow.com/a/1751478/
            (trits[i:i + 3] for i in range(0, len(trits), 3)),

            *args,
            **kwargs
        )

    def __init__(self, trytes, pad=None):
        # type: (TrytesCompatible, Optional[int]) -> None
        """
        :param trytes:
            Byte string or bytearray.

        :param pad:
            Ensure at least this many trytes.

            If there are too few, null trytes will be appended to the
            TryteString.

            .. note::
                If the TryteString is too long, it will *not* be
                truncated!
        """
        super(TryteString, self).__init__()

        if isinstance(trytes, (int, float)):
            raise with_context(
                exc=TypeError(
                    'Converting {type} is not supported; '
                    '{cls} is not a numeric type.'.format(
                        type=type(trytes).__name__,
                        cls=type(self).__name__,
                    ),
                ),

                context={
                    'trytes': trytes,
                },
            )

        if isinstance(trytes, TryteString):
            incoming_type = type(trytes)

            if (
                    (incoming_type is TryteString) or
                    issubclass(incoming_type, type(self))
            ):
                # Create a copy of the incoming TryteString's trytes, to
                # ensure we don't modify it when we apply padding.
                trytes = bytearray(trytes._trytes)

            else:
                raise with_context(
                    exc=TypeError(
                        '{cls} cannot be initialized from a(n) {type}.'.format(
                            type=type(trytes).__name__,
                            cls=type(self).__name__,
                        ),
                    ),

                    context={
                        'trytes': trytes,
                    },
                )

        else:
            if isinstance(trytes, text_type):
                trytes = encode(trytes, 'ascii')

            if not isinstance(trytes, bytearray):
                trytes = bytearray(trytes)

            for i, ordinal in enumerate(trytes):
                if ordinal not in AsciiTrytesCodec.index:
                    raise with_context(
                        exc=ValueError(
                            'Invalid character {char!r} at position {i} '
                            '(expected A-Z or 9).'.format(
                                char=chr(ordinal),
                                i=i,
                            ),
                        ),

                        context={
                            'trytes': trytes,
                        },
                    )

        if pad:
            trytes += b'9' * max(0, pad - len(trytes))

        self._trytes = trytes  # type: bytearray

    def __hash__(self):
        # type: () -> int
        return hash(binary_type(self._trytes))

    def __repr__(self):
        # type: () -> Text
        return '{cls}({trytes!r})'.format(
            cls=type(self).__name__,
            trytes=binary_type(self._trytes),
        )

    def __bytes__(self):
        """
        Converts the TryteString into an ASCII representation.

        .. note::
            This does not decode the trytes into bytes/characters; it
            only returns an ASCII representation of the trytes
            themselves!

        If you want to...

        - ... encode trytes into bytes: use :py:meth:`encode`.
        - ... decode trytes into Unicode: use :py:meth:`decode`.
        """
        return binary_type(self._trytes)

    def __str__(self):
        """
        Same as :py:meth:`__bytes__`, except this method returns a
        unicode string.
        """
        # This causes infinite recursion in Python 2.
        # return binary_type(self).decode('ascii')

        return binary_type(self._trytes).decode('ascii')

    def __bool__(self):
        # type: () -> bool
        return bool(self._trytes) and any(t != b'9' for t in self)

    if PY2:
        # Magic methods have different names in Python 2.
        __nonzero__ = __bool__

    def __len__(self):
        # type: () -> int
        return len(self._trytes)

    def __iter__(self):
        # type: () -> Generator[binary_type, None, None]
        # :see: http://stackoverflow.com/a/14267935/
        return (binary_type(self._trytes[i:i + 1]) for i in range(len(self)))

    def __contains__(self, other):
        # type: (TrytesCompatible) -> bool
        if isinstance(other, TryteString):
            return other._trytes in self._trytes
        elif isinstance(other, text_type):
            return other.encode('ascii') in self._trytes
        elif isinstance(other, (binary_type, bytearray)):
            return other in self._trytes
        else:
            raise with_context(
                exc=TypeError(
                    'Invalid type for TryteString contains check '
                    '(expected Union[TryteString, {binary_type}, bytearray], '
                    'actual {type}).'.format(
                        binary_type=binary_type.__name__,
                        type=type(other).__name__,
                    ),
                ),

                context={
                    'other': other,
                },
            )

    def __getitem__(self, item):
        # type: (Union[int, slice]) -> TryteString
        new_trytes = bytearray()

        sliced = self._trytes[item]

        if isinstance(sliced, int):
            new_trytes.append(sliced)
        else:
            new_trytes.extend(sliced)

        return TryteString(new_trytes)

    def __setitem__(self, item, trytes):
        # type: (Union[int, slice], TrytesCompatible) -> None
        new_trytes = TryteString(trytes)

        if isinstance(item, slice):
            self._trytes[item] = new_trytes._trytes
        elif len(new_trytes) > 1:
            raise with_context(
                exc=ValueError(
                    'Cannot assign multiple trytes to the same index '
                    '(``exc.context`` has more info).'
                ),

                context={
                    'self': self,
                    'index': item,
                    'new_trytes': new_trytes,
                },
            )
        else:
            self._trytes[item] = new_trytes._trytes[0]

    def __add__(self, other):
        # type: (TrytesCompatible) -> TryteString
        if isinstance(other, TryteString):
            return TryteString(self._trytes + other._trytes)
        elif isinstance(other, text_type):
            return TryteString(self._trytes + other.encode('ascii'))
        elif isinstance(other, (binary_type, bytearray)):
            return TryteString(self._trytes + other)
        else:
            raise with_context(
                exc=TypeError(
                    'Invalid type for TryteString concatenation '
                    '(expected Union[TryteString, {binary_type}, bytearray], '
                    'actual {type}).'.format(
                        binary_type=binary_type.__name__,
                        type=type(other).__name__,
                    ),
                ),

                context={
                    'other': other,
                },
            )

    def __eq__(self, other):
        # type: (TrytesCompatible) -> bool
        if isinstance(other, TryteString):
            return self._trytes == other._trytes
        elif isinstance(other, text_type):
            return self._trytes == other.encode('ascii')
        elif isinstance(other, (binary_type, bytearray)):
            return self._trytes == other
        else:
            raise with_context(
                exc=TypeError(
                    'Invalid type for TryteString comparison '
                    '(expected Union[TryteString, {binary_type}, bytearray], '
                    'actual {type}).'.format(
                        binary_type=binary_type.__name__,
                        type=type(other).__name__,
                    ),
                ),

                context={
                    'other': other,
                },
            )

    # :bc: In Python 2 this must be defined explicitly.
    def __ne__(self, other):
        # type: (TrytesCompatible) -> bool
        return not (self == other)

    def count_chunks(self, chunk_size):
        # type: (int) -> int
        """
        Returns the number of constant-size chunks the TryteString can
        be divided into (rounded up).

        :param chunk_size:
            Number of trytes per chunk.
        """
        return len(self.iter_chunks(chunk_size))

    def iter_chunks(self, chunk_size):
        # type: (int) -> ChunkIterator
        """
        Iterates over the TryteString, in chunks of constant size.

        :param chunk_size:
            Number of trytes per chunk.

            The final chunk will be padded if it is too short.
        """
        return ChunkIterator(self, chunk_size)

    def encode(self, errors='strict', codec=AsciiTrytesCodec.name):
        # type: (Text, Text) -> binary_type
        """
        Encodes the TryteString into a lower-level primitive (usually
        bytes).

        :param errors:
            How to handle trytes that can't be converted:

            'strict'
                raise an exception (recommended).

            'replace'
                replace with '?'.

            'ignore'
                omit the tryte from the result.

        :param codec:
            Reserved for future use.

            See https://github.com/iotaledger/iota.lib.py/issues/62 for
            more information.

        :raise:
            - :py:class:`iota.codecs.TrytesDecodeError` if the trytes
              cannot be decoded into bytes.
        """
        # Converting ASCII-encoded trytes into bytes is considered to be
        # a *decode* operation according to
        # :py:class:`AsciiTrytesCodec`.
        #
        # Once we add more codecs, we may need to revisit this.
        # See https://github.com/iotaledger/iota.lib.py/issues/62 for
        # more information.
        return decode(self._trytes, codec, errors)

    def as_bytes(self, *args, **kwargs):
        """
        Deprecated; use :py:meth:`encode` instead.

        https://github.com/iotaledger/iota.lib.py/issues/90
        """
        warn(
            category=DeprecationWarning,
            message='`as_bytes()` is deprecated; use `encode()` instead.',
        )
        return self.encode(*args, **kwargs)

    def decode(self, errors='strict', strip_padding=True):
        # type: (Text, bool) -> Text
        """
        Decodes the TryteString into a higher-level abstraction (usually
        Unicode characters).

        :param errors:
            How to handle trytes that can't be converted, or bytes that can't
            be decoded using UTF-8:

            'strict'
                raise an exception (recommended).

            'replace'
                replace with a placeholder character.

            'ignore'
                omit the invalid tryte/byte sequence.

        :param strip_padding:
            Whether to strip trailing null trytes before converting.

        :raise:
            - :py:class:`iota.codecs.TrytesDecodeError` if the trytes
              cannot be decoded into bytes.
            - :py:class:`UnicodeDecodeError` if the resulting bytes
              cannot be decoded using UTF-8.
        """
        trytes = self._trytes
        if strip_padding and (trytes[-1] == ord(b'9')):
            trytes = trytes.rstrip(b'9')

            # Put one back to preserve even length for ASCII codec.
            trytes += b'9' * (len(trytes) % 2)

        bytes_ = decode(trytes, AsciiTrytesCodec.name, errors)

        return bytes_.decode('utf-8', errors)

    def as_string(self, *args, **kwargs):
        """
        Deprecated; use :py:meth:`decode` instead.

        https://github.com/iotaledger/iota.lib.py/issues/90
        """
        warn(
            category=DeprecationWarning,
            message='`as_string()` is deprecated; use `decode()` instead.',
        )
        return self.decode(*args, **kwargs)

    def as_json_compatible(self):
        # type: () -> Text
        """
        Returns a JSON-compatible representation of the object.

        References:

        - :py:class:`iota.json.JsonEncoder`.
        """
        return self._trytes.decode('ascii')

    def as_integers(self):
        # type: () -> List[int]
        """
        Converts the TryteString into a sequence of integers.

        Each integer is a value between -13 and 13.
        """
        return [
            self._normalize(AsciiTrytesCodec.index[c])
            for c in self._trytes
        ]

    def as_trytes(self):
        # type: () -> List[List[int]]
        """
        Converts the TryteString into a sequence of trytes.

        Each tryte is represented as a list with 3 trit values.

        See :py:meth:`as_trits` for more info.

        .. important::
            :py:class:`TryteString` is not a numeric type, so the result
            of this method should not be interpreted as an integer!
        """
        return [
            trits_from_int(n, pad=3)
            for n in self.as_integers()
        ]

    def as_trits(self):
        # type: () -> List[int]
        """
        Converts the TryteString into a sequence of trit values.

        A trit may have value 1, 0, or -1.

        References:

        - https://en.wikipedia.org/wiki/Balanced_ternary

        .. important::
            :py:class:`TryteString` is not a numeric type, so the result
            of this method should not be interpreted as an integer!
        """
        # http://stackoverflow.com/a/952952/5568265#comment4204394_952952
        return list(chain.from_iterable(self.as_trytes()))

    def _repr_pretty_(self, p, cycle):
        """
        Makes JSON-serializable objects play nice with IPython's default
        pretty-printer.

        Sadly, :py:func:`pprint.pprint` does not have a similar
        mechanism.

        References:

        - http://ipython.readthedocs.io/en/stable/api/generated/IPython.lib.pretty.html
        - :py:meth:`IPython.lib.pretty.RepresentationPrinter.pretty`
        - :py:func:`pprint._safe_repr`
        """
        return p.text(repr(self))

    @staticmethod
    def _normalize(n):
        # type: (int) -> int
        if n > 26:
            raise ValueError(
                '{n} cannot be represented by a single tryte.'.format(
                    n=n,
                ))

        # For values greater than 13, trigger an overflow.
        # E.g., 14 => -13, 15 => -12, etc.
        return (n - 27) if n > 13 else n

class ChunkIterator(Iterator[TryteString]):
    """
    Iterates over a TryteString, in chunks of constant size.
    """

    def __init__(self, trytes, chunk_size):
        # type: (TryteString, int) -> None
        """
        :param trytes:
            :py:class:`TryteString` to iterate over.

        :param chunk_size:
            Number of trytes per chunk.

            The final chunk will be padded if it is too short.
        """
        super(ChunkIterator, self).__init__()

        self.trytes = trytes
        self.chunk_size = chunk_size

        self._offset = 0

    def __iter__(self):
        # type: () -> ChunkIterator
        return self

    def __len__(self):
        # type: () -> int
        """
        Returns how many chunks this iterator will return.

        .. note::
            This method always returns the same result, no matter how
            many iterations have been completed.
        """
        return int(ceil(len(self.trytes) / self.chunk_size))

    def __next__(self):
        # type: () -> TryteString
        """
        Returns the next chunk in the iterator.

        :raise:
            - :py:class:`StopIteration` if there are no more chunks
              available.
        """
        if self._offset >= len(self.trytes):
            raise StopIteration

        chunk = self.trytes[self._offset:self._offset + self.chunk_size]
        chunk += b'9' * max(0, self.chunk_size - len(chunk))

        self._offset += self.chunk_size

        return chunk

    if PY2:
        # In Python 2, iterator methods are named a little differently.
        next = __next__

class Hash(TryteString):
    """
    A TryteString that is exactly one hash long.
    """
    # Divide by 3 to convert trits to trytes.
    LEN = HASH_LENGTH // TRITS_PER_TRYTE

    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(Hash, self).__init__(trytes, pad=self.LEN)

        if len(self._trytes) > self.LEN:
            raise with_context(
                exc=ValueError('{cls} values must be {len} trytes long.'.format(
                    cls=type(self).__name__,
                    len=self.LEN
                )),

                context={
                    'trytes': trytes,
                },
            )

class Address(TryteString):
    """
    A TryteString that acts as an address, with support for generating
    and validating checksums.
    """
    LEN = Hash.LEN

    def __init__(
            self,
            trytes,  # type: TrytesCompatible
            balance=None,  # type: Optional[int]
            key_index=None,  # type: Optional[int]
            security_level=None,  # type: Optional[int]
    ):
        # type: (...) -> None
        super(Address, self).__init__(trytes, pad=self.LEN)

        self.checksum = None
        if len(self._trytes) == (self.LEN + AddressChecksum.LEN):
            self.checksum = AddressChecksum(
                self[self.LEN:]
            )  # type: Optional[AddressChecksum]

        elif len(self._trytes) > self.LEN:
            raise with_context(
                exc=ValueError(
                    'Address values must be '
                    '{len_no_checksum} trytes (no checksum), '
                    'or {len_with_checksum} trytes (with checksum).'.format(
                        len_no_checksum=self.LEN,
                        len_with_checksum=self.LEN + AddressChecksum.LEN,
                    ),
                ),

                context={
                    'trytes': trytes,
                },
            )

        # Make the address sans checksum accessible.
        self.address = self[:self.LEN]  # type: TryteString

        self.balance = balance
        """
        Balance owned by this address.
        Defaults to ``None``; usually set via the ``getInputs`` command.

        References:

        - :py:class:`iota.commands.extended.get_inputs`
        - :py:meth:`ProposedBundle.add_inputs`
        """

        self.key_index = key_index
        """
        Index of the key used to generate this address.
        Defaults to ``None``; usually set via ``AddressGenerator``.

        References:

        - :py:class:`iota.crypto.addresses.AddressGenerator`
        """

        self.security_level = security_level
        """
        Number of hashes in the digest that was used to generate this
        address.
        """

    def as_json_compatible(self):
        # type: () -> dict
        return {
            'trytes': self._trytes.decode('ascii'),
            'balance': self.balance,
            'key_index': self.key_index,
            'security_level': self.security_level,
        }

    def is_checksum_valid(self):
        # type: () -> bool
        """
        Returns whether this address has a valid checksum.
        """
        if self.checksum:
            return self.checksum == self._generate_checksum()

        return False

    def with_valid_checksum(self):
        # type: () -> Address
        """
        Returns the address with a valid checksum attached.
        """
        return Address(
            trytes=self.address + self._generate_checksum(),

            # Make sure to copy all of the ancillary attributes, too!
            balance=self.balance,
            key_index=self.key_index,
            security_level=self.security_level,
        )

    def _generate_checksum(self):
        # type: () -> AddressChecksum
        """
        Generates the correct checksum for this address.
        """
        checksum_trits = []  # type: MutableSequence[int]

        sponge = Kerl()
        sponge.absorb(self.address.as_trits())
        sponge.squeeze(checksum_trits)

        checksum_length = AddressChecksum.LEN * TRITS_PER_TRYTE

        return AddressChecksum.from_trits(checksum_trits[-checksum_length:])

class AddressChecksum(TryteString):
    """
    A TryteString that acts as an address checksum.
    """
    LEN = 9

    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(AddressChecksum, self).__init__(trytes, pad=None)

        if len(self._trytes) != self.LEN:
            raise with_context(
                exc=ValueError(
                    '{cls} values must be exactly {len} trytes long.'.format(
                        cls=type(self).__name__,
                        len=self.LEN,
                    ),
                ),

                context={
                    'trytes': trytes,
                },
            )

class Tag(TryteString):
    """
    A TryteString that acts as a transaction tag.
    """
    LEN = 27

    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(Tag, self).__init__(trytes, pad=self.LEN)

        if len(self._trytes) > self.LEN:
            raise with_context(
                exc=ValueError('{cls} values must be {len} trytes long.'.format(
                    cls=type(self).__name__,
                    len=self.LEN
                )),

                context={
                    'trytes': trytes,
                },
            )


# coding=utf-8
from __future__ import absolute_import, division, print_function, \
    unicode_literals

import sys
from abc import ABCMeta, abstractmethod as abstract_method
from argparse import ArgumentParser
from getpass import getpass as secure_input
from io import StringIO
from sys import exit
from typing import Any, Optional, Text

from six import text_type, with_metaclass

from iota import Iota, __version__
from iota.crypto.types import Seed

__all__ = [
    'IotaCommandLineApp',
]

class IotaCommandLineApp(with_metaclass(ABCMeta)):
    """
    Base functionality for a PyOTA-powered command-line application.
    """
    requires_seed = True
    """
    Whether the command requires the user to provide a seed.
    """

    def __init__(self, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin):
        # type: (StringIO, StringIO, StringIO) -> None
        super(IotaCommandLineApp, self).__init__()

        self.stdout = stdout
        self.stderr = stderr
        self.stdin = stdin

    @abstract_method
    def execute(self, api, **arguments):
        # type: (Iota, **Any) -> Optional[int]
        """
        Executes the command and (optionally) returns an exit code (used by
        the shell to determine if the application exited cleanly).

        :param api:
            The API object used to communicate with the node.

        :param arguments:
            Command-line arguments parsed by the argument parser.
        """
        raise NotImplementedError(
            'Not implemented in {cls}.'.format(cls=type(self).__name__),
        )

    def main(self):
        """
        Executes the command from :py:data:`sys.argv` and exits.
        """
        exit(self.run_from_argv())

    def run_from_argv(self, argv=None):
        # type: (Optional[tuple]) -> int
        """
        Executes the command from a collection of arguments (e.g.,
        :py:data`sys.argv`) and returns the exit code.

        :param argv:
            Arguments to pass to the argument parser.
            If ``None``, defaults to ``sys.argv[1:]``.
        """
        exit_code = self.execute(**self.parse_argv(argv))

        if exit_code is None:
            exit_code = 0

        return exit_code

    def parse_argv(self, argv=None):
        # type: (Optional[tuple]) -> dict
        """
        Parses arguments for the command.

        :param argv:
            Arguments to pass to the argument parser.
            If ``None``, defaults to ``sys.argv[1:]``.
        """
        arguments = vars(self.create_argument_parser().parse_args(argv))

        seed = None
        if self.requires_seed:
            seed_filepath = arguments.pop('seed_file')

            seed = (
                self.seed_from_filepath(seed_filepath)
                if seed_filepath
                else self.prompt_for_seed()
            )

        arguments['api'] = Iota(
            adapter=arguments.pop('uri'),
            seed=seed,
            testnet=arguments.pop('testnet'),
        )

        return arguments

    def create_argument_parser(self):
        # type: () -> ArgumentParser
        """
        Returns the argument parser that will be used to interpret
        arguments and options from argv.
        """
        parser = ArgumentParser(
            description=self.__doc__,
            epilog='PyOTA v{version}'.format(version=__version__),
        )

        parser.add_argument(
            '--uri',
            type=text_type,
            default='http://localhost:14265/',

            help=(
                'URI of the node to connect to '
                '(defaults to http://localhost:14265/).'
            ),
        )

        if self.requires_seed:
            parser.add_argument(
                '--seed-file',
                type=text_type,
                dest='seed_file',

                help=(
                    'Path to a file containing your seed in cleartext. '
                    'If not provided, you will be prompted to enter '
                    'your seed via stdin.'
                ),
            )

        parser.add_argument(
            '--testnet',
            action='store_true',
            default=False,
            help='If set, use testnet settings (e.g., for PoW).',
        )

        return parser

    @staticmethod
    def seed_from_filepath(filepath):
        # type: (Text) -> Seed
        """
        Reads a seed from the first line of a text file.

        Any lines after the first are ignored.
        """
        with open(filepath, 'rb') as f_:
            return Seed(f_.readline().strip())

    @staticmethod
    def prompt_for_seed():
        # type: () -> Seed
        """
        Prompts the user to enter their seed via stdin.
        """
        seed = secure_input(
            'Enter seed and press return (typing will not be shown).\n'
            'If no seed is specified, a random one will be used instead.\n'
        )

        if isinstance(seed, text_type):
            seed = seed.encode('ascii')

        return Seed(seed) if seed else Seed.random()


# coding=utf-8
from __future__ import absolute_import, division, print_function, \
    unicode_literals

from typing import Iterator, List, Sequence

from six import PY2

from iota import Hash, TRITS_PER_TRYTE, TryteString, TrytesCompatible
from iota.crypto import FRAGMENT_LENGTH, HASH_LENGTH
from iota.crypto.kerl import Kerl
from iota.crypto.types import PrivateKey, Seed
from iota.exceptions import with_context
from iota.trits import add_trits, trits_from_int

__all__ = [
    'KeyGenerator',
    'KeyIterator',
    'normalize',
    'SignatureFragmentGenerator',
    'validate_signature_fragments',
]

def normalize(hash_):
    # type: (Hash) -> List[List[int]]
    """
    "Normalizes" a hash, converting it into a sequence of integers
    (not trits!) suitable for use in signature generation/validation.

    The hash is divided up into 3 parts, each of which is "balanced"
    (sum of all the values is equal to zero).
    """
    normalized = []
    source = hash_.as_integers()

    chunk_size = 27

    for i in range(Hash.LEN // chunk_size):
        start = i * chunk_size
        stop = start + chunk_size

        chunk = source[start:stop]
        chunk_sum = sum(chunk)

        while chunk_sum > 0:
            chunk_sum -= 1
            for j in range(chunk_size):
                if chunk[j] > -13:
                    chunk[j] -= 1
                    break

        while chunk_sum < 0:
            chunk_sum += 1
            for j in range(chunk_size):
                if chunk[j] < 13:
                    chunk[j] += 1
                    break

        normalized.append(chunk)

    return normalized

class KeyGenerator(object):
    """
    Generates signing keys for messages.
    """

    def __init__(self, seed):
        # type: (TrytesCompatible) -> None
        super(KeyGenerator, self).__init__()

        self.seed = Seed(seed)

    def get_key(self, index, iterations):
        # type: (int, int) -> PrivateKey
        """
        Generates a single key.

        :param index:
            The key index.

        :param iterations:
            Number of transform iterations to apply to the key, also
            known as security level.
            Must be >= 1.

            Increasing this value makes key generation slower, but more
            resistant to brute-forcing.
        """
        return (
            self.get_keys(
                start=index,
                count=1,
                step=1,
                iterations=iterations,
            )[0]
        )

    def get_key_for(self, address):
        """
        Generates the key associated with the specified address.

        Note that this method will generate the wrong key if the input
        address was generated from a different key!
        """
        return self.get_key(
            index=address.key_index,
            iterations=address.security_level,
        )

    def get_keys(self, start, count=1, step=1, iterations=1):
        # type: (int, int, int, int) -> List[PrivateKey]
        """
        Generates and returns one or more keys at the specified
        index(es).

        This is a one-time operation; if you want to create lots of keys
        across multiple contexts, consider invoking
        :py:meth:`create_iterator` and sharing the resulting generator
        object instead.

        Warning: This method may take awhile to run if the starting
        index and/or the number of requested keys is a large number!

        :param start:
            Starting index.
            Must be >= 0.

        :param count:
            Number of keys to generate.
            Must be > 0.

        :param step:
            Number of indexes to advance after each key.
            This may be any non-zero (positive or negative) integer.

        :param iterations:
            Number of transform iterations to apply to each key, also
            known as security level.
            Must be >= 1.

            Increasing this value makes key generation slower, but more
            resistant to brute-forcing.

        :return:
            Always returns a list, even if only one key is generated.

            The returned list will contain ``count`` keys, except when
            ``step * count < start`` (only applies when ``step`` is
            negative).
        """
        if count < 1:
            raise with_context(
                exc=ValueError('``count`` must be positive.'),

                context={
                    'start': start,
                    'count': count,
                    'step': step,
                    'iterations': iterations,
                },
            )

        if not step:
            raise with_context(
                exc=ValueError('``step`` must not be zero.'),

                context={
                    'start': start,
                    'count': count,
                    'step': step,
                    'iterations': iterations,
                },
            )

        iterator = self.create_iterator(start, step, iterations)

        keys = []
        for _ in range(count):
            try:
                next_key = next(iterator)
            except StopIteration:
                break
            else:
                keys.append(next_key)

        return keys

    def create_iterator(self, start=0, step=1, security_level=1):
        # type: (int, int, int) -> KeyIterator
        """
        Creates a generator that can be used to progressively generate
        new keys.

        :param start:
            Starting index.

            Warning: This method may take awhile to reset if ``start``
            is a large number!

        :param step:
            Number of indexes to advance after each key.

            This value can be negative; the generator will exit if it
            reaches an index < 0.

            Warning: The generator may take awhile to advance between
            iterations if ``step`` is a large number!

        :param security_level:
            Number of _transform iterations to apply to each key.
            Must be >= 1.

            Increasing this value makes key generation slower, but more
            resistant to brute-forcing.
        """
        return KeyIterator(self.seed, start, step, security_level)

class KeyIterator(Iterator[PrivateKey]):
    """
    Creates PrivateKeys from a set of iteration parameters.
    """

    def __init__(self, seed, start, step, security_level):
        # type: (Seed, int, int, int) -> None
        super(KeyIterator, self).__init__()

        if start < 0:
            raise with_context(
                exc=ValueError('``start`` cannot be negative.'),

                context={
                    'start': start,
                    'step': step,
                    'security_level': security_level,
                },
            )

        if security_level < 1:
            raise with_context(
                exc=ValueError('``security_level`` must be >= 1.'),

                context={
                    'start': start,
                    'step': step,
                    'security_level': security_level,
                },
            )

        # In order to work correctly, the seed must be padded so that it
        # is a multiple of 81 trytes.
        seed += b'9' * (Hash.LEN - ((len(seed) % Hash.LEN) or Hash.LEN))

        self.security_level = security_level
        self.seed_as_trits = seed.as_trits()
        self.start = start
        self.step = step

        self.current = self.start

        self.fragment_length = FRAGMENT_LENGTH * TRITS_PER_TRYTE
        self.hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN

    def __iter__(self):
        # type: () -> KeyIterator
        return self

    def __next__(self):
        # type: () -> PrivateKey
        while self.current >= 0:
            sponge = self._create_sponge(self.current)

            key = [0] * (self.fragment_length * self.security_level)
            buffer = [0] * len(self.seed_as_trits)

            for fragment_seq in range(self.security_level):
                # Squeeze trits from the buffer and append them to the
                # key, one hash at a time.
                for hash_seq in range(self.hashes_per_fragment):
                    sponge.squeeze(buffer)

                    key_start = (
                        (fragment_seq * self.fragment_length) +
                        (hash_seq * HASH_LENGTH)
                    )

                    key_stop = key_start + HASH_LENGTH

                    # Ensure we only capture one hash from the buffer,
                    # in case it is longer than that (i.e., if the seed
                    # is longer than 81 trytes).
                    key[key_start:key_stop] = buffer[0:HASH_LENGTH]

            private_key = PrivateKey.from_trits(
                key_index=self.current,
                security_level=self.security_level,
                trits=key,
            )

            self.advance()

            return private_key

    if PY2:
        next = __next__

    def advance(self):
        """
        Advances the generator without creating a key.
        """
        self.current += self.step

    def _create_sponge(self, index):
        # type: (int) -> Kerl
        """
        Prepares the hash sponge for the generator.
        """
        seed = self.seed_as_trits[:]

        sponge = Kerl()
        sponge.absorb(add_trits(seed, trits_from_int(index)))

        # Squeeze all of the trits out of the sponge and re-absorb them.
        # Note that the sponge transforms several times per operation,
        # so this sequence is not as redundant as it looks at first
        # glance.
        sponge.squeeze(seed)
        sponge.reset()
        sponge.absorb(seed)

        return sponge

class SignatureFragmentGenerator(Iterator[TryteString]):
    """
    Used to generate signature fragments progressively.

    Each instance can generate 1 signature per fragment in the private
    key.
    """

    def __init__(self, private_key, hash_):
        # type: (PrivateKey, Hash) -> None
        super(SignatureFragmentGenerator, self).__init__()

        self._key_chunks = private_key.iter_chunks(FRAGMENT_LENGTH)
        self._iteration = -1
        self._normalized_hash = normalize(hash_)
        self._sponge = Kerl()

    def __iter__(self):
        # type: () -> SignatureFragmentGenerator
        return self

    def __len__(self):
        # type: () -> int
        """
        Returns the number of fragments this generator can create.

        Note: This method always returns the same result, no matter how
        many iterations have been completed.
        """
        return len(self._key_chunks)

    def __next__(self):
        # type: () -> TryteString
        """
        Returns the next signature fragment.
        """
        key_trytes = next(self._key_chunks)  # type: TryteString
        self._iteration += 1

        # If the key is long enough, loop back around to the start.
        normalized_chunk = (
            self._normalized_hash[self._iteration % len(self._normalized_hash)]
        )

        signature_fragment = key_trytes.as_trits()

        # Build the signature, one hash at a time.
        for i in range(key_trytes.count_chunks(Hash.LEN)):
            hash_start = i * HASH_LENGTH
            hash_end = hash_start + HASH_LENGTH

            buffer = signature_fragment[hash_start:hash_end]  # type: List[int]

            for _ in range(13 - normalized_chunk[i]):
                self._sponge.reset()
                self._sponge.absorb(buffer)
                self._sponge.squeeze(buffer)

            signature_fragment[hash_start:hash_end] = buffer

        return TryteString.from_trits(signature_fragment)

    if PY2:
        next = __next__

def validate_signature_fragments(
        fragments,
        hash_,
        public_key,
        sponge_type=Kerl,
):
    # type: (Sequence[TryteString], Hash, TryteString, type) -> bool
    """
    Returns whether a sequence of signature fragments is valid.

    :param fragments:
      Sequence of signature fragments (usually
      :py:class:`iota.transaction.Fragment` instances).

    :param hash_:
      Hash used to generate the signature fragments (usually a
      :py:class:`iota.transaction.BundleHash` instance).

    :param public_key:
      The public key value used to verify the signature digest (usually a
      :py:class:`iota.types.Address` instance).

    :param sponge_type:
      The class used to create the cryptographic sponge (i.e., Curl or Kerl).
    """
    checksum = [0] * (HASH_LENGTH * len(fragments))
    normalized_hash = normalize(hash_)

    for i, fragment in enumerate(fragments):
        outer_sponge = sponge_type()

        # If there are more than 3 iterations, loop back around to the
        # start.
        normalized_chunk = normalized_hash[i % len(normalized_hash)]

        buffer = []
        for j, hash_trytes in enumerate(fragment.iter_chunks(Hash.LEN)):
            buffer = hash_trytes.as_trits()  # type: List[int]
            inner_sponge = sponge_type()

            # Note the sign flip compared to
            # :py;class:`SignatureFragmentGenerator`.
            for _ in range(13 + normalized_chunk[j]):
                inner_sponge.reset()
                inner_sponge.absorb(buffer)
                inner_sponge.squeeze(buffer)

            outer_sponge.absorb(buffer)

        outer_sponge.squeeze(buffer)
        checksum[i * HASH_LENGTH:(i + 1) * HASH_LENGTH] = buffer

    actual_public_key = [0] * HASH_LENGTH
    addy_sponge = sponge_type()
    addy_sponge.absorb(checksum)
    addy_sponge.squeeze(actual_public_key)

    return actual_public_key == public_key.as_trits()


# coding=utf-8
from __future__ import absolute_import, division, print_function, \
    unicode_literals

from typing import List, MutableSequence, Optional, Sequence

from iota.exceptions import with_context

__all__ = [
    'Curl',
    'HASH_LENGTH',
]

HASH_LENGTH = 243
"""
Number of trits in a hash.

Note: These constants are usually expressed in _trytes_ in PyOTA, but
for compatibility with other libraries, this value must be _trits_.
"""

STATE_LENGTH = 3 * HASH_LENGTH
"""
Number of trits that a Curl sponge stores internally.
"""

NUMBER_OF_ROUNDS = 81
"""
Number of iterations to perform per transform operation.

References:
  - :py:meth:`Curl._transform`.
"""

TRUTH_TABLE = [1, 0, -1, 1, -1, 0, -1, 1, 0]
"""
Lookup table, used to ensure that the result of a Curl operation is
deterministic but not reversible.

References:
  - :py:meth:`Curl._transform`.
"""

class Curl(object):
    """
    Python implementation of Curl.

    **IMPORTANT: Not thread-safe!**
    """

    def __init__(self):
        # type: (Optional[Sequence[int]]) -> None
        self.reset()

    # noinspection PyAttributeOutsideInit
    def reset(self):
        # type: () -> None
        """
        Resets internal state.
        """
        self._state = [0] * STATE_LENGTH  # type: List[int]

    def absorb(self, trits, offset=0, length=None):
        # type: (Sequence[int], Optional[int], Optional[int]) -> None
        """
        Absorb trits into the sponge.

        :param trits:
            Sequence of trits to absorb.

        :param offset:
            Starting offset in ``trits``.

        :param length:
            Number of trits to absorb.  Defaults to ``len(trits)``.
        """
        pad = ((len(trits) % HASH_LENGTH) or HASH_LENGTH)
        trits += [0] * (HASH_LENGTH - pad)

        if length is None:
            length = len(trits)

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``absorb``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        # Copy trits from ``trits`` into internal state, one hash at a
        # time, transforming internal state in between hashes.
        while offset < length:
            start = offset
            stop = min(start + HASH_LENGTH, length)

            # Copy the next hash worth of trits to internal state.
            #
            # Note that we always copy the trits to the start of the
            # state. ``self._state`` is 3 hashes long, but only the
            # first hash is "public"; the other 2 are only accessible to
            # :py:meth:`_transform`.
            self._state[0:stop - start] = trits[start:stop]

            # Transform.
            self._transform()

            # Move on to the next hash.
            offset += HASH_LENGTH

    def squeeze(self, trits, offset=0, length=HASH_LENGTH):
        # type: (MutableSequence[int], Optional[int], Optional[int]) -> None
        """
        Squeeze trits from the sponge.

        :param trits:
            Sequence that the squeezed trits will be copied to.
            Note: this object will be modified!

        :param offset:
            Starting offset in ``trits``.

        :param length:
            Number of trits to squeeze, default to ``HASH_LENGTH``
        """
        # Squeeze is kind of like the opposite of absorb; it copies
        # trits from internal state to the ``trits`` parameter, one hash
        # at a time, and transforming internal state in between hashes.
        #
        # However, only the first hash of the state is "public", so we
        # can simplify the implementation somewhat.

        # Ensure length can be mod by HASH_LENGTH
        if length % HASH_LENGTH != 0:
            raise with_context(
                exc=ValueError('Invalid length passed to ``squeeze`.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                })

        # Ensure that ``trits`` can hold at least one hash worth of
        # trits.
        trits.extend([0] * max(0, length - len(trits)))

        # Check trits with offset can handle hash length
        if len(trits) - offset < HASH_LENGTH:
            raise with_context(
                exc=ValueError('Invalid offset passed to ``squeeze``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length
                },
            )

        while length >= HASH_LENGTH:
            # Copy exactly one hash.
            trits[offset:offset + HASH_LENGTH] = self._state[0:HASH_LENGTH]

            # One hash worth of trits copied; now transform.
            self._transform()

            offset += HASH_LENGTH
            length -= HASH_LENGTH

    def _transform(self):
        # type: () -> None
        """
        Transforms internal state.
        """
        # Copy some values locally so we can avoid global lookups in the
        # inner loop.
        #
        # References:
        #
        # - https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Local_Variables
        state_length = STATE_LENGTH
        truth_table = TRUTH_TABLE

        # Operate on a copy of ``self._state`` to eliminate dot lookups
        # in the inner loop.
        #
        # References:
        #
        # - https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Avoiding_dots...
        # - http://stackoverflow.com/a/2612990/
        prev_state = self._state[:]
        new_state = prev_state[:]

        # Note: This code looks significantly different from the C
        # implementation because it has been optimized to limit the
        # number of list item lookups (these are relatively slow in
        # Python).
        index = 0
        for _ in range(NUMBER_OF_ROUNDS):
            prev_trit = prev_state[index]

            for pos in range(state_length):
                index += (364 if index < 365 else -365)

                new_trit = prev_state[index]

                new_state[pos] = truth_table[prev_trit + (3 * new_trit) + 4]

                prev_trit = new_trit

            prev_state = new_state
            new_state = new_state[:]

        self._state = new_state


# coding=utf-8
from __future__ import absolute_import, division, print_function, \
    unicode_literals

from typing import Iterable, Optional

from iota import Address, Iota, ProposedTransaction
from iota.commands import discover_commands
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Digest
from iota.multisig import commands
from iota.multisig.types import MultisigAddress

__all__ = [
    'MultisigIota',
]

class MultisigIota(Iota):
    """
    Extends the IOTA API so that it can send multi-signature
    transactions.

    .. caution::
        Make sure you understand how multisig works before attempting to
        use it.  If you are not careful, you could easily compromise the
        security of your private keys, send IOTAs to unspendable
        addresses, etc.

    References:

    - https://github.com/iotaledger/wiki/blob/master/multisigs.md
    """
    commands = discover_commands('iota.multisig.commands')

    def create_multisig_address(self, digests):
        # type: (Iterable[Digest]) -> dict
        """
        Generates a multisig address from a collection of digests.

        :param digests:
            Digests to use to create the multisig address.

            .. important::
                In order to spend IOTAs from a multisig address, the
                signature must be generated from the corresponding private
                keys in the exact same order.

        :return:
            Dict with the following items::

                {
                    'address': MultisigAddress,
                        The generated multisig address.
                }
        """
        return commands.CreateMultisigAddressCommand(self.adapter)(
            digests=digests,
        )

    def get_digests(
            self,
            index=0,
            count=1,
            security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL,
    ):
        # type: (int, int, int) -> dict
        """
        Generates one or more key digests from the seed.

        Digests are safe to share; use them to generate multisig
        addresses.

        :param index:
            The starting key index.

        :param count:
            Number of digests to generate.

        :param security_level:
            Number of iterations to use when generating new addresses.

            Larger values take longer, but the resulting signatures are
            more secure.

            This value must be between 1 and 3, inclusive.

        :return:
            Dict with the following items::

                {
                    'digests': List[Digest],
                        Always contains a list, even if only one digest
                        was generated.
                }
        """
        return commands.GetDigestsCommand(self.adapter)(
            seed=self.seed,
            index=index,
            count=count,
            securityLevel=security_level,
        )

    def get_private_keys(
            self,
            index=0,
            count=1,
            security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL,
    ):
        # type: (int, int, int) -> dict
        """
        Generates one or more private keys from the seed.

        As the name implies, private keys should not be shared.
        However, in a few cases it may be necessary (e.g., for M-of-N
        transactions).

        :param index:
            The starting key index.

        :param count:
            Number of keys to generate.

        :param security_level:
            Number of iterations to use when generating new keys.

            Larger values take longer, but the resulting signatures are
            more secure.

            This value must be between 1 and 3, inclusive.

        :return:
            Dict with the following items::

                {
                    'keys': List[PrivateKey],
                        Always contains a list, even if only one key was
                        generated.
                }

        References:

        - :py:class:`iota.crypto.signing.KeyGenerator`
        - https://github.com/iotaledger/wiki/blob/master/multisigs.md#how-m-of-n-works
        """
        return commands.GetPrivateKeysCommand(self.adapter)(
            seed=self.seed,
            index=index,
            count=count,
            securityLevel=security_level,
        )

    def prepare_multisig_transfer(
            self,
            transfers,  # type: Iterable[ProposedTransaction]
            multisig_input,  # type: MultisigAddress
            change_address=None,  # type: Optional[Address]
    ):
        # type: (...) -> dict
        """
        Prepares a bundle that authorizes the spending of IOTAs from a
        multisig address.

        .. note::
            This method is used exclusively to spend IOTAs from a
            multisig address.

            If you want to spend IOTAs from non-multisig addresses, or
            if you want to create 0-value transfers (i.e., that don't
            require inputs), use
            :py:meth:`iota.api.Iota.prepare_transfer` instead.

        :param transfers:
            Transaction objects to prepare.

            .. important::
                Must include at least one transaction that spends IOTAs
                (i.e., has a nonzero ``value``).  If you want to prepare
                a bundle that does not spend any IOTAs, use
                :py:meth:`iota.api.prepare_transfer` instead.

        :param multisig_input:
            The multisig address to use as the input for the transfers.

            .. note::
                This method only supports creating a bundle with a
                single multisig input.

                If you would like to spend from multiple multisig
                addresses in the same bundle, create the
                :py:class:`iota.multisig.transaction.ProposedMultisigBundle`
                object manually.

        :param change_address:
            If inputs are provided, any unspent amount will be sent to
            this address.

            If the bundle has no unspent inputs, ``change_address` is
            ignored.

            .. important::
                Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this
                method will NOT generate a change address automatically.
                If there are unspent inputs and ``change_address`` is
                empty, an exception will be raised.

                This is because multisig transactions typically involve
                multiple individuals, and it would be unfair to the
                participants if we generated a change address
                automatically using the seed of whoever happened to run
                the ``prepare_multisig_transfer`` method!

                .. danger::
                    Note that this protective measure is not a
                    substitute for due diligence!

                    Always verify the details of every transaction in a
                    bundle (including the change transaction) before
                    signing the input(s)!

        :return:
            Dict containing the following values::

                {
                    'trytes': List[TransactionTrytes],
                        Finalized bundle, as trytes.
                        The input transactions are not signed.
                }

          In order to authorize the spending of IOTAs from the multisig
          input, you must generate the correct private keys and invoke
          the :py:meth:`iota.crypto.types.PrivateKey.sign_input_at`
          method for each key, in the correct order.

          Once the correct signatures are applied, you can then perform
          proof of work (``attachToTangle``) and broadcast the bundle
          using :py:meth:`iota.api.Iota.send_trytes`.
        """
        return commands.PrepareMultisigTransferCommand(self.adapter)(
            changeAddress=change_address,
            multisigInput=multisig_input,
            transfers=transfers,
        )


# coding=utf-8
"""
Provides functions for manipulating sequences of trits.

Based on
https://github.com/iotaledger/iota.lib.js/blob/v0.4.2/lib/crypto/helpers/adder.js
"""

from __future__ import absolute_import, division, print_function, \
    unicode_literals

from typing import Iterable, List, Optional, Sequence, Tuple

__all__ = [
    'add_trits',
    'int_from_trits',
    'trits_from_int',
]

def add_trits(left, right):
    # type: (Sequence[int], Sequence[int]) -> List[int]
    """
    Adds two sequences of trits together.

    The result is a list of trits equal in length to the longer of the
    two sequences.

    .. note::
        Overflow is possible.

        For example, ``add_trits([1], [1])`` returns ``[-1]``.
    """
    target_len = max(len(left), len(right))

    res = [0] * target_len
    left += [0] * (target_len - len(left))
    right += [0] * (target_len - len(right))

    carry = 0
    for i in range(len(res)):
        res[i], carry = _full_add_trits(left[i], right[i], carry)

    return res

def int_from_trits(trits):
    # type: (Iterable[int]) -> int
    """
    Converts a sequence of trits into an integer value.
    """
    # Normally we'd have to wrap ``enumerate`` inside ``reversed``, but
    # balanced ternary puts least significant digits first.
    return sum(base * (3 ** power) for power, base in enumerate(trits))

def trits_from_int(n, pad=1):
    # type: (int, Optional[int]) -> List[int]
    """
    Returns a trit representation of an integer value.

    :param n:
        Integer value to convert.

    :param pad:
        Ensure the result has at least this many trits.

    References:

    - https://dev.to/buntine/the-balanced-ternary-machines-of-soviet-russia
    - https://en.wikipedia.org/wiki/Balanced_ternary
    - https://rosettacode.org/wiki/Balanced_ternary#Python
    """
    if n == 0:
        trits = []
    else:
        quotient, remainder = divmod(n, 3)

        if remainder == 2:
            # Lend 1 to the next place so we can make this trit
            # negative.
            quotient += 1
            remainder = -1

        trits = [remainder] + trits_from_int(quotient, pad=0)

    if pad:
        trits += [0] * max(0, pad - len(trits))

    return trits

def _cons_trits(left, right):
    # type: (int, int) -> int
    """
    Compares two trits.  If they have the same value, returns that
    value.  Otherwise, returns 0.
    """
    return left if left == right else 0

def _add_trits(left, right):
    # type: (int, int) -> int
    """
    Adds two individual trits together.

    The result is always a single trit.
    """
    res = left + right
    return res if -2 < res < 2 else (res < 0) - (res > 0)

def _any_trits(left, right):
    # type: (int, int) -> int
    """
    Adds two individual trits together and returns a single trit
    indicating whether the result is positive or negative.
    """
    res = left + right
    return (res > 0) - (res < 0)

def _full_add_trits(left, right, carry):
    # type: (int, int, int) -> Tuple[int, int]
    """
    Adds two trits together, with support for a carry trit.
    """
    sum_both = _add_trits(left, right)
    cons_left = _cons_trits(left, right)
    cons_right = _cons_trits(sum_both, carry)

    return _add_trits(sum_both, carry), _any_trits(cons_left, cons_right)


# coding=utf-8
"""
Generates a shiny new IOTA address that you can use for transfers!
"""

from __future__ import absolute_import, division, print_function, \
    unicode_literals

from argparse import ArgumentParser
from getpass import getpass as secure_input
from sys import argv
from typing import Optional, Text

from six import binary_type, moves as compat, text_type

from iota import Iota, __version__
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Seed

def main(uri, index, count, security, checksum):
    # type: (Text, int, Optional[int], Optional[int], bool) -> None
    seed = get_seed()

    # Create the API instance.
    # Note: If ``seed`` is null, a random seed will be generated.
    api = Iota(uri, seed)

    # If we generated a random seed, then we need to display it to the
    # user, or else they won't be able to use their new addresses!
    if not seed:
        print('A random seed has been generated. Press return to see it.')
        output_seed(api.seed)

    print('Generating addresses.  This may take a few minutes...')
    print('')

    # Here's where all the magic happens!
    api_response = api.get_new_addresses(index, count, security, checksum)
    for addy in api_response['addresses']:
        print(binary_type(addy).decode('ascii'))

    print('')

def get_seed():
    # type: () -> binary_type
    """
    Prompts the user securely for their seed.
    """
    print(
        'Enter seed and press return (typing will not be shown).  '
        'If empty, a random seed will be generated and displayed on the screen.'
    )
    seed = secure_input('')  # type: Text
    return seed.encode('ascii')

def output_seed(seed):
    # type: (Seed) -> None
    """
    Outputs the user's seed to stdout, along with lots of warnings
    about security.
    """
    print(
        'WARNING: Anyone who has your seed can spend your IOTAs! '
        'Clear the screen after recording your seed!'
    )
    compat.input('')
    print('Your seed is:')
    print('')
    print(binary_type(seed).decode('ascii'))
    print('')

    print(
        'Clear the screen to prevent shoulder surfing, '
        'and press return to continue.'
    )
    print('https://en.wikipedia.org/wiki/Shoulder_surfing_(computer_security)')
    compat.input('')

if __name__ == '__main__':
    parser = ArgumentParser(
        description=__doc__,
        epilog='PyOTA v{version}'.format(version=__version__),
    )

    parser.add_argument(
        '--uri',
        type=text_type,
        default='http://localhost:14265/',

        help=(
            'URI of the node to connect to '
            '(defaults to http://localhost:14265/).'
        ),
    )

    parser.add_argument(
        '--index',
        type=int,
        default=0,
        help='Index of the key to generate.',
    )

    parser.add_argument(
        '--count',
        type=int,
        default=None,

        help=(
            'Number of addresses to generate. '
            'If not specified, the first unused address will be returned.'
        ),
    )

    parser.add_argument(
        '--security',
        type=int,
        default=AddressGenerator.DEFAULT_SECURITY_LEVEL,
        help=(
            'Security level to be used for the private key / address. '
            'Can be 1, 2 or 3'
        ),
    )

    parser.add_argument(
        '--with-checksum',
        action='store_true',
        default=False,
        dest='checksum',
        help='List the address with the checksum.',
    )

    main(**vars(parser.parse_args(argv[1:])))