/*
 *******************************************************************************
 * Copyright (C) 2015, International Business Machines Corporation and         *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.test.format;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ULocale;

/**
 * A representation of a single NumberFormat specification test from a data driven test file.
 * <p>
 * The purpose of this class is to hide the details of the data driven test file from the
 * main testing code.
 * <p>
 * This class contains fields describing an attribute of the test that may or may
 * not be set. The name of each attribute corresponds to the name used in the
 * data driven test file.
 * <p>
 * <b>Adding new attributes</b>
 * <p>
 * Each attribute name is lower case. Moreover, for each attribute there is also a
 * setXXX method for that attribute that is used to initialize the attribute from a
 * String value read from the data file. For example, there is a setLocale(String) method
 * for the locale attribute and a setCurrency(String) method for the currency attribute.
 * In general, for an attribute named abcd, the setter will be setAbcd(String).
 * This naming rule must be strictly followed or else the test runner will not know how to
 * initialize instances of this class.
 * <p>
 * In addition each attribute is listed in the fieldOrdering static array which specifies
 * The order that attributes are printed whenever there is a test failure.
 * <p> 
 * To add a new attribute, first create a public field for it.
 * Next, add the attribute name to the fieldOrdering array.
 * Finally, create a setter method for it.
 * 
 * @author rocketman
 */
public class NumberFormatTestTuple {
    
    /**
     * The locale.
     */
    public ULocale locale = null;
    
    /**
     * The currency.
     */
    public Currency currency = null;
    
    /**
     * The pattern to initialize the formatter, for example 0.00"
     */
    public String pattern = null;
    
    /**
     * The value to format as a string. For example 1234.5 would be "1234.5"
     */
    public String format = null;
    
    /**
     * The formatted value.
     */
    public String output = null;
    
    /**
     * Field for arbitrary comments.
     */
    public String comment = null;
    
    public Integer minIntegerDigits = null;
    public Integer maxIntegerDigits = null;
    public Integer minFractionDigits = null;
    public Integer maxFractionDigits = null;
    public Integer minGroupingDigits = null;
    public Integer useSigDigits = null;
    public Integer minSigDigits = null;
    public Integer maxSigDigits = null;
    public Integer useGrouping = null;
    public Integer multiplier = null;
    public Double roundingIncrement = null;
    public Integer formatWidth = null;
    public String padCharacter = null;
    public Integer useScientific = null;
    public Integer grouping = null;
    public Integer grouping2 = null;
    public Integer roundingMode = null;
    public Currency.CurrencyUsage currencyUsage = null;
    public Integer minimumExponentDigits = null;
    public Integer exponentSignAlwaysShown = null;
    public Integer decimalSeparatorAlwaysShown = null;
    public Integer padPosition = null;
    public String positivePrefix = null;
    public String positiveSuffix = null;
    public String negativePrefix = null;
    public String negativeSuffix = null;
    public String localizedPattern = null;
    public String toPattern = null;
    public String toLocalizedPattern = null;
    public Integer style = null;
    public String parse = null;
    public Integer lenient = null;
    public String plural = null;
    public Integer parseIntegerOnly = null;
    public Integer decimalPatternMatchRequired = null;
    public Integer parseNoExponent = null;
    public String outputCurrency = null;
    
    
    
    /**
     * nothing or empty means that test ought to work for both C and JAVA;
     * "C" means test is known to fail in C. "J" means test is known to fail in JAVA.
     * "CJ" means test is known to fail for both languages.
     */
    public String breaks = null;
    
    private static Map<String, Integer> roundingModeMap =
            new HashMap<String, Integer>();
    
    static {
        roundingModeMap.put("ceiling", BigDecimal.ROUND_CEILING);
        roundingModeMap.put("floor", BigDecimal.ROUND_FLOOR);
        roundingModeMap.put("down", BigDecimal.ROUND_DOWN);
        roundingModeMap.put("up", BigDecimal.ROUND_UP);
        roundingModeMap.put("halfEven", BigDecimal.ROUND_HALF_EVEN);
        roundingModeMap.put("halfDown", BigDecimal.ROUND_HALF_DOWN);
        roundingModeMap.put("halfUp", BigDecimal.ROUND_HALF_UP);
        roundingModeMap.put("unnecessary", BigDecimal.ROUND_UNNECESSARY);
    }
    
    private static Map<String, Currency.CurrencyUsage> currencyUsageMap =
            new HashMap<String, Currency.CurrencyUsage>();
    
    static {
        currencyUsageMap.put("standard", Currency.CurrencyUsage.STANDARD);
        currencyUsageMap.put("cash", Currency.CurrencyUsage.CASH);
    }
    
    private static Map<String, Integer> padPositionMap =
            new HashMap<String, Integer>();
    
    static {
        // TODO: Fix so that it doesn't depend on DecimalFormat.
        padPositionMap.put("beforePrefix", DecimalFormat.PAD_BEFORE_PREFIX);
        padPositionMap.put("afterPrefix", DecimalFormat.PAD_AFTER_PREFIX);
        padPositionMap.put("beforeSuffix", DecimalFormat.PAD_BEFORE_SUFFIX);
        padPositionMap.put("afterSuffix", DecimalFormat.PAD_AFTER_SUFFIX);
    }
    
    private static Map<String, Integer> formatStyleMap =
            new HashMap<String, Integer>();
    
    static {
        formatStyleMap.put("decimal", NumberFormat.NUMBERSTYLE);
        formatStyleMap.put("currency", NumberFormat.CURRENCYSTYLE);
        formatStyleMap.put("percent", NumberFormat.PERCENTSTYLE);
        formatStyleMap.put("scientific", NumberFormat.SCIENTIFICSTYLE);
        formatStyleMap.put("currencyIso", NumberFormat.ISOCURRENCYSTYLE);
        formatStyleMap.put("currencyPlural", NumberFormat.PLURALCURRENCYSTYLE);
        formatStyleMap.put("currencyAccounting", NumberFormat.ACCOUNTINGCURRENCYSTYLE);
        formatStyleMap.put("cashCurrency", NumberFormat.CASHCURRENCYSTYLE);
    }
    
    // Add any new fields here. On test failures, fields are printed in the same order they
    // appear here.
    private static String[] fieldOrdering = {
        "locale",
        "currency",
        "pattern",
        "format",
        "output",
        "comment",
        "minIntegerDigits",
        "maxIntegerDigits",
        "minFractionDigits",
        "maxFractionDigits",
        "minGroupingDigits",
        "breaks",
        "useSigDigits",
        "minSigDigits",
        "maxSigDigits",
        "useGrouping",
        "multiplier",
        "roundingIncrement",
        "formatWidth",
        "padCharacter",
        "useScientific",
        "grouping",
        "grouping2",
        "roundingMode",
        "currencyUsage",
        "minimumExponentDigits",
        "exponentSignAlwaysShown",
        "decimalSeparatorAlwaysShown",
        "padPosition",
        "positivePrefix",
        "positiveSuffix",
        "negativePrefix",
        "negativeSuffix",
        "localizedPattern",
        "toPattern",
        "toLocalizedPattern",
        "style",
        "parse",
        "lenient",
        "plural",
        "parseIntegerOnly",
        "decimalPatternMatchRequired",
        "parseNoExponent",
        "outputCurrency"
    };
    
    static {
        HashSet<String> set = new HashSet<String>();
        for (String s : fieldOrdering) {
            if (!set.add(s)) {
                throw new ExceptionInInitializerError(s + "is a duplicate field.");    
            }
        }
    }
    
    private static <T> T fromString(Map<String, T> map, String key) {
        T value = map.get(key);
        if (value == null) {
            throw new IllegalArgumentException("Bad value: "+ key);
        }
        return value;
    }
    
    // start field setters.
    // add setter for each new field in this block.
    
    public void setLocale(String value) {
        locale = new ULocale(value);
    }
    
    public void setCurrency(String value) {
        currency = Currency.getInstance(value);
    }
    
    public void setPattern(String value) {
        pattern = value;
    }
    
    public void setFormat(String value) {
        format = value;
    }
    
    public void setOutput(String value) {
        output = value;
    }
    
    public void setComment(String value) {
        comment = value;
    }
    
    public void setMinIntegerDigits(String value) {
        minIntegerDigits = Integer.valueOf(value);
    }
    
    public void setMaxIntegerDigits(String value) {
        maxIntegerDigits = Integer.valueOf(value);
    }
    
    public void setMinFractionDigits(String value) {
        minFractionDigits = Integer.valueOf(value);
    }
    
    public void setMaxFractionDigits(String value) {
        maxFractionDigits = Integer.valueOf(value);
    }
    
    public void setMinGroupingDigits(String value) {
        minGroupingDigits = Integer.valueOf(value);
    }
    
    public void setBreaks(String value) {
        breaks = value;
    }
    
    public void setUseSigDigits(String value) {
        useSigDigits = Integer.valueOf(value);
    }
    
    public void setMinSigDigits(String value) {
        minSigDigits = Integer.valueOf(value);
    }
    
    public void setMaxSigDigits(String value) {
        maxSigDigits = Integer.valueOf(value);
    }
    
    public void setUseGrouping(String value) {
        useGrouping = Integer.valueOf(value);
    }
    
    public void setMultiplier(String value) {
        multiplier = Integer.valueOf(value);
    }
    
    public void setRoundingIncrement(String value) {
        roundingIncrement = Double.valueOf(value);
    }
    
    public void setFormatWidth(String value) {
        formatWidth = Integer.valueOf(value);
    }
    
    public void setPadCharacter(String value) {
        padCharacter = value;
    }
    
    public void setUseScientific(String value) {
        useScientific = Integer.valueOf(value);
    }
    
    public void setGrouping(String value) {
        grouping = Integer.valueOf(value);
    }
    
    public void setGrouping2(String value) {
        grouping2 = Integer.valueOf(value);
    }
    
    public void setRoundingMode(String value) {
        roundingMode = fromString(roundingModeMap, value);
    }
    
    public void setCurrencyUsage(String value) {
        currencyUsage = fromString(currencyUsageMap, value);
    }
    
    public void setMinimumExponentDigits(String value) {
        minimumExponentDigits = Integer.valueOf(value);
    }
    
    public void setExponentSignAlwaysShown(String value) {
        exponentSignAlwaysShown = Integer.valueOf(value);
    }
    
    public void setDecimalSeparatorAlwaysShown(String value) {
        decimalSeparatorAlwaysShown = Integer.valueOf(value);
    }
    
    public void setPadPosition(String value) {
        padPosition = fromString(padPositionMap, value);
    }
    
    public void setPositivePrefix(String value) {
        positivePrefix = value;
    }
    
    public void setPositiveSuffix(String value) {
        positiveSuffix = value;
    }
    
    public void setNegativePrefix(String value) {
        negativePrefix = value;
    }
    
    public void setNegativeSuffix(String value) {
        negativeSuffix = value;
    }
    
    public void setLocalizedPattern(String value) {
        localizedPattern = value;
    }
    
    public void setToPattern(String value) {
        toPattern = value;
    }
    
    public void setToLocalizedPattern(String value) {
        toLocalizedPattern = value;
    }
    
    public void setStyle(String value) {
        style = fromString(formatStyleMap, value);
    }
    
    public void setParse(String value) {
        parse = value;
    }
    
    public void setLenient(String value) {
        lenient = Integer.valueOf(value);
    }
    
    public void setPlural(String value) {
        plural = value;
    }
    
    public void setParseIntegerOnly(String value) {
        parseIntegerOnly = Integer.valueOf(value);
    }
    
    public void setDecimalPatternMatchRequired(String value) {
        decimalPatternMatchRequired = Integer.valueOf(value);
    }
    
    public void setParseNoExponent(String value) {
        parseNoExponent = Integer.valueOf(value);
    }
    
    public void setOutputCurrency(String value) {
        outputCurrency = value;
    }
    
    // end field setters.
    
    // start of field clearers
    // Add clear methods that can be set in one test and cleared
    // in the next i.e the breaks field.
    
    public void clearBreaks() {
        breaks = null;
    }
    
    public void clearUseGrouping() {
        useGrouping = null;
    }
    
    public void clearGrouping2() {
        grouping2 = null;
    }
    
    public void clearGrouping() {
        grouping = null;
    }
    
    public void clearMinGroupingDigits() {
        minGroupingDigits = null;
    }
    
    public void clearUseScientific() {
        useScientific = null;
    }
    
    public void clearDecimalSeparatorAlwaysShown() {
        decimalSeparatorAlwaysShown = null;
    }
    
    // end field clearers
    
    public void setField(String fieldName, String valueString)
            throws NoSuchMethodException {
        Method m = getClass().getMethod(
                fieldToSetter(fieldName), String.class);
        try {
            m.invoke(this, valueString);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    public void clearField(String fieldName)
            throws NoSuchMethodException {
        Method m = getClass().getMethod(fieldToClearer(fieldName));
        try {
            m.invoke(this);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("{");
        boolean first = true;
        for (String fieldName : fieldOrdering) {
            try {
                Field field = getClass().getField(fieldName);
                Object optionalValue = field.get(this);
                if (optionalValue == null) {
                    continue;
                }
                if (!first) {
                    result.append(", ");
                }
                first = false;
                result.append(fieldName);
                result.append(": ");
                result.append(optionalValue);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            } catch (SecurityException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        result.append("}");
        return result.toString();
    }

    private static String fieldToSetter(String fieldName) {
        return "set"
                + Character.toUpperCase(fieldName.charAt(0))
                + fieldName.substring(1);
    }
    
    private static String fieldToClearer(String fieldName) {
        return "clear"
                + Character.toUpperCase(fieldName.charAt(0))
                + fieldName.substring(1);
    }

}
