<?php
namespace App\Config;


abstract class ExportData_Fields_GeneralAbstract extends DbMultiAssocVarsAbstract
{

	/**
	 * The column that contains the actual value for this association.
	 * @var String
	 */
	protected $dbTableValueCol = 'value';

	/**
	 * The current entity criteria fields.
	 * @var array
	 */
	protected $entityCriteriaList = [];

	protected $cacheObj;

	/**
	 * Validates the entityCriteriaList.
	 * {@inheritDoc}
	 * @see \App\Config\DbMultiAssocVarsAbstract::constructInit()
	 */
	protected function constructInit()
	{

		// check that the criteria list is set
		if (!$this->entityCriteriaList or !is_array($this->entityCriteriaList)) {
			throw new \Exception('You must set the `entityCriteriaList` attribute for `'.get_called_class().'`.');
		}

		// compose dbTablePkCols based on entity criteria list and by adding the 'field' (field name)
		$this->dbTablePkCols = $this->entityCriteriaList;
		$this->dbTablePkCols[] = 'field';

		return parent::constructInit();

	}

	/**
	 * Gets the export data cache.
	 * @return \Engine\Cache\BaseInterface
	 */
	protected function getCacheObject()
	{
		if ($this->cacheObj) {
			return $this->cacheObj;
		}

		$cacheObj = new \Engine\Cache\DiskWithLRU();
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		$cacheObj->setPath(MODULE_REL_PATH.$CFG['paths']['exportDataCache']);
		$this->cacheObj = $cacheObj;
		return $cacheObj;
	}

	/**
	 * The method returns the value of the all field for the given criteria list.
	 * The value for a given criteria list will also be cached (ORIGKEY_). Further more, each
	 * independent SQL will also be cached (PARTKEY_) and even inexistent records will be cached (nulls to blank).
	 * @param array $criteriaList
	 * @throws \Exception
	 * @return array
	 */
	protected function getFieldValueByCriteriaList($field, Array $criteriaList)
	{

		// GETS THE RESULT FROM CACHE (IF FOUND) --------------------------------------------------
		$cacheKeyOrg = 'ORIGKEY_'.get_called_class().'_'.$field.'_'.serialize($criteriaList);
		$cachedValue = $this->getCacheObject()->get($cacheKeyOrg);
		if ($cachedValue!==null) {
			return $cachedValue;
		}

		// check criteria
		if ($this->entityCriteriaList!=array_keys($criteriaList)) {
			throw new \Exception('Invalid criteria list for '.__METHOD__.' in class `'.get_called_class().'`.');
		}


		// GETS THE VALUE FROM DB (by combining actual values with *s) ----------------------------

		// the number of combinations (2 to the power of count($criteria))
		$criteriaCount = count($criteriaList);
		$combinationsCount = 2**$criteriaCount-1;

		$returnValue = null;

		// look for all combinations
		for ($idx=0; $idx<=$combinationsCount; $idx++) {

			// current activations of 0/1 indicating for each combination which field is set as actual value or '*'
			$switches = str_pad(decbin($idx), $criteriaCount, '0', STR_PAD_LEFT);

			$criteriaFields = [];
			$switchesIndex = 0;
			foreach($criteriaList as $thisCriterionField=>$criterionValue) {
				$criteriaFields[$thisCriterionField] = ($switches[$switchesIndex]?'*':$criterionValue);
				$switchesIndex++;
			}

			// add field in the list of criteria
			$criteriaFields['field'] = $field;

			// searches for current combination - stops if something is found
			$foundValue = $this->searchAssoc( $criteriaFields );
			if ($foundValue!==null) {
				$returnValue = $foundValue;
				break;
			}

		}

		// CACHES AND RETURNS THE CURRENT CRITERIA COMBINATION AND FIELD VALUE --------------------
		$returnValue = (String)$returnValue;
		$this->getCacheObject()->set($cacheKeyOrg, $returnValue); // forces nulls to strings (so they are returned the next time for full criteria selection))
		return $returnValue;

	}


	/**
	 *
	 * {@inheritDoc}
	 * @see \App\Config\BaseAbstract::save()
	 */
	public function save() {
		parent::save();
		$this->getCacheObject()->flush();
	}


	/**
	 *
	 * @param String $field
	 * @throws \App\Config\MessageException
	 * @throws \DBException
	 */
	public function getFieldRulesCount($field)
	{
		if (!$field) {
			throw new \App\Config\MessageException("Undefined field to search by.");
		}

		$db=\DB::getInstance();

		// compose the sql
		$searchSQL="SELECT COUNT(*) FROM `{$this->dbTableName}` WHERE field = ?";
		$fieldRulesCount = $db->GetOne($searchSQL, $field);

		return $fieldRulesCount;
	}


}
