<commit_before>﻿// Copyright (c) Narvalo.Org. All rights reserved. See LICENSE.txt in the project root for license information.

namespace Narvalo.Finance
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.Linq;
    using System.Threading;

    using Narvalo.Finance.Properties;
    using Narvalo.Finance.Utilities;

    /// <summary>
    /// Represents a currency such as Euro or US Dollar.
    /// </summary>
    /// <remarks>
    /// <para>Recognized currencies are defined in ISO 4217.</para>
    /// <para>There's never more than one <see cref="Currency"/> instance for any given currency.
    /// You can not directly construct a currency. You must instead use the
    /// static factories: <see cref="Currency.Of"/>.</para>
    /// <para>This class follows value type semantics when it comes to equality.</para>
    /// <para>This class does not offer extended information about the currency.</para>
    /// </remarks>
    public partial struct Currency : IEquatable<Currency>
    {
        private const char META_CURRENCY_MARK = 'X';

        // The list is automatically generated using data obtained from the SNV website.
        private static Dictionary<string, short?> s_Codes;

        private readonly string _code;

        /// <summary>
        /// Initializes a new instance of the <see cref="Currency" /> class for the specified code.
        /// </summary>
        /// <param name="code">A string that contains the three-letter identifier defined in ISO 4217.</param>
        internal Currency(string code, short? minorUnits)
        {
            Sentinel.Demand.CurrencyCode(code);

            _code = code;
            MinorUnits = minorUnits;
        }

        // The list is automatically generated using data obtained from the SNV website.
        private static decimal[] Epsilons => s_Epsilons;

        // The list is automatically generated using data obtained from the SNV website.
        private static decimal[] Powers10 => s_Powers10;

        /// <summary>
        /// Gets the alphabetic code of the currency.
        /// </summary>
        /// <value>The alphabetic code of the currency.</value>
        public string Code { get { Warrant.NotNull<string>(); return _code; } }

        /// <summary>
        /// Gets the number of minor units.
        /// </summary>
        /// <value>The number of minor units; <see langword="null"/> if none defined.</value>
        public short? MinorUnits { get; }

        public short DecimalPlaces => MinorUnits ?? 0;

        /// <summary>
        /// Gets a value indicating whether the currency is a meta-currency.
        /// </summary>
        /// <remarks>
        /// <para>Meta-currencies include supranational currencies (but notice that EUR
        /// is not part of them...), precious metals, the test currency, the "no"
        /// currency and currencies used in international finance.</para>
        /// <para>Meta-currencies are not attached to a specific country.
        /// Their numeric codes are in the range 900-999 and their codes are in the
        /// range XA(A)-XZ(Z). They fall in the ranges of user-assigned codes
        /// as defined by the ISO 3166 standard, ie they will never clash with
        /// those of a real country.</para>
        /// </remarks>
        /// <value><see langword="true"/> if the currency is a meta-currency; otherwise <see langword="false"/>.</value>
        public bool IsMeta => IsMetaCurrency(Code);

        /// <summary>
        /// Gets the smallest positive (non zero) unit.
        /// </summary>
        /// <remarks>Returns 1m if the currency has no minor unit.</remarks>
        public decimal Epsilon => Epsilons[DecimalPlaces];

        public decimal One => 1m;

        private decimal Factor => Powers10[DecimalPlaces];

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class for the specified alphabetic code.
        /// </summary>
        /// <param name="code">The three letter ISO-4217 code of the currency.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="code"/> is
        /// <see langword="null"/>.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the
        /// specified code.</exception>
        /// <returns>The currency for the specified code.</returns>
        public static Currency Of(string code)
        {
            Require.NotNull(code, nameof(code));
            Sentinel.Expect.CurrencyCode(code);

            Contract.Assume(Codes != null);
            short? minorUnits;
            if (!Codes.TryGetValue(code, out minorUnits))
            {
                throw new CurrencyNotFoundException(Format.Current(Strings.Currency_UnknownCode, code));
            }

            return new Currency(code, minorUnits);
        }

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class associated with the specified region.
        /// </summary>
        /// <param name="regionInfo">A region info.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="regionInfo"/> is
        /// <see langword="null"/>.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the
        /// specified region.</exception>
        /// <returns>The currency for the specified region info.</returns>
        public static Currency OfRegion(RegionInfo regionInfo)
        {
            Require.NotNull(regionInfo, nameof(regionInfo));

            //var code = regionInfo.ISOCurrencySymbol;
            //Contract.Assume(code != null);
            //Contract.Assume(code.Length != 0);   // Should not be necessary, but CCCheck insists.
            //Contract.Assume(Ascii.IsUpperLetter(code));
            //Contract.Assume(code.Length == 3);

            return Of(regionInfo.ISOCurrencySymbol);
        }

        public static Currency OfCurrentRegion() => OfRegion(RegionInfo.CurrentRegion);

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class associated
        /// with the specified culture.
        /// </summary>
        /// <param name="cultureInfo">A culture info.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="cultureInfo"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="cultureInfo"/> is neutral.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the specified culture.</exception>
        /// <returns>The currency for the specified culture info.</returns>
        public static Currency OfCulture(CultureInfo cultureInfo)
        {
            Require.NotNull(cultureInfo, nameof(cultureInfo));

            if (cultureInfo.IsNeutralCulture)
            {
                throw new ArgumentException(Strings.Argument_NeutralCultureNotSupported, nameof(cultureInfo));
            }

            return OfRegion(new RegionInfo(cultureInfo.Name));
        }

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class for the culture
        /// used by the current thread.
        /// </summary>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the current culture.</exception>
        /// <returns>The currency for the culture used by the current thread.</returns>
        public static Currency OfCurrentCulture() => OfCulture(CultureInfo.CurrentCulture);

        // This method allows to register currencies that are not part of ISO 4217.
        // For details, see https://en.wikipedia.org/wiki/ISO_4217.
        // If you have more than one currency to register, you should use RegisterCurrencies()
        // instead; it will be more efficient.
        // This method should be thread-safe.
        public static bool RegisterCurrency(string code, short? minorUnits)
        {
            Sentinel.Require.CurrencyCode(code, nameof(code));

            Contract.Assume(Codes != null);
            if (Codes.ContainsKey(code)) { return false; }

            // We work on a temporary copy of s_Codes (which is guaranteed to be non-null since we
            // just called Codes). This is not very efficient but ensures that s_Codes does not end
            // up in a broken state if anything bad happens. Also note that, with competing
            // threads, we may end up creating 'tmpCopy' more than once. Anyway, we do not expect
            // this method to be called very often, if ever.
            var tmpCopy = s_Codes.ToDictionary(_ => _.Key, _ => _.Value);
            tmpCopy[code] = minorUnits;

            // We use a volatile write to make sure that instructions don't get re-ordered.
            Volatile.Write(ref s_Codes, tmpCopy);

            return true;
        }

        // This method should be thread-safe.
        public static void RegisterCurrencies(Dictionary<string, short?> currencies)
        {
            // See RegisterCurrency() for explanations. Comparing with it, we do not use s_Codes
            // directly, because we don't know yet if it has been initialized.
            var tmpCopy = Codes.ToDictionary(_ => _.Key, _ => _.Value);

            foreach (var pair in currencies)
            {
                var code = pair.Key;
                if (code == null
                    || !Ascii.IsUpperLetter(code)
                    || code.Length != 3)
                {
                    throw new ArgumentException("XXX");
                }

                if (!tmpCopy.ContainsKey(code))
                {
                    tmpCopy[code] = pair.Value;
                }
            }

            Volatile.Write(ref s_Codes, tmpCopy);
        }

        // TODO: What about EUR, CFP... for this, is it enough to check the country code too
        // (the first two letters)?
        internal static bool IsMetaCurrency(string currencyCode)
        {
            Demand.NotNullOrEmpty(currencyCode);

            return currencyCode[0] == META_CURRENCY_MARK;
        }

        public bool IsNativeTo(CultureInfo cultureInfo)
        {
            Require.NotNull(cultureInfo, nameof(cultureInfo));

            if (cultureInfo.IsNeutralCulture) { return false; }

            var ri = new RegionInfo(cultureInfo.Name);

            return ri.ISOCurrencySymbol == Code;
        }

        /// <summary>
        /// Converts an amount to a value expressed in minor units.
        /// </summary>
        /// <remarks>We expect the amount to be normalized.</remarks>
        /// <exception cref="OverflowException">Thrown if the amount is too large to fit into
        /// the Int64 range.</exception>
        internal long ConvertToMinorUnits(decimal major)
            => Convert.ToInt64(Factor * major);

        public long ConvertToMinorUnits(decimal amount, MidpointRounding rounding)
            => ConvertToMinorUnits(Math.Round(amount, DecimalPlaces, rounding));

        public long? TryConvertToMinorUnits(decimal amount, MidpointRounding rounding)
        {
            decimal major = Math.Round(amount, DecimalPlaces, rounding);
            decimal minor = Factor * major;

            if (minor < Int64.MaxValue || minor > Int64.MaxValue) { return null; }

            return Convert.ToInt64(minor);
        }

        public bool TryConvertToMinorUnits(decimal amount, MidpointRounding rounding, out long result)
        {
            decimal major = Math.Round(amount, DecimalPlaces, rounding);
            decimal minor = Factor * major;

            if (minor < Int64.MaxValue || minor > Int64.MaxValue)
            {
                result = major < 0 ? Int64.MinValue : Int64.MaxValue;
                return false;
            }

            result = Convert.ToInt64(minor);
            return true;
        }

        public decimal ConvertToMajorUnits(long amount) => Epsilon * amount;

        /// <summary>
        /// Returns a string containing the code of the currency.
        /// </summary>
        /// <returns>A string containing the code of the currency.</returns>
        public override string ToString()
        {<commit_msg>Fix: typo.<commit_after>﻿// Copyright (c) Narvalo.Org. All rights reserved. See LICENSE.txt in the project root for license information.

namespace Narvalo.Finance
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.Linq;
    using System.Threading;

    using Narvalo.Finance.Properties;
    using Narvalo.Finance.Utilities;

    /// <summary>
    /// Represents a currency such as Euro or US Dollar.
    /// </summary>
    /// <remarks>
    /// <para>Recognized currencies are defined in ISO 4217.</para>
    /// <para>There's never more than one <see cref="Currency"/> instance for any given currency.
    /// You can not directly construct a currency. You must instead use the
    /// static factories: <see cref="Currency.Of"/>.</para>
    /// <para>This class follows value type semantics when it comes to equality.</para>
    /// <para>This class does not offer extended information about the currency.</para>
    /// </remarks>
    public partial struct Currency : IEquatable<Currency>
    {
        private const char META_CURRENCY_MARK = 'X';

        // The list is automatically generated using data obtained from the SNV website.
        private static Dictionary<string, short?> s_Codes;

        private readonly string _code;

        /// <summary>
        /// Initializes a new instance of the <see cref="Currency" /> class for the specified code.
        /// </summary>
        /// <param name="code">A string that contains the three-letter identifier defined in ISO 4217.</param>
        internal Currency(string code, short? minorUnits)
        {
            Sentinel.Demand.CurrencyCode(code);

            _code = code;
            MinorUnits = minorUnits;
        }

        // The list is automatically generated using data obtained from the SNV website.
        private static decimal[] Epsilons => s_Epsilons;

        // The list is automatically generated using data obtained from the SNV website.
        private static decimal[] Powers10 => s_Powers10;

        /// <summary>
        /// Gets the alphabetic code of the currency.
        /// </summary>
        /// <value>The alphabetic code of the currency.</value>
        public string Code { get { Warrant.NotNull<string>(); return _code; } }

        /// <summary>
        /// Gets the number of minor units.
        /// </summary>
        /// <value>The number of minor units; <see langword="null"/> if none defined.</value>
        public short? MinorUnits { get; }

        public short DecimalPlaces => MinorUnits ?? 0;

        /// <summary>
        /// Gets a value indicating whether the currency is a meta-currency.
        /// </summary>
        /// <remarks>
        /// <para>Meta-currencies include supranational currencies (but notice that EUR
        /// is not part of them...), precious metals, the test currency, the "no"
        /// currency and currencies used in international finance.</para>
        /// <para>Meta-currencies are not attached to a specific country.
        /// Their numeric codes are in the range 900-999 and their codes are in the
        /// range XA(A)-XZ(Z). They fall in the ranges of user-assigned codes
        /// as defined by the ISO 3166 standard, ie they will never clash with
        /// those of a real country.</para>
        /// </remarks>
        /// <value><see langword="true"/> if the currency is a meta-currency; otherwise <see langword="false"/>.</value>
        public bool IsMeta => IsMetaCurrency(Code);

        /// <summary>
        /// Gets the smallest positive (non zero) unit.
        /// </summary>
        /// <remarks>Returns 1m if the currency has no minor unit.</remarks>
        public decimal Epsilon => Epsilons[DecimalPlaces];

        public decimal One => 1m;

        private decimal Factor => Powers10[DecimalPlaces];

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class for the specified alphabetic code.
        /// </summary>
        /// <param name="code">The three letter ISO-4217 code of the currency.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="code"/> is
        /// <see langword="null"/>.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the
        /// specified code.</exception>
        /// <returns>The currency for the specified code.</returns>
        public static Currency Of(string code)
        {
            Require.NotNull(code, nameof(code));
            Sentinel.Expect.CurrencyCode(code);

            Contract.Assume(Codes != null);
            short? minorUnits;
            if (!Codes.TryGetValue(code, out minorUnits))
            {
                throw new CurrencyNotFoundException(Format.Current(Strings.Currency_UnknownCode, code));
            }

            return new Currency(code, minorUnits);
        }

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class associated with the specified region.
        /// </summary>
        /// <param name="regionInfo">A region info.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="regionInfo"/> is
        /// <see langword="null"/>.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the
        /// specified region.</exception>
        /// <returns>The currency for the specified region info.</returns>
        public static Currency OfRegion(RegionInfo regionInfo)
        {
            Require.NotNull(regionInfo, nameof(regionInfo));

            //var code = regionInfo.ISOCurrencySymbol;
            //Contract.Assume(code != null);
            //Contract.Assume(code.Length != 0);   // Should not be necessary, but CCCheck insists.
            //Contract.Assume(Ascii.IsUpperLetter(code));
            //Contract.Assume(code.Length == 3);

            return Of(regionInfo.ISOCurrencySymbol);
        }

        public static Currency OfCurrentRegion() => OfRegion(RegionInfo.CurrentRegion);

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class associated
        /// with the specified culture.
        /// </summary>
        /// <param name="cultureInfo">A culture info.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="cultureInfo"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="cultureInfo"/> is neutral.</exception>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the specified culture.</exception>
        /// <returns>The currency for the specified culture info.</returns>
        public static Currency OfCulture(CultureInfo cultureInfo)
        {
            Require.NotNull(cultureInfo, nameof(cultureInfo));

            if (cultureInfo.IsNeutralCulture)
            {
                throw new ArgumentException(Strings.Argument_NeutralCultureNotSupported, nameof(cultureInfo));
            }

            return OfRegion(new RegionInfo(cultureInfo.Name));
        }

        /// <summary>
        /// Obtains an instance of the <see cref="Currency" /> class for the culture
        /// used by the current thread.
        /// </summary>
        /// <exception cref="CurrencyNotFoundException">Thrown if no currency exists for the current culture.</exception>
        /// <returns>The currency for the culture used by the current thread.</returns>
        public static Currency OfCurrentCulture() => OfCulture(CultureInfo.CurrentCulture);

        // This method allows to register currencies that are not part of ISO 4217.
        // For details, see https://en.wikipedia.org/wiki/ISO_4217.
        // If you have more than one currency to register, you should use RegisterCurrencies()
        // instead; it will be more efficient.
        // This method should be thread-safe.
        public static bool RegisterCurrency(string code, short? minorUnits)
        {
            Sentinel.Require.CurrencyCode(code, nameof(code));

            Contract.Assume(Codes != null);
            if (Codes.ContainsKey(code)) { return false; }

            // We work on a temporary copy of s_Codes (which is guaranteed to be non-null since we
            // just called Codes). This is not very efficient but ensures that s_Codes does not end
            // up in a broken state if anything bad happens. Also note that, with competing
            // threads, we may end up creating 'tmpCopy' more than once. Anyway, we do not expect
            // this method to be called very often, if ever.
            var tmpCopy = s_Codes.ToDictionary(_ => _.Key, _ => _.Value);
            tmpCopy[code] = minorUnits;

            // We use a volatile write to make sure that instructions don't get re-ordered.
            Volatile.Write(ref s_Codes, tmpCopy);

            return true;
        }

        // This method should be thread-safe.
        public static void RegisterCurrencies(Dictionary<string, short?> currencies)
        {
            // See RegisterCurrency() for explanations. Comparing with it, we do not use s_Codes
            // directly, because we don't know yet if it has been initialized.
            var tmpCopy = Codes.ToDictionary(_ => _.Key, _ => _.Value);

            foreach (var pair in currencies)
            {
                var code = pair.Key;
                if (code == null
                    || !Ascii.IsUpperLetter(code)
                    || code.Length != 3)
                {
                    throw new ArgumentException("XXX");
                }

                if (!tmpCopy.ContainsKey(code))
                {
                    tmpCopy[code] = pair.Value;
                }
            }

            Volatile.Write(ref s_Codes, tmpCopy);
        }

        // TODO: What about EUR, CFP... for this, is it enough to check the country code too
        // (the first two letters)?
        internal static bool IsMetaCurrency(string currencyCode)
        {
            Demand.NotNullOrEmpty(currencyCode);

            return currencyCode[0] == META_CURRENCY_MARK;
        }

        public bool IsNativeTo(CultureInfo cultureInfo)
        {
            Require.NotNull(cultureInfo, nameof(cultureInfo));

            if (cultureInfo.IsNeutralCulture) { return false; }

            var ri = new RegionInfo(cultureInfo.Name);

            return ri.ISOCurrencySymbol == Code;
        }

        /// <summary>
        /// Converts an amount to a value expressed in minor units.
        /// </summary>
        /// <remarks>We expect the amount to be normalized.</remarks>
        /// <exception cref="OverflowException">Thrown if the amount is too large to fit into
        /// the Int64 range.</exception>
        internal long ConvertToMinorUnits(decimal major)
            => Convert.ToInt64(Factor * major);

        public long ConvertToMinorUnits(decimal amount, MidpointRounding rounding)
            => ConvertToMinorUnits(Math.Round(amount, DecimalPlaces, rounding));

        public long? TryConvertToMinorUnits(decimal amount, MidpointRounding rounding)
        {
            decimal major = Math.Round(amount, DecimalPlaces, rounding);
            decimal minor = Factor * major;

            if (minor < Int64.MinValue || minor > Int64.MaxValue) { return null; }

            return Convert.ToInt64(minor);
        }

        public bool TryConvertToMinorUnits(decimal amount, MidpointRounding rounding, out long result)
        {
            decimal major = Math.Round(amount, DecimalPlaces, rounding);
            decimal minor = Factor * major;

            if (minor < Int64.MinValue || minor > Int64.MaxValue)
            {
                result = major < 0 ? Int64.MinValue : Int64.MaxValue;
                return false;
            }

            result = Convert.ToInt64(minor);
            return true;
        }

        public decimal ConvertToMajorUnits(long amount) => Epsilon * amount;

        /// <summary>
        /// Returns a string containing the code of the currency.
        /// </summary>
        /// <returns>A string containing the code of the currency.</returns>
        public override string ToString()
        {
{'fraction_non_alphanumeric': 0.10531452039198146, 'fraction_numerical': 0.005204733054121092, 'mean_word_length': 2.0838871473354232, 'pattern_counts': {'":': 0, '<': 223, '<?xml version=': 0, '>': 253, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5301712', 'n_tokens_mistral': 6326, 'n_tokens_neox': 6071, 'n_words': 2083}