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

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import net.ranides.assira.collection.iterators.ForwardSpliterator;
import net.ranides.assira.reflection.util.MethodUtils;
import net.ranides.assira.trace.ExceptionUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class CPInspector {
    
    private CPInspector() {
        /* utility class */
    }

    /**
     * 
     * @param clazz
     * @return all methods referenced in class' bytecode
     */
    public static Stream<Method> getMethods(Class<?> clazz) {
        CPP cp = new CPP(clazz);
        return StreamSupport.stream(new CPSpliterator<>(cp.v.getSize(), cp.v::getMethodAt), false)
            .filter(m -> m instanceof Method)
            .map(m -> (Method)m);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> Stream<Constructor<T>> getConstructors(Class<T> clazz) {
        CPP cp = new CPP(clazz);
        return StreamSupport.stream(new CPSpliterator<>(cp.v.getSize(), cp.v::getMethodAt), false)
            .filter(m -> m instanceof Constructor<?>)
            .map(m -> (Constructor<T>)m);
    }
    
    /**
     * 
     * @param clazz
     * @return fields referenced in class' bytecode
     */
    public static Stream<Field> getFields(Class<?> clazz) {
        CPP cp = new CPP(clazz);
        return StreamSupport.stream(new CPSpliterator<>(cp.v.getSize(), cp.v::getFieldAt), false);
    }
    
    /**
     * 
     * @param clazz
     * @return types referenced in class' bytecode
     */
    public static Stream<Class<?>> getClasses(Class<?> clazz) {
        CPP cp = new CPP(clazz);
        return StreamSupport.stream(new CPSpliterator<>(cp.v.getSize(), cp.v::getClassAt), false);
    }
    
    /**
     * 
     * @param clazz
     * @return string constants referenced in class' bytecode
     */
    public static Stream<String> getStrings(Class<?> clazz) {
        CPP cp = new CPP(clazz);
        return StreamSupport.stream(new CPSpliterator<>(cp.v.getSize(), cp.v::getUTF8At), false);
    }
        
    private static final class CPSpliterator<T> extends ForwardSpliterator<T> {
        
        private final IntFunction<T> function;
        private final int max;
        private int index;
        

        public CPSpliterator(int size, IntFunction<T> function) {
            this.function = function;
            this.max = size;
            this.index = 0;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            while(index<max) {
                try {
                    action.accept(function.apply(index++));
                    return true;
                } catch(IllegalArgumentException $0) { // NOPMD
                    // do nothing
                }
            }
            return false;
        }
        
    }

	private static final class Li { // NOPMD - lazy init idiom
		
		public static final Method GET_CONSTANT_POOL;
		
		static {
			try {
				GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool");
				GET_CONSTANT_POOL.setAccessible(true);
			} catch(ReflectiveOperationException cause) {
				throw ExceptionUtils.rethrow(cause);
			}
		}
		
	}
    
    // Cały cyrk z tym wrapperem tylko po to, aby nie mieć 20 warningów z powodu użycia klasy "sun.reflect".
    // Mamy jedno użycie (tutaj). A reszta kodu używa typu niejawnie, używając pola "v"
    private static final class CPP {
        
        public final sun.reflect.ConstantPool v;

        public CPP(Class<?> clazz) {
            this.v = MethodUtils.$invoke(Li.GET_CONSTANT_POOL, clazz);
        }

    }
    
}
