*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\Entity\Scope;
/**
 * Helper for authorizations. 
 * 
 * Provides methods for user and entities information.
 *
 * @author Julien Fastré 
 */
class AuthorizationHelper
{
    /**
     *
     * @var RoleHierarchyInterface
     */
    protected $roleHierarchy;
    
    protected $existingRoles = array('CHILL_MASTER_ROLE', 'CHILL_PERSON_SEE', 
       'CHILL_PERSON_UPDATE',);
    
    public function __construct(RoleHierarchyInterface $roleHierarchy)
    {
        $this->roleHierarchy = $roleHierarchy;
    }
    
    /**
     * Determines if a user is active on this center
     * 
     * @param User $user
     * @param Center $center
     * @return bool
     */
    public function userCanReachCenter(User $user, Center $center)
    {
        foreach ($user->getGroupCenters() as $groupCenter) {
            if ($center->getId() === $groupCenter->getCenter()->getId()) {
                
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 
     * Determines if the user has access to the given entity.
     * 
     * if the entity implements Chill\MainBundle\Entity\HasScopeInterface,
     * the scope is taken into account.
     * 
     * @param User $user
     * @param HasCenterInterface $entity the entity may also implement HasScopeInterface
     * @param string|Role $attribute
     * @return boolean true if the user has access
     */
    public function userHasAccess(User $user, HasCenterInterface $entity, $attribute)
    {
        
        $center = $entity->getCenter();
        
        if (!$this->userCanReachCenter($user, $center)) {
            return false;
        }
        
        $role = ($attribute instanceof Role) ? $attribute : new Role($attribute);
        
        foreach ($user->getGroupCenters() as $groupCenter){
            //filter on center
            if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) {
                $permissionGroup = $groupCenter->getPermissionsGroup();
                //iterate on roleScopes
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
                    //check that the role allow to reach the required role
                    if ($this->isRoleReached($role, 
                          new Role($roleScope->getRole()))){
                        //if yes, we have a right on something...
                        // perform check on scope if necessary
                        if ($entity instanceof HasScopeInterface) {
                            $scope = $entity->getScope();
                            if ($scope === NULL) {
                                return true;
                            }
                            if ($scope->getId() === $roleScope
                                  ->getScope()->getId()) {
                                return true;
                            }
                        } else {
                            return true;
                        }
                    }
                }
                
            }
        }
        
        return false;
    }
    
    /**
     * Get reachable Centers for the given user, role,
     * and optionnaly Scope
     * 
     * @param User $user
     * @param Role $role
     * @param null|Scope $scope
     * @return Center[]
     */
    public function getReachableCenters(User $user, Role $role, Scope $scope = null)
    {
        $centers = array();
        
        foreach ($user->getGroupCenters() as $groupCenter){
            $permissionGroup = $groupCenter->getPermissionsGroup();
            //iterate on roleScopes
            foreach($permissionGroup->getRoleScopes() as $roleScope) {
                //check that the role is in the reachable roles
                if ($this->isRoleReached($role, 
                      new Role($roleScope->getRole()))) {
                    if ($scope === null) {
                        $centers[] = $groupCenter->getCenter();
                        break 1;
                    } else {
                        if ($scope->getId() == $roleScope->getScope()->getId()){
                            $centers[] = $groupCenter->getCenter();
                            break 1;
                        }      
                    }
                }
            }
            
        }
        
        return $centers;
    }
    
    /**
     * Return all reachable scope for a given user, center and role
     * 
     * @deprecated Use getReachableCircles
     *
     * @param User $user
     * @param Role $role
     * @param Center $center
     * @return Scope[]
     */
    public function getReachableScopes(User $user, Role $role, Center $center)
    {
        return $this->getReachableCircles($user, $role, $center);
    }
    
    /**
     * Return all reachable circle for a given user, center and role
     * 
     * @param User $user
     * @param Role $role
     * @param Center $center
     * @return Scope[]
     */
    public function getReachableCircles(User $user, Role $role, Center $center)
    {
        $scopes = array();
        
        foreach ($user->getGroupCenters() as $groupCenter){
            if ($center->getId() === $groupCenter->getCenter()->getId()) {
                //iterate on permissionGroup
                $permissionGroup = $groupCenter->getPermissionsGroup();
                //iterate on roleScopes
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
                    //check that the role is in the reachable roles
                    if ($this->isRoleReached($role, 
                          new Role($roleScope->getRole()))) {
                        $scopes[] = $roleScope->getScope();
                    }
                }
            }
        }
        
        return $scopes;
    }
    
    
    
    /**
     * Test if a parent role may give access to a given child role
     * 
     * @param Role $childRole The role we want to test if he is reachable
     * @param Role $parentRole The role which should give access to $childRole
     * @return boolean true if the child role is granted by parent role
     */
    protected function isRoleReached(Role $childRole, Role $parentRole)
    {
        $reachableRoles = $this->roleHierarchy
                ->getReachableRoles([$parentRole]);
        
        return in_array($childRole, $reachableRoles);
    }
}