// $Id: NumberListFormat.java,v 1.1 2002/04/25 18:02:55 bill Exp $

package com.jclark.xsl.conv;

import java.net.URL;
import java.util.Hashtable;

/**
 *
 */
public class NumberListFormat implements Cloneable
{

    static private NumberFormat defaultFormat = 
        new DecimalNumberFormat('0', 1);

    static private Hashtable formatTokenHandlerTable = new Hashtable();

    static {
        NumberFormat format = 
            new AlphabetNumberFormat("abcdefghijklmnopqrstuvwxyz");
        AlphabetFormatTokenHandler handler = new AlphabetFormatTokenHandler(format);
        formatTokenHandlerTable.put("a", handler);
        format = 
            new AlphabetNumberFormat("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        handler = 
            new AlphabetFormatTokenHandler(format);
        formatTokenHandlerTable.put("A", handler);
        formatTokenHandlerTable.put("i", new RomanNumberFormat("mdclxvi"));
        formatTokenHandlerTable.put("I", new RomanNumberFormat("MDCLXVI"));
    }

    private NumberFormat[] formats = null;
    private String[] formatTokens = new String[] { "1" };
    private String prefix = "";
    private String suffix = "";
    // i-th element is separator between i-th and i+1-th number
    private String[] separators = new String[] { "." };
    private int groupingSize = 0;
    private String groupingSeparator = null;
    private String lang = null;
    private String letterValue = null;

    public static
        void setFormatTokenHandler(String formatToken,
                                   FormatTokenHandler handler)
    {
        formatTokenHandlerTable.put(formatToken, handler);
    }

    public void setFormat(String format)
    {
        formats = null;
        int n = 0;
        for (int i = 0; i < format.length(); i++) {
            if (isAlnum(format.charAt(i))
                && (i == 0 || !isAlnum(format.charAt(i - 1)))) {
                n++;
            }
        }
        if (n == 0) {
            formatTokens = new String[1];
            formatTokens[0] = "1";
            prefix = "";
        } else {
            formatTokens = new String[n];
        }
        if (n <= 1) {
            separators = new String[1];
            separators[0] = ".";
        }
        else {
            separators = new String[n - 1];
        }
        int fi = 0;
        for (int i = 0; i < n; i++) {
            int sepStart = fi;
            while (!isAlnum(format.charAt(fi))) {
                fi++;
            }
            if (i == 0) {
                prefix = format.substring(0, fi);
            } else {
                separators[i - 1] = format.substring(sepStart, fi);
            }
            int numberStart = fi++;
            while (fi < format.length() && isAlnum(format.charAt(fi))) {
                fi++;
            }
            formatTokens[i] = format.substring(numberStart, fi);
        }
        suffix = format.substring(fi);
    }

    static private boolean isAlnum(char c) 
    {
        switch (Character.getType(c)) {
        case Character.UPPERCASE_LETTER:
        case Character.LOWERCASE_LETTER:
        case Character.TITLECASE_LETTER:
        case Character.MODIFIER_LETTER:
        case Character.OTHER_LETTER:
        case Character.DECIMAL_DIGIT_NUMBER:
        case Character.LETTER_NUMBER:
        case Character.OTHER_NUMBER:
            return true;
        }
        return false;
    }

    public void setLang(String lang) 
    {
        formats = null;
        this.lang = lang;
    }

    public void setLetterValue(String letterValue) 
    {
        this.letterValue = letterValue;
    }

    public void setGroupingSeparator(String sep) 
    {
        if (sep == null || sep.length() == 0) {
            groupingSeparator = null;
        } else {
            groupingSeparator = sep;
        }
    }

    public void setGroupingSize(int n) 
    {
        groupingSize = n;
    }

    public String getPrefix(int i) 
    {
        if (i == 0) {
            return prefix;
        }
        i -= 1;
        if (i < separators.length) {
            return separators[i];
        }
        return separators[separators.length - 1];
    }

    public String formatNumber(int i, int n) 
    {
        if (n < 0) {
            throw new IllegalArgumentException("cannot format negative number");
        }
        return group(getFormat(i).format(n));
    }

    public String getSuffix() 
    {
        return suffix;
    }

    private NumberFormat getFormat(int i) 
    {
        if (formats == null) {
            formats = new NumberFormat[formatTokens.length];
        }
        if (i >= formats.length) {
            i = formats.length - 1;
        }
        if (formats[i] == null) {
            formats[i] = findFormat(formatTokens[i]);
        }
        return formats[i];
    }

    private NumberFormat findFormat(String formatToken) 
    {
        NumberFormat f = findDecimalFormat(formatToken);
        if (f != null) {
            return f;
        }
        FormatTokenHandler handler = 
            (FormatTokenHandler)formatTokenHandlerTable.get(formatToken);
        if (handler != null) {
            f = handler.getFormat(lang, letterValue);
            if (f != null) {
                return f;
            }
        }
        return defaultFormat;
    }

    private NumberFormat findDecimalFormat(String format) 
    {
        char digitOne = format.charAt(format.length() - 1);
        if (!Character.isDigit(digitOne) || 
            Character.digit(digitOne, 10) != 1) {
            return null;
        }

        for (int i = 0; i < format.length() - 1; i++) {
            if (format.charAt(i) + 1 != digitOne) {
                return null;
            }
        }
        return new DecimalNumberFormat((char)(digitOne - 1), format.length());
    }

    private String group(String number) 
    {
        if (groupingSeparator == null ||
            groupingSize <= 0 || 
            number.length() <= groupingSize) {
            return number;
        }
        char buf[] = new char[number.length() + 
                              ((number.length() - 1)/groupingSize) * 
                              groupingSeparator.length()];
        int j = 0;
        for (int i = 0; i < number.length(); i++) {
            if (i > 0 && (number.length() - i) % groupingSize == 0) {
                for (int k = 0; k < groupingSeparator.length(); k++) {
                    buf[j++] = groupingSeparator.charAt(k);
                }
            }
            buf[j++] = number.charAt(i);
        }
        return new String(buf);
    }

    public Object clone() 
    {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) { 
            throw new InternalError();
        }
    }

}
