<?php
App::import('Model', 'Log');
App::import('Model', 'LogDetails');

class LoggedBehavior extends ModelBehavior {

	# Skip these fields
	private $ignoreList = [
		'id', 'modified', 'modified_by', 'created', 'created_by', 'slug',
		'ordering', 'filesize', 'mimetype', 'customization'
	];

	/**
	 * Gets previous state of data before save.
	 *
	 * @param    Model
	 *        Model object.
	 *
	 * @return
	 *        Returns always true.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	function beforeSave(Model $Model, $options = []) {

		# If update
		if (isset($Model->id) && !empty($Model->id)) {
			$this->previousState = isset($Model->previousData[$Model->alias]) ? $Model->previousData[$Model->alias] : null;
		}
		return true;
	}

	/**
	 * Compare state before and after save and store differences in log.
	 *
	 * @param    Model
	 *        Model object.
	 * @param    created
	 *        Flag, if set then operation is insert, not update.
	 *        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	function afterSave(Model $Model, $created, $options = []) {

		# Prevent errors
		if (empty($Model->data))
			return true;

		$this->currentState = $Model->data[$Model->alias];
		$polyglotedFields = $Model->polyglotFields;

		# Handle created
		if ($created) {
			return $this->createdDeleted($Model, 'insert');
		}

		foreach ($Model->data[$Model->alias] as $key => $value) {

			# Ignore keys in ignoreList and keys that are not in schema
			if (in_array($key, $this->ignoreList) || !in_array($key, array_keys($Model->schema()))) {
				continue;
			}

			# Get previous value
			$previousValue = isset($this->previousState["{$key}"]) ? $this->previousState["{$key}"] : null;

			# Value type
			$schema = $Model->schema($key);
			switch ($schema['type']) {
				case 'text':
					if (isset($schema['comment']) && $schema['comment'] == 'plain') {

						# Do things for plain text. Every space counts here.
					} else {
						$previousValue = preg_replace('~\s\s+~', ' ', trim(strip_tags($previousValue)));
						$value = preg_replace('~\s\s+~', ' ', trim(strip_tags($value)));
					}
					break;
			}

			# Initialize variables for name and optional language
			$keyName = "";
			$keyLang = "";

			# Polygloted fields
			if (preg_match('/__/i', $key)) {
				$keySplitted = explode("__", $key);
				$keyName = $keySplitted[0];
				$keyLang = $keySplitted[1];
			} else {
				$keyName = $key;
			}

			# Disable logging of polygloted ingnored fields (like slug)
			if (in_array($keyName, $this->ignoreList)) {
				continue;
			}

			# Ordinary fields
			if ($previousValue != $value) {

				# If record is soft-deleted
				if ($key == 'is_deleted') {
					if ($previousValue == 'true') {
						$this->createdDeleted($Model, 'insert');
					} else {
						$this->createdDeleted($Model, 'delete');
					}
				} else {
					$this->fieldUpdated($Model, $keyName, $previousValue, $value, $keyLang);
				}
			}
		}

	}

	/**
	 * Save log for deleted item.
	 *
	 * @param    Model
	 *        Model object.
	 * @param    cascade
	 *        Cascade all connected objects or not.
	 *        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	function beforeDelete(Model $Model, $cascade = true) {
		$this->createdDeleted($Model, 'delete');
		return true;
	}

	/**
	 * Saves log for one field updated.
	 *
	 * @param    Model
	 *        Model object.
	 * @param    key
	 *        Name of the updated key.
	 * @param    value
	 *        New value of key.
	 *        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	private function fieldUpdated($Model, $key, $previousValue, $value, $lang = null) {

		# If model is Upload, skip logging, and just save changed paths
		if ($Model->name == "Upload") {

			# Only if filename changed
			if ($key == 'filename') {
				$this->changedUploadOfModule($Model, 'update');
			}
			return;
		}

		# $this->log saves current log, in case of several fields changed
		if (empty($this->log)) {

			$log = new Log();

			# Save basic log data.
			$logData = [ 'model' => $Model->name, 'foreign_key' => $Model->id, 'type' => "update" ];

			# If there is loggedin administrator, set its id as administrator_id
			if (isset($_SESSION['Auth']['Administrator'])) {
				$logData['administrator_id'] = $_SESSION['Auth']['Administrator']['id'];
			}

			$this->log = $log->save($logData);
		}

		# Save log details.
		$logDetails = new LogDetails();

		$schema = $Model->schema($key);

		$logDetailsData = [ 'log_id' => $this->log['Log']['id'], 'field' => $key, 'type' => $schema['type'],
							'to'     => $value ];

		# If field is not polygloted
		if (!empty($lang)) {
			$logDetailsData['language'] = $lang;
		}
		$logDetailsData['from'] = $previousValue;

		$logDetails->save($logDetailsData);
	}

	/**
	 * Save record in log for insert or delete of a record.
	 *
	 * @param    Model
	 *        Model object.
	 * @param    action
	 *        'insert' or 'delete', depends on action.
	 *
	 * @return
	 *        Returns true.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	private function createdDeleted($Model, $action) {

		# If model is Upload, skip logging
		if ($Model->name == "Upload") {
			$this->changedUploadOfModule($Model, $action);
			return;
		}

		# Create and save new record in table log, with provided action
		$log = new Log();
		$logData = [ 'model' => $Model->name, 'foreign_key' => $Model->id, 'type' => $action ];

		if (isset($_SESSION['Auth']['Administrator'])) {
			$logData['administrator_id'] = $_SESSION['Auth']['Administrator']['id'];
		}
		$savedLog = $log->save($logData);
	}

	private function changedUploadOfModule($Model, $action) {
		if (!empty($Model->data[$Model->alias]['path'])) {

			# Store the default paths
			$oldPath = $newPath = null;

			# Get the data for update and insert
			if ($action != 'delete') {
				$moduleName = $Model->data[$Model->alias]['model'];
				$moduleKey = $Model->data[$Model->alias]['foreign_key'];
			} else {
				$moduleName = $Model->deletedRecord[$Model->alias]['model'];
				$moduleKey = $Model->deletedRecord[$Model->alias]['foreign_key'];
			}

			# On replace and remove there is no enough data in $Model->data
			if (empty($Model->data[$Model->alias]['filename'])) {
				$Model->find('first', $Model->id);
			}

			# Action can be insert, update and delete. On insert, oldPath is null, on delete newPath is null.
			# On update, both fields must be populated.
			switch ($action) {
				case 'insert':
					$newPath = preg_replace('/\//', '/backup/', $Model->data[$Model->alias]['path'], 1) . $Model->data[$Model->alias]['filename'];
					break;

				case 'update':
					$newPath = preg_replace('/\//', '/backup/', $Model->data[$Model->alias]['path'], 1) . $Model->data[$Model->alias]['filename'];
					$oldPath = preg_replace('/\//', '/backup/', $Model->previousData[$Model->alias]['path'], 1) . $Model->previousData[$Model->alias]['filename'];
					break;

				case 'delete':
					$oldPath = preg_replace('/\//', '/backup/', $Model->deletedRecord[$Model->alias]['path'], 1) . $Model->deletedRecord[$Model->alias]['filename'];
					break;

			}

			# Create new Log instance, to save Log record later
			$log = new Log();

			$logData = [ 'model' => $moduleName, 'foreign_key' => $moduleKey, 'type' => "update" ];
			if (isset($_SESSION['Auth']['Administrator'])) {
				$logData['administrator_id'] = $_SESSION['Auth']['Administrator']['id'];
			}

			# If there is no global log data, create it, so that other saved fields can be logged under the same log
			if (empty($this->log)) {
				$savedLog = $log->save($logData);
				$this->log = $savedLog;
			}

			# Save log details
			$logDetails = new LogDetails();
			$logDetails->save([
				'log_id' => $this->log['Log']['id'],
				'field'  => $Model->alias,
				'type'   => 'upload',
				'from'   => $oldPath,
				'to'     => $newPath
			]);
		}
	}
}
