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

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.generic.HashUtils;
import net.ranides.assira.generic.SerializationUtils;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.ResolveException;
import org.junit.AfterClass;
import org.junit.BeforeClass;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class ResolveFormatTest {
    
    private static TimeZone timezone;
    
    private static RFItem ITEM;
    
    @BeforeClass
    public static void init() {
        timezone = TimeZone.getDefault();
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        ITEM = new RFItem();
    }
    
    @AfterClass 
    public static void shutdown() {
        TimeZone.setDefault(timezone);
    }
    
    @Test
    public void testSerialization() throws IOException {
        ResolveFormat a = ResolveFormat.compile("{x} {y} {z}");
        ResolveFormat b = SerializationUtils.copy(a);
        
        assertNotSame(a, b);
        assertEquals(a, b);
        
        assertEquals("5 6 7", b.format(new A(5,6,7)));
    }
    
    @Test
    public void testBasics() throws IOException {
        ResolveFormat a = ResolveFormat.compile("{x} {y} {z}");
        ResolveFormat b = ResolveFormat.compile("US","{x} {y} {z}");
        
        assertEquals("{x} {y} {z}", a.pattern());
        assertEquals("{x} {y} {z}", b.pattern());
        
        assertEquals(ResolveDialect.getDefault(), a.dialect());
        assertEquals(ResolveDialect.getInstance("US"), b.dialect());
    }
    
    @Test
    public void testEquals() {
        ResolveFormat a = ResolveFormat.compile("PL", "{x} {y} {z}");
        ResolveFormat b = ResolveFormat.compile("PL", "{x} {y} {z}");
        ResolveFormat c = ResolveFormat.compile("PL", "{x} {y} {r}");
        ResolveFormat d = ResolveFormat.compile("US", "{x} {y} {z}");
        ResolveFormat e = ResolveFormat.compile("US", "{x} {y} {r}");
        String f = "Hello";
        
        assertSymNotEquals(a, null);
        assertSymNotEquals(a, c);
        assertSymNotEquals(a, d);
        assertSymNotEquals(a, e);
        assertSymNotEquals(a, f);
        
        assertSymEquals(a, a);
        assertSymEquals(a, b);
    }

    @Test
    public void testFormat_Unknown() throws UnsupportedEncodingException {
//        System.out.printf("%s%n", ResolveFormat.compile("{value1} {tag}").format(ITEM));
        System.out.printf("%s%n", ResolveFormat.compile("{value1} {bivalue}").format(ITEM));
    }
    
    @Test
    public void testFormat_Number() throws UnsupportedEncodingException {
        assertEquals("2.1415",      ResolveFormat.compile("{value1}").format(ITEM) );
        
        assertEquals("2,141",       ResolveFormat.compile("PL","{value1,number}").format(ITEM) );
        assertEquals("2",           ResolveFormat.compile("PL","{value1,number,integer}").format(ITEM) );
        assertEquals(" 2,14",       ResolveFormat.compile("PL","{value1,number,currency}").format(ITEM) );
        assertEquals("- 150,50",    ResolveFormat.compile("PL","{value2,number,currency}").format(ITEM) );
        assertEquals("214%",        ResolveFormat.compile("PL","{value1,number,percent}").format(ITEM) );
        
        assertEquals("2.141",       ResolveFormat.compile("US","{value1,number}").format(ITEM) );
        assertEquals("2",           ResolveFormat.compile("US","{value1,number,integer}").format(ITEM) );
        assertEquals(" 2.14",       ResolveFormat.compile("US","{value1,number,currency}").format(ITEM) );
        assertEquals("- 150.50",    ResolveFormat.compile("US","{value2,number,currency}").format(ITEM) );
        assertEquals("214%",        ResolveFormat.compile("US","{value1,number,percent}").format(ITEM) );
        
        assertEquals("-150_456.5",    ResolveFormat.compile("{fvalue,number,java}").format(ITEM) );
        assertEquals("-150'456.5",    ResolveFormat.compile("{fvalue,number,c++}").format(ITEM) );
    }
        
    @Test
    public void testParse_Errors() {
        Integer[] out= new Integer[3];
        ResolveFormat rf = ResolveFormat.compile("{0,number} / {1,number,java} / {2,number,java}");
        assertThrows(IllegalArgumentException.class, ()->{
            rf.parse("12z / 33 / 55_100", out);
        }); 
        assertThrows(IllegalArgumentException.class, ()->{
            rf.parse("12 33 / 55_100", out);
        }); 
        rf.parse("12 / 33 / 55_100", out);
        rf.parse("12,4 / 33 / 55_100", out);
    }
    
    @Test
    public void testParse_Number() {
        Integer[] out= new Integer[3];
        ResolveFormat.compile("{0,number} / {1,number,java} / {2,number,java}").parse("12 / 33 / 55_100", out);
        assertArrayEquals(new Integer[]{12,33,55100}, out);
        
        Double[] out2= new Double[3];
        ResolveFormat.compile("PL","{0,number} / {1,number,java} / {2,number,java}").parse("14,3 / 35 / 55", out2);
        assertArrayEquals(new Double[]{14.3,35.0,55.0}, out2);
        
        Double[] out3= new Double[3];
        ResolveFormat.compile("US","{0,number} / {1,number,percent} / {2,number,java}").parse("14.3 / 35% / 55", out3);
        assertArrayEquals(new Double[]{14.3, 0.35, 55.0}, out3);
    }

    
    @Test
    public void testFormat_Date() {
        assertEquals("Sun Jun 15 13:25:10 UTC 2014", ResolveFormat.compile("{date1}").format(ITEM) );
        
        assertEquals("2014-06-15", ResolveFormat.compile("PL","{date1,date}").format(ITEM) );
        assertEquals("14-06-15", ResolveFormat.compile("PL","{date1,date,short}").format(ITEM) );
        assertEquals("2014-06-15", ResolveFormat.compile("PL","{date1,date,medium}").format(ITEM) );
        assertEquals("15 czerwca 2014", ResolveFormat.compile("PL","{date1,date,long}").format(ITEM) );
        assertEquals("niedziela, 15 czerwca 2014", ResolveFormat.compile("PL","{date1,date,full}").format(ITEM) );
        assertEquals("2014-06-15T13:25Z", ResolveFormat.compile("PL","{date1,date,iso}").format(ITEM) );
        assertEquals("201406151325", ResolveFormat.compile("PL","{date1,date,gnu}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10.000", ResolveFormat.compile("PL","{date1,date,trace}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10", ResolveFormat.compile("PL","{date1,date,local}").format(ITEM) );
        
        assertEquals("Jun 15, 2014", ResolveFormat.compile("US","{date1,date}").format(ITEM) );
        assertEquals("6/15/14", ResolveFormat.compile("US","{date1,date,short}").format(ITEM) );
        assertEquals("Jun 15, 2014", ResolveFormat.compile("US","{date1,date,medium}").format(ITEM) );
        assertEquals("June 15, 2014", ResolveFormat.compile("US","{date1,date,long}").format(ITEM) );
        assertEquals("Sunday, June 15, 2014", ResolveFormat.compile("US","{date1,date,full}").format(ITEM) );
        assertEquals("2014-06-15T13:25Z", ResolveFormat.compile("US","{date1,date,iso}").format(ITEM) );
        assertEquals("201406151325", ResolveFormat.compile("US","{date1,date,gnu}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10.000", ResolveFormat.compile("US","{date1,date,trace}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10", ResolveFormat.compile("US","{date1,date,local}").format(ITEM) );
        
    }
    
    @Test
    public void testParse_Date() {
        Date[] out1 = new Date[5];
        ResolveFormat
                .compile("PL","{0,date} / {1,date,short} / {2,date,medium} / {3,date,long} / {4,date,full}")
                .parse("2014-06-11 / 14-06-12 / 2014-06-13 / 14 czerwca 2014 / niedziela, 15 czerwca 2014", out1);
        assertEquals(
            "[Wed Jun 11 00:00:00 UTC 2014, Thu Jun 12 00:00:00 UTC 2014, Fri Jun 13 00:00:00 UTC 2014, Sat Jun 14 00:00:00 UTC 2014, Sun Jun 15 00:00:00 UTC 2014]",
            Arrays.toString(out1)
        );
        
        Date[] out2 = new Date[4];
        ResolveFormat
                .compile("PL","{0,date,iso} / {1,date,gnu} / {2,date,trace} / {3,date,local}")
                .parse("2014-06-11T11:25Z / 201406121325 / 2014-06-13 14:25:10.000 / 2014-06-14 18:25:10", out2);
        assertEquals(
            "[Wed Jun 11 11:25:00 UTC 2014, Thu Jun 12 13:25:00 UTC 2014, Fri Jun 13 14:25:10 UTC 2014, Sat Jun 14 18:25:10 UTC 2014]",
            Arrays.toString(out2)
        );
        
        
        Date[] out3 = new Date[5];
        ResolveFormat
            .compile("US","{0,date} | {1,date,short} | {2,date,medium} | {3,date,long} | {4,date,full}")
            .parse("Jun 11, 2014 | 6/12/14 | Jun 13, 2014 | June 14, 2014 | Sunday, June 15, 2014", out3);
        assertEquals(
            "[Wed Jun 11 00:00:00 UTC 2014, Thu Jun 12 00:00:00 UTC 2014, Fri Jun 13 00:00:00 UTC 2014, Sat Jun 14 00:00:00 UTC 2014, Sun Jun 15 00:00:00 UTC 2014]",
            Arrays.toString(out3)
        );
    }
    
    @Test
    public void testFormat_Instant() {
        assertEquals("2014-06-15T13:25:10.777Z", ResolveFormat.compile("{date2}").format(ITEM) );
                
        assertEquals("2014-06-15", ResolveFormat.compile("PL","{date2,date}").format(ITEM) );
        assertEquals("14-06-15", ResolveFormat.compile("PL","{date2,date,short}").format(ITEM) );
        assertEquals("2014-06-15", ResolveFormat.compile("PL","{date2,date,medium}").format(ITEM) );
        assertEquals("15 czerwca 2014", ResolveFormat.compile("PL","{date2,date,long}").format(ITEM) );
        assertEquals("niedziela, 15 czerwca 2014", ResolveFormat.compile("PL","{date2,date,full}").format(ITEM) );
        assertEquals("2014-06-15T13:25Z", ResolveFormat.compile("PL","{date2,date,iso}").format(ITEM) );
        assertEquals("201406151325", ResolveFormat.compile("PL","{date2,date,gnu}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10.777", ResolveFormat.compile("PL","{date2,date,trace}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10", ResolveFormat.compile("PL","{date2,date,local}").format(ITEM) );
        
        assertEquals("Jun 15, 2014", ResolveFormat.compile("US","{date2,date}").format(ITEM) );
        assertEquals("6/15/14", ResolveFormat.compile("US","{date2,date,short}").format(ITEM) );
        assertEquals("Jun 15, 2014", ResolveFormat.compile("US","{date2,date,medium}").format(ITEM) );
        assertEquals("June 15, 2014", ResolveFormat.compile("US","{date2,date,long}").format(ITEM) );
        assertEquals("Sunday, June 15, 2014", ResolveFormat.compile("US","{date2,date,full}").format(ITEM) );
        assertEquals("2014-06-15T13:25Z", ResolveFormat.compile("US","{date2,date,iso}").format(ITEM) );
        assertEquals("201406151325", ResolveFormat.compile("US","{date2,date,gnu}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10.777", ResolveFormat.compile("US","{date2,date,trace}").format(ITEM) );
        assertEquals("2014-06-15 13:25:10", ResolveFormat.compile("US","{date2,date,local}").format(ITEM) );
        
    }
    
    @Test
    public void testFormat_Time() {
        
        assertEquals("13:25:10", ResolveFormat.compile("PL","{date1,time}").format(ITEM) );
        assertEquals("13:25", ResolveFormat.compile("PL","{date1,time,short}").format(ITEM) );
        assertEquals("13:25:10", ResolveFormat.compile("PL","{date1,time,medium}").format(ITEM) );
        assertEquals("13:25:10 UTC", ResolveFormat.compile("PL","{date1,time,long}").format(ITEM) );
        assertEquals("13:25:10 UTC", ResolveFormat.compile("PL","{date1,time,full}").format(ITEM) );
        assertEquals("13:25:10Z", ResolveFormat.compile("PL","{date1,time,iso}").format(ITEM) );
        assertEquals("132510", ResolveFormat.compile("PL","{date1,time,gnu}").format(ITEM) );
        assertEquals("13:25:10.000", ResolveFormat.compile("PL","{date1,time,trace}").format(ITEM) );
        assertEquals("13:25:10", ResolveFormat.compile("PL","{date1,time,local}").format(ITEM) );
        
        assertEquals("1:25:10 PM", ResolveFormat.compile("US","{date1,time}").format(ITEM) );
        assertEquals("1:25 PM", ResolveFormat.compile("US","{date1,time,short}").format(ITEM) );
        assertEquals("1:25:10 PM", ResolveFormat.compile("US","{date1,time,medium}").format(ITEM) );
        assertEquals("1:25:10 PM UTC", ResolveFormat.compile("US","{date1,time,long}").format(ITEM) );
        assertEquals("1:25:10 PM UTC", ResolveFormat.compile("US","{date1,time,full}").format(ITEM) );
        assertEquals("13:25:10Z", ResolveFormat.compile("US","{date1,time,iso}").format(ITEM) );
        assertEquals("132510", ResolveFormat.compile("US","{date1,time,gnu}").format(ITEM) );
        assertEquals("13:25:10.000", ResolveFormat.compile("US","{date1,time,trace}").format(ITEM) );
        assertEquals("13:25:10", ResolveFormat.compile("US","{date1,time,local}").format(ITEM) );
    }
    
    @Test
    public void testFormat_HexArray() {
        assertEquals("hex = [4]{7A,0x31,0x45,0xAC}", ResolveFormat.compile("hex = {data1,hex}").format(ITEM) );
        assertEquals("hex = [4]{7A,0x31,0x45,0xAC}", ResolveFormat.compile("hex = {data1,hex,array}").format(ITEM) );
        assertEquals("hex = 7A 31 45 AC", ResolveFormat.compile("hex = {data1,hex,stream}").format(ITEM) );
    }
    
    @SuppressFBWarnings
    @Test
    public void testParse_HexArray() {
        BData out1 = new BData();
        ResolveFormat.compile("array = {value,hex,array}x").parse("array = [3]{0x10,0x3C,0x3A}x", out1);
        assertArrayEquals(new byte[]{0x10,0x3C,0x3A}, out1.value);
        
        BData out2 = new BData();
        ResolveFormat.compile("array = {value,hex,stream}B").parse("array = 10 3C 3AB", out2);
        assertArrayEquals(new byte[]{0x10,0x3C,0x3A}, out2.value);
    }
    
    @Test
    public void testParse_Generic() {
        String out[] = new String[3];
        ResolveFormat.compile("{0} / {1} / last = {2}.").parse("hello / world / last = 23.", out);
        assertArrayEquals(new String[]{"hello","world","23"}, out);
    }
    
    @Test
    public void testFormat_Metric_Int() {
        assertEquals("-154.27k", ResolveFormat.compile("{ivalue,metric}").format(ITEM));
        assertEquals("-154.27k", ResolveFormat.compile("{ivalue,metric/si}").format(ITEM));
        assertEquals("-154.27E3", ResolveFormat.compile("{ivalue,metric/e}").format(ITEM));
        
        assertEquals("-154.270k", ResolveFormat.compile("{ivalue,metric,#.000}").format(ITEM));
        assertEquals("-154.270k", ResolveFormat.compile("{ivalue,metric/si,#.000}").format(ITEM));
        assertEquals("-154.270E3", ResolveFormat.compile("{ivalue,metric/e,#.000}").format(ITEM));
    }
    
    @Test
    public void testFormat_Metric_Float() {
        assertEquals("-150.457k", ResolveFormat.compile("{fvalue,metric}").format(ITEM));
        assertEquals("-150.457k", ResolveFormat.compile("{fvalue,metric/si}").format(ITEM));
        assertEquals("-150.457E3", ResolveFormat.compile("{fvalue,metric/e}").format(ITEM));
        
        assertEquals("-150.46k", ResolveFormat.compile("{fvalue,metric,#.00}").format(ITEM));
        assertEquals("-150.46k", ResolveFormat.compile("{fvalue,metric/si,#.00}").format(ITEM));
        assertEquals("-150.46E3", ResolveFormat.compile("{fvalue,metric/e,#.00}").format(ITEM));
    }
    
    @Test
    public void testFormat_Metric_BigInteger() {
        assertEquals("-1.542k", ResolveFormat.compile("{bdvalue,metric}").format(ITEM));
        assertEquals("-1.542k", ResolveFormat.compile("{bdvalue,metric/si}").format(ITEM));
        assertEquals("-1.542E3", ResolveFormat.compile("{bdvalue,metric/e}").format(ITEM));
        
        assertEquals("-1.5k", ResolveFormat.compile("{bdvalue,metric,#.0}").format(ITEM));
        assertEquals("-1.5k", ResolveFormat.compile("{bdvalue,metric/si,#.0}").format(ITEM));
        assertEquals("-1.5E3", ResolveFormat.compile("{bdvalue,metric/e,#.0}").format(ITEM));
    }
    
    @Test
    public void testFormat_Metric_BigInteger_128() {
        assertEquals("-1.542Y", ResolveFormat.compile("{value128,metric}").format(ITEM));
        assertEquals("-1.542Y", ResolveFormat.compile("{value128,metric/si}").format(ITEM));
        assertEquals("-1.542E24", ResolveFormat.compile("{value128,metric/e}").format(ITEM));
        
        assertEquals("-1.5Y", ResolveFormat.compile("{value128,metric,#.0}").format(ITEM));
        assertEquals("-1.5Y", ResolveFormat.compile("{value128,metric/si,#.0}").format(ITEM));
        assertEquals("-1.5E24", ResolveFormat.compile("{value128,metric/e,#.0}").format(ITEM));
    }
    
    @Test
    public void testFormat_Metric_BigDecimal() {
        assertEquals("-154.2k", ResolveFormat.compile("{bivalue,metric}").format(ITEM));
        assertEquals("-154.2k", ResolveFormat.compile("{bivalue,metric/si}").format(ITEM));
        assertEquals("-154.2E3", ResolveFormat.compile("{bivalue,metric/e}").format(ITEM));
        
        assertEquals("-154.200k", ResolveFormat.compile("{bivalue,metric,#.000}").format(ITEM));
        assertEquals("-154.200k", ResolveFormat.compile("{bivalue,metric/si,#.000}").format(ITEM));
        assertEquals("-154.200E3", ResolveFormat.compile("{bivalue,metric/e,#.000}").format(ITEM));
    }
    
    @SuppressFBWarnings
    @Test
    public void testParse_MetricSI() {
        NumData out = new NumData();
        ResolveFormat.compile("{iv,metric/si}!").parse("12k!", out);
        assertEquals(12000, out.iv);
        
        ResolveFormat.compile("{iv,metric/si}!").parse("12!", out);
        assertEquals(12, out.iv);
        
        ResolveFormat.compile("{dv,metric/si}!").parse("12.5M!", out);
        assertEquals(12500000.0, out.dv, 0.1);
        
        ResolveFormat.compile("{dv,metric/si}!").parse("12.5372!", out);
        assertEquals(12.5372, out.dv, 0.00001);
        
        ResolveFormat.compile("{bd,metric/si}!").parse("125k!", out);
        assertTrue(0 == new BigDecimal("125000").compareTo(out.bd));
        
        ResolveFormat.compile("{bd,metric/si}!").parse("12.5372!", out);
        assertTrue(0 == new BigDecimal("12.5372").compareTo(out.bd));
        
        ResolveFormat.compile("{bi,metric/si}!").parse("127k!", out);
        assertTrue(0 == new BigInteger("127000").compareTo(out.bi));
        
        ResolveFormat.compile("{bi,metric/si}!").parse("19!", out);
        assertTrue(0 == new BigInteger("19").compareTo(out.bi));
    }
    
    @Test
    public void testParse_MetricBinaryIEC() {
        NumData out = new NumData();
        ResolveFormat.compile("{iv,binary/iec}!").parse("12Ki!", out);
        assertEquals(12*1024, out.iv);
        
        ResolveFormat.compile("{iv,binary/iec}!").parse("12!", out);
        assertEquals(12, out.iv);
        
        ResolveFormat.compile("{dv,binary/iec}!").parse("12.5Mi!", out);
        assertEquals(12.5*1024.0*1024.0, out.dv, 0.1);
        
        ResolveFormat.compile("{dv,binary/iec}!").parse("12.5372!", out);
        assertEquals(12.5372, out.dv, 0.00001);
        
        ResolveFormat.compile("{bd,binary/iec}!").parse("125Ki!", out);
        assertTrue(0 == new BigDecimal("128000").compareTo(out.bd));
        
        ResolveFormat.compile("{bd,binary/iec}!").parse("12.5372!", out);
        assertTrue(0 == new BigDecimal("12.5372").compareTo(out.bd));
        
        ResolveFormat.compile("{bi,binary/iec}!").parse("127Ki!", out);
        assertTrue(0 == new BigInteger("130048").compareTo(out.bi));
        
        ResolveFormat.compile("{bi,binary/iec}!").parse("19!", out);
        assertTrue(0 == new BigInteger("19").compareTo(out.bi));
    }
    
    @Test
    public void testFormat_Binary_Int() {
        assertEquals("-150.654Ki", ResolveFormat.compile("{ivalue,binary}").format(ITEM));
        assertEquals("-150.654Ki", ResolveFormat.compile("{ivalue,binary/iec}").format(ITEM));
        assertEquals("-150.654KK", ResolveFormat.compile("{ivalue,binary/knuth}").format(ITEM));
        assertEquals("-150.654B3", ResolveFormat.compile("{ivalue,binary/e}").format(ITEM));
        
        assertEquals("-150.7Ki", ResolveFormat.compile("{ivalue,binary,#.0}").format(ITEM));
        assertEquals("-150.7Ki", ResolveFormat.compile("{ivalue,binary/iec,#.0}").format(ITEM));
        assertEquals("-150.7KK", ResolveFormat.compile("{ivalue,binary/knuth,#.0}").format(ITEM));
        assertEquals("-150.7B3", ResolveFormat.compile("{ivalue,binary/e,#.0}").format(ITEM));
    }
    
    @Test
    public void testFormat_Binary_Float() {
        assertEquals("-146.93Ki", ResolveFormat.compile("{fvalue,binary}").format(ITEM));
        assertEquals("-146.93Ki", ResolveFormat.compile("{fvalue,binary/iec}").format(ITEM));
        assertEquals("-146.93KK", ResolveFormat.compile("{fvalue,binary/knuth}").format(ITEM));
        assertEquals("-146.93B3", ResolveFormat.compile("{fvalue,binary/e}").format(ITEM));
        
        assertEquals("-146.9Ki", ResolveFormat.compile("{fvalue,binary,#.0}").format(ITEM));
        assertEquals("-146.9Ki", ResolveFormat.compile("{fvalue,binary/iec,#.0}").format(ITEM));
        assertEquals("-146.9KK", ResolveFormat.compile("{fvalue,binary/knuth,#.0}").format(ITEM));
        assertEquals("-146.9B3", ResolveFormat.compile("{fvalue,binary/e,#.0}").format(ITEM));
    }
    
    @Test
    public void testFormat_Binary_BigDecimal() {
        assertEquals("-1.506Ki", ResolveFormat.compile("{bdvalue,binary}").format(ITEM));
        assertEquals("-1.506Ki", ResolveFormat.compile("{bdvalue,binary/iec}").format(ITEM));
        assertEquals("-1.506KK", ResolveFormat.compile("{bdvalue,binary/knuth}").format(ITEM));
        assertEquals("-1.506B3", ResolveFormat.compile("{bdvalue,binary/e}").format(ITEM));
        
        assertEquals("-1.5Ki", ResolveFormat.compile("{bdvalue,binary,#.0}").format(ITEM));
        assertEquals("-1.5Ki", ResolveFormat.compile("{bdvalue,binary/iec,#.0}").format(ITEM));
        assertEquals("-1.5KK", ResolveFormat.compile("{bdvalue,binary/knuth,#.0}").format(ITEM));
        assertEquals("-1.5B3", ResolveFormat.compile("{bdvalue,binary/e,#.0}").format(ITEM));
    }
    
    @Test
    public void testFormat_Binary_BigInteger() {
        assertEquals("-150.586Ki", ResolveFormat.compile("{bivalue,binary}").format(ITEM));
        assertEquals("-150.586Ki", ResolveFormat.compile("{bivalue,binary/iec}").format(ITEM));
        assertEquals("-150.586KK", ResolveFormat.compile("{bivalue,binary/knuth}").format(ITEM));
        assertEquals("-150.586B3", ResolveFormat.compile("{bivalue,binary/e}").format(ITEM));
        
        assertEquals("-150.6Ki", ResolveFormat.compile("{bivalue,binary,#.0}").format(ITEM));
        assertEquals("-150.6Ki", ResolveFormat.compile("{bivalue,binary/iec,#.0}").format(ITEM));
        assertEquals("-150.6KK", ResolveFormat.compile("{bivalue,binary/knuth,#.0}").format(ITEM));
        assertEquals("-150.6B3", ResolveFormat.compile("{bivalue,binary/e,#.0}").format(ITEM));
    }
        
    @Test
    public void testFormat_Pattern_Matcher() {
        String source ="AH-ello";
        Matcher hit = Pattern.compile("(.)(.)-(.+)").matcher(source);
        assertTrue(hit.find());
        
        String ret = ResolveFormatUtils.format("Hello {1} world {2}{3}!", hit);
        assertEquals("Hello A world Hello!", ret);
    }
    
    @Test
    public void testFormat_Pattern_Complex() {
        String source ="XY-point";
        Matcher hit = Pattern.compile("(.)(.)-(.+)").matcher(source);
        assertTrue(hit.find());
        
        Wrapper wrapper = new Wrapper("Hello", 77, hit, new Wrapper("world", '?'));
        String r1 = ResolveFormatUtils.format("{[0]} {[1]} {[2][3]} {[3].items[0]} {[3].items[1]}", wrapper.items);
        assertEquals("Hello 77 point world ?", r1);
        
        String r2 = ResolveFormatUtils.format("{0} {1} {2.3.4} {3.items.0} {3.items.1}", wrapper.items);
        assertEquals("Hello 77 t world ?", r2);
    }
    
    @Test
    public void testFormat_Choice() {
        ResolveFormat rf = ResolveFormat.compile("PL","value = {0,number} {1,choice,-1#negative|0#zero|1#one|2#two|2<positive}");

        assertEquals("value = -3 negative", rf.vformat(-3,-3));
        assertEquals("value = -1 negative", rf.vformat(-1,-1));
        assertEquals("value = 0 zero", rf.vformat(0,0));
        assertEquals("value = 1,5 one", rf.vformat(1.5,1.5));
        assertEquals("value = 3 positive", rf.vformat(3.0,3.0));
        
        Double[] out1 = new Double[2];
        rf.parse("value = 3 negative", out1);
        assertEquals("[3.0, -1.0]", Arrays.toString(out1));
        
        Double[] out2 = new Double[2];
        rf.parse("value = 77 one", out2);
        assertEquals("[77.0, 1.0]", Arrays.toString(out2));
        
        assertThrows(ResolveException.class, ()->{
            rf.parse("value = 77 four", out2);
        });
        
    }
    
    private static final class Wrapper {
        public Object[] items;

        public Wrapper(Object... items) {
            this.items = items;
        }
        
    }
    
    private static class A {
        
        public int x;
        public int y;
        public int z;
        
        public A() {
            this(0,0,0);
        }

        public A(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        @Override
        public int hashCode() {
            return HashUtils.hashValues(5, 37, x, y, z);
        }

        @Override
        public boolean equals(Object object) {
            if(!(object instanceof A)) {
                return false;
            }
            final A other = (A) object;
            return 
                (this.x == other.x) &&
                (this.y == other.y) &&
                (this.z == other.z);
        }

        @Override
        public String toString() {
            return "A{" + "x=" + x + ", y=" + y + ", z=" + z + '}';
        }
        
    }

    private static class RFItem {
    
        public float value1 = 2.1415f;
        
        public double value2 = -150.50;
        
        public Date date1 = new GregorianCalendar(2014, 05, 15, 13, 25, 10).getTime();
        
        public Instant date2 = date1.toInstant().plusMillis(777);
        
        public byte[] data1 = new byte[]{(byte)0x7A, (byte)0x31, (byte)0x45, (byte)0xAC };
        
        public double fvalue = -150456.50;
        
        public int ivalue = -154270;
        
        public BigInteger bivalue = BigInteger.valueOf(-154200L);
        
        public BigDecimal bdvalue = BigDecimal.valueOf(-1542.50);
        
        public BigInteger value128 = new BigInteger("-1542000000000000000000000");
        
    };
    
    private static final class BData {
        
        public byte[] value;
        
    }
    
    private static final class NumData {
        
        public BigDecimal bd;
        
        public BigInteger bi;
        
        public AtomicInteger ai;
        
        public double dv;
        
        public long iv;
        
    }

}
