<?php

namespace App\EntityPreference;


/**
 *
 * Abstract class for managing entity (user, customer, supplier, etc) preferences.
 * Methods of use (for customer preferences class):
 *  - Object instantiation:
 *    - $pref=CustomerPreferences::getInstance(1); - singleton instantiation for customer segment id = 1
 *    - $pref=new CustomerPreferences(1); - object instance for customer segment id = 1
 *  - Using the object:
 *    - CustomerPreferences::getInstance(1)->var='value'; -  singleton instantiation with value setting in the same instruction
 *    - $pref->var='value'; echo $pref->var; - standard object use (__set and __get)
 *    - $pref['var']='value'; echo $pref['var']; unset($pref['var']); - ArrayAccess implementation
 *
 */
abstract class BaseAbstract
{

	/**
	 * The main entity id
	 * @var mixed
	 */
	protected $id;

	/**
	 *
	 * Table name
	 * @var String
	 */
	protected $DBTEntityPreferences;

	/**
	 * The name of the entity identifier field in the database table.
	 * @var String
	 */
	protected $field_idEntityName='';

	/**
	 * This indicates the name of the variable field in the database table.
	 * @var String
	 */
	protected $field_var='var';

	/**
	 * This  indicates the name of the value field in the database table.
	 * @var String
	 */
	protected $field_value='value';

	/**
	 * This constant indicates if the database table stores the last update user.
	 * Set to false/blank/null if the field is not available for a certain class.
	 * @var String
	 */
	protected $field_lastUpdateUser='idUpdateUser';

	/**
	 * This indicates if the database table stores the last update time.
	 * Set to false/blank/null if the field is not available for a certain class.
	 * @var String
	 */
	protected $field_lastUpdateTime='lastUpdate';

	/**
	 * The cache type used - check the global configs for more details.
	 * @var String
	 */
	protected $cacheType = 'preference';

	/**
	 * The cache time to live (in seconds): -1 (disabled), 0 (no limit).
	 * @var Int
	 */
	protected $cacheTtl = 86400;

	/**
	 * Stores object instance
	 * @var array
	 */
	static protected $instance = [];

	/**
	 * Use the magic method for setting the values in the database.
	 * @param String $var
	 * @param String $value
	 */
	public function __set($var, $value)
	{

		if (!\DB::isInsideTransaction()) {
			throw new \Exception('Entity Preference entity must be inside a transaction.');
		}

		// THE VALUE SHOULD BE SAVED //////////////////////////////////////////////////////////////
		if ($value!==null){

			// FORM AND EXECUTE SQL ---------------------------------------------------------------
			$updatePreferenceSQL="REPLACE INTO `{$this->DBTEntityPreferences}` SET ";
			$updatePreferenceSQL.='`'.$this->field_idEntityName.'`=?, `'.$this->field_var.'`=?, `'.$this->field_value.'`=?';
			$fields= [$this->id, $var, $value];
			// add las update user
			if ($this->field_lastUpdateUser) {
				$updatePreferenceSQL.=', `'.$this->field_lastUpdateUser.'`=?';
				$fields[]=CURRENT_ID_USER;
			}
			// add las update time
			if ($this->field_lastUpdateTime) {
				$updatePreferenceSQL.=', `'.$this->field_lastUpdateTime.'`=?';
				$fields[]=TIMESTAMP;
			}
			$updatePreferenceEx=\DB::getInstance()->Execute($updatePreferenceSQL, $fields);
			// check sql
			if (!$updatePreferenceEx) {
				\Log::getInstance()->dbError("Could not set `".get_called_class()."` preference for entity `{$this->id}`");
				throw new \DBException('Unable to set `'.get_called_class().'` preference for id `'.$this->id.'`.');
			}

			// ALSO DELETE THE CACHE --------------------------------------------------------------
			\Engine\Cache\General::getInstance($this->cacheType)->delete('Preference-'.get_called_class().'['.$this->id.']:'.$var);

		}

		// THE VALUE SHOULD BE UNSET FROM THE DB //////////////////////////////////////////////////
		else{

			$this->__unset($var);

		}

	}

	/**
	 * Use the magic method for getting the values from the database.
	 * @param string $var
	 * @return string
	 */
	public function __get($var)
	{

		// ATTEMPT TO GET THE RESULT FROM CACHE ---------------------------------------------------
		$cachedValue=\Engine\Cache\General::getInstance($this->cacheType)->get('Preference-'.get_called_class().'['.$this->id.']:'.$var);
		if ($cachedValue!==null) return $cachedValue;

		// NO CACHE FOR THIS ELEMENT - GET DATA FROM DATABASE AND ATTEMPT TO CACHE IT -------------
		// form and execute sql
		$getPreferenceSQL="SELECT `".$this->field_value."` FROM `{$this->DBTEntityPreferences}` WHERE `".$this->field_idEntityName."`= ? AND `".$this->field_var."`= ?";
		$getPreferenceRes = \DB::getInstance()->Execute($getPreferenceSQL, [$this->id, $var]);
		// check for errors
		if (!$getPreferenceRes) {
			\Log::getInstance()->dbError("Could not get `".get_called_class()."` preference for entity `{$this->id}`");
			throw new \DBException('Unable to get `'.get_called_class().'` preference for id `'.$this->id.'`.');
		}
		// get preference from db and return it
		$getPreferenceArr = $getPreferenceRes->FetchRow();
		if (!$getPreferenceArr) $return = null;
		else $return = $getPreferenceArr[$this->field_value];
		// cache the result
		if ($this->cacheTtl!=-1) {
			\Engine\Cache\General::getInstance($this->cacheType)->set('Preference-'.get_called_class().'['.$this->id.']:'.$var, $return, $this->cacheTtl);
		}
		return $return;

	}

	/**
	 * Use the magic method for deleting an entity preference.
	 * @param String $var
	 * @throws \DBException
	 * @return Boolean - if the delete succeeds
	 */
	public function __unset($var)
	{

		// FORM AND EXECUTE SQL -------------------------------------------------------------------
		$updatePreferenceSQL="DELETE FROM `{$this->DBTEntityPreferences}` WHERE `".$this->field_idEntityName."`= ? AND `".$this->field_var."`= ?";
		$updatePreferenceEx=\DB::getInstance()->Execute( $updatePreferenceSQL, [$this->id, $var]);
		// check for errors
		if (!$updatePreferenceEx) {
			\Log::getInstance()->dbError("Could not unset `".get_called_class()."` preference for entity `{$this->id}`");
			throw new \DBException('Unable to unset `'.get_called_class().'` preference for id `'.$this->id.'`.');
		}

		// ALSO DELETE THE CACHE ------------------------------------------------------------------
		\Engine\Cache\General::getInstance($this->cacheType)->delete('Preference-'.get_called_class().'['.$this->id.']:'.$var);

		// return boolean
		return true;
	}


	/**
	 * Used for Singleton instantiation.
	 * @param Int $idEntity
	 * @return \App\EntityPreference\BaseAbstract
	 */
	static public function getInstance($idEntity = null)
	{
		// the current instantiated class name
		$className = get_called_class();

		// instantiate object
		$cache = \Engine\Cache\General::getInstance('localMemory');

		$cacheKey = sprintf("%s:%s:%s", __METHOD__, $className, $idEntity);
		$obj = $cache->get($cacheKey);
		if ($obj===null) {
			$obj = new static($idEntity);
			$cache->set($cacheKey, $obj, PHP_INT_MAX);
		}

		return $obj;
	}


	/**
	 * The class constructor - associates the table name and the id
	 * @param Int $id - the entity id.
	 * @param String $DBTEntityPreferences - the db table name
	 */
	final protected function __construct($id)
	{

		// set/check the table name
		$this->setDbTableName();
		if (!$this->DBTEntityPreferences) {
			throw new \Exception('The class `'.get_called_class().'` does not have the `DBTEntityPreferences` attribute set.');
		}

		// set the internal class attribute 'id' (and validate it)
		$this->setEntityId($id);

	}


	/**
	 * Sets the table name into the 'DBTEntityPreferences' class attribute.
	 */
	abstract protected function setDbTableName() ;

	/**
	 * Sets the id of the entity;
	 * @param Int $id
	 * @throws \Exception
	 */
	protected function setEntityId($id)
	{
		// check input
		if (!is_numeric($id) or $id<0) {
			throw new \Exception('Preference entity id is not valid');
		}
		$this->id = $id;
	}

	/**
	 * Gets the current id of the entity
	 */
	public function getId()
	{
		return $this->id;
	}

}

