// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
*   Copyright (C) 2007-2016, International Business Machines
*   Corporation and others.  All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.impl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.MissingResourceException;

import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;

/*
 * NumberFormat implementation dedicated/optimized for DateFormat,
 * used by SimpleDateFormat implementation.
 * This class is not thread-safe.
 */
public final class DateNumberFormat extends NumberFormat {

    private static final long serialVersionUID = -6315692826916346953L;

    private char[] digits;
    private char zeroDigit; // For backwards compatibility
    private char minusSign;
    private boolean positiveOnly = false;

    private static final int DECIMAL_BUF_SIZE = 20; // 20 digits is good enough to store Long.MAX_VALUE
    private transient char[] decimalBuf = new char[DECIMAL_BUF_SIZE];

    private static SimpleCache<ULocale, char[]> CACHE = new SimpleCache<ULocale, char[]>();

    private int maxIntDigits;
    private int minIntDigits;

    public DateNumberFormat(ULocale loc, String digitString, String nsName) {
        if (digitString.length() > 10) {
            throw new UnsupportedOperationException("DateNumberFormat does not support digits out of BMP.");
        }
        initialize(loc,digitString,nsName);
    }

    public DateNumberFormat(ULocale loc, char zeroDigit, String nsName) {
        StringBuffer buf = new StringBuffer();
        for ( int i = 0 ; i < 10 ; i++ ) {
            buf.append((char)(zeroDigit+i));
        }
        initialize(loc,buf.toString(),nsName);
    }

    private void initialize(ULocale loc,String digitString,String nsName) {
        char[] elems = CACHE.get(loc);
        if (elems == null) {
            // Missed cache
            String minusString;
            ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, loc);
            try {
                minusString = rb.getStringWithFallback("NumberElements/"+nsName+"/symbols/minusSign");
            } catch (MissingResourceException ex) {
                if ( !nsName.equals("latn") ) {
                    try {
                       minusString = rb.getStringWithFallback("NumberElements/latn/symbols/minusSign");
                    } catch (MissingResourceException ex1) {
                        minusString = "-";
                    }
                } else {
                    minusString = "-";
                }
            }
            elems = new char[11];
            for ( int i = 0 ; i < 10 ; i++ ) {
                 elems[i] = digitString.charAt(i);
            }
            elems[10] = minusString.charAt(0);
            CACHE.put(loc, elems);
        }

        digits = new char[10];
        System.arraycopy(elems, 0, digits, 0, 10);
        zeroDigit = digits[0];

        minusSign = elems[10];
    }

    @Override
    public void setMaximumIntegerDigits(int newValue) {
        maxIntDigits = newValue;
    }

    @Override
    public int getMaximumIntegerDigits() {
        return maxIntDigits;
    }

    @Override
    public void setMinimumIntegerDigits(int newValue) {
        minIntDigits = newValue;
    }

    @Override
    public int getMinimumIntegerDigits() {
        return minIntDigits;
    }

    /* For supporting SimpleDateFormat.parseInt */
    public void setParsePositiveOnly(boolean isPositiveOnly) {
        positiveOnly = isPositiveOnly;
    }

    public char getZeroDigit() {
        return zeroDigit;
    }

    public void setZeroDigit(char zero) {
        zeroDigit = zero;
        if (digits == null) {
            digits = new char[10];
        }
        digits[0] = zero;
        for ( int i = 1 ; i < 10 ; i++ ) {
            digits[i] = (char)(zero+i);
        }
    }

    public char[] getDigits() {
        return digits.clone();
    }

    @Override
    public StringBuffer format(double number, StringBuffer toAppendTo,
            FieldPosition pos) {
        throw new UnsupportedOperationException("StringBuffer format(double, StringBuffer, FieldPosition) is not implemented");
    }

    @Override
    public StringBuffer format(long numberL, StringBuffer toAppendTo,
            FieldPosition pos) {

        if (numberL < 0) {
            // negative
            toAppendTo.append(minusSign);
            numberL = -numberL;
        }

        // Note: NumberFormat used by DateFormat only uses int numbers.
        // Remainder operation on 32bit platform using long is significantly slower
        // than int.  So, this method casts long number into int.
        int number = (int)numberL;

        int limit = decimalBuf.length < maxIntDigits ? decimalBuf.length : maxIntDigits;
        int index = limit - 1;
        while (true) {
            decimalBuf[index] = digits[(number % 10)];
            number /= 10;
            if (index == 0 || number == 0) {
                break;
            }
            index--;
        }
        int padding = minIntDigits - (limit - index);
        for (; padding > 0; padding--) {
            decimalBuf[--index] = digits[0];
        }
        int length = limit - index;
        toAppendTo.append(decimalBuf, index, length);
        pos.setBeginIndex(0);
        if (pos.getField() == NumberFormat.INTEGER_FIELD) {
            pos.setEndIndex(length);
        } else {
            pos.setEndIndex(0);
        }
        return toAppendTo;
    }

    @Override
    public StringBuffer format(BigInteger number, StringBuffer toAppendTo,
            FieldPosition pos) {
        throw new UnsupportedOperationException("StringBuffer format(BigInteger, StringBuffer, FieldPosition) is not implemented");
    }

    @Override
    public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo,
            FieldPosition pos) {
        throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPosition) is not implemented");
    }

    @Override
    public StringBuffer format(BigDecimal number,
            StringBuffer toAppendTo, FieldPosition pos) {
        throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPosition) is not implemented");
    }

    /*
     * Note: This method only parse integer numbers which can be represented by long
     */
    private static final long PARSE_THRESHOLD = 922337203685477579L; // (Long.MAX_VALUE / 10) - 1

    @Override
    public Number parse(String text, ParsePosition parsePosition) {
        long num = 0;
        boolean sawNumber = false;
        boolean negative = false;
        int base = parsePosition.getIndex();
        int offset = 0;
        for (; base + offset < text.length(); offset++) {
            char ch = text.charAt(base + offset);
            if (offset == 0 && ch == minusSign) {
                if (positiveOnly) {
                    break;
                }
                negative = true;
            } else {
                int digit = ch - digits[0];
                if (digit < 0 || 9 < digit) {
                    digit = UCharacter.digit(ch);
                }
                if (digit < 0 || 9 < digit) {
                    for ( digit = 0 ; digit < 10 ; digit++ ) {
                        if ( ch == digits[digit]) {
                            break;
                        }
                    }
                }
                if (0 <= digit && digit <= 9 && num < PARSE_THRESHOLD) {
                    sawNumber = true;
                    num = num * 10 + digit;
                } else {
                    break;
                }
            }
        }
        Number result = null;
        if (sawNumber) {
            num = negative ? num * (-1) : num;
            result = num;
            parsePosition.setIndex(base + offset);
        }
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !super.equals(obj) || !(obj instanceof DateNumberFormat)) {
            return false;
        }
        DateNumberFormat other = (DateNumberFormat)obj;
        return (this.maxIntDigits == other.maxIntDigits
                && this.minIntDigits == other.minIntDigits
                && this.minusSign == other.minusSign
                && this.positiveOnly == other.positiveOnly
                && Arrays.equals(this.digits, other.digits));
    }

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

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        if (digits == null) {
            setZeroDigit(zeroDigit);
        }
        // re-allocate the work buffer
        decimalBuf = new char[DECIMAL_BUF_SIZE];
    }

    @Override
    public Object clone() {
        DateNumberFormat dnfmt = (DateNumberFormat)super.clone();
        dnfmt.digits = this.digits.clone();
        dnfmt.decimalBuf = new char[DECIMAL_BUF_SIZE];
        return dnfmt;
    }
}

//eof
