package org.nutz.aop.asm;

import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PROTECTED;
import static java.lang.reflect.Modifier.PUBLIC;
import static java.lang.reflect.Modifier.TRANSIENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.junit.Test;
import org.nutz.Nutzs;
import org.nutz.aop.ClassAgent;
import org.nutz.aop.ClassDefiner;
import org.nutz.aop.interceptor.LoggingMethodInterceptor;
import org.nutz.aop.javassist.lstn.MethodCounter;
import org.nutz.aop.javassist.lstn.RhinocerosListener;
import org.nutz.aop.javassist.meta.Buffalo;
import org.nutz.aop.javassist.meta.Hippo;
import org.nutz.aop.javassist.meta.Moose;
import org.nutz.aop.javassist.meta.Rhinoceros;
import org.nutz.aop.javassist.meta.Vegetarian;
import org.nutz.aop.javassist.meta.Vegetarians;
import org.nutz.aop.javassist.meta.Vegetarian.BEH;
import org.nutz.aop.matcher.MethodMatcherFactory;
import org.nutz.json.Json;
import org.nutz.lang.Mirror;
import org.nutz.lang.random.R;

public class AsmClassAgentTest {

    @Test
    public void test_duplicate_class_exception() throws Exception {
        int[] cc = new int[4];
        AsmClassAgent ca = getNewClassAgent();
        ca.addInterceptor(MethodMatcherFactory.matcher(".*"), new MethodCounter(cc));
        AsmClassAgent ca2 = getNewClassAgent();
        ca2.id = ca.id;
        ca2.addInterceptor(MethodMatcherFactory.matcher(".*"), new MethodCounter(cc));

        ClassDefiner cd = Nutzs.cd();

        Class<? extends Moose> c = ca.define(cd, Moose.class);
        Moose m = c.newInstance();
        m.doSomething(BEH.run);
        assertEquals("[2, 2, 0, 0]", Json.toJson(cc));

        Class<? extends Moose> c2 = ca2.define(cd, Moose.class);
        assertEquals(c, c2);
        m = c.newInstance();
        m.doSomething(BEH.run);
        assertEquals("[4, 4, 0, 0]", Json.toJson(cc));
    }

    @Test
    public void test_return_array_method() {
        int[] cc = new int[4];
        Arrays.fill(cc, 0);
        ClassAgent aca = getNewClassAgent();
        aca.addInterceptor(MethodMatcherFactory.matcher("returnArrayMethod"), new MethodCounter(cc));
        Class<? extends Buffalo> c = aca.define(Nutzs.cd(), Buffalo.class);// RA.class;
        Buffalo r = Mirror.me(c).born();
        String[] ss = r.returnArrayMethod();
        assertEquals("[1, 1, 0, 0]", Json.toJson(cc));
        assertEquals(3, ss.length);
    }

    @Test
    public void test_basice_matcher() throws Exception {
        Method method = Vegetarian.class.getDeclaredMethod("doSomething", BEH.class);
        assertTrue(MethodMatcherFactory.matcher().match(method));
        assertTrue(MethodMatcherFactory.matcher(PUBLIC).match(method));
        assertFalse(MethodMatcherFactory.matcher(PROTECTED).match(method));
        assertFalse(MethodMatcherFactory.matcher(TRANSIENT).match(method));
        assertFalse(MethodMatcherFactory.matcher(PRIVATE).match(method));

        method = Vegetarian.class.getDeclaredMethod("run", int.class);
        assertTrue(MethodMatcherFactory.matcher().match(method));
        assertFalse(MethodMatcherFactory.matcher(PUBLIC).match(method));
        assertTrue(MethodMatcherFactory.matcher(PROTECTED).match(method));
        assertFalse(MethodMatcherFactory.matcher(TRANSIENT).match(method));
        assertFalse(MethodMatcherFactory.matcher(PRIVATE).match(method));

        method = Vegetarian.class.getDeclaredMethod("defaultMethod");
        assertTrue(MethodMatcherFactory.matcher().match(method));
        assertFalse(MethodMatcherFactory.matcher(PUBLIC).match(method));
        assertFalse(MethodMatcherFactory.matcher(PROTECTED).match(method));
        assertTrue(MethodMatcherFactory.matcher(TRANSIENT).match(method));
        assertFalse(MethodMatcherFactory.matcher(PRIVATE).match(method));

        method = Vegetarian.class.getDeclaredMethod("privateMethod");
        assertTrue(MethodMatcherFactory.matcher().match(method));
        assertFalse(MethodMatcherFactory.matcher(PUBLIC).match(method));
        assertFalse(MethodMatcherFactory.matcher(PROTECTED).match(method));
        assertFalse(MethodMatcherFactory.matcher(TRANSIENT).match(method));
        assertTrue(MethodMatcherFactory.matcher(PRIVATE).match(method));
    }

    @Test
    public void test_basice_matcher_by_name() throws Exception {
        Method method = Vegetarian.class.getDeclaredMethod("doSomething", BEH.class);
        assertTrue(MethodMatcherFactory.matcher("doSomething").match(method));
    }

    @Test
    public void test_basice_listener() {
        int[] cc = new int[4];
        int[] crun = new int[4];
        Arrays.fill(cc, 0);
        Arrays.fill(crun, 0);
        ClassAgent aca = getNewClassAgent();
        aca.addInterceptor(MethodMatcherFactory.matcher("run"), new MethodCounter(crun));
        aca.addInterceptor(MethodMatcherFactory.matcher(".*"), new MethodCounter(cc));
        aca.addInterceptor(MethodMatcherFactory.matcher("doSomething"), new RhinocerosListener());
        Class<? extends Rhinoceros> c = aca.define(Nutzs.cd(), Rhinoceros.class);// RA.class;
        Rhinoceros r = Mirror.me(c).born();
        r.doSomething(BEH.run);
        r.doSomething(BEH.fight);
        try {
            r.doSomething(BEH.lecture);
            fail();
        }
        catch (Throwable e) {}
        try {
            r.doSomething(BEH.fly);
            fail();
        }
        catch (Throwable e) {}
//        assertEquals("[5, 3, 1, 1]", Json.toJson(cc));
        assertEquals("[1, 1, 0, 0]", Json.toJson(crun));
    }

    @Test
    public void test_basice_matcher_by_mod() {
        int[] cpub = new int[4];
        int[] cpro = new int[4];
        Arrays.fill(cpub, 0);
        Arrays.fill(cpro, 0);
        ClassAgent aca = getNewClassAgent();
        aca.addInterceptor(MethodMatcherFactory.matcher(PUBLIC), new MethodCounter(cpub));
        aca.addInterceptor(MethodMatcherFactory.matcher(PROTECTED), new MethodCounter(cpro));
        Class<? extends Hippo> c = aca.define(Nutzs.cd(), Hippo.class);// RA.class;
        Hippo r = Mirror.me(c).born();
        Vegetarians.run(r, 78);
        r.doSomething(BEH.run);
        try {
            r.doSomething(BEH.lecture);
            fail();
        }
        catch (Throwable e) {}
        try {
            r.doSomething(BEH.fly);
            fail();
        }
        catch (Throwable e) {}
        assertEquals("[3, 1, 1, 1]", Json.toJson(cpub));
        assertEquals("[2, 2, 0, 0]", Json.toJson(cpro));
    }

    public AsmClassAgent getNewClassAgent() {
        AsmClassAgent classAgent = new AsmClassAgent();
        classAgent.id = R.UU32();
        classAgent.addInterceptor(MethodMatcherFactory.matcher(".*"), new LoggingMethodInterceptor());
        return classAgent;
    }
}
