<?php

  /**
   * Tasks manager class
   *
   * @package activeCollab.modules.tasks
   * @subpackage models
   */
  class Tasks extends ProjectObjects {
    
    // Sharing context
    const SHARING_CONTEXT = 'request';

    // default orders
    const ORDER_ANY = "ISNULL(completed_on) DESC, position ASC, priority DESC, created_on";
    const ORDER_OPEN = "ISNULL(position) ASC, position, priority DESC, created_on";
    const ORDER_COMPLETED = 'ISNULL(position) ASC, position, priority DESC, created_on';
    
    // ---------------------------------------------------
    //  Permissions
    // ---------------------------------------------------
    
    /**
     * Returns true if $user can access tasks section of $project
     *
     * @param IUser $user
     * @param Project $project
     * @param boolean $check_tab
     * @return boolean
     */
    static function canAccess(IUser $user, Project $project, $check_tab = true) {
      return ProjectObjects::canAccess($user, $project, 'task', ($check_tab ? 'tasks' : null));
    } // canAccess
    
    /**
     * Returns true if $user can create a new task in $project
     *
     * @param IUser $user
     * @param Project $project
     * @param boolean $check_tab
     * @return boolean
     */
    static function canAdd(IUser $user, Project $project, $check_tab = true) {
      return ProjectObjects::canAdd($user, $project, 'task', ($check_tab ? 'tasks' : null));
    } // canAdd
    
    /**
     * Returns true if $user can manage tasks in $project
     *
     * @param IUser $user
     * @param Project $project
     * @param boolean $check_tab
     * @return boolean
     */
    static function canManage(IUser $user, Project $project, $check_tab = true) {
      return ProjectObjects::canManage($user, $project, 'task', ($check_tab ? 'tasks' : null));
    } // canManage
    
    // ---------------------------------------------------
    //  Finders
    // ---------------------------------------------------

    /**
     * Return task by task ID
     *
     * @param Project $project
     * @param integer $id
     * @return Task
     */
    static function findByTaskId(Project $project, $id) {
      return Tasks::find(array(
        'conditions' => array('project_id = ? AND integer_field_1 = ? AND type = ?', $project->getId(), $id, 'Task'),
        'one' => true,
      ));
    } // findByTaskId
    
    /**
     * Return tasks by task ids
     * 
     * @param Project $project
     * @param array $ids
     * @return array
     */
    static function findByTaskIds(Project $project, $ids) {
      return Tasks::find(array(
        'conditions' => array('project_id = ? AND integer_field_1 IN (?) AND type = ?', $project->getId(), $ids, 'Task')
      ));
    } // findByTaskIds
    
    /**
     * Return visible and archived tasks in current project that given $user can 
     * access
     * 
     * @param Project $project
     * @param User $user
     * @return Task[]
     */
    static function findByProject(Project $project, User $user) {
      return Tasks::find(array(
        "conditions" => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Task', STATE_ARCHIVED, $user->getMinVisibility()),
        "order" => "priority DESC"
      ));
    } // findByProject

    /**
     * Return visible and archived tasks in current project that given $user can
     * access
     *
     * @param Project $project
     * @param User $user
     * @return Task[]
     */
    static function findActiveByProject(Project $project, User $user) {
      return Tasks::find(array(
        "conditions" => array('project_id = ? AND type = ? AND state = ? AND visibility >= ?', $project->getId(), 'Task', STATE_VISIBLE, $user->getMinVisibility()),
        "order" => "priority DESC"
      ));
    } // findActiveByProject

    /**
     * Return visible and archived tasks in current project that given $user can
     * access
     *
     * @param Project $project
     * @param User $user
     * @return Task[]
     */
    static function findArchivedByProject(Project $project, User $user) {
      return Tasks::find(array(
        "conditions" => array('project_id = ? AND type = ? AND state = ? AND visibility >= ?', $project->getId(), 'Task', STATE_ARCHIVED, $user->getMinVisibility()),
        "order" => "priority DESC"
      ));
    } // findArchivedByProject
    
    /**
     * Count tasks by project
     * 
     * @param Project $project
     * @param Category $category
     * @param integer $min_state
     * @param integer $min_visibility
     * @return number
     */
    static function countByProject(Project $project, $category = null, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
    	if ($category instanceof TaskCategory) {
    		return Tasks::count(array('project_id = ? AND type = ? AND category_id = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Task', $category->getId(), $min_state, $min_visibility));
    	} else {
    		return Tasks::count(array('project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Task', $min_state, $min_visibility));
    	} // if
    } // countByProject
    
    /**
     * Return open tasks by project
     *
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findOpenByProject(Project $project, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return ProjectObjects::find(array(
        'conditions' => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NULL', $project->getId(), 'Task', $min_state, $min_visibility),
        'order' => 'ISNULL(position) ASC, position, priority DESC',
      ));
    } // findOpenByProject
    
    /**
     * Return completed tasks by project
     *
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findCompletedByProject(Project $project, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return Tasks::find(array(
        'conditions' => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NOT NULL', $project->getId(), 'Task', $min_state, $min_visibility),
        'order' => 'completed_on DESC'
      ));
    } // findCompletedByProject
    
    /**
     * Return tasks by a task category
     *
     * @param TaskCategory $category
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findByCategory(TaskCategory $category, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return ProjectObjects::find(array(
        'conditions' => array('category_id = ? AND type = ? AND state >= ? AND visibility >= ?', $category->getId(), 'Task', $min_state, $min_visibility),
        'order' => self::ORDER_ANY,
      ));
    } // findByCategory
    
    /**
     * Return number of tasks from a given category
     * 
     * @param TaskCategory $category
     * @param integer $min_state
     * @param integer $min_visibility
     * @return integer
     */
    static function countByCategory(TaskCategory $category, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return Tasks::count(array('category_id = ? AND type IN (?) AND state >= ? AND visibility >= ?', $category->getId(), 'Task', $min_state, $min_visibility));
    } // countByCategory
    
    /**
     * Return open tasks by category
     *
     * @param TaskCategory $category
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findOpenByCategory(TaskCategory $category, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return ProjectObjects::find(array(
        'conditions' => array('category_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NULL', $category->getId(), 'Task', $min_state, $min_visibility),
        'order' => self::ORDER_ANY,
      ));
    } // findOpenByCategory
    
    /**
     * Return all tasks by a given milestone
     *
     * @param Milestone $milestone
     * @param integer $min_state
     * @param integer $min_visibility
     * @param integer $limit
     * @param array $exclude
     * @param int $timestamp
     * @return array
     */
    static function findByMilestone(Milestone $milestone, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL, $limit = null, $exclude = null, $timestamp = null) {
      $conditions = array('milestone_id = ? AND project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $milestone->getId(), $milestone->getProjectId(), 'Task', $min_state, $min_visibility); // Milestone ID + Project ID (integrity issue from activeCollab 2)
      if ($exclude && $timestamp) {
      	$conditions[0] .= ' AND id NOT IN (?) AND created_on < ?';
      	$conditions[] = $exclude;
      	$conditions[] = date(DATETIME_MYSQL, $timestamp); 
      }
    	return Tasks::find(array(
        'conditions' => $conditions,
        'order' => self::ORDER_ANY,
        'limit' => $limit,
      ));
    } // findByMilestone
    
    /**
     * Return number of tasks by milestone
     *
     * @param Milestone $milestone
     * @param integer $min_state
     * @param integer $min_visibility
     * @return int
     */
    static function countByMilestone(Milestone $milestone, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return Tasks::count(array('milestone_id = ? AND project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $milestone->getId(), $milestone->getProjectId(), 'Task', $min_state, $min_visibility)); // Milestone ID + Project ID (integrity issue from activeCollab 2)
    } // countByMilestone
    
    /**
     * Find open tasks by milestone
     *
     * @param Milestone $milestone
     * @param User $user
     * @param integer $min_state
     * @return array
     */
    static function findOpenByMilestone(Milestone $milestone, User $user, $min_state = STATE_VISIBLE) {
      return ProjectObjects::find(array(
        'conditions' => array('milestone_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NULL', $milestone->getId(), 'Task', $min_state, $user->getMinVisibility()),
        'order' => self::ORDER_OPEN,
      ));
    } // findOpenByMilestone
    
    /**
     * Find milestone tasks
     * 
     * If $limit_result is defined, than top $limit_result tasks will be 
     * returned (great for keeping the list of completed items short)
     *
     * @param Milestone $milestone
     * @param User $user
     * @param integer $min_state
     * @param integer $limit_result
     * @return array
     */
    static function findCompletedByMilestone(Milestone $milestone, User $user, $min_state = STATE_VISIBLE, $limit_result = null) {
      if($limit_result) {
        $offset = 0;
        $limit = (integer) $limit_result;
      } else {
        $offset = null;
        $limit = null;
      } // if
      
      return Tasks::find(array(
        'conditions' => array('milestone_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NOT NULL', $milestone->getId(), 'Task', $min_state, $user->getMinVisibility()),
        'order' => self::ORDER_COMPLETED,
        'limit' => $limit,
        'offset' => $offset,
      ));
    } // findCompletedByMilestone
    
    /**
     * Return total number of completed tasks in a milestone
     *
     * @param Milestone $milestone
     * @param User $user
     * @param integer $min_state
     * @return integer
     */
    static function countCompletedByMilestone(Milestone $milestone, User $user, $min_state = STATE_VISIBLE) {
      return Tasks::count(array('milestone_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NOT NULL', $milestone->getId(), 'Task', $min_state, $user->getMinVisibility()));
    } // countCompletedByMilestone
    
    /**
     * Return ID for next task
     * 
     * $project can be an instance of Project class or project_id
     *
     * @param Project $project
     * @return integer
     */
    static function findNextTaskIdByProject($project) {
      $project_id = $project instanceof Project ? $project->getId() : (integer) $project;
      $project_objects_table = TABLE_PREFIX . 'project_objects';
      
      $row = DB::executeFirstRow("SELECT MAX(integer_field_1) AS 'max_id' FROM $project_objects_table WHERE project_id = ? AND type = ?", $project_id, 'Task');
      if(is_array($row)) {
        return $row['max_id'] + 1;
      } else {
        return 1;
      } // if
    } // findNextTaskIdByProject
    
    /**
     * Paginate complete tasks by project
     *
     * @param Project $project
     * @param integer $page
     * @param integer $per_page
     * @param integer $min_state
     * @param integer $min_visibility
     * @return null
     */
    static function paginateCompletedByProject(Project $project, $page = 1, $per_page = 10, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return Tasks::paginate(array(
        'conditions' => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NOT NULL', $project->getId(), 'Task', $min_state, $min_visibility),
        'order' => self::ORDER_COMPLETED
      ), $page, $per_page);
    } // paginateCompletedByProject
    
    /**
     * Paginate complete tasks by category
     *
     * @param TaskCategory $category
     * @param integer $page
     * @param integer $per_page
     * @param integer $min_state
     * @param integer $min_visibility
     * @return null
     */
    static function paginateCompletedByCategory(TaskCategory $category, $page = 1, $per_page = 10, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return Tasks::paginate(array(
        'conditions' => array('category_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NOT NULL', $category->getId(), 'Task', $min_state, $min_visibility),
        'order' => self::ORDER_COMPLETED
      ), $page, $per_page);
    } // paginateCompletedByCategory
    
    /**
     * Find all tasks in project, and prepare them for objects list
     * 
     * @param Project $project
     * @param User $user
     * @param int $state
     * @return array
     */
    static function findForObjectsList(Project $project, User $user, $state = STATE_VISIBLE) {
    	$result = array();

    	$tasks = DB::execute("SELECT id, name, category_id, milestone_id, completed_on, integer_field_1 as task_id, label_id, assignee_id, priority, delegated_by_id, state, visibility FROM " . TABLE_PREFIX . "project_objects WHERE type = 'Task' AND project_id = ? AND state = ? AND visibility >= ? ORDER BY " . self::ORDER_ANY, $project->getId(), $state, $user->getMinVisibility());
    	if (is_foreachable($tasks)) {
    	  $task_url = Router::assemble('project_task', array('project_slug' => $project->getSlug(), 'task_id' => '--TASKID--'));
    	  $project_id = $project->getId();

        $labels = Labels::getIdDetailsMap('AssignmentLabel');
    	  
    		foreach ($tasks as $task) {
    		  list($total_subtasks, $open_subtasks) = ProjectProgress::getObjectProgress(array(
    		    'project_id' => $project_id, 
    		    'object_type' => 'Task', 
    		    'object_id' => $task['id'], 
    		  ));

    		  $result[] = array(
    				'id'                => $task['id'],
    				'name'              => $task['name'],
    				'project_id'        => $project_id,
    				'category_id'       => $task['category_id'],
    				'milestone_id'      => $task['milestone_id'],
    				'task_id'           => $task['task_id'],
    				'is_completed'      => $task['completed_on'] ? 1 : 0,
    				'permalink'         => str_replace('--TASKID--', $task['task_id'], $task_url),
  			    'label_id'          => $task['label_id'],
            'label'             => $task['label_id'] ? $labels[$task['label_id']] : null,
  			    'assignee_id'       => $task['assignee_id'],
  			    'priority'          => $task['priority'],
  			    'delegated_by_id'   => $task['delegated_by_id'],
    			  'total_subtasks'    => $total_subtasks,
    			  'open_subtasks'     => $open_subtasks,
    			  'estimated_time'    => 0,
    			  'tracked_time'      => 0,
    			  'is_favorite'       => Favorites::isFavorite(array('Task', $task['id']), $user),
    				'is_archived'       => $task['state'] == STATE_ARCHIVED ? 1 : 0,
            'visibility'        => $task['visibility']
    			); 
    		} // foreach
    	} // if
    	
    	return $result;
    } // findForObjectsList
    
    /**
     * Find tasks for printing by grouping and filtering criteria
     * 
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @param string $group_by
     * @param array $filter_by
     * @return DBResult
     */
    static function findForPrint(Project $project, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL, $group_by = null, $filter_by = null) { 	
      // initial condition
      $conditions = array(
      	DB::prepare('(project_id = ? AND type = ? AND state >= ? AND visibility >= ?)', array($project->getId(), 'Task', $min_state, $min_visibility)),
      );
      
      if (!in_array($group_by, array('milestone_id', 'category_id', 'label_id', 'assignee_id','delegated_by_id','priority'))) {
      	$group_by = null;
      } // if
      
      if($group_by == 'priority') {
        $group_by .= ' DESC';
      }//if
                
      // filter by completion status
      $filter_is_completed = array_var($filter_by, 'is_completed', null);
      if ($filter_is_completed === '0') {
				$conditions[] = DB::prepare('(completed_on IS NULL)');        	
      } else if ($filter_is_completed === '1') {
      	$conditions[] = DB::prepare('(completed_on IS NOT NULL)');
      } // if
      
      // do find tasks
      $tasks = Tasks::find(array(
      	'conditions' => implode(' AND ', $conditions),
      	'order' => ($group_by ? $group_by . ', ' : '') . self::ORDER_ANY
      ));
    	
    	return $tasks;
    } // findForPrint
    
    /**
     * Get all items from result and describes array for paged list 
     * 
     * @param DBResult $result
     * @param Project $active_project
     * @param User $logged_user
     * @param int $items_limit
     * @return Array
     */
    
    static function getDescribedTaskArray(DBResult $result, Project $active_project, User $logged_user, $items_limit = null) {
    	$return_value = array();
    	if ($result instanceof DBResult) {
    	  $assignment_labels = Labels::getIdDetailsMap('AssignmentLabel');
    	  
    	  $user_ids = array();
    		foreach($result as $row) {
    			if ($row['created_by_id'] && !in_array($row['created_by_id'], $user_ids)) {
    				$user_ids[] = $row['created_by_id'];
    			} //if
    		} //if

        $users_array = count($user_ids) ? Users::findByIds($user_ids)->toArrayIndexedBy('getId') : array();

    		foreach($result as $row) {
    	    $task = array();
      		// Task Details
      		$task['id'] = $row['id'];
      		$task['name'] = clean($row['name']);
      		$task['is_favorite'] = Favorites::isFavorite(array('Task', $task['id']), $logged_user);
      		$task['is_completed'] = (datetimeval($row['completed_on']) instanceof DateTimeValue) ? 1 : 0;
      		$task['label'] = $assignment_labels[$row['label_id']];
      		
      		// Favorite
      		$favorite_params = $logged_user->getRoutingContextParams();
      		$favorite_params['object_type'] = $row['type'];
      		$favorite_params['object_id'] = $row['id'];
      		
      		// Urls
      		$task['urls']['remove_from_favorites'] = Router::assemble($logged_user->getRoutingContext() . '_remove_from_favorites', $favorite_params);
      		$task['urls']['add_to_favorites'] = Router::assemble($logged_user->getRoutingContext() . '_add_to_favorites', $favorite_params);
          $task['urls']['view'] = Router::assemble('project_task', array('project_slug' => $active_project->getSlug(), 'task_id' => $row['integer_field_1']));
          $task['urls']['edit'] = Router::assemble('project_task_edit', array('project_slug' => $active_project->getSlug(), 'task_id' => $row['integer_field_1']));
          $task['urls']['trash'] = Router::assemble('project_task_trash', array('project_slug' => $active_project->getSlug(), 'task_id' => $row['integer_field_1']));
      		
      		// CRUD

      		$task['permissions']['can_edit'] = Tasks::canManage($logged_user, $active_project);
          $task['permissions']['can_trash'] = Tasks::canManage($logged_user, $active_project);

      		// User & datetime details
      		$task['created_on'] = datetimeval($row['created_on']);
      		
      		if($row['created_by_id'] && isset($users_array[$row['created_by_id']])) {
            $task['created_by'] = $users_array[$row['created_by_id']];
          } elseif($row['created_by_email']) {
            $task['created_by'] = new AnonymousUser($row['created_by_name'], $row['created_by_email']);
          } else {
            $task['created_by'] = null;
          } // if
      		$return_value[] = $task;
      		
      		if (count($return_value) === $items_limit) {
      			break;
      		} //if
    	  } // foreach
    	} //if
    	
    	return $return_value;
    } // getDescribedTaskArray
    
  }