<?php

  /**
   * Framework level comments manager implementation
   *
   * @package angie.frameworks.comments
   * @subpackage models
   */
  abstract class FwComments extends BaseComments {
    
    /**
     * Return comments by ID-s
     * 
     * @param array $ids
     * @return DBResult
     */
    static function findByIds($ids) {
      return self::find(array(
        'conditions' => array('id IN (?)', $ids),
      ));
    } // findByIds
    
    /**
     * Return object comments
     *
     * @param IComments $object
     * @return array
     */
    static function findByObject(IComments $object) {
      return self::find(array(
        'conditions' => array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), ($object instanceof IState ? $object->getState() : STATE_VISIBLE)),
        'order' => 'created_on DESC',
      ));
    } // findByObject
    
    /**
     * Return public comments by object
     * 
     * @param IComments $object
     */
    static function findPublicByObject(IComments $object) {
      return self::find(array(
        'conditions' => array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), STATE_VISIBLE),
        'order' => 'created_on DESC',
      ));
    } // findPublicByObject
    
    /**
     * Return $num latest comments for $object
     *
     * @param IComments $object
     * @param IUser $user
     * @param integer $num
     * @return DBResult
     */
    static function findLatestByObject(IComments $object, IUser $user, $num = 10) {
      if($object instanceof IState) {
        $conditions = array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), $object->getState());
      } else {
        $conditions = array("parent_type = ? AND parent_id = ?", get_class($object), $object->getId());
      } // if
      
      return self::find(array(
        'conditions' => $conditions, 
        'order' => 'created_on DESC', 
        'offset' => 0, 
        'limit' => $num, 
      ));
    } // findLatestByObject
    
    /**
     * Load more comments by object
     *
     * @param IComments $object
     * @param IUser $user
     * @param integer $exclude
     * @param integer $num
     * @return DBResult
     */
    static function loadMoreByObject(IComments $object, IUser $user, DateTimeValue $load_older_than, $exclude) {
      if($object instanceof IState) {
        $conditions = array("parent_type = ? AND parent_id = ? AND created_on < ? AND id NOT IN (?) AND state >= ?", get_class($object), $object->getId(), $load_older_than, $exclude, $object->getState());
      } else {
        $conditions = array("parent_type = ? AND parent_id = ? AND created_on < ? AND id NOT IN (?)", get_class($object), $object->getId(), $load_older_than, $exclude);
      } // if
      
      return self::find(array(
        'conditions' => $conditions,
        'order' => 'created_on DESC',
      ));
    } // loadMoreByObject
    
    /**
     * Return last comment for a given object
     *
     * @param IComments $object
     * @param integer $min_state
     * @return Comment
     */
    static function findLastByObject(IComments $object) {
      return Comments::find(array(
        'conditions' => array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), ($object instanceof IState ? $object->getState() : STATE_VISIBLE)), 
        'order' => 'created_on DESC', 
        'one' => true, 
      ));
    } // findLastByObject
    
    /**
     * Return number of comments that match given criteria
     *
     * @param IComments $object
     * @param boolean $use_cache
     * @return integer
     */
    static function countByParent(IComments $parent, $use_cache = true) {
      $parent_type = get_class($parent);
      $parent_id = $parent->getId();
      
      if($use_cache) {
        $cached_value = cache_get('comments_count');
        if(!is_array($cached_value)) {
          $cached_value = Comments::refreshCountCache();
        } // if
        
        return isset($cached_value[$parent_type]) && isset($cached_value[$parent_type][$parent_id]) ? $cached_value[$parent_type][$parent_id] : 0;
      } // if
      
      return self::count(array("parent_type = ? AND parent_id = ? AND state >= ?", $parent_type, $parent_id, ($parent instanceof IState ? $parent->getState() : STATE_VISIBLE)));
    } // countByParent
    
    /**
     * Return number of public comments
     *
     * @param IComments $object
     * @return integer
     */
    static function countPublicByObject(IComments $object) {
      return self::count(array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), STATE_VISIBLE));
    } // countPublicByObject
    
    /**
     * Return number of new comments since user's visit
     * 
     * @param IComments $object
     * @param User $user $user
     * @param DateTimeValue $visit
     */
    static function countByObjectSinceVisit(IComments $object, User $user, DateTimeValue $visit) {
      return self::count(array("parent_type = ? AND parent_id = ? AND created_by_id != ? AND created_on >= ? AND state >= ?", get_class($object), $object->getId(), $user->getId(), $visit, ($object instanceof IState ? $object->getState() : STATE_VISIBLE)));
    } // countByObjectSinceVisit
    
    /**
     * Paginate comments by object
     *
     * @param IComments $object
     * @param integer $page
     * @param integer $per_page
     * @return array
     */
    static function paginateByObject(IComments $object, $page = 1, $per_page = 30) {
      return self::paginate(array(
        'conditions' => array("parent_type = ? AND parent_id = ? AND state >= ?", get_class($object), $object->getId(), ($object instanceof IState ? $object->getState() : STATE_VISIBLE)),
        'order' => 'created_on DESC',
      ), $page, $per_page);
    } // paginateByObject
    
    /**
     * Return people who commented on $object that $user can see
     *
     * @param IComments $object
     * @param IUser $user
     * @return DBResult
     */
    static function findCommenters(IComments $object, IUser $user) {
  	  $visible_user_ids = Users::findVisibleUserIds($user);
  	  
    	if($visible_user_ids) {
    	  $users_table = TABLE_PREFIX . 'users';
    	  $comments_table = TABLE_PREFIX . 'comments';
    	  
    	  return Users::findBySQL("SELECT DISTINCT $users_table.* FROM $users_table, $comments_table WHERE $comments_table.parent_type = ? AND $comments_table.parent_id = ? AND $users_table.id = $comments_table.created_by_id AND $comments_table.state >= ? AND $users_table.id IN (?) ORDER BY CONCAT($users_table.first_name, $users_table.last_name, $users_table.email)", array(get_class($object), $object->getId(), STATE_VISIBLE, $visible_user_ids));
    	} else {
    		return null;
    	} // if
    } // findCommenters
    
    // ---------------------------------------------------
    //  State
    // ---------------------------------------------------
    
    /**
     * Archive comments attached to a given parent object
     *
     * @param IComments $parent
     */
    static function archiveByParent(IComments $parent) {
      if($parent instanceof IState) {
        $rows = DB::execute('SELECT id, type FROM ' . TABLE_PREFIX . 'comments WHERE parent_type = ? AND parent_id = ?', get_class($parent), $parent->getId());
        if(is_foreachable($rows)) {
          $comment_ids = array();
          $attachment_parents = array();
          
          foreach($rows as $row) {
            $comment_ids[] = (integer) $row['id'];
            $attachment_parents[] = DB::prepare('(parent_type = ? AND parent_id = ?)', array($row['type'], $row['id']));
          } // foreach
          
          $parent->state()->archiveSubitems(TABLE_PREFIX . 'comments', array('id IN (?)', $comment_ids));
          $parent->state()->archiveSubitems(TABLE_PREFIX . 'attachments', implode(' AND ', $attachment_parents));
        } // if
      } else {
        throw new InvalidInstanceError('parent', $parent, 'IState');
      } // if
    } // archiveByParent
    
    /**
     * Unarchive comments attached to a given parent object
     *
     * @param IComments $parent
     */
    static function unarchiveByParent(IComments $parent) {
      if($parent instanceof IState) {
        $rows = DB::execute('SELECT id, type FROM ' . TABLE_PREFIX . 'comments WHERE parent_type = ? AND parent_id = ?', get_class($parent), $parent->getId());
        if(is_foreachable($rows)) {
          $comment_ids = array();
          $attachment_parents = array();
          
          foreach($rows as $row) {
            $comment_ids[] = (integer) $row['id'];
            $attachment_parents[] = DB::prepare('(parent_type = ? AND parent_id = ?)', array($row['type'], $row['id']));
          } // foreach
          
          $parent->state()->unarchiveSubitems(TABLE_PREFIX . 'comments', array('id IN (?)', $comment_ids));
          $parent->state()->unarchiveSubitems(TABLE_PREFIX . 'attachments', implode(' AND ', $attachment_parents));
        } // if
      } else {
        throw new InvalidInstanceError('parent', $parent, 'IState');
      } // if
    } // unarchiveByParent
    
    /**
     * Trash comments attached to a given parent object
     *
     * @param IComments $parent
     */
    static function trashByParent(IComments $parent) {
      if($parent instanceof IState) {
        $rows = DB::execute('SELECT id, type FROM ' . TABLE_PREFIX . 'comments WHERE parent_type = ? AND parent_id = ?', get_class($parent), $parent->getId());
        if(is_foreachable($rows)) {
          $comment_ids = array();
          $attachment_parents = array();
          
          foreach($rows as $row) {
            $comment_ids[] = (integer) $row['id'];
            $attachment_parents[] = DB::prepare('(parent_type = ? AND parent_id = ?)', array($row['type'], $row['id']));
          } // foreach
          
          $parent->state()->trashSubitems(TABLE_PREFIX . 'comments', array('id IN (?)', $comment_ids));
          $parent->state()->trashSubitems(TABLE_PREFIX . 'attachments', implode(' AND ', $attachment_parents));
        } // if
      } else {
        throw new InvalidInstanceError('parent', $parent, 'IState');
      } // if
    } // trashByParent
    
    /**
     * Restore from trash comments attached to a given parent object
     *
     * @param IComments $parent
     */
    static function untrashByParent(IComments $parent) {
      if($parent instanceof IState) {
        $rows = DB::execute('SELECT id, type FROM ' . TABLE_PREFIX . 'comments WHERE parent_type = ? AND parent_id = ?', get_class($parent), $parent->getId());
        if(is_foreachable($rows)) {
          $comment_ids = array();
          $attachment_parents = array();
          
          foreach($rows as $row) {
            $comment_ids[] = (integer) $row['id'];
            $attachment_parents[] = DB::prepare('(parent_type = ? AND parent_id = ?)', array($row['type'], $row['id']));
          } // foreach
          
          $parent->state()->untrashSubitems(TABLE_PREFIX . 'comments', array('id IN (?)', $comment_ids));
          $parent->state()->untrashSubitems(TABLE_PREFIX . 'attachments', implode(' AND ', $attachment_parents));
        } // if
      } else {
        throw new InvalidInstanceError('parent', $parent, 'IState');
      } // if
    } // untrashByParent
    
    /**
     * Trash comments attached to a given parent object
     *
     * @param IComments $parent
     * @param boolean $soft
     */
    static function deleteByParent(IComments $parent, $soft = true) {
      $comments_table = TABLE_PREFIX . 'comments';
      
      $rows = DB::execute("SELECT id, type FROM $comments_table WHERE parent_type = ? AND parent_id = ?", get_class($parent), $parent->getId());
      
      if($rows) {
        $attachments_table = TABLE_PREFIX . 'attachments';
        
        $comment_ids = array();
        $attachment_parents = array();
        
        foreach($rows as $row) {
          $id = (integer) $row['id'];
          $type = $row['type'];
          
          $comment_ids[] = $id;
          
          if(isset($attachment_parents[$type])) {
            $attachment_parents[$type][] = $id;
          } else {
            $attachment_parents[$type] = array($id);
          } // if
        } // foreach
        
        try {
          DB::beginWork('Droping comments @ ' . __CLASS__);
          
          $attachment_parent_conditions = array();
          foreach($attachment_parents as $type => $ids) {
            $attachment_parent_conditions[] = DB::prepare('(parent_type = ? AND parent_id IN (?))', array($type, $ids));
          } // if
          $attachment_parent_conditions = implode(' AND ', $attachment_parent_conditions);
          
          if($soft && $parent instanceof IState) {
            $parent->state()->deleteSubitems($comments_table, array('id IN (?)', $comment_ids));
            
            $parent->state()->deleteSubitems($attachments_table, $attachment_parent_conditions);
          } else {
            $locations = DB::executeFirstColumn("SELECT location FROM $attachments_table WHERE $attachment_parent_conditions");
            DB::execute("DELETE FROM $attachments_table WHERE $attachment_parent_conditions");
            
            if($locations) {
              foreach($locations as $location) {
                if($location) {
                  @unlink(UPLOAD_PATH . '/' . $location);
                } // if
              } // foreach
            } // if
            
            if($parent instanceof IActivityLogs) {
              $activity_logs_table = TABLE_PREFIX . 'activity_logs';
              
              $activity_log_ids = array();
              
              $rows = DB::execute("SELECT id, raw_additional_properties FROM $activity_logs_table WHERE parent_type = ? AND parent_id = ?", get_class($parent), $parent->getId());
              if($rows) {
                foreach($rows as $row) {
                  $attributes = isset($row['raw_additional_properties']) && $row['raw_additional_properties'] ? unserialize($row['raw_additional_properties']) : null;
                  
                  if($attributes && isset($attributes['comment_id']) && in_array($attributes['comment_id'], $comment_ids)) {
                    $activity_log_ids[] = (integer) $row['id'];
                  } // if
                } // if
              } // if
              
              if($activity_log_ids) {
                DB::execute("DELETE FROM $activity_logs_table WHERE id IN (?)", $activity_log_ids);
              } // if
            } // if
          } // if
          
          DB::commit('Comments dropped @ ' . __CLASS__);
        } catch(Exception $e) {
          DB::rollback('Failed to drop comments @ ' . __CLASS__);
          throw $e;
        } // try
      } // if
    } // deleteByParent
    
    /**
     * Delete entries by parents
     * 
     * $parents is an array where key is parent type and value is array of 
     * object ID-s of that particular parent
     * 
     * @param array $parents
     */
    static function deleteByParents($parents) {
      $comments_table = TABLE_PREFIX . 'comments';
      
      try {
        DB::beginWork('Removing comments by parent type and parent IDs @ ' . __CLASS__);
        
        if(is_foreachable($parents)) {
          foreach($parents as $parent_type => $parent_ids) {
            $rows = DB::execute("SELECT id, type FROM $comments_table WHERE parent_type = ? AND parent_id IN (?)", $parent_type, $parent_ids);
            
            if($rows) {
              $comments = array();
              
              foreach($rows as $row) {
                if(array_key_exists($row['type'], $comments)) {
                  $comments[$row['type']][] = (integer) $row['id'];
                } else {
                  $comments[$row['type']] = array((integer) $row['id']);
                } // if
              } // foreach
              
              DB::execute("DELETE FROM $comments_table WHERE parent_type = ? AND parent_id IN (?)", $parent_type, $parent_ids);
              
              ActivityLogs::deleteByParents($comments);
              Attachments::deleteByParents($comments);
              ModificationLogs::deleteByParents($comments);
            } // if
          } // foreach
        } // if
        
        DB::commit('Comments removed by parent type and parent IDs @ ' . __CLASS__);
      } catch(Exception $e) {
        DB::rollback('Failed to remove comments by parent type and parent IDs @ ' . __CLASS__);
        throw $e;
      } // try
    } // deleteByParents
    
    /**
     * Remove comments by parent types
     * 
     * @param array $types
     */
    static function deleteByParentTypes($types) {
      $comments_table = TABLE_PREFIX . 'comments';
      
      $rows = DB::executeFirstColumn("SELECT id, type FROM $comments_table WHERE parent_type IN (?)", $types);
      
      if($rows) {
        $parents = array();
        
        foreach($rows as $row) {
          if(array_key_exists($row['type'], $parents)) {
            $parents[$row['type']][] = (integer) $row['id'];
          } else {
            $parents[$row['type']] = array((integer) $row['id']);
          } // if
        } // foreach
        
        try {
          DB::beginWork('Cleaning up comment data @ ' . __CLASS__);
          
          DB::execute("DELETE FROM $comments_table WHERE parent_type IN (?)", $types);
          
          ActivityLogs::deleteByParents($parents);
          Attachments::deleteByParents($parents);
          ModificationLogs::deleteByParents($parents);
          
          DB::commit('Comment data cleaned up @ ' . __CLASS__);
        } catch(Exception $e) {
          DB::rollback('Failed to clean up comment data @ ' . __CLASS__);
          throw $e;
        } // try
      } // if
    } // deleteByParentTypes
    
    /**
     * Find comments for widget
     * 
     * 	- Return array MUST resemble fwComments->describe() output
     * 
     * @param IComments $parent
     * @param IUser $user
     * @return array
     */
    static function findForWidget(IComments $parent, IUser $user, $offset = 0, $limit = 10) {
    	$comment_fields = array('id', 'type' ,'body', 'created_on', 'created_by_id', 'created_by_name', 'created_by_email', 'state', 'type'); // fields we need from comments
      $min_state = $parent instanceof IState ? $parent->getState() : STATE_VISIBLE;

      $comments = DB::execute("SELECT " . implode(', ', $comment_fields) . " FROM " . TABLE_PREFIX . "comments WHERE parent_type = ? AND parent_id = ? AND state >= ? ORDER BY created_on DESC LIMIT $offset, $limit", get_class($parent), $parent->getId(), $min_state);
      
    	if (!is_foreachable($comments)) {
    		return null;
    	} // if
    	
    	$comments->setCasting(array(
    		'created_on'	=> DBResult::CAST_DATETIME,
    	));
    	      
     	// extract comment and user ids
      $comment_ids = array();
      $user_ids = array();
      $comment_type = null;
      foreach ($comments as $comment) {
      	$comment_ids[] = $comment['id'];
      	if ($comment['created_by_id'] && !in_array($comment['created_by_id'], $user_ids)) {
      		$user_ids[] = $comment['created_by_id'];
      	} // if
      	
      	if ($comment_type === null) {
      		$comment_type = $comment['type'];
      	} // if
      } // foreach
      
    	// find users
    	if (is_foreachable($user_ids)) {
    		$users = Users::getIdDetailsMap($user_ids, array('first_name', 'last_name', 'email', 'company_id'));    		
    	} // if
    	
    	// find attachments
    	$attachments = DB::execute("SELECT id, name, parent_id, location FROM " . TABLE_PREFIX . "attachments WHERE parent_type = ? AND parent_id IN (?) AND state >= ? ORDER BY created_on", $comment_type, $comment_ids, ($parent instanceof IState ? $parent->getState() : STATE_VISIBLE));
    	$attachments_by_parent = array();
    	if (is_foreachable($attachments)) {
    		foreach ($attachments as $attachment) {
          if(isset($attachments_by_parent[$attachment['parent_id']])) {
            if (!is_array($attachments_by_parent[$attachment['parent_id']])) {
              $attachments_by_parent[$attachment['parent_id']] = array();
            }	// if
          } else {
            $attachments_by_parent[$attachment['parent_id']] = array();
          } // if
    			
    			$attachments_by_parent[$attachment['parent_id']][] = $attachment;
    		} // foreach
    	} // if
    	
    	
    	// url bases
    	$user_url_base = Router::assemble('people_company_user', array('company_id' => '--COMPANY--ID--', 'user_id' => '--USER--ID--'));
    	$avatar_url_base = ROOT_URL . '/avatars/--USER--ID--.40x40.png';
    	$avatar_path_base = ENVIRONMENT_PATH . '/' . PUBLIC_FOLDER_NAME . '/avatars/--USER--ID--.40x40.png';
    	$default_avatar_url = str_replace('--USER--ID--', 'default', $avatar_url_base);
    	$routing_context = $parent->getRoutingContext() . '_comment';
    	$routing_context_params = array_merge((array) $parent->getRoutingContextParams(), array('comment_id' => '--COMMENT--ID--'));
    	$edit_comment_url_base = Router::assemble($routing_context . '_edit', $routing_context_params);
    	$delete_comment_url_base = Router::assemble($routing_context . '_trash', $routing_context_params);
    	$attachments_download_url_base = Router::assemble($routing_context . '_attachment_download', array_merge($routing_context_params, array('attachment_id' => '--ATTACHMENT--ID--')));
      $attachments_preview_url_base = Router::assemble($routing_context . '_attachment_preview', array_merge($routing_context_params, array('attachment_id' => '--ATTACHMENT--ID--')));
    	
    	// images
    	$edit_image_url = AngieApplication::getImageUrl('icons/12x12/edit.png', ENVIRONMENT_FRAMEWORK);
    	$trash_image_url = AngieApplication::getImageUrl('/icons/12x12/delete.png', ENVIRONMENT_FRAMEWORK);
    	
    	$result = array();
    	foreach ($comments as $comment) {
				$new_comment = array(
					'id' => $comment['id'],
					'class' => $comment['type'],
					'body_formatted' => HTML::toRichText($comment['body']),
					'created_on' => $comment['created_on'],
					'attachments' => null,
					'options' =>  null,
					'event_names' => array(
						'updated' => 'comment_updated'
					)				
				);
				
				$user_id = $comment['created_by_id'];
				$creator = array_var($users, $user_id);
				if ($creator) {
					$permalink = str_replace('--COMPANY--ID--', $creator['company_id'], str_replace('--USER--ID--', $user_id, $user_url_base));
					$new_comment['created_by'] = array(
						'id' => $user_id,
						'short_display_name' => Users::getUserDisplayName($creator, true),
						'email' => $comment['created_by_email'],
						'urls' => array(
								'view' => $permalink,
						),
						'avatar' => array(
							'large' => is_file(str_replace('--USER--ID--', $user_id, $avatar_path_base)) ? str_replace('--USER--ID--', $user_id, $avatar_url_base) : $default_avatar_url
						),
						'permalink' => $permalink
					);
				} else {
					$permalink = 'mailto:' . $comment['created_by_email'];
					$new_comment['created_by'] = array(
						'id' => null,
						'short_display_name' => $comment['created_by_name'] ? $comment['created_by_name'] : $comment['created_by_email'],
						'email' => $comment['created_by_email'],
						'urls' => array(
								'view' => $permalink,
						),
						'avatar' => array(
							'large' => $default_avatar_url,
						),
						'permalink' => $permalink
					);
				} // if

				$new_comment['options'] = new NamedList();
					      
				// creator can edit
	      $creator_can_edit = ($user_id == $user->getId()) && (($comment['created_on']->getTimestamp() + 1800) > DateTimeValue::now()->getTimestamp());
	      
	      // can edit
	      $can_edit = ($comment['state'] > STATE_TRASHED) && ($user->isAdministrator() || $creator_can_edit);
	      
	      if ($parent instanceof ProjectObject) {
	      	$can_edit = $can_edit || $user->isProjectManager();
	      } // if
	      
				if ($can_edit) {
	        $new_comment['options']->add('edit', array(
	        	'text'	=> lang('Edit'),
          	'url'		=> str_replace('--COMMENT--ID--', $comment['id'], $edit_comment_url_base),
	        	'icon'	=> $edit_image_url,  
	        ));
	        
	        $new_comment['options']->add('trash', array(
	        	'text'	=> lang('Trash'),
          	'url'		=> str_replace('--COMMENT--ID--', $comment['id'], $delete_comment_url_base),
	        	'icon'	=> $trash_image_url,  
	        ));
				} // if
				
				$comment_attachments = array_var($attachments_by_parent, $comment['id']);
								
				if (is_foreachable($comment_attachments)) {
					$new_comment['attachments'] = array();
					foreach ($comment_attachments as $comment_attachment) {

            $described_attachment = array(
              'id' => $comment_attachment['id'],
              'name' => $comment_attachment['name'],
              'urls' => array(
                'view' => str_replace('--COMMENT--ID--', $comment['id'], str_replace('--ATTACHMENT--ID--', $comment_attachment['id'], $attachments_download_url_base))
              ),
              'preview' => array(
                'icons' => array(
                  'large' => get_file_icon_url($comment_attachment['name'], '48x48')
                )
              ),
            );

            $preview_type = Attachments::getPreviewType(array($comment_attachment['name'], UPLOAD_PATH . '/' . $comment_attachment['location']));
            if ($preview_type) {
              $described_attachment['urls']['preview'] = str_replace('--COMMENT--ID--', $comment['id'], str_replace('--ATTACHMENT--ID--', $comment_attachment['id'], $attachments_preview_url_base));
            } // if

						$new_comment['attachments'][] = $described_attachment;
					} // foreach
				} // if
								
				$result[] = $new_comment;
    	} // foreach
    	
    	EventsManager::trigger('on_comments_for_widget_options', array(&$parent, &$user, &$result, &$comment_ids, array(
    		'routing_context'					=> $routing_context,
    		'routing_context_params'	=> $routing_context_params,
    	)));
    	
    	return $result;
    } // findForWidget
    
    /**
     * Name of the cache variable where we'll cache counts
     *
     * @var string
     */
    const COUNT_CACHE_INDEX = 'comments_count';
    
    /**
     * Refresh comments count cache
     * 
     * @param mixed $context
     * @return array
     */
    static function refreshCountCache($context = null) {
      if($context instanceof IComments) {
        $cached_value = cache_get(Comments::COUNT_CACHE_INDEX);
        
        if(is_array($cached_value)) {
          $parent_type = get_class($context);
          $parent_id = $context->getId();
          
          if(!isset($cached_value[$parent_type])) {
            $cached_value[$parent_type] = array();
          } // if
          
          $cached_value[$parent_type][$parent_id] = (integer) DB::executeFirstCell('SELECT COUNT(id) FROM ' . TABLE_PREFIX . 'comments WHERE parent_type = ? AND parent_id = ? AND state >= ? GROUP BY parent_type, parent_id', $parent_type, $parent_id, STATE_ARCHIVED);
          
          cache_set(Comments::COUNT_CACHE_INDEX, $cached_value);
        } // if
        
        return $cached_value;
        
      // Rebuild entire counts cache
      } elseif($context === null) {
        $cached_value = array();
          
        $rows = DB::execute('SELECT parent_type, parent_id, COUNT(id) AS comments_count FROM ' . TABLE_PREFIX . 'comments WHERE state >= ? GROUP BY parent_type, parent_id', STATE_ARCHIVED);
        if($rows) {
          foreach($rows as $row) {
            if(!isset($cached_value[$row['parent_type']])) {
              $cached_value[$row['parent_type']] = array();
            } // if
            
            $cached_value[$row['parent_type']][(integer) $row['parent_id']] = (integer) $row['comments_count'];
          } // foreach
        } // if
        
        cache_set('comments_count', $cached_value);
        
        return $cached_value;
      } else {
        throw new InvalidInstanceError('context', $context, 'IComments', '$context should be NULL or valid IComments instance');
      } // if
    } // refreshCountCache
    
    /**
     * Get trashed map
     * 
     * @param User $user
     * @return array
     */
    static function getTrashedMap($user) {
    	$trashed_comments = DB::execute('SELECT id, type FROM ' . TABLE_PREFIX . 'comments WHERE state = ?', STATE_TRASHED);
    	    	
    	if (!is_foreachable($trashed_comments)) {
    		return null;
    	} // if
    	
    	$result = array();
    	
    	foreach ($trashed_comments as $trashed_comment) {
    		$type = strtolower($trashed_comment['type']);
    		
    		if (!isset($result[$type])) {
    			$result[$type] = array();
    		} // if 
    		
    		$result[$type][] = $trashed_comment['id'];
    	} // foreach
    	
    	return $result;
    } // getTrashedMap
    
    /**
     * Find trashed comments
     * 
     * @param User $user
     * @param array $map
     * @return array
     */
    static function findTrashed(User $user, &$map) {
    	$query = Trash::getParentQuery($map);    	
    	if ($query) {
    		$trashed_comments = DB::execute('SELECT id, body, type, parent_id, parent_type FROM ' . TABLE_PREFIX . 'comments WHERE state = ? AND ' . $query . ' ORDER BY updated_on DESC, created_on DESC', STATE_TRASHED);
    	} else {
    		$trashed_comments = DB::execute('SELECT id, body, type, parent_id, parent_type FROM ' . TABLE_PREFIX . 'comments WHERE state = ? ORDER BY updated_on DESC, created_on DESC', STATE_TRASHED);
    	} // if
    	
    	if (!is_foreachable($trashed_comments)) {
    		return null;
    	} // if
    	
    	$items = array();
    	foreach ($trashed_comments as $comment) {
    		$items[] = array(
    			'id'						=> $comment['id'],
    			'name'					=> HTML::toPlainText($comment['body']),
    			'type'					=> $comment['type'],
    		);
    	} // foreach
    	
    	return $items;
    } // findTrashed
    
    /**
     * Delete trashed comments
     * 
     * @param User $user
     * @return boolean
     */
    static function deleteTrashed(User $user) {
    	$comments = Comments::find(array(
    		'conditions' => array('state = ?', STATE_TRASHED)
    	));
    	
    	if (is_foreachable($comments)) {
    		foreach ($comments as $comment) {
    			$comment->state()->delete();
    		} // foreach
    	} // if
    	
    	return true;
    } // deleteTrashed
    
  }