<?php 

namespace app\models;

use \MongoDate; 
use \lithium\util\String;
use \lithium\util\Validator;
use \App\Libraries\openID\LightOpenID;
use \lithium\security\Password;

class User extends \lithium\data\Model {

	public $hasMany = array('Post');

	public static function __init()
	{                        
		//Initialize the parent if you want the database and everything setup (which of course we do)
		parent::__init();
		                                                    	
		//Confirms that the username isn't already in use.                           
		Validator::add('isUniqUser', function($username) {
			//If we can't find a user with the same user name then the name is unique. 
			return User::count(array('conditions' => compact('username'))) == 0;
		});

		//Checks if the username contains profanity
		Validator::add('isClean', function($username) {
			//Needs to do a dictonary lookup, but too lazy to implement right now.
			return true;	
		});

		Validator::add('isValidGender', function($gender) {
			//If the geneder is male or female return true.
			return ($gender == 'Male' || $gender == 'Female');
		});
		
		Validator::add('isUniqueEmail', function($email) {
			//Find all the email address that match the one inputted,
			//If there is none found (count == 0) then return true (Email address is unique)
			return User::count(array('conditions' => compact('email'))) == 0;
		});	

		Validator::add('validBirthday', function($birthday) {
			// :TODO: 
			//*birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older); 
			return true;	
		});
	}


	/* Validation code */
	/*
		Things that need to be validated
		*The username cannot be taken
		*the username cannot cotain special chars or spaces
		*there must be an email address
			*The email address needs to be a valid email
			*it cannot be in use already
		*the password needs to be atleast 6 characters 
		*the username cannot contain profanity
		*birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older); 
		*gender must be Male or Female
	*/
	public $validates = array(
		'username' => array(array('isUniqUser',   'message' => "Username is already taken."),
							array('notEmpty',     'message' => 'Please enter a Username.'),
							array('isClean',      'message' => 'Profanity is not allowed in Usernames'),
							array('alphaNumeric',  'message' => "Usernames cant contain special characters")
							),

		'email' => array(array('email', 		  'message' => 'The email address is not valid.'),
						 array('notEmpty', 		  'message' => 'An email address must be entered.'),
						 array('isUniqueEmail',	  'message' => 'That email address is already in use. Did you forget your password?')
						) /*,

		
		'newpass' => array(array('lengthBetween' => array('min' => '6', 'max' =>'20'),
												  'message' => 'Your password must be between 6 and 20 characters')
						   )*/ /*,
						   
		//It's always possible for people to submit invalid data using cURL or something.
		'gender'  => array('isValidGender',     'message' => 'Please check for dangly bits or lack thereof.')
		*/
	);
	

	/* Defaults */
	/*
		joindate = today,
		accesslevel = "user"
	*/
	public function updateuser($entity, $data)
	{
		$conditions = array('_id' => $entity->_id, 'state' => 'default');
		$options = array('multiple' => false, 'safe' => true, 'upsert'=>true);
		return static::update($data, $conditions, $options);
	}

	/** 
	 * Creates a post and stores it into the appropriate user(s) array(s)
	 * @param User $entity the instance of the user posting the message
	 * @param Array $data The data from the request (usually submiited from the form)
	 * @param Array $options Currently not implemented 
	 * @return null Nothing for now, though should return true or false in the future :TODO:
	 */ 
	public function post($entity, $data, array $options = array())
	{
		//TODO, fix all methods so that they don't take $data directly
		//TODO add validators to these methods/models
		
		//Create the post
		$post = Post::create(array('datetime' => new MongoDate(),
								   'username' => $entity->username,
								   'user_id' => $entity->_id,
								   'level' => null));
		//1. Parse 
		//Break the string into an array of words
		$search = explode(" ", $data['body']);

		//if the first word is DM
		if ($search[0] == "DM")
		{
			//Remove the '@' symbol before we search for that username
			$to = substr($search[1], 1);

			$post->type = "DM";
			//:TODO: Catch the return incase it's false.
			return $post->directMessage($to);
		}

		//If the post beings with a mention (it's a reply / note)
		if ($search[0] == "@")
		{
			//Set the post level to hidden
			$post->level = "hidden";
		}


		//Check if there are any mentions or topics
		$post->body = $post->parse($search);


		//Because there is a chance that parse will set post level to something
		//We pass the current value to level since it will just
		//return the same it not set.
		//Yes, we could use an if ! null but whatever. 
		$post->level = $this->postLevel($entity, $post, $post->level);
		
		//Save the post to the posts database. 
		$post->save();
		
		//If the user has friends 
		if (count($entity->friends) > 0)
		{
			//Save this posts to the feed of all the Users Friends 
			$post->storeAll($entity->friends);
		}
		//Add this post to the user's feed array
		$post->store($entity);




		//$save = $entity->save(null, array('validate' => false));
		/* In the future, there should be a way to make this method quicker and non-blocking*/
		//For each friend, post it in their feed (friends is an array of usernames as strings)
		/*if (!empty($entity->friends))
		{
			foreach ($entity->friends as $friend)
			{
				//Grab the friend from the database
				$curFriend = User::find('first', array('conditions' => array('username' => $friend)));
				
				//Add the post to their feed.
				//Array unshift puts the new post at the first element of the feed array.
				$curFriend->feed = array_unshift($post->_id, $curFriend->feed); 
				
				//Save the friend to the database, 
				$curFriend->save(array('validates' => false));

				//Eventually, we can call a node.js endpoint here 
				//To tell all subscribers that there is a new a post
				//Something like "curl POST $post->_id nodeserver://endpoint/myFeed"
			}
		} */
	}

	/**
	 * Store's the post to the specified user's feed 
	 * @param User $user The user to add the post to
	 * @param Post $post The post too add to the feed 
	 * @return boolean True if the operation sucsceeded, false otherwise
	 */
	private function storePost($user, $post)
	{
		$updateData = array('$push' => array('feed' =>  $post['_id']->__toString()));
		$conditions =  array('_id' => $user['_id']);
		$result = User::update($updateData, $conditions, array('atomic' => false));

		return $result; 
	}


	/**
	 * Returns the appropriate post level for the post 
	 * @see app\models\Post
	 * @param User $user The user instance of the user that the post will be posted to
	 * @param Post $post The Post to determine the level for
	 * @param string $level The level (if you want to override the output)
	 * @return string $level if one is passed in, otherwise hidden if the post begins with a mention or private if the user has his posts protected
	 */
	public static function postLevel($user, $post, $level = null)
	{
		//if for some crazy reason you need to set the post to a specific type using this
		// method then if $level is not null, return $level
		if ($level != null)
		{
			return $level;
		}
		//If the post is directed at user (begins with @username)
			//This is done in parse right now
			//return "hidden"
		
		//If the user has their post set to private
		if (isset($user->settings['private']));
		{
			//return private
			return "private";
		}
		
		//If none of the above apply
		return "public";
	}

	//When we switch to a graph database, there is a bidirection vertex function
	//So we can cut this search down to one query, and one line of code :P
	/**
	 * Check wether user1 and user2 are friends
	 *
	 * @param string $user1 The username of the first user
	 * @param string $user2 The username of the second user
	 * @return boolean True if the users are friends, false otherwise 
	 */
	public static function areFriends($user1, $user2)
	{
		//Get the first user from the database, 
		$usr1 = User::find('first', array('conditions' => array('username' => $user1)));

		//If user 2 is in user1's friends,
		if (in_array($user2, $usr1->friends))
		{
			$usr2 = User::find('first', array('conditions' => array('username' => $user2)));
			//And user1 is in user2s friends
			if (in_array($user1, $usr2->friends))
			{
				return true;
			}
		}
		//otherwise
		return false;
	}

	/**
	* GetPosts gets posts from the user (entity) based on some params, and returns them
	* @param User $entity, the calling object
	* @param Array $options, the options that will be used, you can see the defaults below 
	* @return array(), with all the posts found or false, if somehow that didn't happen  
	*/
	public function getPosts($entity, array $options = array())
	{
		$posts; 
		$defaults = array(
				'limit' => '20',
				'qualifier' => 'all',
				'level' => 'public',
			);

		//If the user passed in options 
		if (!empty($options))
		{
			//merge the options with the defaults (upsert them) 
			array_merge($defaults, $options);
		}	

		//var_dump($entity);
		//exit();
		//If the user has more posts than the limit, return the limit, if the have less return 
		$count = (count($entity->feed) >= $defaults['limit']) ? $defaults['limit'] : count($entity->feed); 
		if ($count == 0)
		{
			return false;
		}
		//else
		var_dump(Post::find('all', array('conditions' => array('$in' => $entity->feed))));
		exit();
		for ($i = 0; $i < $count; $i++)
		{
			$posts[] = Post::find('all', array('conditions' => array('$in' => $entity->feed)));
			$posts[] = Post::find($qaulifier, array('conditions' => array('_id' => $entity->feed[$i], 'level' => $defaults[level])));
		}
		return $posts;
	}

    /**
	 * Increments the amount profile views for the user.
	 * This is the command we are going to give to Mongo, which breaks down to db.users.update({_id : $id}, {$inc: {profileViews : 1}} )
	 * Which says, find the user by their mongo ID, then increment their profile views by one.
	 * @param User $entity The instance of user to increment
	 * @param string $type Not implemented but in the future will allow you to increment anime manga and kdrama list views :TODO:
	 * @return null 
	*/
	public function incrementViews($entity, $type = null)
	{
		if ($type = null)
		{
			$views = 'profileViews';
		}
		$updateData = array('$inc' => array('profileViews' => 1));
		$conditions =  array('_id' => $entity['_id']);
		$result = User::update($updateData, $conditions, array('atomic' => false));
		return $result;
	}


	//Overrides save so we can do some stuff before the data is commited to the database. 
	public function save($entity, $data = null, array $options = array()) 
	{
		//If the new password field is empty, or this is a new user 
		if(!empty($entity->newpass) || !$entity->exists())
		{
			//Generate Salt for the user.
			$salt = Password::salt('bf', 6);

			//Hash their password.
			$data['password'] = Password::hash($entity->newpass, $salt);
			$data['pepper'] = $salt;
			unset($entity->newpass);
		}
		//If the entity doesn't exist or if the password password has been modified
		return parent::save($entity, $data, $options);
	}
}

?>	