using PostSharp.Samples.Authorization.Framework;
using System;
using System.Collections.Generic;
 
namespace PostSharp.Samples.Authorization.RoleBased
{
  /// <summary>
  ///   Implementation of <see cref="ISecurityPolicy" /> for the role-based model.
  /// </summary>
  public class RoleBasedSecurityPolicy : ISecurityPolicy
  {
    private readonly Dictionary<Type, List<Assignment>> rolePermissionAssignments =
      new Dictionary<Type, List<Assignment>>();
 
    /// <inheritdoc />
    public bool Evaluate(ISubject subject, IPermission permission, object securable)
    {
      var securableAncestor = (IRoleBasedSecurable) securable;
      var granted = false;
      for (var type = securableAncestor.GetType(); type != null; type = type.BaseType)
      {
        List<Assignment> assignments;
        if (rolePermissionAssignments.TryGetValue(type, out assignments))
        {
          foreach (var assignment in assignments)
          {
            if (assignment.Permission.Equals(permission) && securableAncestor.HasRole(subject, assignment.Role))
            {
              switch (assignment.Action)
              {
                case PermissionAction.Grant:
                  granted = true;
                  break;
 
                case PermissionAction.Revoke:
                  // If only one permission evaluates to Revoke, the permission is refused.
                  return false;
              }
            }
          }
        }
      }
 
      return granted;
    }
 
 
    /// <summary>
    ///   Grants or revokes a permission to or from members of a role for a given type of entity.
    /// </summary>
    /// <param name="entitytype">
    ///   Base type of entities for which the permission is granted. To assign the permission to all
    ///   entity types, pass <c>typeof(object)</c>.
    /// </param>
    /// <param name="permission">The permission being granted or revoked.</param>
    /// <param name="role">The role for which the permissio is being granted or revoked.</param>
    /// <param name="action">Whether the permission is being granted or revoked.</param>
    public void AddRolePermissionAssignment(Type entitytype, IPermission permission, IRole role,
      PermissionAction action)
    {
      var assignment = new Assignment(permission, role, action);
      List<Assignment> list;
      if (!rolePermissionAssignments.TryGetValue(entitytype, out list))
      {
        list = new List<Assignment>();
        rolePermissionAssignments.Add(entitytype, list);
      }
 
      list.Add(assignment);
    }
 
    private class Assignment
    {
      internal Assignment(IPermission permission, IRole role, PermissionAction action)
      {
        Action = action;
        Permission = permission;
        Role = role;
      }
 
      public PermissionAction Action { get; }
 
      public IPermission Permission { get; }
 
      public IRole Role { get; }
 
      public override string ToString()
      {
        return $"{Action} {Permission} to {Role}";
      }
    }
  }
}