<?php

namespace App\Config;

class CurrencyTypes extends XmlDynamicRecordsAbstract
{

	/**
	 * 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=CONFIG_FILE_CURRENCYTYPES;


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::getAll_rawData_extractDataFromFile_list()
	 */
	protected function getAll_rawData_extractDataFromFile_list(\SimpleXMLElement $xml)
	{

		// GETS THE FINAL LIST OF CURRENCIES TO BE RETURNED ---------------------------------------
		$return=[];
		foreach($xml->currencyExchangeType  as $currTypeXml) {

			$currTypeCode=(String)$currTypeXml['code'];

			$return[$currTypeCode]['code'] = $currTypeCode;
			$return[$currTypeCode]['name'] = (String)$currTypeXml['name'];
			$return[$currTypeCode]['disabled'] = (Int)$currTypeXml['disabled'];

			// save formula (if any)
			if ($currTypeXml->formula) {
				$return[$currTypeCode]['parent'] = 		(String)$currTypeXml->formula['parent'];
				$return[$currTypeCode]['operation'] =	(String)$currTypeXml->formula['operation'];
				$return[$currTypeCode]['argument'] =	(Float)$currTypeXml->formula['argument'];
			}

			// save complex formula (if any)
			if (isset($currTypeXml->currency)) {
				foreach($currTypeXml->currency as $currXml) {
					$return[$currTypeCode]['currencies'][ (String)$currXml['code'] ]= [
						'parent' =>		(String)$currXml->formula['parent'],
						'operation' =>	(String)$currXml->formula['operation'],
						'argument' =>	(Float)$currXml->formula['argument'],
					];
				}
			}

		}
		return $return;

	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::getAll_rawData_extractDataFromFile_default()
	 */
	protected function getAll_rawData_extractDataFromFile_default(\SimpleXMLElement $xml)
	{
		return '';
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::getAll_rawData_extractDataFromFile_mandatory()
	 */
	protected function getAll_rawData_extractDataFromFile_mandatory(\SimpleXMLElement $xml)
	{
		return true;
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::itemCheck()
	 */
	protected function itemCheck($code, $itemDetails)
	{

		if (!is_string($code)) {
			throw new \Exception('Currency type must be set as a string.');
		}
		if (!$code) {
			$this->addErrorMessage('The `currency type code` was not defined.');
		}
		// check details
		if (!is_array($itemDetails)) {
			$this->addErrorMessage('The `currency type` does not have its details correctly set.');
		}
		// check code length
		if (mb_strlen($code)>10) {
			$this->addErrorMessage('The `currency type code` length must be smaller than 10 chars.');
		}
		// check that the code key is the same as the code field
		if ($itemDetails['code']!=$code) {
			throw new \Exception('Currency type code is not correctly set in the itemDetails.');
		}
		if (htmlClean($itemDetails['code'])!= $itemDetails['code']) {
			$this->addErrorMessage('Currency type code cannot contain html special characters.');
		}

		// check that the name is set
		if (!$itemDetails['name']) {
			$this->addErrorMessage('Currency type name is not set.');
		}
		// check that the name does not exist
		foreach($this->itemsList as $c=>$det) {
			if ($c!=$code and $itemDetails['name']==$det['name']) {
				$this->addErrorMessage(sprintf('The name `%s` already exists.',$det['name']));
			}
		}
		// check simple formula
		if ($itemDetails['parent']) {
			// cannot have both simple and advanced formula
			if ($itemDetails['currencies']) {
				throw new \Exception('You cannot set both simple and advanced formula at the same time for a currency type.');
			}
			// check that the argument is valid
			if (!is_numeric($itemDetails['argument'])) {
				$this->addErrorMessage(sprintf('The currency type formula argument is not valid (`%s`).', $itemDetails['argument']));
			}
			if ($itemDetails['argument']<0 and $itemDetails['operation']=='multiply') {
				$this->addErrorMessage(sprintf('The currency type formula argument (`%s`) cannot be negative when using multiplication.', $itemDetails['argument']));
			}
			// check that the operation is valid
			if ($itemDetails['operation']!='multiply' and $itemDetails['operation']!='add') {
				$this->addErrorMessage(sprintf('The currency type formula operation is not valid (`%s`).', $itemDetails['operation']));
			}
			// check if the formula parent is valid
			if (!$this->itemsList[$itemDetails['parent']]) {
				$this->addErrorMessage('Currency type formula parent is not valid.');
			}
			// the parent formula must not be defined using a formula (we do not want to create loops or complicated formulae)
			if ($this->itemsList[$itemDetails['parent']]['parent'] or $this->itemsList[$itemDetails['parent']]['currencies']) {
				$this->addErrorMessage('The currency type formula parent cannot be defined using a formula.');
			}
			if ($itemDetails['parent'] == $code) {
				$this->addErrorMessage('The currency type formula parent cannot be the same as the current currency type.');
			}
		}
		// check advanced formula
		if ($itemDetails['currencies']) {
			// check the currencies key
			if (!is_array($itemDetails['currencies'])) {
				throw new \Exception('The advanced formula for this currency type must be set as an associative array of currencies.');
			}
			// check each currency and its formula
			foreach($itemDetails['currencies'] as $curr=>$currDet) {
				if (!Currencies::getInstance()->check($curr)) {
					$this->addErrorMessage(sprintf('The currency type advanced formula cannot be defined for this currency `%s`.', $curr));
				}
				if (!is_numeric($currDet['argument'])) {
					$this->addErrorMessage(sprintf('The currency type formula argument is not valid (`%s`).', $currDet['argument']));
				}
				if ($currDet['argument']<0 and $currDet['operation']=='multiply') {
					$this->addErrorMessage(sprintf('The currency type formula argument (`%s`) cannot be negative when using multiplication.', $currDet['argument']));
				}
				if ($currDet['operation']!='multiply' and $currDet['operation']!='add') {
					$this->addErrorMessage(sprintf('The currency type formula operation is not valid (`%s`).', $currDet['operation']));
				}
				if (!$this->itemsList[$currDet['parent']]) {
					$this->addErrorMessage('Currency type formula parent is not valid.');
				}
				if ($this->itemsList[$currDet['parent']]['parent'] or $this->itemsList[$currDet['parent']]['currencies']) {
					$this->addErrorMessage('The currency type formula parent cannot be defined using a formula.');
				}
				if ($currDet['parent'] == $code) {
					$this->addErrorMessage(sprintf('The currency type formula parent for `%s` cannot be the same as the current currency type.', $curr));
				}
			}
		}
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::setDefaultItem()
	 */
	public function setDefaultItem($code)
	{
		throw new \Exception('You cannot use `'.__METHOD__.'`.');
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::setMandatory()
	 */
	public function setMandatory($mandatoryFlag=true)
	{
		throw new \Exception('You cannot use `'.__METHOD__.'`.');
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::itemDelete()
	 */
	public function itemDelete($code)
	{
		// check that the deleting currency type is not the default one
		if ($code==$this->getDefaultCurrencyType()) {
			$this->addErrorMessage('You cannot delete the reference currency type.');
		}

		// check that the currency type is not parent for another currency type
		if ($this->isParent($code)) {
			$this->addErrorMessage('You cannot delete a currency exchange type that is used as parent for other currency exchange types');
		}

		return parent::itemDelete($code);

	}


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

		// CHECK THAT THE REFER CURRENCY TYPE EXISTS //////////////////////////////////////////////
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		if (!$this->itemsList[$CFG['currencies']['defaultCurrencyType']]) {
			$this->addErrorMessage(sprintf('The reference currency type (code = %s) must exist in the list of currency types.', $CFG['currencies']['defaultCurrencyType']));
		}

		// Check that the REFER currency type doesn't have a formula
		$defaultCurrencyType = $this->itemsList[$this->getDefaultCurrencyType()];
		if ($defaultCurrencyType['parent']) {
			$this->addErrorMessage('You cannot have a formula for the default currency exchange type.');
		}

		// Check that the REFER currency type doesn't have currencies (which implies having formulae)
		if ($defaultCurrencyType['currencies']) {
			$this->addErrorMessage('You cannot have complex formula for the the default currency exchange type.');
		}

		// Check that the default value cannot be disabled
		if ($this->itemsList[$this->getDefaultCurrencyType()]['disabled']) {
			$this->addErrorMessage("You cannot disable the default currency type.");
		}

		// CHECK THAT ALL CURRENCIES THAT HAVE SOME RATES DEFINED - ARE STILL PRESENT /////////////
		$currTypesRates = \App\CurrencyExchange\Date::getInstance()->getRates();
		foreach($currTypesRates as $currTypeCode=>$ratesList) {
			if (!$this->itemsList[$currTypeCode]) {
				$this->addErrorMessage(sprintf('You cannot delete the currency type code `%s` as it was already defined and probably used inside the application.', $currTypeCode));
			}
		}

		$currencyTypes = $this->itemsList;
		foreach($currencyTypes as $details) {

			// CHECK THAT THE CURRENCY EXCHANGES DO NOT HAVE PARENTS THAT HAVE FORMULAE
			if ($currencyTypes[$details['parent']]['parent'] or $currencyTypes[$details['parent']]['currencies']) {
				$this->addErrorMessage(sprintf('The `%s` has an exchange rate with formula inside its formula (`%s`)', $details['name'], $details['parent']));
			}

			// CHECK THAT THE CURRENCY EXCHANGES DO NOT HAVE DISABLED PARENTS IN THEIR FORMULAE
			if ($currencyTypes[$details['parent']]['disabled']) {
				$this->addErrorMessage(sprintf('The `%s` has a disabled exchange rate as parent in its formula (`%s`)', $details['name'], $details['parent']));
			}

			// CHECK THAT THE CURRENCIES (in complex formulae) DO NOT HAVE DISABLED PARENTS IN THEIR FORMULAE
			if ($details['currencies']) {
				foreach ($details['currencies'] as $subC) {
					if ($currencyTypes[$subC['parent']]['disabled']) {
						$this->addErrorMessage(sprintf('The `%s` has a disabled exchange rate as parent in its formulae (`%s`)', $details['name'], $currencyTypes[$subC['parent']]['name']));
					}
				}
			}
		}

	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlBasicAbstract::save_write_do_getXml()
	 */
	protected function save_write_do_getXml()
	{
		$xml=simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><currecyExchangeTypes />');
		foreach ($this->itemsList as $currTypeCode=>$currTypeDet) {
			$thisCurrTypeXml=$xml->addChild('currencyExchangeType', '');
			$thisCurrTypeXml['code']=$currTypeCode;
			$thisCurrTypeXml['name']=$currTypeDet['name'];
			$thisCurrTypeXml['disabled'] = (bool)$currTypeDet['disabled'];
			// add simple formula
			if ($currTypeDet['parent']) {
				$simpleFormulaXml = $thisCurrTypeXml->addChild('formula', '');
				$simpleFormulaXml['parent'] = $currTypeDet['parent'];
				$simpleFormulaXml['operation'] = $currTypeDet['operation'];
				$simpleFormulaXml['argument'] = $currTypeDet['argument'];
			}
			// add complex formula
			if ($currTypeDet['currencies']) {
				foreach($currTypeDet['currencies'] as $curr=>$currDet) {
					$thisCurrXml=$thisCurrTypeXml->addChild('currency', '');
					$thisCurrXml['code']=$curr;
					$complexFormulaXml = $thisCurrXml->addChild('formula', '');
					$complexFormulaXml['parent'] = $currDet['parent'];
					$complexFormulaXml['operation'] = $currDet['operation'];
					$complexFormulaXml['argument'] = $currDet['argument'];
				}
			}
		}

		// return the xml content of the current object
		return $xml;

	}


	/**
	 * Gets a simplified list of all the currency types.
	 * @throws \Exception
	 */
	public function getSimple()
	{
		// check constructor type
		if ($this->constructType!='instance') {
			throw new \Exception("You can use the method `".get_called_class().'::'.__FUNCTION__."` only if the constructor type is `instance`.");
		}
		// get the currency types and extract a simplified list of them
		$return=[];
		foreach($this->getAll() as $ctCode=>$ctDet) {
			$return[$ctCode]=$ctDet['name'];
		}
		return $return;
	}

	/**
	 * Gets a simplified list of currency types that are enabled.
	 * @throws \Exception
	 */
	public function getSimpleActive()
	{
		// check constructor type
		if ($this->constructType!='instance') {
			throw new \Exception("You can use the method `".get_called_class().'::'.__FUNCTION__."` only if the constructor type is `instance`.");
		}
		// get the currency types and extract a simplified list of them
		$return=[];
		foreach($this->getAll() as $ctCode=>$ctDet) {
			if (!$ctDet['disabled']) {
				$return[$ctCode]=$ctDet['name'];
			}
		}
		return $return;
	}

	/**
	 * Returns the name of a currency type.
	 * @param String $currencyType
	 * @return String
	 */
	public function getName($currencyType)
	{
		// check constructor type
		if ($this->constructType!='instance') {
			throw new \Exception("You can use the method `".get_called_class().'::'.__FUNCTION__."` only if the constructor type is `instance`.");
		}
		// get list of currency types
		$list=$this->getSimple();
		// return the name of the currency type code
		return $list[$currencyType];
	}

	/**
	 * Returns the default currency type configured in the application.
	 * @throws \Exception - if no default currency type
	 * @return String.
	 */
	public function getDefaultCurrencyType()
	{
		// return the default currency types from configurations
		global $CFG;
		if (!$CFG['currencies']['defaultCurrencyType']) {
			throw new \Exception('The default currency type is not configured.');
		}
		return $CFG['currencies']['defaultCurrencyType'];

	}

	/**
	 * Indicates if an item (by code) exists and is not disabled.
	 * @param String $code
	 */
	public function checkActive($code)
	{
		$details = $this->get($code);
		return ($details and !$details['disabled']);
	}

	/**
	 * Indicates if an item (by code) is parent for another currency type or currency type currencies
	 * @param String $code
	 * @return string|boolean
	 */
	public function isParent($code)
	{
		foreach ($this->getAll() as $ctCode=>$ctDet) {
			if ($ctDet['parent'] == $code) {
				return true;
			}
			if ($ctDet['currencies']) {
				foreach ($ctDet['currencies'] as $details) {
					if ($details['parent'] == $code) {
						return true;
					}
				}
			}
		}
		return false;
	}

}
