/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html


package android.icu.impl.units;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.HashMap;

import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.UResource;
import android.icu.util.MeasureUnit;
import android.icu.util.UResourceBundle;

/**
 * @hide Only a subset of ICU is exposed in Android
 */
public class ConversionRates {

    /**
     * Map from any simple unit (i.e. "meter", "foot", "inch") to its basic/root conversion rate info.
     */
    private HashMap<String, ConversionRateInfo> mapToConversionRate;

    public ConversionRates() {
        // Read the conversion rates from the data (units.txt).
        ICUResourceBundle resource;
        resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
        ConversionRatesSink sink = new ConversionRatesSink();
        resource.getAllItemsWithFallback(UnitsData.Constants.CONVERSION_UNIT_TABLE_NAME, sink);
        this.mapToConversionRate = sink.getMapToConversionRate();
    }

    /**
     * Extracts the factor from a `SingleUnitImpl` to its Basic Unit.
     *
     * @param singleUnit
     * @return
     */
    // In ICU4C, this is called loadCompoundFactor().
    private UnitsConverter.Factor getFactorToBase(SingleUnitImpl singleUnit) {
        int power = singleUnit.getDimensionality();
        MeasureUnit.MeasurePrefix unitPrefix = singleUnit.getPrefix();
        UnitsConverter.Factor result = UnitsConverter.Factor.processFactor(mapToConversionRate.get(singleUnit.getSimpleUnitID()).getConversionRate());

        // Prefix before power, because:
        // - square-kilometer to square-meter: (1000)^2
        // - square-kilometer to square-foot (approximate): (3.28*1000)^2
        return result.applyPrefix(unitPrefix).power(power);
    }

    public UnitsConverter.Factor getFactorToBase(MeasureUnitImpl measureUnit) {
        UnitsConverter.Factor result = new UnitsConverter.Factor();
        for (SingleUnitImpl singleUnit :
                measureUnit.getSingleUnits()) {
            result = result.multiply(getFactorToBase(singleUnit));
        }

        return result;
    }

    // In ICU4C, this functionality is found in loadConversionRate().
    protected BigDecimal getOffset(MeasureUnitImpl source, MeasureUnitImpl target, UnitsConverter.Factor
            sourceToBase, UnitsConverter.Factor targetToBase, UnitsConverter.Convertibility convertibility) {
        if (convertibility != UnitsConverter.Convertibility.CONVERTIBLE) return BigDecimal.valueOf(0);
        if (!(checkSimpleUnit(source) && checkSimpleUnit(target))) return BigDecimal.valueOf(0);

        String sourceSimpleIdentifier = source.getSingleUnits().get(0).getSimpleUnitID();
        String targetSimpleIdentifier = target.getSingleUnits().get(0).getSimpleUnitID();

        BigDecimal sourceOffset = this.mapToConversionRate.get(sourceSimpleIdentifier).getOffset();
        BigDecimal targetOffset = this.mapToConversionRate.get(targetSimpleIdentifier).getOffset();
        return sourceOffset
                .subtract(targetOffset)
                .divide(targetToBase.getConversionRate(), MathContext.DECIMAL128);


    }

    public MeasureUnitImpl extractCompoundBaseUnit(MeasureUnitImpl measureUnit) {
        ArrayList<SingleUnitImpl> baseUnits = this.extractBaseUnits(measureUnit);

        MeasureUnitImpl result = new MeasureUnitImpl();
        for (SingleUnitImpl baseUnit :
                baseUnits) {
            result.appendSingleUnit(baseUnit);
        }

        return result;
    }

    public ArrayList<SingleUnitImpl> extractBaseUnits(MeasureUnitImpl measureUnitImpl) {
        ArrayList<SingleUnitImpl> result = new ArrayList<>();
        ArrayList<SingleUnitImpl> singleUnits = measureUnitImpl.getSingleUnits();
        for (SingleUnitImpl singleUnit :
                singleUnits) {
            result.addAll(extractBaseUnits(singleUnit));
        }

        return result;
    }

    /**
     * @param singleUnit An instance of SingleUnitImpl.
     * @return The base units in the `SingleUnitImpl` with applying the dimensionality only and not the SI prefix.
     * <p>
     * NOTE:
     * This method is helpful when checking the convertibility because no need to check convertibility.
     */
    public ArrayList<SingleUnitImpl> extractBaseUnits(SingleUnitImpl singleUnit) {
        String target = mapToConversionRate.get(singleUnit.getSimpleUnitID()).getTarget();
        MeasureUnitImpl targetImpl = MeasureUnitImpl.UnitsParser.parseForIdentifier(target);

        // Each unit must be powered by the same dimension
        targetImpl.applyDimensionality(singleUnit.getDimensionality());

        // NOTE: we do not apply SI prefixes.

        return targetImpl.getSingleUnits();
    }

    /**
     * Checks if the `MeasureUnitImpl` is simple or not.
     *
     * @param measureUnitImpl
     * @return true if the `MeasureUnitImpl` is simple, false otherwise.
     */
    private boolean checkSimpleUnit(MeasureUnitImpl measureUnitImpl) {
        if (measureUnitImpl.getComplexity() != MeasureUnit.Complexity.SINGLE) return false;
        SingleUnitImpl singleUnit = measureUnitImpl.getSingleUnits().get(0);

        if (singleUnit.getPrefix() != MeasureUnit.MeasurePrefix.ONE) return false;
        if (singleUnit.getDimensionality() != 1) return false;

        return true;
    }

    /**
     * @hide Only a subset of ICU is exposed in Android
     */
    public static class ConversionRatesSink extends UResource.Sink {
        /**
         * Map from any simple unit (i.e. "meter", "foot", "inch") to its basic/root conversion rate info.
         */
        private HashMap<String, ConversionRateInfo> mapToConversionRate = new HashMap<>();

        @Override
        public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
            assert (UnitsData.Constants.CONVERSION_UNIT_TABLE_NAME.equals(key.toString()));

            UResource.Table conversionRateTable = value.getTable();
            for (int i = 0; conversionRateTable.getKeyAndValue(i, key, value); i++) {
                assert (value.getType() == UResourceBundle.TABLE);

                String simpleUnit = key.toString();

                UResource.Table simpleUnitConversionInfo = value.getTable();
                String target = null;
                String factor = null;
                String offset = "0";
                for (int j = 0; simpleUnitConversionInfo.getKeyAndValue(j, key, value); j++) {
                    assert (value.getType() == UResourceBundle.STRING);


                    String keyString = key.toString();
                    String valueString = value.toString().replaceAll(" ", "");
                    if ("target".equals(keyString)) {
                        target = valueString;
                    } else if ("factor".equals(keyString)) {
                        factor = valueString;
                    } else if ("offset".equals(keyString)) {
                        offset = valueString;
                    } else {
                        assert false : "The key must be target, factor or offset";
                    }
                }

                // HERE a single conversion rate data should be loaded
                assert (target != null);
                assert (factor != null);

                mapToConversionRate.put(simpleUnit, new ConversionRateInfo(simpleUnit, target, factor, offset));
            }


        }

        public HashMap<String, ConversionRateInfo> getMapToConversionRate() {
            return mapToConversionRate;
        }
    }

    /**
     * @hide Only a subset of ICU is exposed in Android
     */
    public static class ConversionRateInfo {

        @SuppressWarnings("unused")
        private final String simpleUnit;
        private final String target;
        private final String conversionRate;
        private final BigDecimal offset;

        public ConversionRateInfo(String simpleUnit, String target, String conversionRate, String offset) {
            this.simpleUnit = simpleUnit;
            this.target = target;
            this.conversionRate = conversionRate;
            this.offset = forNumberWithDivision(offset);
        }

        private static BigDecimal forNumberWithDivision(String numberWithDivision) {
            String[] numbers = numberWithDivision.split("/");
            assert (numbers.length <= 2);

            if (numbers.length == 1) {
                return new BigDecimal(numbers[0]);
            }

            return new BigDecimal(numbers[0]).divide(new BigDecimal(numbers[1]), MathContext.DECIMAL128);
        }

        /**
         * @return the base unit.
         * <p>
         * For example:
         * ("meter", "foot", "inch", "mile" ... etc.) have "meter" as a base/root unit.
         */
        public String getTarget() {
            return this.target;
        }

        /**
         * @return The offset from this unit to the base unit.
         */
        public BigDecimal getOffset() {
            return this.offset;
        }

        /**
         * @return The conversion rate from this unit to the base unit.
         */
        public String getConversionRate() {
            return conversionRate;
        }
    }
}
