# Copyright 2019 PrivateStorage.io, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
A module for logic controlling the manner in which ZKAPs are spent.
"""

from zope.interface import (
    Interface,
    Attribute,
    implementer,
)

import attr

from .eliot import (
    GET_PASSES,
)

class IPassGroup(Interface):
    """
    A group of passed meant to be spent together.
    """
    passes = Attribute(":ivar list[Pass] passes: The passes themselves.")

    def split(select_indices):
        """
        Create two new ``IPassGroup`` providers.  The first contains all passes in
        this group at the given indices.  The second contains all the others.

        :param list[int] select_indices: The indices of the passes to include
            in the first resulting group.

        :return (IPassGroup, IPassGroup): The two new groups.
        """

    def expand(by_amount):
        """
        Create a new ``IPassGroup`` provider which contains all of this group's
        passes and some more.

        :param int by_amount: The number of additional passes the resulting
            group should contain.

        :return IPassGroup: The new group.
        """

    def mark_spent():
        """
        The passes have been spent successfully.  Ensure none of them appear in
        any ``IPassGroup`` provider created in the future.

        :return: ``None``
        """

    def mark_invalid(reason):
        """
        The passes could not be spent.  Ensure none of them appear in any
        ``IPassGroup`` provider created in the future.

        :param unicode reason: A short description of the reason the passes
            could not be spent.

        :return: ``None``
        """

    def reset():
        """
        The passes have not been spent.  Return them to for use in a future
        ``IPassGroup`` provider.

        :return: ``None``
        """


class IPassFactory(Interface):
    """
    An object which can create passes.
    """
    def get(message, num_passes):
        """
        :param unicode message: A request-binding message for the resulting passes.

        :param int num_passes: The number of passes to request.

        :return IPassGroup: A group of passes bound to the given message and
            of the requested size.
        """


@implementer(IPassGroup)
@attr.s
class PassGroup(object):
    """
    Track the state of a group of passes intended as payment for an operation.

    :ivar unicode _message: The request binding message for this group of
        passes.

    :ivar IPassFactory _factory: The factory which created this pass group.

    :ivar list[Pass] passes: The passes of which this group consists.
    """
    _message = attr.ib()
    _factory = attr.ib()
    passes = attr.ib()

    def split(self, select_indices):
        selected = []
        unselected = []
        for idx, p in enumerate(self.passes):
            if idx in select_indices:
                selected.append(p)
            else:
                unselected.append(p)
        return (
            attr.evolve(self, passes=selected),
            attr.evolve(self, passes=unselected),
        )

    def expand(self, by_amount):
        return attr.evolve(
            self,
            passes=self.passes + self._factory.get(self._message, by_amount).passes,
        )

    def mark_spent(self):
        self._factory._mark_spent(self.passes)

    def mark_invalid(self, reason):
        self._factory._mark_invalid(reason, self.passes)

    def reset(self):
        self._factory._reset(self.passes)


@implementer(IPassFactory)
@attr.s
class SpendingController(object):
    """
    A ``SpendingController`` gives out ZKAPs and arranges for re-spend
    attempts when necessary.
    """
    extract_unblinded_tokens = attr.ib()
    tokens_to_passes = attr.ib()

    def get(self, message, num_passes):
        unblinded_tokens = self.extract_unblinded_tokens(num_passes)
        passes = self.tokens_to_passes(message, unblinded_tokens)
        GET_PASSES.log(
            message=message,
            count=num_passes,
        )
        return PassGroup(message, self, passes)

    def _mark_spent(self, group):
        # TODO
        pass

    def _mark_invalid(self, reason, group):
        # TODO
        pass

    def _reset(self, group):
        # TODO
        pass