<?php 
/**
 * @package    Proxim
 * @author     Dominic Tanui <dominic@codeworldweb.com>
 * @copyright  2019 Proxim
 * @version    1.5.0
 * @since      File available since Release 1.0.0
 */

namespace Proxim\User;

use Db;
use Proxim\Database\DbQuery;
use Proxim\Cache\Cache;
use Proxim\ObjectModel;
use Proxim\Notification;
use Proxim\Configuration;
use Proxim\Application;
use Proxim\Order\Order;
use Proxim\Validate;
use Proxim\Tools;
use Proxim\Util\ArrayUtils;
use Proxim\Util\DateUtils;
use Proxim\Crypto\Hashing;
use Proxim\Hook;
use Proxim\Order\Message;

/**
 * Employee
 */
class Employee extends ObjectModel {
    const START_ID = 1353442;

    /** User groups */
    const GROUP_ADMIN = 1;

    const GROUP_EDITOR = 2;
    
    const GROUP_WRITER = 3;

    const GROUP_SUB_ADMIN = 4;

    /** Writer Category **/
    const CATEGORY_BEST = 2;

    const CATEGORY_ENL = 4;

    const CATEGORY_ESL = 3;

    /** Writer Working Status **/
    const STATUS_WORKING = 2;

    const STATUS_CANNOT_WORK = 4;

    const STATUS_NEVER_WORK = 5;

    /** @var $id Employee ID */
    public $id;

    /** @var string $employee_group */
    public $employee_group;

    /** @var string $email */
    public $email;

    /** @var string $first_name */
    public $first_name;

    /** @var string $middle_name */
    public $middle_name;

    /** @var string $last_name */
    public $last_name;

    /** @var string $gender */
    public $gender = 'male';

    /** @var string $phone */
    public $phone;

    /** @var int $writer_category */
    public $writer_category = self::CATEGORY_BEST;

    /** @var int $academic_level */
    public $academic_level = LEVEL_COLLEGE;

    /** @var string $employee_rating */
    public $employee_rating;

    /** @var int $employee_status */
    public $employee_status = self::STATUS_WORKING;

    /** @var string $avatar */
    public $avatar;

    /** @var string $about */
    public $about;

    /** @var string $facebook_profile */
    public $facebook_profile;

    /** @var string $linkedin_profile */
    public $linkedin_profile;

    /** @var string $payment_method */
    public $payment_method;

    /** @var string $payment_method_value */
    public $payment_method_value;

    /** @var int $last_pay */
    public $last_pay = 0;

    /** @var int $total_paid */
    public $total_paid = 0;

    /** @var int $total_orders_revised */
    public $total_orders_revised = 0;

    /** @var int $total_orders_rejected */
    public $total_orders_rejected = 0;

    /** @var string $cpp_type */
    public $cpp_type = 'system';

    /** @var int $cpp_amount */
    public $cpp_amount = 0;
    
    /** @var bool $email_notifications */
    public $email_notifications = 1;

    /** @var string $password */
    public $password;

    /** @var string $reset_password_token */
    public $reset_password_token;

    /** @var string $reset_password_validity */
    public $reset_password_validity;

    /** @var string $last_passwd_gen */
    public $last_passwd_gen;

    /** @var string $secure_key */
    public $secure_key;

    /** @var bool $policy_accepted */
    public $policy_accepted = 0;

    /** @var string $policy_accepted_at */
    public $policy_accepted_at;

    /** @var bool $is_started */
    public $is_started = 0;

    /** @var bool $is_banned */
    public $is_banned = 0;

    /** @var int $push_notifications */
    public $push_notifications = 0;

    /** @var int $live_notifications_counter */
    public $live_notifications_counter = 0;

    /** @var int $live_notifications_lastid */
    public $live_notifications_lastid;

    /** @var int $live_messages_counter */
    public $live_messages_counter = 0;

    /** @var int $live_messages_lastid */
    public $live_messages_lastid;

    /** @var bool $notification_sound */
    public $notification_sound = 1;

    /** @var string $default_region */
    public $default_region;

    /** @var string $timezone */
    public $timezone;

    /** @var string $last_login */
    public $last_login;

    /** @var string $last_activity */
    public $last_activity;

    /** @var string $reg_date */
    public $reg_date;

    /** @var bool is the user logged in */
    public $logged = false;

    /** @var bool is_admin */
    public $is_admin = false;

    /** @var bool is_sub_admin */
    public $is_sub_admin = false;

    /** @var bool is_editor */
    public $is_editor = false;

    /** @var bool is_writer */
    public $is_writer = false;

    /** Writer Percent **/
    public $writer_percent = 0;

    /** Default profile picture */
    public $user_picture_default;

    /** User picture */
    public $user_picture;

    /** @var string writer_category_title */
    public $writer_category_title;

    /** @var string academic_level_title */
    public $academic_level_title;

    /** @var string employee_status_title */
    public $employee_status_title;

    public $order_stats = array(
        'new_paid' => 0,
        'available' => 0,
        'bids' => 0,
        'progress' => 0,
        'done' => 0,
        'delivered' => 0,
        'revision' => 0,
        'finished' => 0,
        'dispute' => 0,
        'unpaid' => 0,
        'cancelled' => 0,
        'inquiry' => 0
    );
    
    /**
     * @see ObjectModel::$definition
     */
    public static $definition = array(
        'table' => 'employee',
        'primary' => 'employee_id',
        'fields' => array(
            'employee_group' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
            'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 255),
            'first_name' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => false, 'size' => 255),
            'middle_name' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => false, 'size' => 255),
            'last_name' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => false, 'size' => 255),
            'gender' => array('type' => self::TYPE_STRING),
            'phone' => array('type' => self::TYPE_STRING, 'validate' => 'isPhoneNumber', 'required' => false, 'size' => 50),
            'writer_category' => array('type' => self::TYPE_INT, 'validate' => 'isInt'),
            'academic_level' => array('type' => self::TYPE_INT, 'validate' => 'isInt'),
            'employee_rating' => array('type' => self::TYPE_INT, 'validate' => 'isInt'),
            'employee_status' => array('type' => self::TYPE_INT, 'validate' => 'isInt'),
            'avatar' => array('type' => self::TYPE_STRING),
            'about' => array('type' => self::TYPE_STRING),
            'facebook_profile' => array('type' => self::TYPE_STRING),
            'linkedin_profile' => array('type' => self::TYPE_STRING),
            'payment_method' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'payment_method_value' => array('type' => self::TYPE_STRING),
            'last_pay' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
            'total_paid' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
            'total_orders_revised' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'total_orders_rejected' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'cpp_type' => array('type' => self::TYPE_STRING, 'size' => 20),
            'cpp_amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
            'email_notifications' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'password' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 255),
            'reset_password_token' => array('type' => self::TYPE_STRING, 'validate' => 'isSha1', 'size' => 40),
            'reset_password_validity' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'last_passwd_gen' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'secure_key' => array('type' => self::TYPE_STRING, 'validate' => 'isMd5'),
            'policy_accepted' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'policy_accepted_at' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'is_started' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'is_banned' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'push_notifications' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'live_notifications_counter' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'live_notifications_lastid' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'live_messages_counter' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'live_messages_lastid' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'notification_sound' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'timezone' => array('type' => self::TYPE_STRING),
            'last_login' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'last_activity' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'reg_date' => array('type' => self::TYPE_DATE, 'validate' => 'isDate')
        )
    );

    /**
     * constructor.
     *
     * @param null $id
     */
    public function __construct($id = null)
    {
        global $globals;
        parent::__construct($id);

        if ($this->id) {
            $this->is_admin = ($this->employee_group == self::GROUP_ADMIN)? true: false;
            $this->is_sub_admin = ($this->employee_group == self::GROUP_SUB_ADMIN)? true: false;
            $this->is_editor = ($this->employee_group == self::GROUP_EDITOR)? true: false;
            $this->is_writer = ($this->employee_group == self::GROUP_WRITER)? true: false;
            
            if (!$this->timezone) {
                $this->timezone = Configuration::get('DEFAULT_TIMEZONE', PROX_SITE_ID, 'Africa/Nairobi');
            }

            $this->default_region = preg_replace("/(.*)\/.*/", "$1", $this->timezone );

            $this->getStats();

            /* get profile picture */

            $this->user_picture_default = !$this->avatar ? true : false;
            $this->user_picture = $this->getPicture($this->avatar, $this->gender);

            $academicLevels = $globals['academicLevels'];
            $academic_level = $this->academic_level ? ArrayUtils::get($academicLevels, $this->academic_level) : ArrayUtils::get($academicLevels, LEVEL_COLLEGE);
            $this->academic_level_title = ArrayUtils::get($academic_level, 'title');
        
            $writerPreferences = $globals['writerPreferences'];
            $writer_preference = $this->writer_category ? ArrayUtils::get($writerPreferences, $this->writer_category) : ArrayUtils::get($writerPreferences, self::CATEGORY_BEST);
            $this->writer_category_title = ArrayUtils::get($writer_preference, 'title');
            $this->writer_percent = ArrayUtils::get($writer_preference, 'percent', 0);

            $workStatus = $globals['workingStatus'];
            $working_status = $this->employee_status ? ArrayUtils::get($workStatus, $this->employee_status) : ArrayUtils::get($workStatus, self::STATUS_WORKING);
            $this->employee_status_title = ArrayUtils::get($working_status, 'title');
        }
    }

    public function add($autoDate = true, $nullValues = true)
    {
        $this->secure_key = md5(uniqid(rand(), true));
        $this->last_passwd_gen = DateUtils::now();

        if ( !$this->getNextEmployeeId() ) {
            $this->employee_group = self::GROUP_ADMIN;
            $this->id = self::START_ID;
            $this->is_started = true;
        } else {
            $next_employee_id = Db::getInstance()->getValue('SELECT MAX(`employee_id`)+2 FROM `' . DB_PREFIX . 'employee`');
            $this->id = $next_employee_id;
        }

        $this->force_id = true;

        $success = parent::add($autoDate, $nullValues);

        return $success;
    }

    /* ------------------------------- */
    /* Get Picture */
    /* ------------------------------- */

    /**
     * getPicture
     * 
     * @param string $picture
     * @param string $type
     * @return string
     */
    public function getPicture( $picture, $type ) {
        $app = Application::getInstance();
        
        if(!$picture) {
            switch ($type) {
                case 'male':
                    $picture = $app->base_uri . '/static/images/blank_profile_male.jpg';
                    break;
                
                case 'female':
                    $picture = $app->base_uri . '/static/images/blank_profile_female.jpg';
                    break;

                case 'other':
                    $picture = $app->base_uri . '/static/images/blank_profile_other.jpg';
                    break;
            }
        } else {
            $picture = Configuration::get('UPLOADS_PATH') . '/' . $picture;
        }

        return $picture;
    }

    public function getStats() {
        $sql = new DbQuery();
        $sql->select('COUNT(o.order_id) AS count');
        $sql->from('order', 'o');

        if ($this->is_writer) {
            $sql->where('o.`writer_id` = ' . (int) $this->id );
            $sql->where( 'o.writer_assigned = 1' );
        }

        if ($this->is_editor) {
            $sql->where('o.`editor_id` = ' . (int) $this->id );
            $sql->where( 'o.editor_assigned = 1' );
        }

        $stats = array();

        /** Clone SQL */
        $new_paid_query = clone $sql;
        $unconfirmed_query = clone $sql;
        $inprogress_query = clone $sql;
        $done_query = clone $sql;
        $delivered_query = clone $sql;
        $finished_query = clone $sql;
        $revision_query = clone $sql;
        $dispute_query = clone $sql;
        $cancelled_query = clone $sql;
        $unpaid_query = clone $sql;
        $inquiry_query = clone $sql;

        /** Available Orders */
        $available_query = new DbQuery();
        $available_query->select('COUNT(o.order_id) AS count');
        $available_query->from('order', 'o');

        if ( $this->is_writer ) {
            $available_status = array(
                Order::ORDER_PUBLISHED,
                Order::WRITER_ACCEPTED,
                Order::INQUERY_PUBLISHED
            );
            $available_query->where('o.`status_id` IN ('. implode(", ", $available_status) .')');
            $available_query->leftJoin('order_bid', 'bid', 'o.order_id = bid.order_id AND bid.writer_id = ' . (int) $this->id );
            $available_query->where( 'bid.bid_id IS NULL' );
            $available_query->where( 'o.academic_level_id <= ' . (int) $this->academic_level );
            $available_query->where( 'o.writer_category_id <= ' . (int) $this->writer_category );

        } elseif ( $this->is_editor ) {
            $available_status = array(
                Order::DONE,
            );
            $available_query->where('o.`status_id` IN ('. implode(", ", $available_status) .')');
            $available_query->where( 'o.editor_assigned = 0' );
            $available_query->where( 'o.academic_level_id <= ' . (int) $this->academic_level );
            $available_query->where( 'o.writer_category_id <= ' . (int) $this->writer_category );
            
        } else {
            $available_status = array(
                Order::ORDER_PUBLISHED,
                Order::INQUERY_PUBLISHED,
                Order::WRITER_ACCEPTED
            );
            
            $available_query->leftJoin('order_bid', 'bid', 'o.order_id = bid.order_id' );
            $available_query->where( 'bid.bid_id IS NULL' );
            $available_query->where('o.`status_id` IN ('. implode(", ", $available_status) .')');
        }

        $available_orders_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $available_query );
        $stats['available'] = $available_orders_count;

        /** Bidded Orders */
        $bids_query = new DbQuery();
        $bids_query->select('COUNT(DISTINCT o.order_id) AS count');
        $bids_query->from('order', 'o');
        $bids_query->where('o.status_id = ' . (int) Order::WRITER_ACCEPTED );
        if ( $this->is_admin || $this->is_sub_admin ) {
            $bids_query->innerJoin('order_bid', 'bid', 'o.order_id = bid.order_id' );
        } elseif( $this->is_writer ) {
            $bids_query->innerJoin('order_bid', 'bid', 'o.order_id = bid.order_id AND bid.writer_id = ' . $this->id );
        }

        $bids_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $bids_query );
        $stats['bids'] = $bids_count;
    
        /** Writer Assigned */
        if ($this->is_editor) {
            $inprogress_query->where('status_id = ' . (int) Order::DONE );
            $sql->where( 'is_editor_confirmed = 1' );
        } else {
            $inprogress_query->where('status_id = ' . (int) Order::WRITER_ASSIGNED );
            $inprogress_query->where( 'is_writer_confirmed = 1' );
        }

        $inprogress_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $inprogress_query );
        $stats['progress'] = $inprogress_count;

        /** Done */
        $done_query ->where('status_id = ' . (int) Order::DONE );
        $done_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $done_query );
        $stats['done'] = $done_count;

        /** Delivered */
        $delivered_query->where('status_id = ' . (int) Order::DELIVERED );
        $delivered_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $delivered_query );
        $stats['delivered'] = $delivered_count;
        
        /** Delivered */
        $finished_query->where('status_id = ' . (int) Order::FINISHED );
        $finished_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $finished_query );
        $stats['finished'] = $finished_count;
        
        /** Dispute Orders */
        $dispute_query->where('status_id = ' . (int) Order::DISPUTE);
        $dispute_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $dispute_query );
        $stats['disputes'] = $dispute_count;

        /** Revision Orders */
        $revision_query->where('status_id = ' . (int) Order::REVISION );
        $revision_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $revision_query );
        $stats['revision'] = $revision_count;

        if( $this->is_admin ||
            $this->is_sub_admin ||
            $this->is_writer
        ) {
            /** Uncomfirmed */
            $unconfirmed_query->where('status_id = ' . (int) Order::WRITER_ASSIGNED );
            $unconfirmed_query->where( 'is_writer_confirmed = 0' );
            $unconfirmed_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $unconfirmed_query );
            $stats['unconfirmed'] = $unconfirmed_count;
        }

        if($this->is_admin || $this->is_sub_admin) {
            /** Inquiry */
            $inquiry_query->where('status_id = ' . (int) Order::FREE_INQUIRY );
            $inquiry_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $inquiry_query );
            $stats['inquiry'] = $inquiry_count;

            /** New Paid */
            $new_paid_query->where('status_id = ' . (int) Order::NEW_PAID );
            $new_paid_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $new_paid_query );
            $stats['new_paid'] = $new_paid_count;

            /** Unpaid */
            $unpaid_query->where('status_id = ' . (int) Order::FAKE );
            $unpaid_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $unpaid_query );
            $stats['unpaid'] = $unpaid_count;

            /** Cancelled */
            $cancelled_query->where('status_id = ' . (int) Order::CANCELLED );
            $cancelled_count = Db::getInstance(PROX_USE_SQL_SLAVE)->getValue( $cancelled_query );
            $stats['cancelled'] = $cancelled_count;
        }

        $this->order_stats = ArrayUtils::defaults( $this->order_stats, $stats );
    }   

    /**
     * get_notifications
     * 
     * @param integer $offset
     * @param integer $last_notification_id
     * @return array
     */
    public function getNotifications($offset = 0, $last_notification_id = null)
    {
        $notifications = array();
        $max_results = Configuration::get('MAX_RESULTS', null, 10);
        $offset *= $max_results;

        $notifications_query = new DbQuery();
        $notifications_query->select('notification.notification_id');
        $notifications_query->from('notification', 'notification');
        $notifications_query->where('notification.to_user_id = ' . (int) $this->id );

        if(!is_null($last_notification_id)) {
            $notifications_query->where('notification.notification_id > ' . (int) $last_notification_id );
        }

        $notifications_query->orderBy('notification.notification_id DESC');
        $notifications_query->limit($max_results, $offset);

        $result = Db::getInstance(PROX_USE_SQL_SLAVE)->executeS( $notifications_query );
            
        if(!$result) {
            return $notifications;
        }

        foreach( $result as $notification ) {
            $notification = new Notification( (int) $notification['notification_id'] );
            if(Validate::isLoadedObject($notification)) {
                $notifications[] = $notification->present();
            }
        }

        return $notifications;
    }

    /**
     * get_messages
     * 
     * @param integer $offset
     * @param integer $last_message_id
     * @return array
     */
    public function getMessages($offset = 0, $last_message_id = null)
    {
        $messages = array();
        $max_results = Configuration::get('MAX_RESULTS', null, 10);
        $offset *= $max_results;

        $messages_query = new DbQuery();
        $messages_query->select('message.message_id');
        $messages_query->from('order_message', 'message');
        $messages_query->innerJoin('order', 'order', 'message.order_id = order.order_id');
        $messages_query->where('message.sender_id != ' . (int) $this->id );

        if($this->is_writer || $this->is_editor) {
            if($this->is_writer) {
                $messages_query->where('order.writer_id = ' . (int) $this->id );
            }

            if($this->is_editor) {
                $messages_query->where('order.editor_id = ' . (int) $this->id );
            }

            $messages_query->where(' 
                ( message.`to_department` = ' . (int) DEPARTMENT_WRITER . ' AND is_verified = 1 ) 
            ');
        }

        if(!is_null($last_message_id)) {
            $messages_query->where('message.message_id > ' . (int) $last_message_id );
        }

        $messages_query->orderBy('message.message_id DESC');
        $messages_query->limit($max_results, $offset);

        $result = Db::getInstance(PROX_USE_SQL_SLAVE)->executeS( $messages_query );
            
        if(!$result) {
            return $messages;
        }

        foreach( $result as $message ) {
            $message = new Message( (int) $message['message_id'] );
            if(Validate::isLoadedObject($message)) {
                $messages[] = $message->present();
            }
        }

        return $messages;
    }

    /**
     * Return user instance from its e-mail (optionally check password).
     *
     * @param string $email e-mail
     * @param string $plaintextPassword Password is also checked if specified
     *
     * @return bool|User User instance
     */
    public function getByEmail($email, $plaintextPassword = null)
    {
        if (!Validate::isEmail($email) || ($plaintextPassword && !Validate::isPasswd($plaintextPassword))) {
            return false;
        }
        
        $sql = new DbQuery();
        $sql->select('e.*');
        $sql->from('employee', 'e');
        $sql->where('e.`email` = \'' . pSQL($email) . '\'');
        
        $result = Db::getInstance()->getRow($sql);
        if (!$result) {
            return false;
        }

        $crypto = new Hashing();

        $passwordHash = $result['password'];
        $shouldCheckPassword = !is_null($plaintextPassword);
        if ($shouldCheckPassword && !$crypto->checkHash($plaintextPassword, $passwordHash)) {
            return false;
        }
        
        $this->id = $result['employee_id'];
        foreach ($result as $key => $value) {
            if (property_exists($this, $key)) {
                $this->{$key} = $value;
            }
        }
        
        if ($shouldCheckPassword && !$crypto->isFirstHash($plaintextPassword, $passwordHash)) {
            $this->password = $crypto->hash($plaintextPassword);
            $this->update();
        }

        return $this;

    }

    /**
     * Get outstanding payment.
     *
     * @return float Outstanding payment
     */
    public function getOutstanding()
    {
        $query = new DbQuery();
        
        if ($this->is_writer) {
            $query->select('SUM(o.writer_pay)');
            $query->where('o.writer_id = ' . (int) $this->id);
            $query->where('o.`writer_assigned` = 1');
            $query->where('o.is_writer_paid = 0');
        }

        if ($this->is_editor) {
            $query->select('SUM(o.editor_pay)');
            $query->where('o.editor_id = ' . (int) $this->id);
            $query->where('o.`editor_assigned` = 1');
            $query->where('o.is_editor_paid = 0');
        }

        if( Configuration::get('PAY_DELIVERED_ORDERS') ) {
            $query->where( sprintf('o.status_id = %s OR o.status_id = %s',Order::DELIVERED, Order::FINISHED) );
        } else {
            $query->where( sprintf('o.status_id = %s', Order::FINISHED) );
        }

        $query->from('order', 'o');
        $totalOutstanding = (float) Db::getInstance()->getValue($query->build());

        return $totalOutstanding;
    }
    
    /**
     * Check user information and return user validity.
     *
     * @return bool user validity
     */
    public function isLogged()
    {
        /* Employee is valid only if it can be load and if session is active */
        return $this->logged == 1 && (int) $this->id && Employee::checkPassword($this->id, $this->password);
    }

    /**
     * Soft logout, delete everything that links to the user
     *
     * @since 1.0.0
     */
    public function logout() {
		Hook::exec('actionEmployeeLogoutBefore', ['employee' => $this]);

        // remove auth_token
        if (isset(Application::getInstance()->container->cookie)) {
            Application::getInstance()->container->cookie->userLogout();
        }

		Hook::exec('actionEmployeeLogoutAfter', ['employee' => $this]);

        $this->logged = false;
    }

    /**
     * Check if employee password is the right one.
     *
     * @param int $employee_id Employee ID
     * @param string $passwordHash Hashed password
     *
     * @return bool result
     */
    public static function checkPassword($employee_id, $passwordHash)
    {
        if (!Validate::isUnsignedId($employee_id)) {
            die(Tools::displayError());
        }

        $cacheId = 'Employee::checkPassword' . (int) $employee_id . '-' . $passwordHash;
        if (!Cache::isStored($cacheId)) {
            $sql = new DbQuery();
            $sql->select('e.`employee_id`');
            $sql->from('employee', 'e');
            $sql->where('e.`employee_id` = ' . (int) $employee_id);
            $sql->where('e.`password` = \'' . pSQL($passwordHash) . '\'');

            $result = (bool) Db::getInstance(PROX_USE_SQL_SLAVE)->getValue($sql);

            Cache::store($cacheId, $result);

            return $result;
        }

        return Cache::retrieve($cacheId);
    }

    /**
     * Fill Reset password unique token with random sha1 and its validity date. For forgot password feature.
     */
    public function stampResetPasswordToken()
    {
        $salt = $this->id . '-' . $this->secure_key;
        $this->reset_password_token = sha1(time() . $salt);
        $this->reset_password_validity = date('Y-m-d H:i:s', strtotime('+' . 1440 . ' minutes'));
    }

    /**
     * Test if a reset password token is present and is recent enough to avoid creating a new one (in case of customer triggering the forgot password link too often).
     */
    public function hasRecentResetPasswordToken()
    {
        if (!$this->reset_password_token || $this->reset_password_token == '') {
            return false;
        }

        // TODO maybe use another 'recent' value for this test. For instance, equals password validity value.
        if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) {
            return false;
        }

        return true;
    }
    
    /**
     * Returns the valid reset password token if it validity date is > now().
     */
    public function getValidResetPasswordToken()
    {
        if (!$this->reset_password_token || $this->reset_password_token == '') {
            return false;
        }

        if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) {
            return false;
        }

        return $this->reset_password_token;
    }

    /**
     * Delete reset password token data.
     */
    public function removeResetPasswordToken()
    {
        $this->reset_password_token = null;
        $this->reset_password_validity = null;
    }

    /**
     * This method return the ID of the next employee.
     *
     * @since 1.0.0
     *
     * @return int
     */
    public function getNextEmployeeId()
    {
        return Db::getInstance()->getValue('
            SELECT employee_id
            FROM ' . DB_PREFIX . 'employee
            WHERE employee_id > ' . (int) $this->id . ' 
            ORDER BY employee_id ASC');
    }
}