<?php

namespace App\Config;

abstract class XmlBasicAbstract extends BaseAbstract
{

	/**
	 * Stores the configuration file (relative to the "files/config/" dir). For config files stored
	 * inside subdirectories, the subdirectory is included (Ex: services/servicesList.xml).
	 * @var String
	 */
	protected $fileName=null;

	/**
	 * The filename with its full path.
	 * @var String
	 */
	protected $fileNameWithPath=null;


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\BaseAbstract::constructInit()
	 */
	protected function constructInit()
	{
		// check if the file name is okay
		if (!$this->fileName) {
			throw new \Exception('You did not set the `fileName` attribute for the class `'.get_called_class().'`.');
		}

		// set the
		$this->fileNameWithPath = APP_REAL_PATH . FILEPATH_APPCONFIGS . $this->fileName;

	}

	/**
	 * Sets a reference to the current data in session so it can be stored during session lifetime.
	 * @see \App\Config\BaseAbstract::constructAdmin()
	 */
	protected function constructAdmin()
	{

		global $S;
		$className=get_called_class();

		// if items list and changed flag are set in session - they are copied into class attributes
		if (isset($S['admin']['configApp'][$className]['itemsList'])) {
			$this->itemsList=$S['admin']['configApp'][$className]['itemsList'];
		}
		if (isset($S['admin']['configApp'][$className]['changedFlag'])) {
			$this->changedFlag=$S['admin']['configApp'][$className]['changedFlag'];
		}

		// these references will keep the data over different page loads
		$S['admin']['configApp'][$className]['itemsList']=&$this->itemsList;
		$S['admin']['configApp'][$className]['changedFlag']=&$this->changedFlag;

	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\BaseAbstract::getAll_rawData()
	 */
	protected function getAll_rawData()
	{

		// return the output in case the file does not exist
		if (!file_exists($this->fileNameWithPath)) return $this->getAll_rawData_fileNotExists();

		// the file exists - load the content as XML (and check it)
		$xml=@simplexml_load_file($this->fileNameWithPath);
		if (!($xml instanceof \SimpleXMLElement)) throw new \Exception('The config file is not a well formed XML (class = '.get_called_class().' ; file = '.$this->fileNameWithPath.').');

		// return the array from the parsed XML content
		return $this->getAll_rawData_extractDataFromFile($xml);

	}

	/**
	 * Sets the default output in case the XML file is missing.
	 * @return Array
	 */
	protected function getAll_rawData_fileNotExists()
	{
		return [];
	}

	/**
	 * Returns the extracted data after the XML content is parsed.
	 * @param \SimpleXMLElement $xml - the parsed xml
	 * @return Array  - the items list or whatever output is needed.
	 */
	abstract protected function getAll_rawData_extractDataFromFile(\SimpleXMLElement $xml);



	/**
	 * Sets an element in the internal attribute $this->itemsList. The method can be called multiple
	 * times and they will overwrite the older values.
	 * @param String $var - or the name of one key (a string);
	 * @param Mixed $value - the value of the key;
	 * @throws \Exception - if input is bad
	 */
	public function set($var, $value) {

		// check constructor type
		if ($this->constructType!='admin') throw new \Exception("You can use the method `".get_called_class().'::'.__FUNCTION__."` only if the constructor type is `admin`.");
		// check inputs
		if (!is_string($var) and !is_numeric($var)) throw new \Exception("You did not set() a correct name for the key.");

		// check the item
		$this->keyValueCheck($var, $value);

		// (re)set one key
		$this->itemsList[$var]=$value;

		// update the changed flag
		$this->changedFlag=true;
	}

	/**
	 * Checks the current adding/updating item.
	 * @param String $code
	 * @param Mixed $itemDetails
	 */
	protected function keyValueCheck($code, $itemDetails)
	{}


	/**
	 * Sets all the elements in the internal attribute $this->itemsList (rewrites any old data
	 * already stored). The parameter is an associative array and the keys will be
	 * stored one by one.
	 * @param Array $vars - associative array of keys
	 * @throws \Exception - if input is bad
	 */
	public function setAll($vars) {

		// check constructor type
		if ($this->constructType!='admin') throw new \Exception("You can use the method `".get_called_class().'::'.__FUNCTION__."` only if the constructor type is `admin`.");
		// check inputs
		if (!is_array($vars)) throw new \Exception("You did not setAll() a correct list of elements - the parameter is not an array.");

		// (re)set a list of keys
		$this->itemsList=$vars;

		// update the changed flag
		$this->changedFlag=true;
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\BaseAbstract::save_write_do()
	 */
	protected function save_write_do()
	{

		// GETS THE SIMPLEXML OBJECT TO BE WRITTEN ON DISK ////////////////////////////////////////
		// gets the simplexml object
		$xmlObj=$this->save_write_do_getXml();
		// checks that the object is correct (it is a valid simplexml object)
		if (!($xmlObj instanceof \SimpleXMLElement)) throw new \Exception('The output of the method `'.get_called_class().'::save_write_do_getXml` must be a `SimpleXMLElement` object.');


		// (RE)WRITE FILE ON DISK /////////////////////////////////////////////////////////////////

		// check if the file is writeable
		$fileChmod=false;
		if (file_exists($this->fileNameWithPath)) {
			if (!is_writeable($this->fileNameWithPath)) $this->addErrorMessage('The config file exists but it is not writeable.');
		} else {
			$fileChmod=true;
		}

		// (OVER)WRITE THE FILE CONTENT -----------------------------------------------------------
		if (!file_put_contents($this->fileNameWithPath, $xmlObj->asXml())) {
			$this->addErrorMessage('The config file could not be (over)written.');
		}
		// if the file did not exist - chmod it
		if ($fileChmod) {
			if (!chmod($this->fileNameWithPath, 0777)) {
				$this->addErrorMessage('The config file could not have its acess rights updated.');
			}
		}

	}


	/**
	 * Returns the SimpleXML object to be written on disk.
	 * @return \SimpleXMLElement
	 */
	abstract protected function save_write_do_getXml();

}

