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

import com.google.caliper.SimpleBenchmark;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.ranides.assira.collection.Grid;
import net.ranides.assira.io.MultiOutputStream;
import net.ranides.assira.reflection.inspect.ClassInspector;
import net.ranides.assira.reflection.inspect.MethodInspector;
import net.ranides.assira.reflection.inspect.PackageInspector;
import net.ranides.assira.reflection.util.ReflectUtils;
import net.ranides.assira.reflection.util.ClassUtils;
import net.ranides.assira.trace.StackInspector;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class BenchmarkRunner {
    
    private BenchmarkRunner() {
        // utility class
    }
    
    public static void main(String[] args) throws IOException {
        List<Class<?>> found = find();
        print(found);
        OutputStream collector = MultiOutputStream.tee();
        for(Class<?> clazz : found) {
            invoke(clazz);
        }
        BenchmarkReport.print(System.out, found, collector.toString());
    }
    
    public static void run() {
        String clazzname = StackInspector.frame(StackInspector.CALLER).getClassName();
        Class<?> clazz = ClassInspector.forName(clazzname);
        run(clazz);
    }
    
    public static void run(Class<?> clazz) {
        OutputStream collector = MultiOutputStream.tee();
        invoke(clazz);
        BenchmarkReport.print(System.out, clazz, collector.toString());
    }
    
    public static List<Integer> getParams(Class<?> clazz) {
        ParamsField params = new ParamsField(clazz);
        if(params.exists()) {
            return params.value.column("param", Integer.class);
        }
        return Collections.emptyList();
    }
    
    private static void invoke(Class<?> clazz) {
        if (!isBenchmark(clazz) ) {
            MethodInspector.inclass(clazz).published()
                .name("main")
                .require(Modifier.PUBLIC | Modifier.STATIC)
                .params(String[].class)
                .first()
                .call(new Object[]{new String[0]});
            return;
        }
        String[] args = new String[]{
            "--trials", 
            "3",
//            "--warmupMillis", 
//            "100",
//            "--runMillis", 
//            "100",
            "-Dparam=0",
            clazz.getName()
        };
        
        List<Integer> params = getParams(clazz);
        if( params.isEmpty() ) {
            new com.google.caliper.Runner().run(args);
            return;
        }
        for(int param : params) {
            args[ args.length - 2 ] = String.format("-Dparam=%d",param);
            new com.google.caliper.Runner().run(args);
        }
        
    }
    
    private static void print(List<Class<?>> list) {
        for(Class<?> clazz : list ) {
            String type = isBenchmark(clazz) ? 
                "caliper" : "generic";
            System.out.printf("[INFO] %s: %s%n", type, clazz.getName());
        }
    }
    
    private static List<Class<?>> find() throws IOException {
        List<Class<?>> list = new ArrayList<>();
        for(Class<?> clazz : PackageInspector.scan("net.ranides.assira")) {
            if(clazz == BenchmarkRunner.class) {
                continue;
            }
            if( isRunnable(clazz) ) {
                list.add(clazz);
            }
        }
        return list;
    }
    
    private static boolean isRunnable(Class<?> clazz) {
        if(clazz.isAnonymousClass() || null != clazz.getDeclaringClass()) {
            return false;
        }
        if(ClassUtils.isAbstract(clazz)) {
            return false;
        }
        // only public
        if(!ClassUtils.isPublic(clazz)) {
            return false;
        }
        // benchmarks
        if(isBenchmark(clazz)) {
            return true;
        }
        // runnable classes
        return MethodInspector.inclass(clazz).published()
            .name("main")
            .require(Modifier.PUBLIC | Modifier.STATIC)
            .params(String[].class)
            .exists();
    }
    
    private static boolean isBenchmark(Class<?> clazz) {
        return SimpleBenchmark.class.isAssignableFrom(clazz);
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ParamGrid {
        
        String[] value();
                
    }
    
    static class ParamsField {
        
        private final String[] keys;
        private final Grid<?> value;

        public ParamsField(String[] keys, Grid<?> value) {
            this.keys = keys;
            this.value = value;
        }
        
        public ParamsField(Class<?> clazz) {
            String[] ikeys = null;
            Grid<?> ivalue = null;
        
            for(Field field : clazz.getDeclaredFields()) {
                ReflectUtils.access(field);
                BenchmarkRunner.ParamGrid info = field.getAnnotation(BenchmarkRunner.ParamGrid.class);
                if( null != info) {
                    try {
                        ikeys = info.value();
                        ivalue = (Grid<?>)field.get(null);
                        break;
                    } catch(ReflectiveOperationException cause) {
                        throw new IllegalArgumentException(cause);
                    }
                }
            }
            this.keys = ikeys;
            this.value = ivalue;
        }
        
        public boolean exists() {
            return keys != null && value!=null;
        }

        public String[] keys() {
            return keys;
        }

        public Grid<?> value() {
            return value;
        }
        
    }
}

