<?php

  /**
   * Static class used to calculate and cache data used to display project and 
   * milestone progress
   * 
   * @package activeCollab.modules.system
   * @subpackage model
   */
  final class ProjectProgress {
    
    // Cache ID-s
    const TOTAL_TASKS = 0;
    const OPEN_TASKS = 1;
    
    /**
     * Return progress of a given project
     * 
     * $project can be instance of Project class of ID of project that we want 
     * to get progress of
     * 
     * Result is array where first element is total number of tasks and secon 
     * element is number of open tasks
     * 
     * @param Project $project
     * @return array
     */
    static function getProjectProgress($project) {
      $project_id = $project instanceof Project ? $project->getId() : $project;
      
      $cached_value = cache_get(self::getCacheId($project_id));
      
      if(empty($cached_value)) {
        $cached_value = self::refreshProjectProgressCache($project_id);
      } // if
      
      return array($cached_value[self::TOTAL_TASKS], $cached_value[self::OPEN_TASKS]);
    } // getProjectProgress
    
    /**
     * Return progress of a given milestone
     * 
     * Result is array where first element is total number of tasks and secon 
     * element is number of open tasks
     * 
     * @param Milestone $milestone
     * @return array
     */
    static function getMilestoneProgress(Milestone $milestone) {
      $project = $milestone->getProject();
      
      if($project instanceof Project) {
        $cached_value = cache_get(self::getCacheId($project->getId()));
      
        if(empty($cached_value)) {
          $cached_value = self::refreshProjectProgressCache($project->getId());
        } // if
        
        if(isset($cached_value['milestone_data'][$milestone->getId()])) {
          return array($cached_value['milestone_data'][$milestone->getId()][self::TOTAL_TASKS], $cached_value['milestone_data'][$milestone->getId()][self::OPEN_TASKS]);
        } // if
      } // if
      
      return array(0, 0);
    } // getMilestoneProgress
    
    /**
     * Return progress of a given project object
     * 
     * @param ProjectObject $object
     * @return array
     */
    static function getObjectProgress($object) {
      if($object instanceof ProjectObject) {
        $project = $object->getProject();
        
        if($project instanceof Project) {
          $project_id = $project->getId();
          $object_id = $object->getId();
        } else {
          return array(0, 0);
        } // if
      } elseif(is_array($object)) {
        $project_id = isset($object['project_id']) ? (integer) $object['project_id'] : null;
        $object_id = isset($object['object_id']) ? (integer) $object['object_id'] : null;
      } else {
        throw new InvalidParamError('object', $object, '$object should be an instance of ProjectObject class or an array that describes it');
      } // if
      
      if($project_id && $object_id) {
        $cached_value = cache_get(self::getCacheId($project_id));
      
        if(empty($cached_value)) {
          $cached_value = self::refreshProjectProgressCache($project_id);
        } // if
        
        if(isset($cached_value['object_data'][$object_id])) {
          return array(
            $cached_value['object_data'][$object_id][self::TOTAL_TASKS], 
            $cached_value['object_data'][$object_id][self::OPEN_TASKS]
          );
        } // if
      } // if
      
      return array(0, 0);
    } // getObjectProjectss
    
    /**
     * Drop project progress data cache for given project
     * 
     * If $project is not a valid Project class instance, cache for all projects 
     * will be dropped
     * 
     * @param Project $project
     */
    static function dropProjectProgressCache($project = null) {
      if($project instanceof Project) {
        cache_remove(self::getCacheId($project->getId()));
      } else if (is_int($project)) {
        cache_remove(self::getCacheId($project));
      } else {
        cache_remove_by_pattern('project_progress_*');
      } // if
    } // dropProjectProgressCache
    
    /**
     * Refresh project progress data cache
     * 
     * $project can be Project instance or project ID
     * 
     * @param mixed $project
     * @return array
     */
    static private function refreshProjectProgressCache($project_id) {
      $project_objects_table = TABLE_PREFIX . 'project_objects';
      $subtasks_table = TABLE_PREFIX . 'subtasks';
      
      $milestone_ids = DB::executeFirstColumn("SELECT id FROM $project_objects_table WHERE project_id = ? AND type = ? AND state >= ?", $project_id, 'Milestone', STATE_ARCHIVED);
      
      $result = array(
        self::TOTAL_TASKS => 0, 
        self::OPEN_TASKS => 0, 
        'milestone_data' => array(), 
        'object_data' => array(), 
      );
      
      if($milestone_ids) {
        foreach($milestone_ids as $milestone_id) {
          $result['milestone_data'][$milestone_id] = array(
            self::TOTAL_TASKS => 0, 
            self::OPEN_TASKS => 0, 
          );
        } // foerach
      } // if
      
      $rows = DB::execute("SELECT id, UPPER(type) AS 'type', completed_on, milestone_id FROM $project_objects_table WHERE project_id = ? AND type IN (?) AND state >= ?", $project_id, array('Task', 'TodoList'), STATE_ARCHIVED);
      if($rows) {
        $project_object_milestones_map = array();
        $subtask_parent_ids = array();
        
        // Lets go through project objects
        foreach($rows as $row) {
          
          // Count project objects
          $result[self::TOTAL_TASKS] += 1;
          if($row['completed_on'] === null) {
            $result[self::OPEN_TASKS] += 1;
          } // if
          
          $result['object_data'] = array(
            self::TOTAL_TASKS => 0, 
            self::OPEN_TASKS => 0, 
          );
          
          // Make type map, so we can use it to query subtasks
          if(!isset($subtask_parent_ids[$row['type']])) {
            $subtask_parent_ids[$row['type']] = array();
          } // if
          
          $subtask_parent_ids[$row['type']][] = (integer) $row['id'];
          
          // Make sure that we remember object's milestone
          $milestone_id = (integer) $row['milestone_id'];
          
          if($milestone_id) {
            $project_object_milestones_map[(integer) $row['id']] = $milestone_id;
            
            // Refresh milestone counts
            if(isset($result['milestone_data'][$milestone_id])) {
              $result['milestone_data'][$milestone_id][self::TOTAL_TASKS] += 1;
            } else {
              $result['milestone_data'][$milestone_id] = array(
                self::TOTAL_TASKS => 1, 
                self::OPEN_TASKS => 0, 
              );
            } // if
            
            if($row['completed_on'] === null) {
              $result['milestone_data'][$milestone_id][self::OPEN_TASKS] += 1;
            } // if
          } // if
        } // foreach
        
        $subtask_parent_conditions = array();
        foreach($subtask_parent_ids as $type => $ids) {
          $subtask_parent_conditions[] = DB::prepare("(parent_type = ? AND parent_id IN (?))", array($type, $ids));
        } // foreach
        $subtask_parent_conditions = '(' . implode(' OR ', $subtask_parent_conditions) . ')';
        
        $subtask_rows = DB::execute("SELECT id, parent_id, completed_on FROM $subtasks_table WHERE $subtask_parent_conditions AND state >= ?", STATE_ARCHIVED);
        if($subtask_rows) {
          foreach($subtask_rows as $subtask_row) {
            $parent_id = (integer) $subtask_row['parent_id'];
            
            if(!isset($result['object_data'][$parent_id])) {
              $result['object_data'][$parent_id] = array(
                self::TOTAL_TASKS => 0, 
                self::OPEN_TASKS => 0, 
              );
            } // if
            
            // Total
            $result[self::TOTAL_TASKS] += 1;
            $result['object_data'][$parent_id][self::TOTAL_TASKS] += 1;
            
            // Open
            if($subtask_row['completed_on'] === null) {
              $result[self::OPEN_TASKS] += 1;
              $result['object_data'][$parent_id][self::OPEN_TASKS] += 1;
            } // if
            
            if(isset($project_object_milestones_map[$parent_id]) && $project_object_milestones_map[$parent_id]) {
              $milestone_id = $project_object_milestones_map[$parent_id];
              
              $result['milestone_data'][$milestone_id][self::TOTAL_TASKS] += 1;
              
              if($subtask_row['completed_on'] === null) {
                $result['milestone_data'][$milestone_id][self::OPEN_TASKS] += 1;
              } // if
            } // if
          } // foreach
        } // if
      } // if
      
      cache_set(self::getCacheId($project_id), $result);
      
      return $result;
    } // refreshProjectProgressCache
    
    /**
     * Return cache ID for a given project
     * 
     * @param integer $project_id
     * @return string
     */
    static private function getCacheId($project_id) {
      return "project_progress_{$project_id}";
    } // getCacheId
    
  }