<?php
/**
 * @brief		Money Object
 *
 * @copyright	(c) 2001 - SVN_YYYY Invision Power Services, Inc.
 *
 * @package		IPS Social Suite
 * @subpackage	Nexus
 * @since		11 Feb 2014
 * @version		SVN_VERSION_NUMBER
 */

namespace IPS\nexus;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}

/**
 * Money Object
 */
class _Money
{	
	/**
	 * Get available currencies
	 *
	 * @return	array
	 */
	public static function currencies()
	{
		if ( $currencies = json_decode( \IPS\Settings::i()->nexus_currency, TRUE ) )
		{
			return array_keys( $currencies );
		}
		else
		{
			return array( \IPS\Settings::i()->nexus_currency );
		}
	}
		
	/**
	 * @brief	Amount
	 */
	public $amount;
	
	/**
	 * @brief	Currency
	 */
	public $currency;
	
	/**
	 * @brief	Number of decimal points
	 */
	protected $numberOfDecimalPlaces = 2;
	
	/**
	 * Contructor
	 *
	 * @param	float	$amount		Amount
	 * @param	string	$currency	Currency code
	 * @return	void
	 * @note	If you specify $1.005, this will be deliberately accepted. Call ->round() to round
	 */
	public function __construct( $amount, $currency )
	{
		$this->amount = floatval( $amount );
		$this->currency = $currency;
	}
	
	/**
	 * Round to the correct number of decimal places for the currency
	 *
	 * @param	bool	$alwaysUp	If TRUE, 0.051 would be rounded to 0.06 rather than 0.05
	 * @return	\IPS\nexus\Money
	 */
	public function round( $alwaysUp = TRUE )
	{
		if ( mb_strlen( mb_substr( mb_strrchr( $this->amount, '.' ), 1 ) ) > $this->numberOfDecimalPlaces )
		{
			if ( $alwaysUp )
			{
				$mult = pow( 10, $this->numberOfDecimalPlaces );
				$this->amount = ceil( $this->amount * $mult ) / $mult;
			}
			else
			{
				$this->amount = round( $this->amount, $this->numberOfDecimalPlaces );
			}
		}
		return $this;
	}
	
	/**
	 * Amount as string (not formatted, not locale aware)
	 * Used for gateways where, for example, (float) 10.5 is not acceptable, and (string) "10.50" is required
	 *
	 * @return	\IPS\nexus\Money
	 */
	public function amountAsString()
	{
		return sprintf( "%01." . $this->numberOfDecimalPlaces . "F", $this->amount );
	}
	
	/**
	 * To string
	 *
	 * @return	string
	 */
	public function __toString()
	{
		return $this->toString( \IPS\Member::loggedIn()->language() );
	}
	
	/**
	 * To string for language
	 *
	 * @return	string
	 */
	public function toString( \IPS\Lang $language )
	{		
		/* If the server has intl installed, use that */
		if ( function_exists( 'numfmt_format_currency' ) )
		{
			/* Unfortunately a bug in intl means that locales which use commas for decimal separation are broken.
				@link https://community.---.com/4bugtrack/active-reports/nexus-money-formatting-r6911/ .
				What we'll do is store the current locale, reset to English, then reset back to current locale afterwards. */
			$currentLocale = setlocale( LC_ALL, '0' );

			if( mb_strtoupper( mb_substr( PHP_OS, 0, 3 ) ) === 'WIN' )
			{
				setlocale( LC_ALL, 'english' );
			}
			else
			{
				setlocale( LC_ALL, 'en_US' );
			}

			$format = numfmt_create( $language->short, \NumberFormatter::CURRENCY );

			if ( $return = numfmt_format_currency( $format, $this->amount, $this->currency ) and $return !== 'NaN' )
			{
				/* Now we have to reset locale back to what it was */
				foreach( explode( ";", $currentLocale ) as $locale )
				{
					if( mb_strpos( $locale, '=' ) !== FALSE )
					{
						$parts = explode( "=", $locale );
						if( in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
						{
							setlocale( constant( $parts[0] ), $parts[1] );
						}
					}
					else
					{
						setLocale( LC_ALL, $locale );
					}
				}

				return $return;
			}
		}
		
		/* If this currency matches the locale the user is using, and money_format is supported (Windows doesn't support it), use that */
		if ( function_exists( 'money_format' ) and trim( $language->locale['int_curr_symbol'] ) === $this->currency )
		{
			return money_format( '%n', $this->amount );
		}
				
		/* If it matches any of the installed languages, we can do something with the locale data */
		foreach ( \IPS\Lang::languages() as $lang )
		{
			if ( isset( $lang->locale['int_curr_symbol'] ) AND trim( $lang->locale['int_curr_symbol'] ) === $this->currency )
			{
				$currencySymbol = $lang->locale['currency_symbol'];
				$currencySymbolPreceeds = ( $this->amount >= 0 ) ? $lang->locale['p_cs_precedes'] : $lang->locale['n_cs_precedes'];
				$spaceBetweenCurrencySymbolAndAmount = ( ( $this->amount >= 0 ) ? $lang->locale['p_sep_by_space'] : $lang->locale['n_sep_by_space'] ) ? ' ' : '';
				$amount = number_format( $this->amount, $lang->locale['frac_digits'] );
				
				$positiveNegativeSymbol = ( $this->amount >= 0 ) ? $lang->locale['positive_sign'] : $lang->locale['negative_sign'];
				if ( $positiveNegativeSymbol )
				{
					$positiveNegativeSymbolFormat = ( $this->amount >= 0 ) ? $lang->locale['p_sign_posn'] : $lang->locale['n_sign_posn'];
					for ( $i=0; $i < \strlen( $positiveNegativeSymbolFormat ); $i++ )
					{
						switch ( $positiveNegativeSymbolFormat[ $i ] )
						{
							case 0:
								if ( $currencySymbolPreceeds )
								{
									$currencySymbol = "({$currencySymbol}";
									$amount = "{$amount})";
								}
								else
								{
									$currencySymbol = "{$currencySymbol})";
									$amount = "({$amount}";
								}
								break;
							case 1:
								if ( $currencySymbolPreceeds )
								{
									$currencySymbol = "{$positiveNegativeSymbol}{$currencySymbol}";
								}
								else
								{
									$amount = "{$positiveNegativeSymbol}{$amount}";
								}
								break;
							case 2:
								if ( $currencySymbolPreceeds )
								{
									$amount = "{$amount}{$positiveNegativeSymbol}";
								}
								else
								{
									$currencySymbol = "{$currencySymbol}{$positiveNegativeSymbol}";
								}
								break;
							case 3:
								$currencySymbol = ( $positiveNegativeSymbol . $currencySymbol );
								break;
							case 4:
								$currencySymbol = ( $currencySymbol . $positiveNegativeSymbol );
								break;
						}
					}
				}
				
				return ( $currencySymbolPreceeds ? ( $currencySymbol . $spaceBetweenCurrencySymbolAndAmount . $amount ) : ( $amount . $space . $currencySymbol ) );
			}
		}

		/* And if all else fails, just use the currency code */
		return number_format( $this->amount, 2 ) . " {$this->currency}";
	}
}