<?php

  /**
   * Milestones manager class
   *
   * @package activeCollab.modules.milestones
   * @subpackage models
   */
  class Milestones extends ProjectObjects {
  	
  	/**
  	 * Default ordering of milestones
  	 * 
  	 * @var string
  	 */
  	static private $order_milestones_by = 'NOT ISNULL(completed_on), ISNULL(date_field_1), date_field_1, position, created_on';
    
    /**
     * Returns true if $user can access milestones seciton 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, 'milestone', ($check_tab ? 'milestones' : null));
    } // canAccess
    
    /**
     * Returns true if $user can add milestones to $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, 'milestone', ($check_tab ? 'milestones' : null));
    } // canAdd
    
    /**
     * Returns true if $user can manage discussions 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, 'milestone', ($check_tab ? 'milestones' : null));
    } // canManage
    
    // ---------------------------------------------------
    //  Finders
    // ---------------------------------------------------
    
    /**
     * Return milestones by a given list of ID-s
     *
     * @param array $ids
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findByIds($ids, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      return ProjectObjects::find(array(
        'conditions' => array('id IN (?) AND type = ? AND state >= ? AND visibility >= ?', $ids, 'Milestone', $min_state, $min_visibility),
        'order' => self::$order_milestones_by,
      ));
    } // findByIds
    
    /**
     * Return all visible milestone by a project
     *
     * @param Project $project
     * @param integer $min_visibility
     * @return array
     */
    static function findAllByProject($project, $min_visibility = VISIBILITY_NORMAL) {
    	return ProjectObjects::find(array(
        'conditions' => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Milestone', STATE_VISIBLE, $min_visibility),
        'order' => self::$order_milestones_by,
      ));
    } // findAllByProject
  
    /**
     * Return all milestones for a given project
     *
     * @param Project $project
     * @param User $user
     * @return Milestone[]
     */
    static function findByProject(Project $project, User $user) {
      if(Milestones::canAccess($user, $project)) {
        return ProjectObjects::find(array(
          'conditions' => array('project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Milestone', STATE_VISIBLE, $user->getMinVisibility()),
          'order' => self::$order_milestones_by,
        ));
      } // if
      return null;
    } // findByProject

    /**
     * Find archived notebooks by project
     *
     * @param Project $project
     * @param integer $state
     * @param integer $min_visibility
     * @return array
     */
    static function findArchivedByProject(Project $project, $min_visibility = VISIBILITY_NORMAL) {
      return ProjectObjects::find(array(
        'conditions' => array('project_id = ? AND type = ? AND state = ? AND visibility >= ?', $project->getId(), 'Milestone', STATE_ARCHIVED, $min_visibility),
        'order' => self::$order_milestones_by
      ));
    } // findArchivedByProject
    
    /**
     * Count milestones by project
     * 
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @return number
     */
    static function countByProject(Project $project, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
   	  return Milestones::count(array('project_id = ? AND type = ? AND state >= ? AND visibility >= ?', $project->getId(), 'Milestone', $min_state, $min_visibility));
    } // countByProject
    
    /**
     * Return milestones for timeline
     * 
     * @param Project $project
     * @param User $user
     * @return DBResult
     */
    static function findForTimeline(Project $project, User $user, $state = STATE_VISIBLE) {
      if(Milestones::canAccess($user, $project)) {
        return ProjectObjects::find(array(
          'conditions' => array('project_id = ? AND type = ? AND state = ? AND visibility >= ?', $project->getId(), 'Milestone', $state, $user->getMinVisibility()),
          'order' => self::$order_milestones_by,
        ));
      } // if
      return null;    	
    } // findForTimeline

    /**
     * Find for outline
     *
     * @static
     * @param Project $project
     * @param User $user
     * @param int $state
     */
    static function findForOutline(Project $project, User $user, $state = STATE_VISIBLE) {
      $results = array();

      $milestone_ids = DB::executeFirstColumn('SELECT id FROM ' . TABLE_PREFIX . 'project_objects WHERE project_id = ? AND type = ? AND state >= ? AND visibility >= ? AND completed_on IS NULL', $project->getId(), 'Milestone', $state, $user->getMinVisibility());
      $milestones = DB::execute('SELECT id, name, body, priority, due_on, date_field_1 AS start_on, assignee_id, visibility, created_by_id FROM ' . TABLE_PREFIX . 'project_objects WHERE ID IN(?) ORDER BY ' . self::$order_milestones_by, $milestone_ids);

      if (is_foreachable($milestones)) {
        // casting
        $milestones->setCasting(array(
          'due_on'        => DBResult::CAST_DATE,
          'start_on'      => DBResult::CAST_DATE
        ));

        // urls
        $milestone_id_prefix_pattern = '--MILESTONE-ID--';
        $milestone_url_params = array('project_slug' => $project->getSlug(), 'milestone_id' => $milestone_id_prefix_pattern);
        $view_milestone_url_pattern = Router::assemble('project_milestone', $milestone_url_params);
        $edit_milestone_url_pattern = Router::assemble('project_milestone_edit', $milestone_url_params);
        $trash_milestone_url_pattern = Router::assemble('project_milestone_trash', $milestone_url_params);
        $subscribe_milestone_url_pattern = Router::assemble('project_milestone_subscribe', $milestone_url_params);
        $unsubscribe_milestone_url_pattern = Router::assemble('project_milestone_unsubscribe', $milestone_url_params);
        $reschedule_milestone_url_pattern = Router::assemble('project_milestone_reschedule', $milestone_url_params);
        $complete_milestone_url_pattern = Router::assemble('project_milestone_complete', $milestone_url_params);

        // can_manage_milestones
        $can_manage_milestones = ($user->projects()->getPermission('milestone', $project) >= ProjectRole::PERMISSION_MANAGE);

        // all assignees
        $user_assignments_on_milestones = DB::executeFirstColumn('SELECT parent_id FROM ' . TABLE_PREFIX . 'assignments WHERE parent_id IN (?) AND parent_type = ? AND user_id = ?', $milestone_ids, 'Milestone', $user->getId());

        // all subscriptions
        $user_subscriptions_on_milestones = DB::executeFirstColumn('SELECT parent_id FROM ' . TABLE_PREFIX . 'subscriptions WHERE parent_id IN (?) AND parent_type = ? AND user_id = ?', $milestone_ids, 'Milestone', $user->getId());

        $other_assignees = array();
        $raw_other_assignees = DB::execute('SELECT user_id, parent_id FROM ' . TABLE_PREFIX . 'assignments WHERE parent_type = ? AND parent_id IN (?)', 'Milestone', $milestone_ids);
        foreach ($raw_other_assignees as $raw_assignee) {
          if (!is_array($other_assignees[$raw_assignee['parent_id']])) {
            $other_assignees[$raw_assignee['parent_id']] = array();
          } // if
          $other_assignees[$raw_assignee['parent_id']][] = array('id' => $raw_assignee['user_id']);
        } // foreach

        foreach ($milestones as $subobject) {
          $milestone_id = array_var($subobject, 'id');

          $results[] = array(
            'id'                  => $milestone_id,
            'name'                => array_var($subobject, 'name'),
            'body'                => array_var($subobject, 'body'),
            'priority'            => array_var($subobject, 'priority'),
            'class'               => 'Milestone',
            'start_on'            => array_var($subobject, 'start_on'),
            'due_on'              => array_var($subobject, 'due_on'),
            'assignee_id'         => array_var($subobject, 'assignee_id'),
            'other_assignees'     => array_var($other_assignees, $milestone_id, null),
            'user_is_subscribed'  => in_array($milestone_id, $user_subscriptions_on_milestones),
            'event_names'         => array(
              'updated'             => 'milestone_updated'
            ),
            'urls'                => array(
              'view'                => str_replace('--MILESTONE-ID--', $milestone_id, $view_milestone_url_pattern),
              'edit'                => str_replace('--MILESTONE-ID--', $milestone_id, $edit_milestone_url_pattern),
              'trash'               => str_replace('--MILESTONE-ID--', $milestone_id, $trash_milestone_url_pattern),
              'subscribe'           => str_replace('--MILESTONE-ID--', $milestone_id, $subscribe_milestone_url_pattern),
              'unsubscribe'         => str_replace('--MILESTONE-ID--', $milestone_id, $unsubscribe_milestone_url_pattern),
              'reschedule'          => str_replace('--MILESTONE-ID--', $milestone_id, $reschedule_milestone_url_pattern),
              'complete'            => str_replace('--MILESTONE-ID--', $milestone_id, $complete_milestone_url_pattern),
            ),
            'permissions'         => array(
              'can_edit'            => can_edit_project_object($subobject, $user, $project, $can_manage_milestones, $user_assignments_on_milestones),
              'can_trash'           => can_trash_project_object($subobject, $user, $project, $can_manage_milestones, $user_assignments_on_milestones),
            )
          );
        } // foreach
      } // if

      return $results;
    } // findForOutline
    
    /**
     * Return all active milestones in a given project
     *
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findActiveByProject($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(), 'Milestone', $min_state, $min_visibility),
        'order' => self::$order_milestones_by
      ));
    } // findActiveByProject
    
    /**
     * Return completed milestones by project
     *
     * @param Project $project
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findCompletedByProject($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 NOT NULL', $project->getId(), 'Milestone', $min_state, $min_visibility),
        'order' => self::$order_milestones_by,
      ));
    } // findCompletedByProject
    
    /**
     * Find successive milestones by a given milestone
     *
     * @param Milestone $milestone
     * @param integer $min_state
     * @param integer $min_visibility
     * @return array
     */
    static function findSuccessiveByMilestone(Milestone $milestone, $min_state = STATE_VISIBLE, $min_visibility = VISIBILITY_NORMAL) {
      $start_on = $milestone->getStartOn();
      
      if($start_on instanceof DateValue) {
        return Milestones::find(array(
          'conditions' => array('project_id = ? AND type = ? AND date_field_1 > ? AND state >= ? AND visibility >= ? AND id != ?', $milestone->getProjectId(), 'Milestone', $start_on, $min_state, $min_visibility, $milestone->getId()),
          'order' => 'date_field_1',
        ));
      } else {
        return null;
      } // if
    } // findSuccessiveByMilestone

    // ---------------------------------------------------
    //  Utilities
    // ---------------------------------------------------
    
    /**
     * Returns ID name map
     * 
     * $filter can be:
     * 
     * - Project instance, only milestones from that project will be returned
     * - Array of milestone IDs
     * - NULL, in that case all milestones with given state will be returned
     *
     * @param mixed $filter
     * @param integer $min_state
     * @return array
     */
    static function getIdNameMap($filter = null, $min_state = STATE_VISIBLE) {
      if($filter instanceof Project) {
        $rows = DB::execute('SELECT id, name FROM ' . TABLE_PREFIX . 'project_objects WHERE project_id = ? AND type = ? AND state >= ? ORDER BY ' . self::$order_milestones_by, $filter->getId(), 'Milestone', $min_state);
      } elseif(is_array($filter)) {
        $rows = DB::execute('SELECT id, name FROM ' . TABLE_PREFIX . 'project_objects WHERE id IN (?) AND type = ? AND state >= ? ORDER BY ' . self::$order_milestones_by, $filter, 'Milestone', $min_state);
      } else {
        $rows = DB::execute('SELECT id, name FROM ' . TABLE_PREFIX . 'project_objects WHERE type = ? AND state >= ? ORDER BY ' . self::$order_milestones_by, 'Milestone', $min_state);
      } // if
      
      if(is_foreachable($rows)) {
        $result = array();
        
        foreach($rows as $row) {
          $result[(integer) $row['id']] = $row['name'];
        } // foreach
        
        return $result;
      } else {
        return null;
      } // if
    } // getIdNameMap
    
    /**
     * Return ID-s by list of milestone names
     * 
     * @param array $names
     * @param Project $project
     * @return array
     */
    static function getIdsByNames($names, $project = null) {
      if($names) {
        if($project instanceof Project) {
          $ids = DB::executeFirstColumn('SELECT id FROM ' . TABLE_PREFIX . 'project_objects WHERE project_id = ? AND name IN (?) AND type = ?', $project->getId(), $names, 'Milestone');
        } else {
          $ids = DB::executeFirstColumn('SELECT id FROM ' . TABLE_PREFIX . 'project_objects WHERE name IN (?) AND type = ?', $names, 'Milestone');
        } // if
        
        if($ids) {
          foreach($ids as $k => $v) {
            $ids[$k] = (integer) $v;
          } // foreach
        } // if
        
        return $ids;
      } else {
        return null;
      } // if
    } // getIdsByNames
    
    /**
     * Return date when first project milestone starts on
     * 
     * @param Project $project
     * @return DateValue
     */
    static function getFirstMilestoneStartsOn(Project $project) {
      $first_milestone_starts_on = DB::executeFirstCell('SELECT date_field_1 FROM ' . TABLE_PREFIX . 'project_objects WHERE project_id = ? AND type = ? AND state >= ?', $project->getId(), 'Milestone', STATE_VISIBLE);
      
      if($first_milestone_starts_on) {
        return DateValue::makeFromString($first_milestone_starts_on);
      } else {
        return DateValue::make($project->getCreatedOn()->getMonth(), $project->getCreatedOn()->getDay(), $project->getCreatedOn()->getYear());
      } // if
    } // getFirstMilestoneStartsOn

    /**
     * Fix milestone IDs for a given project
     *
     * @param Project $project
     */
    static function fixMilestoneIds(Project $project) {
      $project_objects_table = TABLE_PREFIX . 'project_objects';

      $milestone_ids = DB::executeFirstColumn("SELECT DISTINCT id FROM $project_objects_table WHERE type = 'Milestone' AND project_id = ? AND state >= ?", $project->getId(), STATE_TRASHED);

      // Reset all milestone ID-s if they are set, but don't belong to project's milestone
      if($milestone_ids) {
        DB::execute("UPDATE $project_objects_table SET milestone_id = NULL WHERE milestone_id IS NOT NULL AND milestone_id NOT IN (?) AND project_id = ?", $milestone_ids, $project->getId());

      // Reset all milestone ID-s if they are set, because this project does not have any visible milestones
      } else {
        DB::execute("UPDATE $project_objects_table SET milestone_id = NULL WHERE milestone_id IS NOT NULL AND project_id = ?", $project->getId());
      } // if
    } // fixMilestoneIds
  
  }