package net.ranides.assira.trace;

import net.ranides.assira.text.Wildcard;
import org.junit.Test;

import java.util.List;

import static net.ranides.assira.junit.NewAssert.assertThrows;
import static org.junit.Assert.*;

public class FStackWalker8Test {

    @Test
    public void testCallerType() {
        assertTrue( SI0.runCallerType0() );
    }

    @Test
    public void testCalleeType() {
        assertTrue( SI0.runCalleeType0() );
    }

    @Test
    public void testCallerName() {
        assertTrue( SI0.runCallerName0() );
    }

    @Test
    public void testCalleeName() {
        assertTrue( SI0.runCalleeName0() );
    }

    @Test
    public void testGetTypes0() {
        assertTrue( SI0.runGetTypes0() );
    }

    @Test
    public void testGetNames() {
        assertTrue( SI0.runGetNames0() );
    }

    @Test
    public void testFrame() {
        assertTrue( SI0.runFrame0() );
    }

    private static final class SI0 {

        private SI0() { /* utility class */ }

        public static boolean runCallerType0() {
            SI1.runCallerType1();
            return true;
        }

        public static boolean runCalleeType0() {
            SI1.runCalleeType1();
            return true;
        }

        public static boolean runCallerName0() {
            SI1.runCallerName1();
            return true;
        }

        public static boolean runCalleeName0() {
            SI1.runCalleeName1();
            return true;
        }

        public static boolean runGetTypes0() {
            SI1.runGetTypes1();
            return true;
        }

        public static boolean runGetNames0() {
            SI1.runGetNames1();
            return true;
        }

        public static boolean runFrame0() {
            SI1.runFrame1();
            return true;
        }

    }

    private static final class SI1 {

        private SI1() { /* utility class */ }

        public static void runCallerType1() {
            Class<?> caller = SI0.class;

            Class callerAuto = TraceUtils.getCallerType();
            Class callerSM   = FStackWalker8.test_getCallerType(FStackWalker8.RESOLVER_SECURITY);
            Class callerSR   = FStackWalker8.test_getCallerType(FStackWalker8.RESOLVER_SUN);

            assertEquals(caller, callerAuto);
            assertEquals(caller, callerSM);
            assertEquals(caller, callerSR);

            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getCallerType(FStackWalker8.RESOLVER_THREAD);
            });
            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getCallerType(FStackWalker8.RESOLVER_EXCEPT);
            });
        }

        public static void runCalleeType1() {
            Class<?> callee = SI1.class;

            Class calleeAuto = TraceUtils.getCalleeType();
            Class calleeSM   = FStackWalker8.test_getCalleeType(FStackWalker8.RESOLVER_SECURITY);
            Class calleeSR   = FStackWalker8.test_getCalleeType(FStackWalker8.RESOLVER_SUN);

            assertEquals(callee, calleeAuto);
            assertEquals(callee, calleeSM);
            assertEquals(callee, calleeSR);

            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getCalleeType(FStackWalker8.RESOLVER_THREAD);
            });
            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getCalleeType(FStackWalker8.RESOLVER_EXCEPT);
            });
        }

        public static void runCallerName1() {
            String caller = SI0.class.getName();

            String callerAuto = TraceUtils.getCallerName();
            String callerSM   = FStackWalker8.test_getCallerName(FStackWalker8.RESOLVER_SECURITY);
            String callerSR   = FStackWalker8.test_getCallerName(FStackWalker8.RESOLVER_SUN);
            String callerTS   = FStackWalker8.test_getCallerName(FStackWalker8.RESOLVER_THREAD);
            String callerES   = FStackWalker8.test_getCallerName(FStackWalker8.RESOLVER_EXCEPT);

            assertEquals(caller, callerAuto);
            assertEquals(caller, callerSM);
            assertEquals(caller, callerSR);
            assertEquals(caller, callerTS);
            assertEquals(caller, callerES);
        }

        public static void runCalleeName1() {
            String callee = SI1.class.getName();

            String calleeAuto = TraceUtils.getCalleeName();
            String calleeSM   = FStackWalker8.test_getCalleeName(FStackWalker8.RESOLVER_SECURITY);
            String calleeSR   = FStackWalker8.test_getCalleeName(FStackWalker8.RESOLVER_SUN);
            String calleeTS   = FStackWalker8.test_getCalleeName(FStackWalker8.RESOLVER_THREAD);
            String calleeES   = FStackWalker8.test_getCalleeName(FStackWalker8.RESOLVER_EXCEPT);

            assertEquals(callee, calleeAuto);
            assertEquals(callee, calleeSM);
            assertEquals(callee, calleeSR);
            assertEquals(callee, calleeTS);
            assertEquals(callee, calleeES);
        }

        public static void runGetTypes1() {
            Class<?> callee = SI1.class;
            Class<?> caller = SI0.class;

            List<Class<?>> listAuto = TraceUtils.getTypes();
            List<Class<?>> listSM   = FStackWalker8.test_getTypes(FStackWalker8.RESOLVER_SECURITY);
            List<Class<?>> listSR   = FStackWalker8.test_getTypes(FStackWalker8.RESOLVER_SUN);

            if(false) { //NOPMD
                dump(listAuto);
                dump(listSM);
                dump(listSR);
            }

            assertTrue(
                    listAuto.equals(listSM) ||
                            listAuto.equals(listSR)
            );

            assertEquals(listSM, listSR);

            assertTrue(listSM.size() > 5);

            assertEquals(callee, listSM.get(0));
            assertEquals(callee, listSR.get(0));

            assertEquals(caller, listSM.get(1));
            assertEquals(caller, listSR.get(1));

            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getTypes(FStackWalker8.RESOLVER_THREAD);
            });
            assertThrows(UnsupportedOperationException.class, ()->{
                FStackWalker8.test_getTypes(FStackWalker8.RESOLVER_EXCEPT);
            });
        }

        public static void runGetNames1() {
            String callee = SI1.class.getName();
            String caller = SI0.class.getName();

            List<String> listAuto = TraceUtils.getNames();
            List<String> listSM  = FStackWalker8.test_getNames(FStackWalker8.RESOLVER_SECURITY);
            List<String> listSR  = FStackWalker8.test_getNames(FStackWalker8.RESOLVER_SUN);
            List<String> listTS  = FStackWalker8.test_getNames(FStackWalker8.RESOLVER_THREAD);
            List<String> listES  = FStackWalker8.test_getNames(FStackWalker8.RESOLVER_EXCEPT);

            if(false) { //NOPMD
                System.out.printf("%n%nauto%n");
                dump(listAuto);
                System.out.printf("%n%nsm%n");
                dump(listSM);
                System.out.printf("%n%nsr%n");
                dump(listSR);
                System.out.printf("%n%nts%n");
                dump(listTS);
                System.out.printf("%n%nes%n");
                dump(listES);
            }

            assertTrue(
                    listAuto.equals(listSM) ||
                            listAuto.equals(listSR) ||
                            listAuto.equals(listTS) ||
                            listAuto.equals(listES)
            );

            assertEquals(listSM, listSR);
            assertEquals(listTS, listES);

            assertTrue(listSM.size() > 5);
            assertTrue(listSM.size() <= listTS.size());

            assertEquals(callee, listSM.get(0));
            assertEquals(callee, listSR.get(0));
            assertEquals(callee, listTS.get(0));
            assertEquals(callee, listES.get(0));

            assertEquals(caller, listSM.get(1));
            assertEquals(caller, listSR.get(1));
            assertEquals(caller, listTS.get(1));
            assertEquals(caller, listES.get(1));
        }

        public static void runFrame1() {
            List<StackTraceElement> frames = TraceUtils.getFrames();
            List<String> names = FStackWalker8.test_getNames(FStackWalker8.RESOLVER_THREAD);

            if(false) { // NOPMD
                System.out.printf("frames = %d%n", frames.size());
                System.out.printf("names = %d%n", names.size());
                dump(frames);
                dump(names);
            }

            Wildcard callee = Wildcard.compile("net.ranides.assira.trace.FStackWalker8Test$SI1.runFrame1(FStackWalker8Test.java:*)");
            Wildcard caller = Wildcard.compile("net.ranides.assira.trace.FStackWalker8Test$SI0.runFrame0(FStackWalker8Test.java:*)");
            Wildcard frame2 = Wildcard.compile("net.ranides.assira.trace.FStackWalker8Test.testFrame(FStackWalker8Test.java:*)");

            assertTrue(callee.test(TraceUtils.getCalleeFrame().toString()));
            assertTrue(caller.test(TraceUtils.getCallerFrame().toString()));

            assertTrue(callee.test(frames.get(0).toString()));
            assertTrue(caller.test(frames.get(1).toString()));
            assertTrue(frame2.test(frames.get(2).toString()));

            assertEquals(frames.size(), names.size());
        }

    }

    @SuppressWarnings("PMD")
    private static void dump(List<?> list) {
        int i = 0;
        for(Object c : list) {
            System.err.printf("%d. %s%n", i++, c);
        }
        System.out.printf("%n");
    }

}