/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.text;

import java.math.BigDecimal;
import java.util.IllegalFormatConversionException;
import java.util.Locale;
import net.ranides.assira.collection.maps.IntHashMap;
import net.ranides.assira.collection.maps.IntMap;

/**
 *
 * @author ranides
 */
public final class FormatNumber {
    
    private FormatNumber() {
        /* utility class */ 
    }
    
    // metric (SI)
    static final UnitPrefix[] MP = new UnitPrefix[]{
        new UnitPrefix(Math.pow(1000, 8),"Y"),
        new UnitPrefix(Math.pow(1000, 7),"Z"),
        new UnitPrefix(Math.pow(1000, 6),"E"),
        new UnitPrefix(Math.pow(1000, 5),"P"),
        new UnitPrefix(Math.pow(1000, 4),"T"),
        new UnitPrefix(Math.pow(1000, 3),"G"),
        new UnitPrefix(Math.pow(1000, 2),"M"),
        new UnitPrefix(Math.pow(1000, 1),"k"),
        new UnitPrefix(Math.pow(1000, 0),""),
        new UnitPrefix(Math.pow(1000, -1),"m"),
        new UnitPrefix(Math.pow(1000, -2),"μ"),
        new UnitPrefix(Math.pow(1000, -3),"n"),
        new UnitPrefix(Math.pow(1000, -4),"p"),
        new UnitPrefix(Math.pow(1000, -5),"f"),
        new UnitPrefix(Math.pow(1000, -6),"a"),
        new UnitPrefix(Math.pow(1000, -7),"z"),
        new UnitPrefix(Math.pow(1000, -8),"y")
    };
    
    // metric (SI)
    static final UnitPrefix[] ME = new UnitPrefix[]{
        new UnitPrefix(Math.pow(1000, 8),"E24"),
        new UnitPrefix(Math.pow(1000, 7),"E21"),
        new UnitPrefix(Math.pow(1000, 6),"E18"),
        new UnitPrefix(Math.pow(1000, 5),"E15"),
        new UnitPrefix(Math.pow(1000, 4),"E12"),
        new UnitPrefix(Math.pow(1000, 3),"E9"),
        new UnitPrefix(Math.pow(1000, 2),"E6"),
        new UnitPrefix(Math.pow(1000, 1),"E3"),
        new UnitPrefix(Math.pow(1000, 0),"E0"),
        new UnitPrefix(Math.pow(1000, -1),"E-3"),
        new UnitPrefix(Math.pow(1000, -2),"E-6"),
        new UnitPrefix(Math.pow(1000, -3),"E-9"),
        new UnitPrefix(Math.pow(1000, -4),"E-12"),
        new UnitPrefix(Math.pow(1000, -5),"E-15"),
        new UnitPrefix(Math.pow(1000, -6),"E-18"),
        new UnitPrefix(Math.pow(1000, -7),"E-21"),
        new UnitPrefix(Math.pow(1000, -8),"E-24")
    };
    
    // binary: IEC 80000-13
    static final UnitPrefix[] BP = new UnitPrefix[]{
        new UnitPrefix(Math.pow(1024, 8),"Yi"),
        new UnitPrefix(Math.pow(1024, 7),"Zi"),
        new UnitPrefix(Math.pow(1024, 6),"Ei"),
        new UnitPrefix(Math.pow(1024, 5),"Pi"),
        new UnitPrefix(Math.pow(1024, 4),"Ti"),
        new UnitPrefix(Math.pow(1024, 3),"Gi"),
        new UnitPrefix(Math.pow(1024, 2),"Mi"),
        new UnitPrefix(Math.pow(1024, 1),"Ki"),
        new UnitPrefix(Math.pow(1024, 0),""),
    };
    
    // binary: exponents
    static final UnitPrefix[] BE = new UnitPrefix[]{
        new UnitPrefix(Math.pow(1024, 8),"B24"),
        new UnitPrefix(Math.pow(1024, 7),"B21"),
        new UnitPrefix(Math.pow(1024, 6),"B18"),
        new UnitPrefix(Math.pow(1024, 5),"B15"),
        new UnitPrefix(Math.pow(1024, 4),"B12"),
        new UnitPrefix(Math.pow(1024, 3),"B9"),
        new UnitPrefix(Math.pow(1024, 2),"B6"),
        new UnitPrefix(Math.pow(1024, 1),"B3"),
        new UnitPrefix(Math.pow(1024, 0),"B0"),
    };
    
    // binary: Knuth
    static final UnitPrefix[] BK = new UnitPrefix[]{
        new UnitPrefix(Math.pow(1024, 8),"YY"),
        new UnitPrefix(Math.pow(1024, 7),"ZZ"),
        new UnitPrefix(Math.pow(1024, 6),"EE"),
        new UnitPrefix(Math.pow(1024, 5),"PP"),
        new UnitPrefix(Math.pow(1024, 4),"TT"),
        new UnitPrefix(Math.pow(1024, 3),"GG"),
        new UnitPrefix(Math.pow(1024, 2),"MM"),
        new UnitPrefix(Math.pow(1024, 1),"KK"),
        new UnitPrefix(Math.pow(1024, 0),""),
    };
    
    private static final IntMap<UnitPrefix[]> PREFIXES = new IntHashMap<>(
        new int[]{ 'M', 'E', 'S', 'B', 'K' },
        new UnitPrefix[][]{ MP, ME, BP, BE, BK }
    );
 
    /**
     * Supported formats (unit prefixes): 
     * <ul>
     *      <li>M (metric, SI prefix)</li>
     *      <li>E (metric, exponent)</li>
     *      <li>S (binary, IEC prefix)</li>
     *      <li>K (binary, Donald Knuth prefix)</li>
     *      <li>B (binary, exponent)</li>
     * </ul>
     * @param format "printf-compatible" number format, e.g {@code %2.5M}
     * @param value
     * @return 
     */
    public static String asEngineer(String format, double value) {
        return asEngineer(new StringBuilder(), format, value).toString();
    }

    /**
     * Supported formats (unit prefixes): 
     * <ul>
     *      <li>M (metric, SI prefix)</li>
     *      <li>E (metric, exponent)</li>
     *      <li>S (binary, IEC prefix)</li>
     *      <li>K (binary, Donald Knuth prefix)</li>
     *      <li>B (binary, exponent)</li>
     * </ul>
     * @param target
     * @param format format string compatible with String#format. 
     * @param value
     * @return 
     */
    public static StringBuilder asEngineer(StringBuilder target, String format, double value) {
        if(value < 0) {
            target.append('-');
            return asEngineer(target, format, -value);
        }
        
        char fu = format.charAt(format.length()-1);
        
        UnitPrefix[] au = PREFIXES.get(fu);
        if(null == au) {
            throw new IllegalFormatConversionException(fu, UnitPrefix.class);
        }
        
        UnitPrefix c = au[0];
        for (UnitPrefix u : au) {
            c = u;
            if (value > c.doubleValue()) {
                break;
            }
        }

        String fs = format.substring(0, format.length()-1)+"f";        
        return target
            .append(String.format(Locale.ENGLISH, fs, value/c.doubleValue()))
            .append(c.text());
    }
    
    public static String group(int value) {
        return group(Integer.toString(value, 10), "'");
    }

    public static String group(long value) {
        return group(Long.toString(value, 10), "'");
    }
    
    private static String group(String text, String separator) {
        StringBuilder ret = new StringBuilder(text.length() + (text.length()/3 + 1) * separator.length());
        for(int i=0, n=text.length()-1; i<=n; i++) {
            ret.append(text.charAt(i));
            if( n>i && ((n-i)%3 == 0) ) {
                ret.append(separator);
            }
        }
        return ret.toString();
    }
    
    static class UnitPrefix {
        
        private final double v;
        private final BigDecimal dv;
        private final String u;

        UnitPrefix(double v, String u) {
            this.v = v;
            this.dv = BigDecimal.valueOf(v);
            this.u = u;
        }
        
        public long longValue() {
            return (long)v;
        }

        public double doubleValue() {
            return v;
        }
        
        public BigDecimal decimalValue() {
            return dv;
        }

        public String text() {
            return u;
        }
        
    }
    
}
