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

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.generic.Wrapper;
import net.ranides.assira.reflection.mockup.ForResolver.*;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.reflection.mockup.ForIClass;
import net.ranides.assira.reflection.mockup.ForResolver;
import net.ranides.assira.text.StringUtils;
import net.ranides.assira.text.Wildcard;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class ResolvePatternTest {

    @Test
    public void testSerialization() throws IOException {
        ResolvePattern a = ResolvePattern.compile("content.item.height");
        ResolvePattern b = SerializationUtils.copy(a);
        
        assertNotSame(a, b);
        assertEquals(a, b);
    }
    
    @Test
    public void testCompile() {
        ResolvePattern r1 = ResolvePattern.compile("object.item.5");
        assertEquals("object.item.5", r1.toString());
        assertEquals(r1, ResolvePattern.compile("..object...item.5"));
        assertEquals(r1, ResolvePattern.compile("[object]...item[5]"));
    }
    
    @Test
    public void testResolveError() {
        assertThrows(IllegalArgumentException.class, ()->{
            ResolveUtils.set(null, "1", 0);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            ResolveUtils.replace(null, "1", 0);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            ResolveUtils.get(null, "1");
        });
        assertThrows(IllegalArgumentException.class, ()->{
            ResolveUtils.type(null, "data");
        });
        
    }
    
    @Test
    public void testResolve() {
        A context = new A();
        int numbers[] = new int[]{1,2,3,4,5,6};

        // get: getter, field, final field
        assertEquals(5, ResolveUtils.get(context, "x"));
        assertEquals(16, ResolveUtils.get(context, "content.item.height"));
        assertEquals(1000, ResolveUtils.get(context, "content.item.hello"));
        
        // set: setter, field
        ResolveUtils.set(context, "x", 8);
        ResolveUtils.set(context, "content.item.height", 80);
        ResolveUtils.set(context, "content.item.hello", 800);
        
        assertEquals(8, ResolveUtils.replace(context, "x", 9));
        assertEquals(80, ResolveUtils.replace(context, "content.item.height", 81));
        assertEquals(800, ResolveUtils.replace(context, "content.item.hello", 801));
        
        assertEquals(9, context.x);
        assertEquals(81, context.content.item.height);
        assertEquals(801, context.content.item.getHello());
        

        Object newitemE1 = new E(0, 0, 801);
        Object newitemE2 = new E(0, 0, 801);
        ResolveUtils.set(context, "content.item", newitemE1);
        assertSame(newitemE1, ResolveUtils.replace(context, "content.item", newitemE2));
        assertSame(newitemE2, context.content.item);
        
        // get: map, list, array
        assertEquals((Integer)801, ResolveUtils.get(context, "content.item.hello"));
        assertEquals((Long)7L, ResolveUtils.get(context, "list[1].numbers.seven"));
        assertNull(ResolveUtils.get(context, "list[1].numbers.eight"));
        assertEquals("B", ResolveUtils.get(context, "list[1].name"));
        assertEquals("b", ResolveUtils.get(context, "array[1].name"));
        
        // set: map.put
        ResolveUtils.set(context, "list[1].numbers.seven", 77);
        assertEquals(77, ResolveUtils.replace(context, "list[1].numbers.seven", 78));
        assertEquals(78, context.list.get(1).numbers.get("seven"));
        
        // set: field (proxy map)
        Map<String,Integer> newmap1 = new HashMap<>();
        Map<String,Integer> newmap2 = new HashMap<>();
        newmap1.put("eight", 8);
        ResolveUtils.set(context, "list[1].numbers", newmap1);
        assertSame(newmap1, ResolveUtils.replace(context, "list[1].numbers", newmap2));
        assertSame(newmap2, context.list.get(1).numbers);
        
        // set: list
        Object newitem1 = new C("Q");
        Object newitem2 = new C("V");
        ResolveUtils.set(context, "list[1]", newitem1);
        assertSame(newitem1, ResolveUtils.replace(context, "list[1]", newitem2));
        assertSame(newitem2, context.list.get(1));
        
        // set: field (proxy list)
        List<?> newlist1 = Arrays.asList(new C("P"),new C("R"),new C("T"),new C("U"));
        List<?> newlist2 = Arrays.asList(new C("P"),new C("R"),new C("T"));
        ResolveUtils.set(context, "list", newlist1);
        assertSame(newlist1, ResolveUtils.replace(context, "list", newlist2));
        assertSame(newlist2, context.list);
        
        
        // get: (edge case) property handling
        assertEquals("R", ResolveUtils.get(context, "list[1]name"));
        assertEquals("R", ResolveUtils.get(context, ".list[1]name"));
        assertEquals("R", ResolveUtils.get(context, ".list[1].name"));
        
        // get: (edge case) index handling
        assertEquals("R", ResolveUtils.get(context, "list.1.name"));

        // get: (edge case) index handling if context is an array
        assertEquals(3, ResolveUtils.get(numbers, ".2"));
        assertEquals(3, ResolveUtils.get(numbers, "[2]"));
        assertEquals(3, ResolveUtils.get(numbers, "2"));
        assertEquals(3, ResolveUtils.get(numbers, ".[2]"));

        assertThrows(NumberFormatException.class, ()->{
            ResolveUtils.get(numbers, "[q]");
        });
        assertThrows(NumberFormatException.class, ()->{
            ResolveUtils.get(numbers, "0q");
        });

        // get: (edge case) key handling if context is a map
        assertEquals(8, ResolveUtils.get(newmap1, "eight"));
        assertNull(ResolveUtils.get(newmap1, "unknown"));
        
        // set: (edge case) property handling
        ResolveUtils.set(context, "x", 71);
        ResolveUtils.set(context, ".y", 72);
        ResolveUtils.set(context, "list[0]name", "S1");
        ResolveUtils.set(context, ".list[1]name", "S2");
        ResolveUtils.set(context, ".list[2].name", "S3");
        
        assertEquals(71, ResolveUtils.replace(context, "x", 171));
        assertEquals(72, ResolveUtils.replace(context, ".y", 172));
        assertEquals("S1", ResolveUtils.replace(context, "list[0]name", "S1q"));
        assertEquals("S2", ResolveUtils.replace(context, ".list[1]name", "S2q"));
        assertEquals("S3", ResolveUtils.replace(context, ".list[2].name", "S3q"));
        
        assertEquals(171, context.x);
        assertEquals(172, context.y);
        assertEquals("S1q", context.list.get(0).name);
        assertEquals("S2q", context.list.get(1).name);
        assertEquals("S3q", context.list.get(2).name);
        
        // set: (edge case) index handling
        ResolveUtils.set(context, "list.1.name", "U1");
        assertEquals("U1", ResolveUtils.replace(context, "list.1.name", "U2"));
        assertEquals("U2", context.list.get(1).name);

        // set: (edge case) index handling if context is an array
        ResolveUtils.set(numbers, ".1", 501);
        ResolveUtils.set(numbers, "[2]", 502);
        ResolveUtils.set(numbers, "3", 503);
        
        assertArrayEquals(new int[]{1,501,502,503,5,6}, numbers);
        
        assertEquals(501, ResolveUtils.replace(numbers, ".1", 601));
        assertEquals(502, ResolveUtils.replace(numbers, "[2]", 602));
        assertEquals(503, ResolveUtils.replace(numbers, "3", 603));
        assertArrayEquals(new int[]{1,601,602,603,5,6}, numbers);
        
    }
    
    
    
    @Test
    public void testMatcher() {
        Matcher hit = Pattern.compile("([a-z]+) ([a-z]+) ([a-z]+)").matcher("one two last");
        assertTrue(hit.find());
        assertEquals("two", ResolveUtils.get(hit, "2"));
        
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.set(hit, "2", "text");
        });
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.set(hit, "2[1]", 'R');
        });
        
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.replace(hit, "2", "text");
        });
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.replace(hit, "2[1]", 'R');
        });

    }
    
    @Test
    public void testCharSequence() {
        String a = "aeRto";
        CharSequence b = StringUtils.wrap("uAdd".toCharArray());
        assertEquals('R', ResolveUtils.get(a, "2"));
        assertEquals('A', ResolveUtils.get(b, "1"));
        
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.set(a, "2", 'S');
        });
        assertThrows(UnsupportedOperationException.class, ()->{
            ResolveUtils.replace(a, "2", 'S');
        });
    }
    
    @Test
    public void testType() {
        IClass<?> TYPE_C = IClass.typeinfo(ForResolver.C.class);
        IClass<?> TYPE_B = IClass.typeinfo(ForResolver.B.class);
        IClass<?> NUMBER = IClass.typeinfo(Number.class);
        
        A context = new A();
        int numbers[] = new int[]{1,2,3,4,5,6};
        
        assertEquals(ForIClass.INT, ResolveUtils.type(numbers, "1") );
        assertEquals(ForIClass.INT, ResolveUtils.type(context, "x") );
        
        assertEquals(TYPE_B, ResolveUtils.type(context, "content") );
        assertEquals(TYPE_C, ResolveUtils.type(context, "array[2]") );


        assertEquals(new TypeToken<List<C>>(){}, ResolveUtils.type(context, "list") );
        assertEquals(TYPE_C, ResolveUtils.type(context, "list[0]") );
        assertEquals(TYPE_C, ResolveUtils.type(context, "list[1]") );
        
        assertEquals(new TypeToken<Map<String, Number>>(){}, ResolveUtils.type(context, "list[1].numbers") );
        assertEquals(NUMBER, ResolveUtils.type(context, "list[1].numbers.two") );
        assertEquals(NUMBER, ResolveUtils.type(context, "list[1].numbers.seven") );
        assertEquals(NUMBER, ResolveUtils.type(context, "list[1].numbers.unknown") );
        
        assertEquals(new TypeToken<Map<Integer, C>>(){}, ResolveUtils.type(context, "empty") );
        assertEquals(TYPE_C, ResolveUtils.type(context, "empty[22]") );
        assertEquals(ForIClass.STRING, ResolveUtils.type(context, "empty[22].name") );
        
        assertEquals(new TypeToken<Map<Integer, C>>(){}, ResolveUtils.type(context, "nullmap") );
        assertEquals(TYPE_C, ResolveUtils.type(context, "nullmap.22") );
        assertEquals(ForIClass.STRING, ResolveUtils.type(context, "nullmap.22.name") );
        
        assertEquals(ForIClass.CHAR, ResolveUtils.type(context, "nullmap[22].name[88]") );
        assertEquals(ForIClass.CHAR, ResolveUtils.type(context, "list[1].name[0]") );
        
        Matcher hit = Pattern.compile("([a-z]+) ([a-z]+) ([a-z]+)").matcher("one two last");
        assertTrue(hit.find());
        
        assertEquals(ForIClass.STRING, ResolveUtils.type(hit, "[1]"));
        assertEquals(ForIClass.CHAR, ResolveUtils.type(hit, "[1][1]"));
        
        assertEquals(NUMBER, ResolveUtils.type(context, "list[1].numbers[77]"));
    }
    
    @Test
    public void testBind() {
        A context = new A();
        Wrapper<Object> item = ResolveUtils.bind(context, "content.item.hello");
        assertEquals(1000, item.get());
        item.set(1001);
        assertEquals(1001, context.content.item.getHello());
        assertEquals(1001, item.replace(1002));
        assertEquals(1002, context.content.item.getHello());
        assertEquals(ForIClass.INT, item.type());
    }
    
    @Test
    public void testStrategy_Format() {
        A context = new A();
        ResolveStrategy strategy = ResolveStrategy.FORMAT;
        
        assertEquals("{list[1].unknown}", ResolvePattern.compile(strategy, "list[1].unknown").get(context));
        
        assertEquals("{list[1].unknown.some}", ResolvePattern.compile(strategy, "list[1].unknown.some").get(context));
        
        assertEquals(IClass.typeinfo(String.class), ResolvePattern.compile(strategy, "list[1].unknown").type(context));
        
        assertEquals(IClass.typeinfo(String.class), ResolvePattern.compile(strategy, "list[1].unknown.some").type(context));

        assertThrows(ResolveException.class, "No writable expression: {list[1].unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown").set(context, 0);
        });
        assertThrows(ResolveException.class, "No writable expression: {list[1].unknown.some}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown.some").set(context, 0);
        });
        assertThrows(ResolveException.class, "No replaceable expression: {list[1].unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown").replace(context, 0);
        });
        assertThrows(ResolveException.class, "No replaceable expression: {list[1].unknown.some}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown.some").replace(context, 0);
        });
    }
    
    @Test
    public void testStrategy_Silent() {
        A context = new A();
        ResolveStrategy strategy = ResolveStrategy.SILENT;
        
        assertEquals(null, ResolvePattern.compile(strategy, "list[1].unknown").get(context));
        
        assertEquals(null, ResolvePattern.compile(strategy, "list[1].unknown.some").get(context));
        
        assertEquals(IClass.NULL, ResolvePattern.compile(strategy, "list[1].unknown").type(context));
        
        assertEquals(IClass.NULL, ResolvePattern.compile(strategy, "list[1].unknown.some").type(context));
        
        ResolvePattern.compile(strategy, "list[1].unknown").set(context, 0);
        ResolvePattern.compile(strategy, "list[1].unknown.some").set(context, 0);
        ResolvePattern.compile(strategy, "list[1].unknown").replace(context, 0);
        ResolvePattern.compile(strategy, "list[1].unknown.some").replace(context, 0);
    }
    
    @Test
    public void testStrategy_Default() {
        A context = new A();
        ResolveStrategy strategy = ResolveStrategy.withDefault(777L);
        
        assertEquals(777L, ResolvePattern.compile(strategy, "list[1].unknown").get(context));
        
        assertThrows(ResolveException.class, "Invalid expression: {list[1].unknown.some} at {unknown}", ()->{
            ResolvePattern.compile(ResolveStrategy.withDefault("##"), "list[1].unknown.some").get(context);
        });
        
        assertEquals(IClass.typeinfo(Long.class), ResolvePattern.compile(strategy, "list[1].unknown").type(context));
        
        assertThrows(ResolveException.class, "Invalid expression: {list[1].unknown.some} at {unknown}", ()->{
            ResolvePattern.compile(ResolveStrategy.withDefault("##"), "list[1].unknown.some").type(context);
        });
        
        assertThrows(ResolveException.class, "No writable expression: {list[1].unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown").set(context, 0);
        });
        assertThrows(ResolveException.class, "Invalid expression: {list[1].unknown.some} at {unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown.some").set(context, 0);
        });
        assertThrows(ResolveException.class, "No replaceable expression: {list[1].unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown").replace(context, 0);
        });
        assertThrows(ResolveException.class, "Invalid expression: {list[1].unknown.some} at {unknown}", ()->{
            ResolvePattern.compile(strategy, "list[1].unknown.some").replace(context, 0);
        });
    }
    
        
}
