<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Classes\Util;

use InvalidArgumentException;
use Shopware\Models\Shop\Currency as CurrencyModel;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\CurrencyException;

class Currency
{
    /**
     * Returns a string containing the formated price as well as the symbol of the default currency.
     *
     * @param float|int $price
     * @return string
     */
    public static function getFormattedPriceString($price)
    {
        return static::getFormattedPriceStringByCurrency($price, static::getDefaultCurrency());
    }

    /**
     * Converts the given float amount from one currency to another.
     *
     * Uses the currencies factors to calculate the new amount and rounds the result to two decimals precision.
     *
     * @param float $amount
     * @param CurrencyModel $fromCurrency
     * @param CurrencyModel $toCurrency
     * @return float
     */
    public static function convertAmountBetweenCurrencies($amount, CurrencyModel $fromCurrency, CurrencyModel $toCurrency)
    {
        if ($fromCurrency->getFactor() === 0.0) {
            throw CurrencyException::currencyFactorInvalid($fromCurrency->getCurrency());
        }
        if ($toCurrency->getFactor() === 0.0) {
            throw CurrencyException::currencyFactorInvalid($toCurrency->getCurrency());
        }

        return round($amount * $toCurrency->getFactor() / $fromCurrency->getFactor(), 2);
    }

    /**
     * Returns a string containing the formatted price as well as the symbol of the given currency.
     *
     * @param float|int $price
     * @param CurrencyModel $currency
     * @return string
     */
    public static function getFormattedPriceStringByCurrency($price, CurrencyModel $currency)
    {
        return self::getFormattedPriceStringBySymbolAndPosition(
            $price,
            $currency->getSymbol(),
            !static::isCurrencySymbolOnLeft($currency)
        );
    }

    /**
     * Returns a string containing the formatted price with the given symbol and symbol position.
     *
     * @param float $price
     * @param string $symbol
     * @param bool $symbolAtEnd
     * @return string
     */
    private static function getFormattedPriceStringBySymbolAndPosition($price, $symbol, $symbolAtEnd)
    {
        $formattedPriceValue = number_format($price, 2, ',', '');
        $decodedSymbol = html_entity_decode($symbol, ENT_COMPAT | ENT_HTML5, 'UTF-8');

        if ($symbolAtEnd) {
            return sprintf('%s %s', $formattedPriceValue, $decodedSymbol);
        }

        return sprintf('%s %s', $decodedSymbol, $formattedPriceValue);
    }

    /**
     * Returns the currency marked as default.
     *
     * @return CurrencyModel
     * @throws \Exception If the default shop does not have a currency.
     */
    public static function getDefaultCurrency()
    {
        $standardCurrency = Shopware()->Models()->getRepository('Shopware\\Models\\Shop\\Currency')->findOneBy(
            [
                'default' => true,
            ]
        );
        if (!$standardCurrency) {
            throw new \Exception('Could not determine standard currency');
        }

        return $standardCurrency;
    }

    /**
     * Determines wether the currency symbol is on the left or the right side of the price
     * (0: default, 16: right, 32: left).
     *
     * @see https://github.com/shopware/shopware/blob/fae85ada8fdcad3b537f01eb67b444029ee052f1/themes/Backend/ExtJs/backend/config/view/form/currency.js#L83-L85
     *
     * @param CurrencyModel $currency
     * @return bool
     */
    public static function isCurrencySymbolOnLeft(CurrencyModel $currency)
    {
        return $currency->getSymbolPosition() == 32;
    }

    /**
     * Registers the "viison_currency" smarty modifier plugin, which is used like this:
     *
     * <code>
     * {$currencyAmountFloat|viison_currency:$currency}
     * </code>
     *
     * where $currency is an instance of Currency and is optional (default currency is used instead). The modifier will
     * format the currency amount, respecting the defined currency symbol position.
     *
     * This method is idempotent.
     *
     * @param \Smarty $smarty
     */
    public static function addViisonCurrencyModifier(\Smarty $smarty)
    {
        // Make this method idempotent so calling it more than once on the same Smarty instance won't throw
        $smarty->unregisterPlugin('modifier', 'viison_currency');
        $smarty->registerPlugin(
            'modifier',
            'viison_currency',
            [
                'Shopware\\Plugins\\ViisonCommon\\Classes\\Util\\Currency',
                'viisonCurrencyModifier',
            ]
        );
    }

    /**
     * Implements the viison_currency smarty modifier plugin.
     *
     * Accepts no currency, a \Shopware\Models\Shop\Currency, or a currency definition (array) that holds a symbol or
     * code and symbol position (symbolAtEnd). If no currency was given at all, the default currency is used. If no
     * valid currency parameter was given, an InvalidArgumentException is thrown.
     *
     * @internal
     * @param float $amount the currency amount to format
     * @param CurrencyModel|array|null $currency the currency to use if different from the default currency
     * @return string the formatted currency amount
     */
    public static function viisonCurrencyModifier($amount, $currency)
    {
        $currency = $currency ?: self::getDefaultCurrency();
        if ($currency instanceof CurrencyModel) {
            return self::getFormattedPriceStringByCurrency($amount, $currency);
        }

        if (is_array($currency)) {
            return self::getFormattedPriceStringBySymbolAndPosition(
                $amount,
                $currency['symbol'] ?: $currency['code'],
                $currency['symbolAtEnd']
            );
        }

        throw new InvalidArgumentException(
            'Invalid parameter given. "$currency" must be of type Shopware\\Models\\Shop\\Currency|array|null.'
        );
    }
}
