<?php
namespace Ipol\DPD;

use \Ipol\DPD\API\User\User as API;
use \Ipol\DPD\API\User\UserInterface;
use \Ipol\DPD\Currency\ConverterInterface;

/**
 * Класс калькулятор стоимости доставки
 */
class Calculator
{
	protected static $lastResult = false;

	protected $api;

	protected $shipment;

	protected $currencyConverter;

	/**
	 * Возвращает список поддерживаемых тарифов
	 *
	 * @return array
	 */
	public static function TariffList()
	{
		return array(
			"PCL" => "DPD Online Classic",
			// "CUR" => "DPD CLASSIC domestic",
			"CSM" => "DPD Online Express",
			"ECN" => "DPD ECONOMY",
			"ECU" => "DPD ECONOMY CU",
			// "NDY" => "DPD EXPRESS",
			// "TEN" => "DPD 10:00",
			// "DPT" => "DPD 13:00",
			// "BZP" => "DPD 18:00",
		);
	}

	/**
	 * Возвращает список тарифов которые будут использованы в расчете
	 *
	 * @return array
	 */
	public function AllowedTariffList()
	{
		$disableTariffs = (array) $this->getConfig()->get('TARIFF_OFF');
		return array_diff_key(static::TariffList(), array_flip($disableTariffs));
	}

	/**
	 * Возвращает последний расчет
	 * 
	 * @return array
	 */
	public static function getLastResult()
	{
		return static::$lastResult;
	}

	/**
	 * Конструктор
	 * 
	 * @param \Ipol\DPD\Shipment               $shipment  отправление
	 * @param \Ipol\DPD\API\User\UserInterface $api       инстанс API который будет использован в расчете,
	 *                                                    по умолчанию будет взят из конфига
	 */
	public function __construct(Shipment $shipment, UserInterface $api = null)
	{
		$this->shipment                  = $shipment;
		$this->api                       = $api ?: API::getInstanceByConfig($this->getConfig());
		$this->defaultTariffCode         = $this->getConfig()->get('DEFAULT_TARIFF_CODE');
		$this->minCostWhichUsedDefTariff = $this->getConfig()->get('DEFAULT_TARIFF_THRESHOLD', 0);
	}

	/**
	 * Возвращает конфиг
	 */
	public function getConfig()
	{
		return $this->getShipment()->getConfig();
	}

	/**
	 * Устанавливает конвертер валюты
	 * 
	 * @param \Ipol\DPD\Currency\ConverterInterface $converter
	 * 
	 * @return self
	 */
	public function setCurrencyConverter(ConverterInterface $converter)
	{
		$this->currencyConverter = $converter;

		return $this;
	}

	/**
	 * Возвращает конвертер валюты
	 * 
	 * @return \Ipol\DPD\Currency\ConverterInterface $converter
	 */
	public function getCurrencyConverter()
	{
		return $this->currencyConverter;
	}


	/**
	 * Устанавливает посылку для расчета стоимости
	 * 
	 * @param \Ipol\DPD\Shipment $shipment
	 * 
	 * @return self
	 */
	public function setShipment(Shipment $shipment)
	{
		$this->shipment = $shipment;

		return $this;
	}

	/**
	 * Возвращает посыдку для расчета стоимости
	 * 
	 * @return \Ipol\DPD\Shipment $shipment
	 */
	public function getShipment()
	{
		return $this->shipment;
	}

	/**
	 * Устанавливает тариф и порог мин. стоимости доставки
	 * при не достижении которого будет использован переданный тариф
	 * 
	 * @param string  $tariffCode
	 * @param float   $minCostWhichUsedTariff
	 * 
	 * @return self
	 */
	public function setDefaultTariff($tariffCode, $minCostWhichUsedTariff = 0)
	{
		$this->defaultTariffCode = $tariffCode;
		$this->minCostWhichUsedDefTariff = $minCostWhichUsedTariff;

		return $this;
	}

	/**
	 * Возвращает тариф по умолчанию
	 * 
	 * @return string
	 */
	public function getDefaultTariff()
	{
		return $this->defaultTariffCode;
	}

	/**
	 * Возвращает порог стоимости доставки при недостижении которого
	 * будет использован тариф по умолчанию
	 * 
	 * @return float
	 */
	public function getMinCostWhichUsedDefTariff()
	{
		return $this->minCostWhichUsedDefTariff;
	}

	/**
	 * Расчитывает стоимость доставки.
	 * 
	 * Возвращает оптимальный тариф доставки (минимальный по цене для клиента)
	 * 
	 * При передачи параметра $currency и установки конвертера, стоимость будет
	 * автоматически сконвертирована в переданную валюту
	 * 
	 * @see setCurrencyConverter
	 * 
	 * @param string $currency валюта
	 * 
	 * @return array
	 */
	public function calculate($currency = false)
	{
		if (!$this->getShipment()->isPossibileDelivery()) {
			return false;
		}

		$parms = $this->getServiceParmsArray();
		$tariffs = $this->getListFromService($parms);

		if (empty($tariffs)) {
			return false;
		}

		$tariff = $this->getActualTariff($tariffs);
		$tariff = $this->adjustTariffWithCommission($tariff);
		$tariff = $this->convertCurrency($tariff, $currency);

		return self::$lastResult = $tariff;
	}

	/**
	 * Расчитывает стоимость доставки с помощью конкретного тарифа
	 * 
	 * Возвращает стоимость доставки c помощью указанного тарифа
	 * 
	 * @param  string $tariffCode код тарифа
	 * @param  string $currency   валюта
	 * 
	 * @return array
	 */
	public function calculateWithTariff($tariffCode, $currency = false)
	{
		if (!$this->getShipment()->isPossibileDelivery()) {
			return false;
		}

		$parms = $this->getServiceParmsArray();
		$tariffs = $this->getListFromService($parms);

		if (empty($tariffs)) {
			return false;
		}

		foreach($tariffs as $tariff) {
			if ($tariff['SERVICE_CODE'] == $tariffCode) {
				$tariff = $this->adjustTariffWithCommission($tariff);
				$tariff = $this->convertCurrency($tariff, $currency);

				return self::$lastResult = $tariff;
			}
		}

		return false;
	}

	/**
	 * Корректирует стоимость тарифа с учетом комиссии на наложенный платеж 
	 * 
	 * @param  array $tariff
	 * @param  int   $personTypeId
	 * @param  int   $paySystemId
	 * 
	 * @return array
	 */
	public function adjustTariffWithCommission($tariff)
	{
		if (!$this->getShipment()->isPaymentOnDelivery()) {
			return $tariff;
		}

		$payment = $this->getShipment()->getPaymentMethod();

		$useCommission     = $this->getConfig()->get('COMMISSION_NPP_CHECK',   false, $payment['PERSON_TYPE_ID']);
		$commissionPercent = $this->getConfig()->get('COMMISSION_NPP_PERCENT', 2,     $payment['PERSON_TYPE_ID']);
		$minCommission     = $this->getConfig()->get('COMMISSION_NPP_MINSUM',  0,     $payment['PERSON_TYPE_ID']);

		if (!$useCommission) {
			return $tariff;
		}

		$sum = ($this->getShipment()->getPrice() * $commissionPercent / 100);
		$tariff['COST'] += $sum < $minCommission ? $minCommission : $sum;

		return $tariff;
	}

	/**
	 * Возвращает параметры для передачи в API
	 * 
	 * @return array
	 */
	public function getServiceParmsArray()
	{
		return array(
			'PICKUP'         => $this->getShipment()->getSender(),
			'DELIVERY'       => $this->getShipment()->getReceiver(),
			'WEIGHT'         => $this->getShipment()->getWeight(),
			'VOLUME'         => $this->getShipment()->getVolume(),
			'SELF_PICKUP'    => $this->getShipment()->getSelfPickup()   ? 1 : 0,
			'SELF_DELIVERY'  => $this->getShipment()->getSelfDelivery() ? 1 : 0,
			'DECLARED_VALUE' => $this->getShipment()->getDeclaredValue() ? round($this->shipment->getPrice(), 2) : 0,
		);
	}

	/**
	 * Выполняет расчет через API
	 * 
	 * Возвращает список тарифов и их стоимость с учетом используемых тарифов
	 *  
	 * @param  array $parms массив параметров для расчета
	 * 
	 * @return array
	 */
	public function getListFromService($parms)
	{
		$tariffs = $this->api->getService('calculator')->getServiceCost($parms);
		return array_intersect_key($tariffs, $this->AllowedTariffList());
	}

	/**
	 * Ищет оптимальный тариф среди списка
	 * 
	 * @param  array $tariffs
	 * 
	 * @return array
	 */
	protected function getActualTariff(array $tariffs)
	{
		$defaultTariff = false;
		$actualTariff = reset($tariffs);

		foreach($tariffs as $tariff) {
			if ($tariff['SERVICE_CODE'] == $this->getDefaultTariff()) {
				$defaultTariff = $tariff;
			}

			if ($tariff['COST'] < $actualTariff['COST']) {
				$actualTariff = $tariff;
			}
		}

		if ($defaultTariff
			&& $actualTariff['COST'] < $this->getMinCostWhichUsedDefTariff()
		) {
			return $defaultTariff;
		}

		return $actualTariff;
	}

	/**
	 * Конвертирует стоимость доставки в указанную валюту
	 * 
	 * @param array  $tariff
	 * @param string $currencyTo
	 * 
	 * @return array
	 */
	protected function convertCurrency($tariff, $currencyTo)
	{
		$converter = $this->getCurrencyConverter();
		
		if ($converter) {
			$currencyFrom = $this->api->getClientCurrency();
			$currencyTo   = $currencyTo ?: $currencyFrom;

			$tariff['COST']     = $converter->convert($tariff['COST'], $currencyFrom, $currencyTo);
			$tariff['CURRENCY'] = $currencyTo;
		}

		return $tariff;
	}
}