/* UtilLib - Copyright (c) 2018-2021 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.ParseException;
import java.util.Currency;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.mapeadores.util.annotation.Nullable;


/**
 *
 * @author Vincent Calame
 */
public final class ExtendedCurrency implements Comparable<ExtendedCurrency> {

    private final static ConcurrentMap<String, ExtendedCurrency> internMap = new ConcurrentHashMap<String, ExtendedCurrency>();
    private final String code;
    private final String symbol;
    private final int fractionDigits;
    private final Currency currency;
    private final boolean symbolBefore;

    static {
        init("€", "EUR", true);
        init("$", "USD", false);
        init("¥", "JPY", false);
        init("£", "GBP", false);
        init("CFA", 0, false, null);
    }

    private ExtendedCurrency(String code, int fractionDigits, boolean symbolBefore, String symbol) {
        if (fractionDigits < 0) {
            throw new IllegalArgumentException("fractionDigits < 0");
        }
        this.code = code;
        this.fractionDigits = fractionDigits;
        this.currency = null;
        this.symbolBefore = symbolBefore;
        this.symbol = symbol;
    }

    private ExtendedCurrency(Currency currency, String symbol) {
        int defaulFractionDigits = currency.getDefaultFractionDigits();
        if (defaulFractionDigits < 0) {
            defaulFractionDigits = 0;
        }
        this.code = currency.getCurrencyCode();
        this.fractionDigits = defaulFractionDigits;
        this.currency = currency;
        this.symbolBefore = isSymbolBefore(currency.getCurrencyCode());
        this.symbol = symbol;
    }

    public String getCurrencyCode() {
        return code;
    }

    /**
     * Contrairement à Currency.getDefaultFractionDigits(), ne retourne jamais
     * un nombre négatif
     *
     * @return
     */
    public int getDefaultFractionDigits() {
        return fractionDigits;
    }

    @Nullable
    public Currency getCurrency() {
        return currency;
    }

    public String getSymbol() {
        if (symbol != null) {
            return symbol;
        } else if (currency != null) {
            return currency.getSymbol();
        } else {
            return code;
        }
    }

    public boolean isSymbolBefore() {
        return symbolBefore;
    }

    @Override
    public int hashCode() {
        return code.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }
        if (other == this) {
            return true;
        }
        if (other.getClass() != this.getClass()) {
            return false;
        }
        ExtendedCurrency otherCurrency = (ExtendedCurrency) other;
        return (otherCurrency.code.equals(this.code));
    }

    @Override
    public String toString() {
        return code;
    }

    @Override
    public int compareTo(ExtendedCurrency other) {
        return this.code.compareTo(other.code);
    }

    public static ExtendedCurrency parse(String currencyCode) throws ParseException {
        ExtendedCurrency instance = internMap.get(currencyCode);
        if (instance != null) {
            return instance;
        }
        try {
            Currency currency = Currency.getInstance(currencyCode);
            instance = new ExtendedCurrency(currency, null);
        } catch (IllegalArgumentException iae) {
            checkCode(currencyCode);
            instance = new ExtendedCurrency(currencyCode, 2, false, null);
        }
        ExtendedCurrency previous = internMap.putIfAbsent(currencyCode, instance);
        return (previous != null ? previous : instance);
    }

    private static void init(String symbol, String code, boolean symbolFix) {
        try {
            Currency currency = Currency.getInstance(code);
            ExtendedCurrency instance;
            if (symbolFix) {
                instance = new ExtendedCurrency(currency, symbol);
            } else {
                instance = new ExtendedCurrency(currency, null);
            }
            internMap.putIfAbsent(code, instance);
            internMap.putIfAbsent(symbol, instance);
        } catch (IllegalArgumentException iae) {

        }
    }

    private static void checkCode(String code) throws ParseException {
        int length = code.length();
        if (length != 3) {
            throw new ParseException("length != 3", 0);
        }
        for (int i = 0; i < 3; i++) {
            char carac = code.charAt(i);
            if ((carac < 'A') || (carac > 'Z')) {
                throw new ParseException("Invalid character: " + carac, i);
            }
        }
    }

    private static void init(String code, int fractionDigits, boolean symbolBefore, String symbol) {
        ExtendedCurrency instance = new ExtendedCurrency(code, fractionDigits, symbolBefore, symbol);
        internMap.putIfAbsent(code, instance);
    }

    private static boolean isSymbolBefore(String code) {
        switch (code) {
            case "USD":
            case "GBP":
                return true;
            default:
                return false;
        }
    }

}
