<?php
/**
 * Copyright 2016 David Vivier on behalf of Smartic.ca
 * 
 * This file is part of the Smarticops Package
 * 
 * License : MIT
 * 
 * @author David Vivier
 */

namespace Dvivier\Smarticops;

use Illuminate\Foundation\Auth\User as Authenticatable;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;


use Dvivier\Smarticops\Permission;
use Dvivier\Smarticops\Role;

use Dvivier\Smarticops\Facades\Smarticops;

use DB;
use Session;
use Auth;


/**
 *  The main User class for Smarticops features.
 * 
 *  Extends Authenticable which itself extends Eloquent User model.
 * 
 *  So this class includs Authentication and Eloquent features.
 *  
 * To use a User class in your application, use the App\User class, which inherits this Smarticops\User class.
 * 
 *  @see Authenticatable Authenticatable
 */
class User extends Authenticatable
{       //          Auth\User already extends Eloquent\Model !
    //use Authenticatable;
    
    use SoftDeletes;
    
    protected $dates = [ 'deleted_at' ];
        
    /**
     * 
     * Allows to temporarly store the permissions that a user has.
     *  Avoids too many useless queries to the database
     * 
     * @var array
     * @see loadPermissions() loadPermissions()
     * @deprecated
     */
    protected $_permissions = array();
    
    
    protected $guarded = array('*');
    
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'first_name', 'last_name', 'email',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token', 'confirmation_code',
    ];
    
    
    public function roles() {
        return $this->belongsToMany(Role::class, 'users_roles')->withTimeStamps();
    }
    
    
    /**
     * Get all the roles assigned to the user as an array of strings
     * 
     * @return  array
     */
    public function getRoles() {
        //$roles = $this->with('roles')->get();
            // problem because 2 DB requests each time
        $roles = $this->fresh()->roles;
        //var_dump($roles);
        $roles_strings = array();
        
        foreach($roles as $key => $role) {
            //print($key);
            $roles_strings[] = $role->name;
        }
        
        return $roles_strings;
    }
    
    /**
     * Checks if a user has a specific role
     * 
     * @param  string  $roleName  The name of the role to test for, case sensitive.
     * @return  boolean  true if the user has the role, false otherwise.
     * 
     * @see User::assignRole()  assignRole()
     */
    public function hasRole($roleName) {
        //print('hasRole('.$roleName.') : ');
        //var_dump($this->role);
        //print('user:');
        //var_dump($this);
        
        /*
        print('roleName=');
        var_dump($roleName);
        print('$user->roleName()=');
        var_dump($this->roleName());
        return $this->roleName() === $roleName;*/
        
        //print('$user->roles : ');
        
        $roles = $this->getRoles();
        
        if ( in_array($roleName, $roles) ) {
            //print('user has the role');
            return true;
        }
        
        return false;
    }
    
    /**
     * Just an alias for `hasRole()`.
     * 
     * @param  string  $roleName  The name of the role to test for, case sensitive.
     * @see  Dvivier\Smarticops\User::hasRole()  hasRole()
     * 
     * @return  boolean  true if the user has the role.
     */
    public function is($roleName) {
        return $this->hasRole($roleName);
    }
    
    /**
     * Defines a new query scope.
     * 
     * Allows to access directly all the users having a specific role.  
     * 
     * Method written as described in the Laravel documentation.  
     * 
     * Usage example : `User->havingRole('Student');`
     * 
     * @param  mixed  $query
     * @param  string  $rolename
     * 
     * @see  https://laravel.com/docs/5.2/eloquent#local-scopes  Laravel documentation
     * 
     * @todo  allow to select more than one role with the syntax 'role1|role2'
     */
    public function scopeHavingRole( $query, $roleName ) {
        
        $role = Role::byName($roleName);
        
        // retrieve all the users ID that have this role
        $users = DB::table('users_roles')->where('role_id', $role->id)->select('user_id')->get();
        $ids = array();
        foreach($users as $user) {
            $ids[] = $user->user_id;
        }
        
        // return only those users in the query
        return $query->whereIn('id', $ids);
    }
    
    /**
     * Pivot access to the users' permissions.
     * 
     */
    public function permissions() {
        return $this->belongsToMany(Permission::class, 'users_permissions')->withPivot('value', 'created_by', 'updated_by')->withTimeStamps();
    }
    
    /**
     * Assigns a role to the user.
     *  Since a user can have multiple roles,
     *  the user keeps the roles it had before being assigned a new role.
     *  To remove a role from a user, use User->revokeRole().
     *  If the user already has the roles, the method does nothing and true is returned.
     * 
     * @param  Role|string $role The Role instance, otherwise a string. Must exist before assigning.
     * @param  boolean  $givePermissions  If set to true, after assigning the role, the method will
     *                                     also give to the user all the permissions associated to the role.
     * 
     * @return  boolean  true if the role was succesfully added or was already assigned
     *                   false if the assignation failed or if role doesn't exist
     * 
     * @see  revokeRole() revokeRole()
     */
    public function assignRole($role = null, $givePermissions = false) {
        //print_r($role);
        
        if ($role === null) {
            return false;
        }
        
        //print_r(' 1-');
        if ( $role instanceof Role ) {
            //print_r(' 1.1- ');
            $roleObject = $role;
        } else {  // $role is a string
            //print_r(' 1.2- ');
            $roleObject = Role::byName($role);
        }
        
        if ( null === $roleObject ) {
            // role doesn't exist
            return false;
        }
        
        //print_r(' 2- ');
        
        //$this->role()->dissociate();
        if ( $roleObject->id > 0 ) {
            
            if ( $this->hasRole($roleObject->name) ) {
                // the user already has the role, nothing to be done
                $res = true;
            }
            else {
                //var_dump($roleObject->id);
                //var_dump($this->roles()->attach($roleObject->id));
                $auth_user_id = Auth::check() ? Auth::user()->id : 0;
                // attach returns void
                $this->roles()->attach($roleObject->id, [
                                                         'created_by' => $auth_user_id]);

                
                //$role->users()->save($this);
                //$this->role()->associate($roleObject);
                //var_dump($this);
                //$this->role->save();
                if ($givePermissions) {
                    $this->assignRolePermissions($roleObject);
                }
            }
        }
        
        Smarticops::logAssignRole($this, $roleObject, true);
        //return $res;
    }
    
    /**
     * For the given role, assign all the role permissions to the user.
     * 
     * @param  $role  Role
     */
    private function assignRolePermissions(Role $role) {
        $ok = true;
        foreach ($role->permissions as $permission) {
            $res = $this->givePermission($permission->code);
            if (!$res) {
                $ok = false;
            }
        }
        return($ok);
    }
    
    /**
     * Removes the role from a user.
     * 
     * @param  $role  The role to revoke, object or string
     *  
     * @return  true if (the user had the role AND it has not any more), 
     *          false if the removal failed or the user didn't have the role
     * @see  assignRole()
     */
    public function revokeRole($role) {
        
        if ($role === null) {
            return false;
        }
        
        if ( $role instanceof Role ) {
            $roleObject = $role;
        }
        else {
            $roleObject = Role::byName($role);
        }
        
        if ($roleObject === null) {
            return false;
        }
        

        if ($this->hasRole($roleObject->name)) {
            $res = $this->roles()->detach($roleObject->id);
            Smarticops::logRevokeRole($this, $roleObject, $res);
        }
        // don't log if he doesn't have the role
        

        return ($res == 1);
    }
    
    /**
     * Revokes all roles for this user.
     * 
     */
    public function revokeAllRoles() {
        foreach ($this->roles as $role) {
            $this->revokeRole($role);
        }
    }
    
    /**
     * The full name of the user.
     * 
     * @return  string  firstname + " " + last name
     */
    public function fullName() {
        return $this->first_name.' '.$this->last_name;
    }
    
    /**
     * Loads all the permissions associated to the user from the database to the Session
     *      To avoid too many requests to the DB
     * 
     * @return  Collection of Permissions  
     */
    public function loadPermissions() {
        /*
        DB::Select( 'SELECT *
                        FROM permissions, users_permissions
                        WHERE users_permissions.user_id = ');*/
        //$this->_permissions = $this->fresh()->permissions;
        $permissions = $this->fresh()->permissions;
        Session::put('permissions', $permissions);
        return $permissions; 
    }
    
    
    /**
     * Gives a specific permission to the user. 
     * The permission must exist before giving it to the user.
     * By default the permission is set to true, you may set it to false
     *  to explicitly mean that the user does NOT have that permission.
     * 
     * @param  permissionCode  string
     * @param  boolean  $value
     * 
     * @return boolean  true if the permission was successfully given to the user
     *                          or if the user already had this permission
     *                          false if the permission doesn't exist
     */
    public function givePermission( $permissionCode, $value = true ) {
        
        //var_dump($this->_permissions);
        
        //print_r('code='.$permissionCode.',');
        //print_r($value);
        
        $permissions = Permission::where('code', $permissionCode )->get();
        //var_dump($permissions->count());
        if ($permissions->count() == 0) {
            // the permission doesn't even exist
            Smarticops::logError('Failed giving permission "'.$permissionCode.
                                 '", which doesn\'t exist, to User "'.$this->fullName().'"',
                                [
                                    'user_id'   => $this->id,
                                    'user_fullname'         => $this->fullName(),
                                    'user_email'        => $this->email,
                                    'permission_code'   => $permissionCode,
                                    ]);
            return false;
        }
        
        // the permission exists. 
        //  now we check if the user was already given that permission
        
        $permission = $permissions->first();
        $permissionId = $permission->id;
        //print_r('  -id=');
        //print_r($permissionId);
        
        $count = $this->permissions()->where('code', $permissionCode)->get()->count();
        //print_r(' - count=');
        //print_r($count);
        
        if ($count != 0) {
            $hasPermission = true;
        }
        else {
            $hasPermission = false;
        }
        
        $auth_user_id = Auth::check() ? Auth::user()->id : 0;
        
        $res = true;
        if ($hasPermission) {
        
                // the user already has the permission ; update the value
                //print_r(' has : '.$permissionCode.' updatingTo='.$value);
                
                $res = $this->permissions()->updateExistingPivot($permission->id,[
                                                                        'value' => $value,
                                                                        'updated_by' => $auth_user_id,
                                                                        ]);
                //print_r(' res='.$res.' ') ;                                
                //$table = DB::select('Select * from users_permissions');
                //print_r(' table : ');
                //print_r($table);

        }
        else {
            //print_r(' new : '.$permissionCode.' ');
            // user doesn't already has the permission 
        
            $this->permissions()->attach($permission->id, [
                                                            'value' => $value,
                                                            'created_by' => $auth_user_id,
                                                            ]);
        }
        
        Smarticops::logPermissionGivenToUser($permission, $this, $value, $res);

        // we update the temporary stored permission in the session
        //$this->loadPermissions();
        //print_r('   ');
        return true;
    }
    
    /**
     * Removes a Permission from a user, specifying the permission by its code.
     * 
     * @param  $permissionCode  $string
     * 
     * @return  true if the permission was removed successfully,
     *          false if the permission does not exists,
     *                or if the user does not have the permission.
     */
    public function removePermission( $permissionCode ) {
        
        $permissions = Permission::where('code', $permissionCode )->get();

        if ($permissions->count() == 0) {
            // the permission doesn't even exist
            return false;
        }
        
        $permission = $permissions->first();
        
        $count = $this->permissions()->where('code', $permissionCode)->get()->count();

        if ($count != 0) {
            $hasPermission = true;
        }
        else {
            $hasPermission = false;
        }

        if ($hasPermission) {
            $this->permissions()->detach($permission->id);
            //$this->loadPermissions();
            Smarticops::logPermissionRemovedFromUser($permission, $this);
            return true;
        }
        else {
            Smarticops::logError('Failed removing permission "'.$permission->code.
                                 '" from User "'.$this->fullName().'", : this user didn\'t have this permission.',
                                 [
                                    'user_id'   => $this->id,
                                    'user_fullname'     => $this->fullName(),
                                    'user_email'        => $this->email,
                                    'permission_id'     => $permission->id,
                                    'permission_code'   => $permission->code,
                                    'permission_description'    => $permission->description,
                                    ]);
            return false;
        }
        
    }
    
    
    /**
     * Tests if the user has a permission or not.
     * By default, the method looks only at the session where the permissions
     *   has been stored _for the user currently logged in_ , Auth::user().
     * This allows to reduce the number of DB queries.
     *
     * In most cases, there is no need to get info from database since the
     *  permissions are put in the session at login.
     *
     * Pass true as 2nd argument if you're testing another user's permissions
     *  than the one who is logged in.
     *
     * @param  $permissionCode  string  the permission to test for
     * @param  $fromDB  boolean  (optional) whether or not do a request to the database
     *
     */
    public function hasPermission( $permissionCode, $fromDB = false ) {
        //print_r($this->_permissions[0]);
        //print_r($this->_permissions[0]->code);
        //print_r('checking:'.$permissionCode.' ');
        
        if ($fromDB) {
            // only when needed
            $permissions = $this->fresh()->permissions;
        }
        else {
            // most often
            $id = (Auth::check() ? Auth::user()->id : 0);
            if ($id == $this->id) {
                $permissions = Session::get('permissions');
            }
            else {
                // just in case the user checked is not the authenticated user
                $permissions = $this->fresh()->permissions;
            }
        }
        
        if (count($permissions) === 0) {
            return false;
        }
        
        foreach($permissions as $p) {
            /*
            if (Session::get('func', 'nofunc') == 'post') {
                var_dump($p->code);
            }*/
            if ($p->code == $permissionCode) {
                //print_r('value='.$p->pivot->value);
                if ($p->pivot->value == true) {
                    return true;
                }
            }
        }
        return false;
    }
    
    
    /**
     * Checks if the user has confirmed his account by email or not.
     * 
     * @return  true if the user has confirmed his account,
     *          false otherwise.
     */
    public function isConfirmed() {
        return $this->confirmed;
    }
    
}

