<?php 

class user {

	private $query;
	public $login_status;
	public $last_login;
	public $last_logout;
	public $duration_online;
	public $user_id;
	public $username;
	private $retries = 0;
	private $max_retries = 3;

	public function __construct() {
		$this->query = new query;
		$this->cache = new cache;
		$this->use_cache = true;
		// $this->object_cache = Object_Cache::getInstance();
	}//construct()


	//User Data Functions
	public function register_user($args = array()) {
		$response = array();
		if (empty($args)) {
			$response['msg'] = 'Sorry registration is unsuccessful. We apologize for the inconvenience. Please try again later';
			$response['error'] = 1;
			return $response;
		}

		if(isset($args['preactivate_account'])) {
			$preactivate = ($args['preactivate_account'] == 1) ? true : false;
			unset($args['preactivate_account']);
		} else {
			$preactivate = get_option('preactivate_all_registration');
		}
		if(isset($args['unverified_role'])) {
			$unverified = ($args['unverified_role'] == 1) ? true : false;
			unset($args['unverified_role']);
		} else {
			$unverified = get_option('use_unverified_role');
		}
		
		$default_role = get_option('default_user_role');

		if(isset($args['role'])) {
			if($unverified) {
				$args['temp_role'] = $args['role'];
				$args['role'] = 'unverified';
			}
		} else {
			$args['role'] = ($unverified) ? 'unverified' : $default_role;
		}
		
		if(!isset($args['status'])) {
			$args['status'] = ($preactivate || $unverified) ? 'active' : 'inactive';
		}
		
		if(trim($args['role']) == '') 
			$args['role'] = DEFAULT_USER_ROLE;

		$args['username'] = _generate_user_name($args);
		if(isset($args['country_code'])) {
			$args['phone'] = internationalize_phone_number($args['phone'] ?? '', $args['country_code'] ?? '');
		} else $args['phone'] = internationalize_phone_number($args['phone'] ?? '');
		
		
		$args['email_verification'] = 'not verified';
		$args['sms_verification'] = 'not verified';
		$args['activation'] = SHA1(rand(9999, 99999));

		do_action("registration_authentication", $args);
		$email = (isset($args['email'])) ? $args['email'] : '';
		$username = (isset($args['username'])) ? $args['username'] : '';
		$password = (isset($args['password'])) ? $args['password'] : '';
		unset($args['email']);
		unset($args['username']);
		unset($args['password']);
		if ($this->email_exists($email) && $email !== "") {
			$response['msg'] = 'Sorry, email is already in use by another user.';
			$response['error'] = 1;
			return $response;
		}
		if ($this->username_exists($username)) {
			$response['msg'] = 'Sorry, username "'.$username.'" is already existing or created in error.';
			$response['error'] = 1;
			return $response;
		}
		$email = strtolower(sanitize($email));
		$username = strtolower(sanitize($username));
		$password = $this->encrypt_password($password);
		$date_created = time();
		// $args['db_section_id'] = isset($args['db_section_id']) ? $args['db_section_id'] : -1;
		$user_id = $this->query->insert('users', array('email', 'username', 'password', 'date_created'), array($email, $username, $password, $date_created));
		if ($user_id) {
			if(!empty($args)){
				// $args = sanitize($args);
				if(!isset($args['role'])) {
					$args['role'] = DEFAULT_USER_ROLE;
				}
				$this->register_user_meta($user_id, $args);
			}
			$args['id'] = $user_id;
			do_action("after_user_registration", ['args' => $args, 'user_id' => $user_id]);
			$company_name = get_option('company_name');
			$link = get_option('home_url').ACTIVATION_PAGE.'?action='.base64_encode('activate_account').'&id='.base64_encode($user_id).'&authe='.base64_encode($args['activation']);
			$response['user_id'] = $user_id;
			$response['msg'] = 'Account registered successfully.';
			if(!$preactivate && $unverified) {
				if($email_activation = get_option('confirm_via_email')) {
					$response['msg'] .= ' Please check user email for activation link.';
					$support = new support;
					$info = "<p>Welcome to ".$company_name.". Account was registered successfully.</p><p>To verify your account, please follow this link: </p><a title=\"activate account\" href=\"".$link."\">".$link."</a>";
					$support->subject = "Welcome to: ".$company_name;
					$support->action = $link;
					$support->inform_user($args, $info, $support->subject, EMAIL);
				}
				if($sms_activation = get_option('confirm_via_sms')) {
					$response['msg'] = str_replace(' for activation link.', '', $response['msg']);
					$response['msg'] .= ($email_activation) ? ' or SMS for activation link' : ' Please check user SMS for activation link';
					$support = new support;
					$info = "<p>Welcome to ".$company_name.". Your account was created successfully.</p><p>To verify your account, please follow this link: </p><a title=\"activate account\" href=\"".$link."\">".$link."</a>";
					$support->subject = "Welcome to: ".$company_name;
					$support->action = $link;
					$support->inform_user($args, $info, $support->subject, SMS, EMAIL);
				}
			}
			
			$response['error'] = 0;	

			$support = new support;
			$info = 'A new user '.get_fullname($args).' was registered on the system.';
			$subject = "User Registration: ".get_fullname($args);
			$support->action = get_option("home_url")."profile?id=".$user_id;
			$support->db_section_id = isset($args['db_section_id']) ? $args['db_section_id'] : -1;
			$support->inform_admins($info, $subject, "software_engineer", "super_admin", "admin", "admin_tech", "super_admin_tech");
			return $response;
		}
		$response['msg'] = 'Sorry registration is unsuccessful. We apologize for the inconvenience. Please try again later';
		$response['error'] = 1;
		return $response;

	}

	private function register_user_meta($user_id, $args) {
		$meta_arr = array();
		foreach ($args as $meta_name => $meta_value) {
			$meta_name = strtolower($meta_name);
			array_push($meta_arr, array($user_id, $meta_name, $meta_value));
		}
		$this->query->insert('user_meta', array('user_id', 'meta_name', 'meta_value'), $meta_arr, true);
	}

	public function get_users_by_meta($meta_name, $meta_value, $order_by = 0, $sort = 'ASC', $limit = -1) {
		$result = array();
		$users_data = $this->query->query_to_array($this->query->select('user_meta', array('meta_name' => $meta_name, 'meta_value' => $meta_value), 0, 'ASC', 0, 'LIKE', array('AND'), '*', 'user_id'), 'user_id');
		return $users_data;
	}

	public function get_user_meta($selector, $sel_col = false) {

		if(!$selector) return false;

		if(!$sel_col) {
			if (_is_numeric($selector)) :
				$sel_col = "user_id";
			elseif (is_email($selector)) :
				$sel_col = "email";
			else :
				$sel_col = "username";
			endif;
		}
		

		if($this->use_cache && ($cache = $this->cache->__get_cache($selector, $sel_col))) {
			return $cache;
		}

		$query = "
			SELECT DISTINCT meta_name, meta_value, username, email, users.id, password, date_created
    		FROM user_meta
    		LEFT JOIN users ON users.id = user_meta.user_id
    		WHERE user_id IN (SELECT id FROM users WHERE ".$sel_col." = %s)
    		ORDER BY meta_value ASC
		";

		$res = $this->query->get_results( $this->query->prepare($query, $selector), OBJECT_K );
		if(empty($res)) {
			return false;
		}

		switch($sel_col) {
			case "user_id":
				if($res['role']->id != $selector && $this->retries < $this->max_retries) {
					$this->get_user_meta($selector, "username");
				}
				$this->retries++;
				break;

			case "username":
				if($res['role']->username != $selector && $this->retries < $this->max_retries) {
					$this->get_user_meta($selector, "user_id");
				}
				$this->retries++;
				break;
		}
		$meta = array_map(function($v){
		    return htmlspecialchars_decode($v->meta_value);
		}, $res);
		$meta['email'] = $res['role']->email;
		$meta['username'] = $res['role']->username;
		$meta['password'] = $res['role']->password;
		$meta['date_created'] = $res['role']->date_created;
		$meta['id'] = $res['role']->id;
		if($this->use_cache)
			$this->cache->__save_new_cache($selector, $sel_col, $meta);
		return $meta;
	}

	public function update_user_meta($selector, $args) {
		if($selector === NULL) return false;
		$user_meta = $this->get_user_meta($selector);
		$user_id = $user_meta['id'];
		$to_update = array();
		$updated = 0;
		// don't update username
		if (isset($args['username'])) {
			unset($args['username']);
		}
		// don't update id
		if (isset($args['id'])) {
			unset($args['id']);
		}
		// don't update user id
		if (isset($args['user_id'])) {
			unset($args['user_id']);
		}
		//update email
		if(isset($args['email']) && strtolower($args['email']) != $user_meta['email'] && !$this->email_exists(strtolower($args['email']))) :
			array_push($to_update, 'email');
			$temp_email = $args['email'];
			unset($args['email']);
		endif;
		if(isset($args['password']) && $user_meta['password'] != $this->encrypt_password($args['password'])) :
			array_push($to_update, 'password');
			$temp_password = $args['password'];
			unset($args['password']);
		endif;
		if(isset($args['date_created']) && isValidTimeStamp($args['date_created'])) :
			array_push($to_update, 'date_created');
			$temp_date_created = $args['date_created'];
			unset($args['date_created']);
		endif;
		if (!empty($to_update)) :
			$set = array();
			foreach ($to_update as $key) {
				if($key == 'password') {
					$value = $this->encrypt_password($temp_password);	
				} elseif ($key == 'email') {
					$value = strtolower(sanitize($temp_email));	
				} elseif ($key == 'date_created') {
					$value = strtolower(sanitize($temp_date_created));	
				}
				$set[$key] = $value;
			}
			$result = $this->query->update( 'users', $set, array('id' => $user_id) );
			if($result) {
				$updated = 1;
			}
		endif;

		//update other meta data
		if (empty($args))
			return $updated;

		$args = sanitize($args);
		foreach ($args as $key => $value) {
			if(isset($user_meta[strtolower($key)])) {
				if ($user_meta[strtolower($key)] == $value) {
					continue;
				}
				$result = $this->query->update( 'user_meta', array('meta_value' => $value), array('user_id' => $user_id, 'meta_name' => strtolower($key)) );
				if($result) {
					$updated = 1;
				}	
			} else {
				$meta_name = strtolower($key);
				$result = $this->query->insert('user_meta', array('user_id', 'meta_name', 'meta_value'), array($user_id, $meta_name, $value));
				if($result) {
					$updated = 1;
				}
			}
		}
		if($updated) {
			$this->cache->__drop_cache($user_id, 'user_id');
			$this->cache->__drop_cache($user_meta['email'], 'email');
			$this->cache->__drop_cache($user_meta['username'], 'username');
		}
		return $updated;
	}

	public function drop_user($selector) {
		$user_meta = $this->get_user_meta($selector);
		if (!isset($user_meta['id'])) {
			return false;
		}
		$user_id = $user_meta['id'];
		$this->cache->__drop_cache($user_id, 'user');
		$this->query->delete( 'users', array('id' => $user_id));
		$this->query->delete( 'user_meta', array('user_id' => $user_id));
	}


	//
	public function user_login($selector, $password) {
		$session = Session::getInstance();
		$session->valid = false;
		$user_meta = $this->get_user_meta($selector);
		if($user_meta === false) {
			return 0;
		}
		if(strtolower($user_meta['status']) !== 'active') {
			return -1;
		}
		if(isset($user_meta['password']) && $user_meta['password'] === $this->encrypt_password($password)) {
			$loggon_key = SHA1(time(). SALT);
			$login_meta = (isset($user_meta['login_meta'])) ? unserialize($user_meta['login_meta']) : array();
			$login_meta = [
				'last_login' => time(),
				'loggon_key' => $loggon_key,
				'state' => 'online'
			];
			$login_meta = serialize($login_meta);
			$this->update_user_meta($user_meta['id'], array('loggon_key' => $loggon_key, 'login_meta' => $login_meta));
			unset($session->menu);
			$session->id = $user_meta['id'];
			$session->key = $loggon_key;
			$session->valid = true;
			return 1;
		}
		return 0;
	}


	//Validation functions
	public function email_exists($email) {
		$query = $this->query->select('users', array('email' => $email), 0, 'ASC', 0, 'LIKE');
		$result = $this->query->query_to_array($query);
		if(empty($result)) {
			return apply_filters("email_exists", false);
		} else return apply_filters("email_exists", true);
	}

	public function username_exists($username) {
		$query = $this->query->select('users', array('username' => $username), 0, 'ASC', 0, 'LIKE');
		$result = $this->query->query_to_array($query);
		if(empty($result)) {
			return apply_filters("username_exists", false);
		} else return apply_filters("username_exists", true);
	}

	public function generate_username($prefix = '', $suffix = '') {
		$username = $prefix.rand(10000000, 99999999).$suffix;
		do {
			$username = $prefix.rand(10000000, 99999999).$suffix;
		} while($this->username_exists($username));
		return $username;
	}

	//Other Functions
	private function encrypt_password($password) {
		return SHA1(md5($password.PASSWORD_SALT.$password));
	}

	public function get_all_users_meta() {
		$users = $this->query->query_to_array($this->query->select('users'));
		foreach ($users as $user) {
			$user_id = $user['id'];
			$user_meta = $this->get_user_meta($user_id);
			$result[$user_id] = $user_meta;
		}
		return $result;
	}

	public function get_user_count_by_meta_data($args, $compare = "LIKE", $id_as_key = false, $limit = 0, $offset = 0, $order_by = 'user_id', $order = 'ASC', $return_type = "user_meta") {
		$result = $this->get_user_by_meta_data($args, $compare, $id_as_key, $limit, $offset, $order_by, $order, $return_type);
		return $result ? _count($result) : 0;
	}

	public function get_user_by_meta_data($args, $compare = "LIKE", $id_as_key = false, $limit = 0, $offset = 0, $order_by = 'user_id', $order = 'ASC', $return_type = "user_meta") {
		// db section
		$add_db_section = add_db_section();
		if($add_db_section && $args['apply_db_session']) {
			$args['db_section_id'] = [$add_db_section, -1];
		}
		if(isset($args['apply_db_session'])) unset($args['apply_db_session']);
		// db section

		$compare = (!$compare) ? "LIKE" : $compare;

		$num_args = _count($args);
		$return_key = $id_as_key ? OBJECT_K : OBJECT;

		if(is_array($args) && $num_args) :
			$last_arg = end($args);
			$sql = "SELECT user_id
			FROM user_meta
			WHERE ";
			foreach ($args as $meta_name => $meta_value) {
				if($meta_value === "_all") {
					$num_args--;
					continue;
				}
				if(is_array($meta_value)) {
					if(in_array("_all", $meta_value)) {
						$num_args--;
						continue;
					}
				}
				if(is_array($compare)) {
					$compare = array_merge(['default' => 'LIKE'], $compare);

					if(isset($compare[$meta_name])) {
						
						if(is_array($meta_value) && _count($meta_value)) {
							$sql .= "( `meta_name` = '".$meta_name."' AND (";
							foreach ($meta_value as $mv) {
								$sql .= "`meta_value` ".$compare[$meta_name]." '".$mv."' OR ";
							}
							$sql = substr($sql, 0, -4);
							$sql .= ") )";
						} else {
							$sql .= "( `meta_name` = '".$meta_name."' AND (`meta_value` ".$compare[$meta_name]." '".$meta_value."') )";
						}

					} else {

						if(is_array($meta_value) && _count($meta_value)) {
							$sql .= "( `meta_name` = '".$meta_name."' AND (";
							foreach ($meta_value as $mv) {
								$sql .= "`meta_value` ".$compare['default']." '".$mv."' OR ";
							}
							$sql = substr($sql, 0, -4);
							$sql .= ") )";
						} else {
							$sql .= "( `meta_name` = '".$meta_name."' AND `meta_value` ".$compare['default']." '".$meta_value."' )";
						}

					} 
					

				} else {
					if(is_array($meta_value) && _count($meta_value)) {
						$sql .= "( `meta_name` = '".$meta_name."' AND (";
						foreach ($meta_value as $mv) {
							$sql .= "`meta_value` ".$compare." '".$mv."' OR ";
						}
						$sql = substr($sql, 0, -4);
						$sql .= ") )";
					} else {
						$sql .= "( `meta_name` = '".$meta_name."' AND `meta_value` ".$compare." '".$meta_value."' )";
					}
					
				}
				$sql .= " OR ";
			}
			$sql = substr($sql, 0, -4);
			$sql .= " GROUP BY user_id";
			$sql .= " HAVING COUNT(user_id) = ".$num_args;
			$sql .= ($order == "RAND" || $order == "RAND()") ? " ORDER BY RAND()" :" ORDER BY `".$order_by."` ".$order;
			$sql .= ($limit && $limit > 0) ? " LIMIT ".$limit : "";
			$sql .= ($offset && $offset > 0) ? " OFFSET ".$offset : "";
			
			$match = $this->query->get_results($sql, $return_key);
			$result = array_map(function($v) use ($return_type){
			    return ($return_type === "user_meta") ? $this->get_user_meta($v->user_id) : $v->user_id;
			}, $match);
		endif;
		return $result;
	}

	public function set_login_status($selector) {
		if(!is_array($selector)) {
			$user_meta = $this->get_user_meta($selector);
		} else $user_meta = $selector;
		$login_meta = (isset($user_meta['login_meta'])) ? unserialize($user_meta['login_meta']) : array();
		if($login_meta['state'] == 'offline') {
			$this->login_status = 'offline';
			$this->last_login = $login_meta['last_login'];
			$this->last_logout = $login_meta['last_logout'];
			$this->duration_online = $login_meta['last_logout'] - $login_meta['last_login'];
		} else if($login_meta['state'] == 'online') {
			$this->user_id = $user_meta['id'];
			$this->username = $user_meta['username'];
			$this->login_status = 'online';
			$this->last_login = $login_meta['last_login'];
			unset($this->last_logout);
			$this->duration_online = time() - $login_meta['last_login'];
		} else {
			unset($this->last_logout);
			unset($this->duration_online);
			unset($this->last_login);
			$this->login_status = 'offline';
		}
	}

	public function get_user_login_status($selector) {
		// returns user object
		$this->set_login_status($selector);
		return $this;
	}

	public function get_next_username($prefix) {
		$last_id = (int) $this->query->get_last_id_from_table("users", true);
		do {
			++$last_id;
			$username = $prefix.$last_id;
		} while ($this->username_exists($username));
		return $username;
	}
	
}