<?php

namespace App\Config;

/**
 * The class is used for managing the calculation types
 * @author Bogdan
 * @static \App\Config\Services_Calculations getAdmin()
 * @static \App\Config\Services_Calculations getInstance()
 */
class Services_Calculations 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_SERVICE_CALCULATIONS;


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

		// gets the final list of items to be returned
		$return=[];

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// loop through each service
		foreach($xml->service as $serviceDetails) {

			// scan each calculation type
			foreach ($serviceDetails as $calculationType) {

				// if this location is valids
				if (in_array( (String)$calculationType['location'], $CFG['countriesCriteria']) ) {

					$calculationDetails = [];

					// service code
					$calculationDetails['serviceCode'] = (String)$serviceDetails['code'];

					// location code
					$calculationDetails['location'] = (String)$calculationType['location'];

					// calculation code
					$calculationDetails['code'] = (String)$calculationType['code'];

					// calculation name
					$calculationDetails['name']	= (String)$calculationType;

					// active calculation
					$calculationDetails['deleted'] = (int)$calculationType['deleted'];

					$return[(String)$serviceDetails['code'].'_'.(String)$calculationType['location'].'_'.(String)$calculationType['code']]=$calculationDetails;

				}

			}

		}

		// return linear list with calculation type
		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;
	}

	/**
	 * Gets a list that has three levels
	 * The method returns an array [serviceCode][locationCode][calculationCode]= array('name' => calcName, 'deleted' => TRUE/ FALSE)
	 * @param boolean (false (default) for active calculation types and true for all calculation types)
	 * @return array
	 */
	public function getExtendedList($includeDeleted=false) {

		$extendedList=[];
		foreach($this->getAll() as $calculationDetails) {

			if ($includeDeleted or !$calculationDetails['deleted']) {
				$extendedList[$calculationDetails['serviceCode']][$calculationDetails['location']][$calculationDetails['code']] = ['name' => $calculationDetails['name'], 'deleted' => $calculationDetails['deleted']];
			}
		}
		return $extendedList;
	}


	public function getAllSimpleList($includeDeleted=false)
	{
		$return = [];
		foreach ($this->getAll() as $calcDet) {
			if ($includeDeleted or !$calcDet['deleted']) {
				$return[$calcDet['serviceCode']][$calcDet['location']][$calcDet['code']] = $calcDet['name'];
			}
		}
		return $return;
	}

	/**
	 * Gets all the calculations for a certain service (all locations).
	 * @param String $serviceCode
	 * @param Boolean $includeDeleted
	 * @throws \Exception - if service code or location not set
	 * @return Array - the list of locationa and calculations
	 */
	public function getServiceDetails($serviceCode, $includeDeleted=false)
	{

		// if no service code set - return an empty array
		if (!$serviceCode) throw new \Exception('The service code was not set.');

		// determine the list
		$return=[];
		foreach($this->getAll() as $calculationDetails) {
			// if the current service is selected
			if ($calculationDetails['serviceCode']==$serviceCode) {
				// include a deleted calculation (if requested)
				if ($includeDeleted or !$calculationDetails['deleted']) {
					$return[$calculationDetails['location']][$calculationDetails['code']] = ['name' => $calculationDetails['name'], 'deleted' => $calculationDetails['deleted']];
				}
			}
		}
		return $return;
	}

	/**
	 * Gets an associative array of locationCode=>calculationCode=>calculationName
	 * @param String $serviceCode
	 * @param String $includeDeleted
	 * @return Array
	 */
	public function getServiceSimpleList($serviceCode, $includeDeleted=false)
	{
		$return = [];
		foreach ($this->getServiceDetails($serviceCode, $includeDeleted) as $locCode=>$calcList) {
			foreach($calcList as $calcCode=>$calcDet) {
				$return[$locCode][$calcCode] = $calcDet['name'];
			}
		}
		return $return;
	}


	/**
	 * Gets all the calculations for a certain service and location
	 * @param String $serviceCode
	 * @param String $location
	 * @param Boolean $includeDeleted
	 * @throws \Exception - if service code or location not set
	 * @return Array - the list of calculations
	 */
	public function getServiceLocationDetails($serviceCode, $location, $includeDeleted=false)
	{

		// if no service code set - return an empty array
		if (!$serviceCode) throw new \Exception('The service code was not set.');
		if (!$location) throw new \Exception('The location was not set.');

		// determine the list
		$return=[];
		foreach($this->getAll() as $calculationDetails) {
			// if the current service is selected
			if ($calculationDetails['serviceCode']==$serviceCode) {
				// if all locations or the selected one
				if ($calculationDetails['location']==$location) {
					// include a deleted calculation (if requested)
					if ($includeDeleted or !$calculationDetails['deleted']) {
						$return[$calculationDetails['code']] = ['name' => $calculationDetails['name'], 'deleted' => $calculationDetails['deleted']];
					}
				}
			}
		}
		return $return;
	}

	/**
	 * Returns an associative array of calculations for the current service-location.
	 * @param String $serviceCode
	 * @param String $location
	 * @param Boolean $includeDeleted
	 * @return Array - associative array of calculations
	 */
	public function getServiceLocationSimpleList($serviceCode, $location, $includeDeleted=false)
	{
		$return = [];
		foreach ($this->getServiceLocationDetails($serviceCode, $location, $includeDeleted) as $calcCode=>$calcDet) {
			$return[$calcCode] = $calcDet['name'];
		}
		return $return;
	}


	/**
	 * Returns the name of a service-location-calculation.
	 * @param String $serviceCode
	 * @param String $location
	 * @param Int $calculation
	 * @return String
	 */
	public function getServiceLocationCalculationName($serviceCode, $location, $calculation)
	{
		$calcs=$this->getServiceLocationDetails($serviceCode, $location, true);
		return $calcs[$calculation]['name'];

	}

	/**
	 * Returns the default calculation code (first, for now).
	 * @param String $serviceCode
	 * @param String $location
	 * @return Int
	 */
	public function getDefaultCalculationCode($serviceCode, $location)
	{
		return key($this->getServiceLocationDetails($serviceCode, $location, false));
	}

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

		// 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`.");
		// itemCheck - to throw more messages (extended class specific)
		$itemDetailsCheck=$itemDetails;
		$itemDetailsCheck['operation']='insert';
		$this->itemCheck($code, $itemDetailsCheck);

		// updates the item in the list
		$calcCode=$this->doItemInsert($code, $itemDetails);

		$this->changedFlag=true;
		return $calcCode;
	}

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

		// get linear list containing all calculation (active and deleted)
		$linearList=$this->getExtendedList(true);

		// GETS CODE FOR THIS NEW CALCULATION

		// there is not a list of calculation type in this location for this service
		$calculationCode=0;

		// if exists another calculations type
		if ($linearList[$itemDetails['serviceCode']][$itemDetails['location']]) {
			$calculationCode=max(array_keys($linearList[$itemDetails['serviceCode']][$itemDetails['location']]));
			$calculationCode++;
		}

		$itemDetails['code']=$calculationCode;

		// default this calculation type is active
		$itemDetails['deleted']=0;

		$itemDetails['new']=true;

		// insert
		$this->itemsList[$itemDetails['serviceCode'].'_'.$itemDetails['location'].'_'.$calculationCode]=$itemDetails;

		return $calculationCode;
	}


	/**
	 * The method changes deleted flag false=inactive / true=active
	 * @see \App\Config\XmlDynamicRecordsAbstract::doItemDelete()
	 */
	protected function doItemDelete($code) {

		// code is only in session (it is not in the xml file)
		if ($this->itemsList[$code]['new']) {
			unset($this->itemsList[$code]);
		}

		// the calculation type is in the xml file)
		else {
			$this->itemsList[$code]['deleted']=true;
		}

	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::itemMoveUp()
	 */
	public function itemMoveUp($code) {

		// 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`.");
		// at least two elements must be present in the array
		if (count($this->itemsList)<2) $this->addErrorMessage('There has to be at least two elements so that moving them would make sense.');

		$calcDet=$this->itemsList[$code];
		$serviceCode=$calcDet['serviceCode'];
		$location=$calcDet['location'];
		$item=$calcDet['code'];
		$deleted=$calcDet['deleted'];

		$extendedList=$this->getExtendedList();
		$extendedList[$serviceCode][$location];

		// move the element up
		$codesList=array_keys($this->itemsList);
		$detailsList=array_values($this->itemsList);

		// get the position of the moving item
		$pos=array_search($code, $codesList);

		// get the position of the muving item from the service and location zone
		$codeListByServiceLocation=array_keys($extendedList[$serviceCode][$location]);
		$detailsListByServiceLocation=array_values($extendedList[$serviceCode][$location]);

		$posByServiceLocation=array_search($item, $codeListByServiceLocation);

		// if this is the first position - do not do anything
		if (!$posByServiceLocation) {
			return $this;
		}

		// new position in the linear list
		$newPos=array_search($serviceCode.'_'.$location.'_'.($codeListByServiceLocation[$posByServiceLocation-1]), $codesList);

		// interchange variables
		interchangeVariables($codesList[$newPos], $codesList[$pos]);
		interchangeVariables($detailsList[$newPos], $detailsList[$pos]);

		// save the new list after the two items were replace
		$this->itemsList=array_combine($codesList, $detailsList);

		$this->changedFlag=true;
		return $this;
	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlDynamicRecordsAbstract::itemMoveDown()
	 */
	public function itemMoveDown($code) {

		// 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`.");

		// at least two elements must be present in the array
		if (count($this->itemsList)<2) $this->addErrorMessage('There has to be at least two elements so that moving them would make sense.');

		$calcDet=$this->itemsList[$code];
		$serviceCode=$calcDet['serviceCode'];
		$location=$calcDet['location'];
		$item=$calcDet['code'];
		$deleted=$calcDet['deleted'];

		$extendedList=$this->getExtendedList();
		$extendedList[$serviceCode][$location];

		// move the element up
		$codesList=array_keys($this->itemsList);
		$detailsList=array_values($this->itemsList);

		// get the position of the moving item
		$pos=array_search($code, $codesList);

		// get the position of the mouving item from the service and location zone
		$codeListByServiceLocation=array_keys($extendedList[$serviceCode][$location]);
		$detailsListByServiceLocation=array_values($extendedList[$serviceCode][$location]);

		$posByServiceLocation=array_search($item, $codeListByServiceLocation);

		// if this is the last position - do not do anything
		if ($posByServiceLocation==count($extendedList[$serviceCode][$location])-1) {
			return $this;
		}

		// new position in the linear list
		$newPos=array_search($serviceCode.'_'.$location.'_'.($codeListByServiceLocation[$posByServiceLocation+1]), $codesList);

		// interchange variables
		interchangeVariables($codesList[$newPos], $codesList[$pos]);
		interchangeVariables($detailsList[$newPos], $detailsList[$pos]);

		// save the new list after the two items were replace
		$this->itemsList=array_combine($codesList, $detailsList);

		$this->changedFlag=true;
		return $this;

	}


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

		// check if the code is set
		if (!$code) {
			$this->addErrorMessage('The code was not defined.');
		}

		// check if details list is an array
		if (!is_array($itemDetails)) {
			throw new \Exception('Details were not defined.');
		}

		// check if the calculation name is set
		if (!strlen($itemDetails['name'])) {
			$this->addErrorMessage(sprintf('The name of calculation type was not defined') );
		}

		// check that the name does not exist
		$calcActiveList=$this->getExtendedList();

		// location contains active calculation type
		if ($calcActiveList[$itemDetails['serviceCode']][$itemDetails['location']]) {

			// scan each active(not deleted) calculation type from this location
			foreach($calcActiveList[$itemDetails['serviceCode']][$itemDetails['location']] as $c => $details) {

				// for insert operation the code does not exist - check that the name does not exist
				if ($itemDetails['operation']=='insert') {
					if ($itemDetails['name']==$details['name'] ) {
						$this->addErrorMessage(sprintf('This calculation type `%s` already exists', $itemDetails['name']) );
					}
				}

				// for update operation check the code - check that the name does not exist
				else {
					if ($c!=$itemDetails['code'] and $itemDetails['name']==$details['name']) {
						$this->addErrorMessage('This `calculation type name` already exists.');
					}
				}
			}
		}
	}


	/**
	 * Checks the input  for `save()` operation.
	 * The method calls the method from ServicesCalculationsClientType to check the client type from session
	 * then calls the method from ServicesCalculationsOfferDefaults to check the currency, currency type, commission percent from session.
	 * then calls the method from ServicesCalculationsOfferVAT to check vat percent for offer row types from session.
	 * @throws \App\Config\MessageException - the error message in case something is wrong.
	 */
	protected function save_check() {

		// check client type
		Services_Calculations_ClientTypes::getAdmin()->check();

		// check add offer default
		Services_Calculations_OfferDefaults::getAdmin()->check();

		// check service offer VAT details
		Services_Calculations_OfferVat::getAdmin()->check();

		// check service invoices
		Services_Calculations_InvoiceVat::getAdmin()->check();
	}


	/**
	 * Returns the simplexml object so it can be written on disk.
	 * First the method writes client types, then offer default values and vat default values, finally calculation types
	 * @see \App\Config\XmlDynamicRecordsAbstract::save_write_do_getXml()
	 */
	protected function save_write_do_getXml() {

		global $CFG;

		// WRITES XML FILES FOR OTHER OFFER AND INVOICE DETAILS ////////////////////////////////////

		// writes the calculation client types
		Services_Calculations_ClientTypes::getAdmin()->save_write();

		// writes the offer default
		Services_Calculations_OfferDefaults::getAdmin()->save_write();

		// writes the offer VAT Details
		Services_Calculations_OfferVat::getAdmin()->save_write();

		// writes the service invoice details
		Services_Calculations_InvoiceVat::getAdmin()->save_write();

		// WRITES CALCULATIONS TYPES ///////////////////////////////////////////////////////////////
		$xml=simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><serviceCalculationTypes />');

		// the list with all calculations
		$calculationsList=$this->getExtendedList(true);

		// scan each service
		foreach (Services_Basic::getInstance()->getListLinear() as $serviceCode => $serviceDetails) {

			// write if this service exists in calculation list
			if ($calculationsList[$serviceCode]) {

				// add service code
				$serviceObj=$xml->addChild('service');
				$serviceObj['code']=$serviceCode;

				// scan each location from config
				foreach ($CFG['countriesCriteria'] as $location) {

					// write if this location exists in calculation list
					if ($calculationsList[$serviceCode][$location]) {

						// scan each calculation type; surely there is at least one mode of calculation
						foreach ($calculationsList[$serviceCode][$location] as $calculationCode => $calculationDetails) {

							// calculation name
							$calculationTypeObj=$serviceObj->addChild('calculationType', $calculationDetails['name']);

							// location
							$calculationTypeObj['location']=$location;

							// calculation code
							$calculationTypeObj['code']=$calculationCode;

							// write inactive calculation
							if ($calculationDetails['deleted']) {
								$calculationTypeObj['deleted']=$calculationDetails['deleted'];
							}

						}
					}
				}
			}
		}

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

	}


}