<?php

  /**
   * Framework level history renderer implementation
   *
   * @package angie.frameworks.history
   * @subpackage models
   */
  abstract class FwHistoryRenderer {
    
    /**
     * Object instance
     *
     * @var IHistory
     */
    protected $object;

    /**
     * Cached user id-name map
     *
     * @var array
     */
    static protected $cached_users = array();
    
    /**
     * Construct history renderer
     *
     * @param IHistory $object
     */
    function __construct(IHistory $object) {
      $this->object = $object;
    } // __construct
    
    /**
     * Prepare and render all logged modification
     *
     * @param IUser $user
     * @param Smarty $smarty
     * @return array
     */
    function render(IUser $user, &$smarty) {
      $result = array();
      
      $logs = ModificationLogs::findByParent($this->object);
      
      if(is_foreachable($logs)) {
        require_once ENVIRONMENT_FRAMEWORK_PATH . '/helpers/function.action_on_by.php';
        
        $logs = $logs->toArray();
        
        $rows = DB::execute('SELECT * FROM ' . TABLE_PREFIX . 'modification_log_values WHERE modification_id IN (?)', objects_array_extract($logs, 'getId'));
        if(is_foreachable($rows)) {
          $modifications = array();
          $cached_user_id_values = array();
          
          // Loop through rows and get modification => field => value map
          for($i = 0, $count = $rows->count(); $i < $count; $i++) {
            $field = $rows[$i]['field'];

            if (in_array($field, array('leader_id', 'user_id', 'assignee_id'))) {
              $cached_user_id_values[] = $rows[$i]['value'];
            } // if

            // Loop through older rows and get old value, if present
            $old_value = null;
            for($j = $i-1; $j >= 0; $j--) {
              if($rows[$j]['field'] == $field) {
                $old_value = $rows[$j]['value'];
                break;
              } // if
            } // for
            // And now map the data that we collected
            $modification_id = (integer) $rows[$i]['modification_id'];
            
            if(isset($modifications[$modification_id])) {
              $modifications[$modification_id][$field] = array($rows[$i]['value'], $old_value);
            } else {
              $modifications[$modification_id] = array(
                $field => array($rows[$i]['value'], $old_value)
              );
            } // if
          } // for
        } // if

        // Cache modification log creators and users in log values table
        $cached_user_ids = array();

        if (is_array($cached_user_id_values)) {
          $cached_user_ids = array_merge($cached_user_ids, $cached_user_id_values); // merge values from modification_log_values
        } // if

        $modification_author_ids = array_unique(objects_array_extract($logs, "getFieldValue", "created_by_id"));
        if (is_array($modification_author_ids)) {
          $cached_user_ids = array_merge($cached_user_ids, $modification_author_ids); // merge values from modification_logs
        } // if

        if (is_foreachable($cached_user_ids)) {
          self::$cached_users = Users::getIdNameMap($cached_user_ids, true);
        } // if

        foreach($logs as $log) {
          AngieApplication::useHelper('ago', GLOBALIZATION_FRAMEWORK, 'modifier');

          // Object is created
          if($log->getIsFirst()) {
            $result[] = array(
              'head' => lang("Created by <b>:created_by</b>", array("created_by" => self::getCachedUserDetails($log->getFieldValue('created_by_id'))))." ".smarty_modifier_ago($log->getCreatedOn(), null, true),
              'modifications' => lang(':type Created', array('type' => $this->object->getVerboseType())),
            );

          // Object is modified
          } else {
            $result[] = array(
              'head' => lang("Updated by <b>:updated_by</b>", array("updated_by" => self::getCachedUserDetails($log->getFieldValue('created_by_id'))))." ".smarty_modifier_ago($log->getCreatedOn(), null, true),
              'modifications' => isset($modifications[$log->getId()]) ? $this->renderModifications($user, $modifications[$log->getId()]) : null,
            );
          } // if

        } // foreach

      } // if
      
      return $result;
    } // render
    
    /**
     * Render all modifications and return them as array
     *
     * @param IUser $user
     * @param array $modifications
     * @return array
     */
    function renderModifications(IUser $user, $modifications) {
      $result = array();
      
      foreach($modifications as $field => $v) {
        list($value, $old_value) = $v;
        
        $rendered_field = $this->renderField($user, $field, $value, $old_value);
        if($rendered_field) {
          $result[] = $rendered_field;
        } // if
      } // if
      
      return $result;
    } // renderModifications
    
    /**
     * Render single field value
     *
     * @param IUser $user
     * @param string $field
     * @param mixed $value
     * @param mixed $old_value
     */
    protected function renderField(IUser $user, $field, $value, $old_value) {
      switch($field) {
        
        // Name
        case 'name':
          return lang('Name changed from <b>:old_value</b> to <b>:new_value</b>', array(
            'old_value' => $old_value, 
            'new_value' => $value, 
          ));
        
        // Render body change
        case 'body':
          if($value) {
            return $old_value ? lang('Description updated') : lang('Description added');
          } else {
            if($old_value) {
              return lang('Description removed');
            } // if
          } // if
          
          break;
          
        // State
        case 'state':
          if($value == STATE_TRASHED) {
            return lang('Moved to trash');
          } elseif($value == STATE_ARCHIVED) {
            if($old_value == STATE_VISIBLE) {
              return lang('Moved to archive');
            } elseif($old_value == STATE_TRASHED) {
              return lang('Restored from trash');
            } // if
          } elseif($value == STATE_VISIBLE) {
            if($old_value == STATE_ARCHIVED) {
              return lang('Restored from archive');
            } elseif($old_value == STATE_TRASHED) {
              return lang('Restored from trash');
            } // if
          } // if
          
          break;
          
        // Assignee ID
        case 'assignee_id':
          $new_assignee = $value ? self::getCachedUserDetails($value) : null;
          $old_assignee = $old_value ? self::getCachedUserDetails($old_value) : null;
          
          if($new_assignee && $old_assignee) {
            return lang('Reassigned from <b>:old_assignee</b> to <b>:new_assignee</b>', array(
              'old_assignee' => $old_assignee, 
              'new_assignee' => $new_assignee, 
            ));
          } elseif($new_assignee) {
            return lang('<b>:new_assignee</b> is responsible for this :type', array(
              'new_assignee' => $new_assignee, 
            	'type' => $this->object->getVerboseType(true),  
            ));
          } elseif($old_assignee) {
            return lang('<b>:old_assignee</b> is no longer responsible for this :type', array(
              'old_assignee' => $old_assignee, 
              'type' => $this->object->getVerboseType(true), 
            ));
          } // if
          
          break;
          
        // Render priority change
        case 'priority':
          $priorities = array(
            PRIORITY_LOWEST => lang('Lowest'), 
            PRIORITY_LOW => lang('Low'), 
            PRIORITY_NORMAL => lang('Normal'), 
            PRIORITY_HIGH => lang('High'), 
            PRIORITY_HIGHEST => lang('Highest'), 
          );
          
          return lang('Priority changed from <b>:old_value</b> to <b>:new_value</b>', array(
            'old_value' => isset($priorities[$old_value]) ? $priorities[$old_value] : lang('Normal'), 
            'new_value' => isset($priorities[$value]) ? $priorities[$value] : lang('Normal'), 
          ));
          
          break;
          
        // Completed on
        case 'completed_on':
          $new_completed_on = $value ? new DateTimeValue($value) : null;
          
          if($new_completed_on instanceof DateTimeValue && $new_completed_on->getTimestamp() > 0) {
            return lang('Marked as completed');
          } else {
            return lang('Marked as open');
          } // if
          
          break;
          
        // Set due date
        case 'due_on':
          $new_due_on = $value ? new DateValue($value) : null;
          $old_due_on = $old_value ? new DateValue($old_value) : null;
          
          if($new_due_on instanceof DateValue) {
            AngieApplication::useHelper('date', GLOBALIZATION_FRAMEWORK, 'modifier');
            
            if($old_due_on instanceof DateValue) {
              return lang('Due date changed from <b>:old_value</b> to <b>:new_value</b>', array(
                'old_value' => smarty_modifier_date($old_due_on, 0), 
                'new_value' => smarty_modifier_date($new_due_on, 0), 
              ));
            } else {
              return lang('Due date changed to <b>:new_value</b>', array(
                'new_value' => smarty_modifier_date($new_due_on, 0), 
              ));
            } // if
          } else {
            if($old_due_on instanceof DateValue || is_null($new_due_on)) {
              return lang('Due date set to empty value');
            } // if
          } // if
          
          break;
          
        // Label ID
        case 'label_id':
          if($value) {
            if($old_value) {
              return lang('Label changed from <b>:old_value</b> to <b>:new_value</b>', array('old_value' => Labels::getLabelName($old_value, lang('Unknown Label')), 'new_value' => Labels::getLabelName($value, lang('Unknown Label'))));
            } else {
              return lang('Label set to <b>:new_value</b>', array('new_value' => Labels::getLabelName($value, lang('Unknown Label'))));
            } // if
          } else {
            if($old_value) {
              return lang('Label <b>:old_value</b> removed', array('old_value' => Labels::getLabelName($old_value, lang('Unknown Label'))));
            } // if
          } // if

          break;

        // Visibility
        case 'visibility':
          $verbose_visibility = array(
            VISIBILITY_NORMAL => lang('Normal'),
            VISIBILITY_PRIVATE => lang('Private'),
            VISIBILITY_PUBLIC => lang('Public')
          );

          return lang('Visibility changed from <b>:old_value</b> to <b>:new_value</b>', array('old_value' => $verbose_visibility[$old_value], 'new_value' => $verbose_visibility[$value]));

        // Is locked
        case 'is_locked':
          if($value) {
            return lang('Comments locked');
          } else {
            return lang('Comments unlocked');
          } // if
        
        // Default behavior
        default:
          if($value) {
            if($old_value) {
              return lang(':field changed from :old_value to :new_value', array('field' => $field, 'old_value' => $old_value, 'new_value' => $value));
            } else {
              return lang(':field set to :new_value', array('field' => $field, 'new_value' => $value));
            } // if
          } else {
            if($old_value) {
              return lang(':field set to empty value', array('field' => $field));
            } // if
          } // if
      } // switch
      
      return "BUG: $field ($old_value -> $value)";
    } // renderField

    /**
     * Return cached user value for given user ID
     * 
     * @param integer $user_id
     * @return string
     */
    private static function getCachedUserDetails($user_id) {
      if (array_key_exists($user_id, self::$cached_users)) {
        return self::$cached_users[$user_id];
      } else {
        return lang('Unknown User');
      } // if
    } // getCachedUserDetails
    
  }