/* UtilLib - Copyright (c) 2013-2018 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.money;

import java.text.DecimalFormatSymbols;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.primitives.Decimal;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class MoneyUtils {

    public final static MoneyConversion SAME_MONEYCONVERSION = new SameMoneyConversion();
    private final static Decimal UN = new Decimal(1L, (byte) 0, 0);
    private final static Decimal PETIT = new Decimal(0L, (byte) 4, 1);

    private MoneyUtils() {
    }

    /**
     * Découpe la chaine en deux, la valeur (premier élement du tableau) et la
     * monnaie (deuxième élément). Retourne nul si la découpe n'est pas
     * possible. Gère aussi bien EUR 20.000 que 20.000 EUR
     *
     */
    public static String[] splitMoney(String s) {
        char firstChar = s.charAt(0);
        if (isDigitOrMinus(firstChar)) {
            return splitCurrencyAtEnd(s);
        } else {
            return splitCurrencyAtFirst(s);
        }
    }

    private static String[] splitCurrencyAtEnd(String s) {
        int index = -1;
        for (int i = (s.length() - 1); i >= 0; i--) {
            char c = s.charAt(i);
            if (isDigit(c)) {
                index = i;
                break;
            }
        }
        if ((index == -1) || (index == (s.length() - 1))) {
            return null;
        }
        String[] result = new String[2];
        result[0] = s.substring(0, index + 1).trim();
        result[1] = s.substring(index + 1).trim();
        return result;
    }

    private static String[] splitCurrencyAtFirst(String s) {
        int index = -1;
        for (int i = 1; i < s.length(); i++) {
            char c = s.charAt(i);
            if (isDigitOrMinus(c)) {
                index = i;
                break;
            }
        }
        if (index == -1) {
            return null;
        }
        String[] result = new String[2];
        result[0] = s.substring(index);
        result[1] = s.substring(0, index).trim();
        return result;
    }

    private static boolean isDigitOrMinus(char carac) {
        if (carac == '-') {
            return true;
        }
        return isDigit(carac);
    }

    private static boolean isDigit(char carac) {
        if (carac > '9') {
            return false;
        }
        if (carac < '0') {
            return false;
        }
        return true;
    }

    /**
     * 1 otherCurrency = taux pivotCurrency
     */
    public static MoneyConversion toMoneyConversion(ExtendedCurrency pivotCurrency, ExtendedCurrency otherCurrency, Decimal decimal) {
        return new DefaultMoneyConversion(pivotCurrency, otherCurrency, decimal);
    }

    public static Decimal getConversionRate(ExtendedCurrency otherCurrency, long otherMoneyLong, ExtendedCurrency pivotCurrency, long pivotMoneyLong) {
        int otherMultiplicator = toMultiplicator(otherCurrency.getDefaultFractionDigits());
        int pivotMultiplicator = toMultiplicator(pivotCurrency.getDefaultFractionDigits());
        double otherDouble = ((double) otherMoneyLong) / otherMultiplicator;
        double pivotDouble = ((double) pivotMoneyLong) / pivotMultiplicator;
        double taux = pivotDouble / otherDouble;
        long tauxLong = (long) (taux * 100000);
        if (tauxLong < 0) {
            tauxLong = -tauxLong;
        }
        if (tauxLong == 0) {
            return PETIT;
        }
        String tauxString = Long.toString(tauxLong);
        int length = tauxString.length();
        if (length > 5) {
            try {
                return StringUtils.parseDecimal(tauxString.substring(0, length - 5), tauxString.substring(length - 5));
            } catch (NumberFormatException nfe) {
                throw new ShouldNotOccurException(nfe);
            }
        } else {
            StringBuilder buf = new StringBuilder();
            for (int i = 0; i < (5 - length); i++) {
                buf.append('0');
            }
            buf.append(tauxString);
            try {
                return StringUtils.parseDecimal("0", buf.toString());
            } catch (NumberFormatException nfe) {
                throw new ShouldNotOccurException(nfe);
            }
        }
    }

    public static int toMultiplicator(int fractionDigits) {
        int result = 1;
        for (int i = 0; i < fractionDigits; i++) {
            result = result * 10;
        }
        return result;
    }

    public static String toLitteralString(long moneyLong, ExtendedCurrency currency, DecimalFormatSymbols symbols, boolean avoidDecimal) {
        String litteral = MoneyLong.toString(moneyLong, currency.getDefaultFractionDigits(), symbols.getDecimalSeparator(), symbols.getGroupingSeparator(), avoidDecimal);
        return toLitteral(litteral, currency);
    }

    public static String toLitteralString(Decimal decimal, ExtendedCurrency currency, DecimalFormatSymbols symbols) {
        String litteral = decimal.toString(symbols.getDecimalSeparator(), symbols.getGroupingSeparator());
        return toLitteral(litteral, currency);
    }

    private static String toLitteral(String litteral, ExtendedCurrency currency) {
        if (currency.isSymbolBefore()) {
            litteral = currency.getSymbol() + litteral;
        } else {
            litteral = litteral + " " + currency.getSymbol();
        }
        litteral = litteral.replace(' ', '\u00A0');
        return litteral;
    }


    private static class SameMoneyConversion implements MoneyConversion {

        private SameMoneyConversion() {
        }

        @Override
        public long convertFromPivot(long pivotMoneyLong) {
            return pivotMoneyLong;
        }

        @Override
        public long convertToPivot(long otherMoneyLong) {
            return otherMoneyLong;
        }

        @Override
        public Decimal getConversionRate() {
            return UN;
        }

    }


    private static class DefaultMoneyConversion implements MoneyConversion {

        private final int pivotMultiplicator;
        private final int otherMultiplicator;
        private final Decimal conversionRate;
        private final double taux;

        /*
         * 1 pivot = taux * other
         */
        private DefaultMoneyConversion(ExtendedCurrency pivotCurrency, ExtendedCurrency otherCurrency, Decimal conversionRate) {
            this.pivotMultiplicator = toMultiplicator(pivotCurrency.getDefaultFractionDigits());
            this.otherMultiplicator = toMultiplicator(otherCurrency.getDefaultFractionDigits());
            this.conversionRate = conversionRate;
            this.taux = conversionRate.toDouble();
        }

        @Override
        public Decimal getConversionRate() {
            return conversionRate;
        }

        @Override
        public long convertFromPivot(long pivotMoneyLong) {
            boolean opposite = false;
            if (pivotMoneyLong < 0) {
                //Note : le passage en positif permet d'éviter des différences d'arrondi suivant si le nombre est positif ou négatif
                opposite = true;
                pivotMoneyLong = -pivotMoneyLong;
            }
            double pivotDouble = ((double) pivotMoneyLong) / pivotMultiplicator;
            double otherDouble = pivotDouble / taux;
            otherDouble = otherDouble * otherMultiplicator;
            long lg = (long) Math.round(otherDouble);
            if (opposite) {
                lg = -lg;
            }
            return lg;
        }

        @Override
        public long convertToPivot(long otherMoneyLong) {
            boolean opposite = false;
            if (otherMoneyLong < 0) {
                opposite = true;
                otherMoneyLong = -otherMoneyLong;
            }
            double otherDouble = ((double) otherMoneyLong) / otherMultiplicator;
            double pivotDouble = otherDouble * taux;
            pivotDouble = pivotDouble * pivotMultiplicator;
            long lg = (long) Math.round(pivotDouble);
            if (opposite) {
                lg = -lg;
            }
            return lg;
        }

    }

}
