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

## @file
#  This file defines a class that manages a list of subscriptions which
#  define the access rights of a specific group.

from Plugins.DataSource.DataLists import ADataList
from Library.IO.Logging.Logs import AMessageWithTimeStamp
from Library.String.Utilities import String

## The error message used when no data source is provided to the subscription
#  list instance
slNoDataSource = "no data source was provided"

# #The error message used when failing to retrieve the subscription list
slErrorRetrievingList = ("failed to retrieve the list of subscriptions for the "
                         "groups: ({}) because: {}")
## The error message used when failing to append to the subscription list
slErrorAppendingToList = "failed to subscribe \"{}\" to the channel \"{}\""
## The error message used when failing to delete from the subscription list
slErrorDeletingFromList = ("failed to remove the subscription to the channel "
                           " \"{}\"")
## The error message used when failing to update the subscription list
slErrorUpdatingList = "failed to modify permissions for the channel \"{}\""

## The rights assigned to channels by means of a subscription.
class Right:
    ## The right to read from and access the channel
    toRead = "r"
    ## The right to edit (modify) existing channel content (posts)
    toEdit = "m"
    ## The right to create new channel content (posts)
    toPost = "w"
    ## The right to reply to (comment on) channel content
    toComment = "y"
    ## The right to delete channel content
    toDelete = "d"

    ## The right to insert subchannels within the channel
    toInsert = "C"
    ## The right to remove subchannels within the channel
    toRemove = "D"
    ## The right to modify subchannels within the channel
    toModify = "M"

## A utility class to make managing rights easier.
class Rights:
    ## The rights allowed to exist within a rights string
    allowedRights = {
      "r": Right.toRead,
      "m": Right.toEdit,
      "w": Right.toPost,
      "y": Right.toComment,
      "d": Right.toDelete,
      "C": Right.toInsert,
      "D": Right.toRemove,
      "M": Right.toModify
    }

    ## Zip the rights in the specified iterable into a string that
    #  represents each right (useful for storing rights information in a
    #  database).
    @classmethod
    def zip(Self, *rights):
        result = ""

        if len(rights) > 0:
            for right in rights:
                if right in Self.allowedRights:
                    result += right

        return result

    ## Unzip the rights in the specified string into a list of rights
    #  (useful for retrieving rights information from a database).
    @classmethod
    def unzip(Self, rights):
        result = []

        if (isinstance(rights, basestring)) and (len(rights) > 0):
            for right in rights:
                if right in Self.allowedRights:
                    result.append(Self.allowedRights[right])

        return result

    ## Validate that the specified list of rights contains only those
    #  rights which are allowed.
    @classmethod
    def validate(Self, rights):
        if len(rights) > 0:
            for right in rights:
                if right in Self.allowedRights == False:
                    return False

            else:
                return True

        return False

## The default rights granted to a newly-created subscription (read only)
slDefaultRights = [Right.toRead]

## This class represents an error that may occur while attempting to
#  access and modify group subscription information.
class ASubscriptionError(AMessageWithTimeStamp):
    pass

## This class represents a list of subscriptions that define the access
#  rights of a specific group.
class ASubscriptionList(ADataList):
    ## Constructor.
    #
    #  In addition to the parameters expected by ADataList.__init__(), this
    #  method expects that \p params will contain the following items:
    #  - \p category -- The name of the category (or table) in the data source 
    #                   to use as the source of subscription information.
    def __init__(Self, **params):
        ADataList.__init__(Self, **params)
        
        if "category" in Self._params:
            Self._category = Self._params["category"]
            del Self._params["category"]

    ## Initializer
    def init(Self):
        result = ADataList.init(Self)

        if result:
            # Establish private attributes
            ## The name of the table that contains subscription information
            Self._category = ""

        return result

    ## Fetch subscription information for the specified groups and channels.
    def fetch(Self, groups):
        result = -1
        
        Event = Self.generate("fetch", groups=groups)
        if Event.handled():
            result = Event.result()
            del Event
            return result
            
        del Event
        
        if len(groups) > 0:
            try:
                subscriptions = Self._Source.fetch(Self._category,
                    criteria = [
                        ("{field:groupName} IN ({})", groups)
                    ]
                )
            
                for subscription in subscriptions:
                    Self._items[subscription["channelName"]] = {
                        "id": subscription["id"],
                        "groupName": subscription["groupName"],
                        "rights": subscription["rights"]
                    }
                
                result = len(Self._items)

            except Exception, Err:
                Self.error(ASubscriptionError(slErrorRetrievingList, 
                    String.implode(groups), str(Err)))
                    
        return result

    ## Subscribe the group to the specified channel with the specified
    #  rights.
    def append(Self, channelName, groupName, rights=slDefaultRights):
        result = -1
        
        Event = Self.generate("append", channelName=channelName,
            groupName=groupName, rights=rights)
        if Event.handled():
            result = Event.result()
            del Event
            return result
            
        del Event

        if channelName in Self._items == False:
            result = Self._Source.insert(Self._category,
                fieldsValues = {
                    "groupName": groupName,
                    "channelName": channelName,
                    "rights": Rights.zip(rights)
                }
            )
            
            if result > 0:
                Self._items[channelName] = {
                    "id": result,
                    "groupName": groupName,
                    "rights": Rights.zip(rights)
                }
                
            else:
                Self.error(ASubscriptionError(slErrorAppendingToList, 
                    groupName, channelName))

        return result

    ## Remove the subscription to the specified channel
    def remove(Self, channelName):
        result = -1
        
        Event = Self.generate("remove", channelName=channelName)
        if Event.handled():
            result = Event.result()
            del Event
            return result
            
        del Event

        if channelName in Self._items:
            result = Self._Source.delete(
                criteria = [
                    ("{field:id} = {}", Self._items[channelName]["id"])
                ]
            )
            
            if result < 0:
                Self.error(ASubscriptionError(slErrorDeletingFromList, 
                    channelName))

        return result

    ## Determine if the specified right is granted to the specified channel by a
    #  subscription in the list.
    def has(Self, thisRight, thisChannel):
        result = False

        if thisChannel in Self._items:
            result = thisRight in Self._items[thisChannel]["rights"]
            
        return result
        
    ## Determine if any of the rights specified are granted to the specified
    #  channel by a subscription in the list.
    def hasAnyOf(Self, theseRights, thisChannel):
        result = ""
        
        for right in theseRights:
            if Self.has(right, thisChannel):
                result += right
                
        return result

    ## Return a list of rights granted by a subscription to the specified 
    #  channel.
    def rightsFor(Self, thisChannel):
        result = []

        if thisChannel in Self._items:
            result = Rights.unzip(Self._items[thisChannel]["rights"])

        return result

    # Properties ---------------------------------------------------------------
    ## Retrieve the name of the data source category used by the subscription 
    #  list.
    def category(Self):
        return Self._category

    ## Alter the rights for the specified channel.
    #
    #   Note that this routine overrides the behavior inherited from ADataList.
    #   Note, too, that it expects that rights specified will either be a
    #   string or list of the new rights that apply to the specified channel.
    def __setitem__(Self, thisChannel, rights):
        if thisChannel in Self._items:
            rights = Rights.zip(rights)
            
            result = Self._Source.update(Self._category,
                fieldsValues = {
                    "rights": rights
                },
                criteria = [
                    ("{field:id} = {}", Self._items[thisChannel]["id"])
                ]
            )

            if result >= 0:
                Self._items[thisChannel]["rights"] = rights

            else:
                Self.error(ASubscriptionError(slErrorUpdatingList, 
                    thisChannel))

