<?php

/**
 * The Behavior for AuditTrail
 * @package application.modules.AuditTrail.Behaviors
 *
 */
class LoggableBehavior extends CActiveRecordBehavior {

    private $_oldattributes = array();
    public $allowed = array();
    public $ignored = array();
    public $ignored_class = array();
    public $dateFormat = 'Y-m-d H:i:s';
    public $userAttribute = null;
    public $storeTimestamp = false;
    public $skipNulls = true;

    public function afterSave($event) {
	$allowedFields = $this->allowed;
	$ignoredFields = $this->ignored;
	$ignoredClasses = $this->ignored_class;

	$newattributes = $this->getOwner()->getAttributes();
	$oldattributes = $this->getOldAttributes();

	// Lets check if the whole class should be ignored
	if (sizeof($ignoredClasses) > 0) {
	    if (array_search(get_class($this->getOwner()), $ignoredClasses) !== false)
		return;
	}

	// Lets unset fields which are not allowed
	if (sizeof($allowedFields) > 0) {
	    foreach ($newattributes as $f => $v) {
		if (array_search($f, $allowedFields) === false)
		    unset($newattributes[$f]);
	    }

	    foreach ($oldattributes as $f => $v) {
		if (array_search($f, $allowedFields) === false)
		    unset($oldattributes[$f]);
	    }
	}

	// Lets unset fields which are ignored
	if (sizeof($ignoredFields) > 0) {
	    foreach ($newattributes as $f => $v) {
		if (array_search($f, $ignoredFields) !== false)
		    unset($newattributes[$f]);
	    }

	    foreach ($oldattributes as $f => $v) {
		if (array_search($f, $ignoredFields) !== false)
		    unset($oldattributes[$f]);
	    }
	}

	// If no difference then WHY?
	// There is some kind of problem here that means "0" and 1 do not diff for array_diff so beware: stackoverflow.com/questions/12004231/php-array-diff-weirdness :S
	if (count(array_diff_assoc($newattributes, $oldattributes)) <= 0) {
	    return;
	}



	//        // If this is a new record lets add a CREATE notification
	//        if ($this->getOwner()->getIsNewRecord())
	//            $this->leaveTrail('CREATE');
	// Now lets actually write the attributes
	$this->auditAttributes($newattributes, $oldattributes);

	// Reset old attributes to handle the case with the same model instance updated multiple times
	$this->setOldAttributes($this->getOwner()->getAttributes());

	return parent::afterSave($event);
    }

    /**
     * Prepare attribute for audit
     * @param string[] $newattributes the new values of the attributes of the model
     * @param string $oldattributes the old values of the attributes of the model
     */
    public function auditAttributes($newattributes, $oldattributes = array()) {
	$old_value_string = "";
	$new_value_string = "";
	$field_string = "";

	$labels = $this->getOwner()->attributeLabels();
	foreach ($newattributes as $name => $value) {
	    $old = isset($oldattributes[$name]) ? $oldattributes[$name] : '';
	    $model = get_class($this->getOwner());
	    // If we are skipping nulls then lets see if both sides are null
	    if ($this->skipNulls && empty($old) && empty($value)) {
		continue;
	    }
	    if (($old == null && $value == '') || ($value == null && $old == ''))
		continue;

	    if (LoggableBehavior::isDate($name)) {
		if ($old == '0000-00-00' && $value == '')
		    continue;
		$old = ($old != '0000-00-00') ? date('Y-m-d', strtotime($old)) : '';
		$value = ($value != '0000-00-00') ? date('Y-m-d', strtotime($value)) : '';
		if ($old == '0000-00-00')
		    $old = '';
		if ($value == '0000-00-00')
		    $value = '';
	    }
	    if ($name == "under_processing" && $value == '0' && $old == '') {
		continue;
	    }

	    if ($name == "under_processing") {
		$value = LoggableBehavior::getText($value);
		$old = LoggableBehavior::getText($old);
	    }

	    // If they are not the same lets write an audit log
	    if ($value != $old) {

		//---------------------------------------------------------
		//to save names instead of ids
		//password mustn't appeared
		if ($name != "password") {
		    if (LoggableBehavior::isId($name)) {
			$result = $this->getIdValue($name, $value, $old);
			$value = $result['new'];
			$old = $result['old'];
		    }
//		    //empty values in Change case
		    if ($old == "" || $old == null)
			$old = "N/A";
		    if ($value == "" || $value == null)
			$value = "N/A";
		    if ($model == "Setting") {
			$old_value_string .= $old . ', ';
			$new_value_string .= $value . ', ';
			$field_string .= LoggableBehavior::realName($oldattributes['name'], $labels) . ', ';
		    } else {
			$old_value_string .= $old . ', ';
			$new_value_string .= $value . ', ';
			$field_string .= LoggableBehavior::realName($name, $labels) . ', ';
		    }
		} else if ($model == "User") {
		    $field_string .= LoggableBehavior::realName($name, $labels) . ', ';
		}
		//---------------------------------------------------------
		// $this->leaveTrail($this->getOwner()->getIsNewRecord() ? 'SET' : 'CHANGE', $name, $value, $old);
	    }
	}


	$action = 'Update';
	//create case
	if ($this->getOwner()->getIsNewRecord()) {
	    $old_value_string = "";
	}

	if ($model == 'TidStatus' && $this->getOwner()->getIsNewRecord()) {
	    $status = $this->getOwner();
	    if ($status->tid->tidStatuses != null && count($status->tid->tidStatuses) > 0) {
		$action = 'Renew TID';
		$last_status = $status->tid->getBeforeLastStatus();
		if ($last_status != null)
		    $old_value_string = $last_status->tiType->name . ', ' . $last_status->ti_issue_date . ', ' .
			    (($last_status->ti_expiry_date != '0000-00-00') ? $last_status->ti_expiry_date . ', ' : '') .
			    $last_status->contract->reference . ', ' . $last_status->cca->full_name . ', ' .
			    (($last_status->comments != '') ? $last_status->comments . ', ' : '') . $last_status->tid->tid_no . ', ' . $last_status->ti_status;
	    }
	}

	if ($model == 'AssetStatus' && $this->getOwner()->getIsNewRecord()) {
	    $status = $this->getOwner();
	    if ($status->asset->assetStatuses != null && count($status->asset->assetStatuses) > 0) {
		$action = 'Renew Asset';
		$last_status = $status->asset->getBeforeLastStatus();
		if ($last_status != null)
		    $old_value_string = $last_status->asset->asset_number . ', ' .
			    ($last_status->vehicle_book_expiry_date == null || $last_status->vehicle_book_expiry_date == '' ? $last_status->radioactive_license_expiry_date : $last_status->vehicle_book_expiry_date) . ', ' .
			    ($last_status->vehicle_book_status == null || $last_status->vehicle_book_status == '' ? $last_status->radioactive_license_status : $last_status->vehicle_book_status);
	    }
	}


	if ($field_string == '' && $new_value_string == '' && $old_value_string == '' && $action == 'Update')
	    return;

	$this->leaveTrail($this->getOwner()->getIsNewRecord() && $action != 'Renew TID' && $action != 'Renew Asset' ? 'Create' : $action, trim($field_string, ", "), trim($new_value_string, ", "), trim($old_value_string, ", "));
    }

//---------------------------------------------------------

    /**
     * a functoin that checks if some field is an id i.e. it's a refrence to another table
     *
     * @param string $name field name
     *
     * @return boolean true or false
     */
    public static function isId($name) {
	if (strpos($name, '_id') !== false) {
	    return true;
	}
	return false;
    }

    /**
     * a functoin that checks if some field is an date
     *
     * @param string $name field name
     *
     * @return boolean true or false
     */
    public static function isDate($name) {
	if (strpos($name, 'date') !== false) {
	    return true;
	}
	return false;
    }

    /**
     * a functoin that gets the value of the id which it is a refrence to another table
     *
     * @param string $name field name
     * @param string $value new value
     * @param string $old old value
     *
     * @return array of two values new and old
     */
    public function getIdValue($name, $value, $old) {
	$attr = LoggableBehavior::ConvertCamelCasetoSpaces(LoggableBehavior::remove_id($name));
	$current_model = trim(LoggableBehavior::ConvertCamelCasetoSpaces(get_class($this->getOwner())));
	$model = get_class($this->getOwner());
	$model_name = LoggableBehavior::ConvertCamelCasetoUnderscores($attr, true);


	if ($model_name == "TransferredToContract")
	    $model_name = 'Contract';

	//----------------------------------------------
	$model = $model_name::model();
	$old_res = $model::model()->findByPk(array('id' => $old));
	$new_res = $model::model()->findByPk(array('id' => $value));
	//----------------------------------------------
	$old_name = "";
	$new_name = "";

	if ($model_name != "Cca" && $model_name != "Tid" && $model_name != "Invoice" && $model_name != "Asset") {
	    if ($old_res != null)
		$old_name = $old_res->name;
	    if ($new_res != null)
		$new_name = $new_res->name;
	} else {
	    if ($model_name == "Cca") {
		if ($old_res != null)
		    $old_name = $old_res->full_name;
		if ($new_res != null)
		    $new_name = $new_res->full_name;
	    }
	    if ($model_name == "Tid") {
		if ($old_res != null)
		    $old_name = $old_res->tid_no;
		if ($new_res != null)
		    $new_name = $new_res->tid_no;
	    }
	    if ($model_name == "Invoice") {
		if ($old_res != null)
		    $old_name = $old_res->number;
		if ($new_res != null)
		    $new_name = $new_res->number;
	    }
	    if ($model_name == "Asset") {
		if ($old_res != null)
		    $old_name = $old_res->asset_number;
		if ($new_res != null)
		    $new_name = $new_res->asset_number;
	    }
	}
	//----------------------------------------------
	return array('old' => $old_name, 'new' => $new_name);
    }

    /**
     * Get mandentory Yes or No
     * @return  Yes or No
     */
    public static function getText($value) {
	if (isset($value)) {
	    if (is_int($value)) {
		if ($value == 1)
		    return yii::t('default', "Yes");
		else if ($value == 0)
		    return yii::t('default', "No");
	    }
	    else
	    if ($value == '1')
		return yii::t('default', "Yes");
	    else if ($value == '0')
		return yii::t('default', "No");
	    else
		return $value;
	}
	return "";
    }

    /**
     * a functoin to get the label for the given atteribute
     *
     * @param string $name field name
     *
     * @return string label
     */
    public static function realName($name, $labels) {
	$label = "";
	try {
	    $label = $labels[$name];
	    if (strpos($label, 'font')) {
		$arr = explode("<", $label);
		$label = $arr[0];
	    }
	} catch (Exception $exc) {
	    return $name;
	}
	return $label;
    }

    /**
     * a functoin to remove "_id" from the end of the variable if variable ends with "_id"
     *
     * @param string $name field name
     *
     * @return field after formatting
     */
    public static function remove_id($name) {
	if (strpos($name, '_id'))
	    $name = substr($name, 0, -3);
	return $name;
    }

    /**
     * a functoin to convert from camel case to string sepreated by space using regular expression
     * e.g. NonLDAPPerson ==> Non LDAP Person
     * @param string $model model name
     *
     * @return model after formatting
     */
    public static function ConvertCamelCasetoSpaces($model) {
	if ($model == 'RenewalStage')
	    $model = 'ProcessingStage';
	$parts = preg_split("/((?<=[a-z])(?=[A-Z])|(?=[A-Z][a-z]))/", $model);
	if ($parts != null)
	    for ($i = 0; $i < count($parts); $i++) {
		if ($parts[$i] == "Of")
		    $parts[$i] = "of";
	    }
	$model = implode(" ", $parts);
	return $model;
    }

    /**
     * Translates a string with underscores
     * into camel case (e.g. first_name -> firstName)
     *
     * @param string $str String in underscore format
     * @param bool $capitalise_first_char If true, capitalise the first char in $str
     * @return string $str translated into camel caps
     */
    function ConvertCamelCasetoUnderscores($str, $capitalise_first_char = false) {
	if ($capitalise_first_char) {
	    $str[0] = strtoupper($str[0]);
	}
	$func = create_function('$c', 'return strtoupper($c[1]);');
	return preg_replace_callback('/_([a-z])/', $func, $str);
    }

//---------------------------------------------------------
//---------------------------------------------------------
    public function afterDelete($event) {
	$this->leaveTrail('Delete');
	return parent::afterDelete($event);
    }

    public function afterFind($event) {
	$this->setOldAttributes($this->getOwner()->getAttributes());
	return parent::afterFind($event);
    }

    /**
     * Get the old attribute array
     */
    public function getOldAttributes() {
	return $this->_oldattributes;
    }

    /**
     * Set value for the array that hold the old attributes of the current model
     * @param string $value the new value
     */
    public function setOldAttributes($value) {
	$this->_oldattributes = $value;
    }

    /**
     * Register audit trail information
     * @param string $action the name of the action (Create, Update , Delete)
     * @param string $name If true, capitalise the first char in $str
     * @param string $value translated into camel caps
     * @param string $old_value translated into camel caps
     */
    public function leaveTrail($action, $name = null, $value = null, $old_value = null) {
	$log = new AuditTrail();
	$log->old_value = $old_value;
	$log->new_value = $value;
	$log->action = Yii::t('default', $action);
	if ($action == 'Renew TID') {
	    $log->model = Yii::t('default', 'TID');
	    $log->model_id = $this->getOwner()->tid->getModelIdentifierValue();
	} else
	if ($action == 'Renew Asset') {
	    $log->model = Yii::t('default', 'Asset');
	    $log->model_id = $this->getOwner()->asset->getModelIdentifierValue();
	} else {
	    $log->model = Yii::t('default', trim(LoggableBehavior::ConvertCamelCasetoSpaces(get_class($this->getOwner()))));
	    $log->model_id = $this->getNormalizedPk();
	}
	$log->field = $name;
	$log->stamp = $this->storeTimestamp ? time() : date($this->dateFormat); // If we are storing a timestamp lets get one else lets get the date
	$log->user_id = $this->getUserId(); // Lets get the user id

	return $log->save();
    }

    public function getUserId() {
	if (isset($this->userAttribute)) {
	    $data = $this->getOwner()->getAttributes();
	    return isset($data[$this->userAttribute]) ? $data[$this->userAttribute] : null;
	} else {
	    try {
		$userid = Yii::app()->user->id;
		$user = User::model()->findByPk($userid);
		return empty($userid) ? null : $userid;
	    } catch (Exception $e) { //If we have no user object, this must be a command line program
		return null;
	    }
	}
    }

    protected function getNormalizedPk() {
	return $this->getOwner()->getModelIdentifierValue();
	//return is_array($pk) ? json_encode($pk) : $pk;
    }

}
