/*
 * @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.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.ranides.assira.BenchmarkRunner.ParamsField;
import net.ranides.assira.collection.Grid;
import net.ranides.assira.text.Strings;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class BenchmarkReport {
    
    private final Map<String, Grid<?>> params = new HashMap<>();
    
    public static void print(PrintStream out, Class<?> clazz, String data) {
        print(out, Arrays.<Class<?>>asList(clazz), data);
    }
    
    public static void print(PrintStream out, List<Class<?>> list, String data) {
        BenchmarkReport formatter = new BenchmarkReport();
        for(Class<?> clazz : list ) {
            formatter.putParams(clazz);
        }
        formatter.format(System.out, data);
    }
    
    private void putParams(Class<?> clazz) {
        ParamsField field = new ParamsField(clazz);
        if(!field.exists()) {
            return;
        }
        for(String key : field.keys()) {
            params.put(key, field.value());
        }
    }
    
    private void format(PrintStream out, String input) {
        Pattern RE_BENCHMARK = Pattern.compile("\\{vm=(.+?), trial=(.+?), benchmark=(.+?)(?:, param=(.+?))?\\} ([0-9,]+) ns; \\?=([0-9,]+) ns @ ([0-9]+) trials");
        
        BInfoCollection result = new BInfoCollection();
        for(String line : input.split("[\\n\\r]+")) {
            Matcher hit = RE_BENCHMARK.matcher(line);
            if(hit.find()) {
                result.add(new BInfo(hit));
            }
        }
        
        for(BInfoForName biname : result.map.values()) {
            Grid<?> ptable = params.get( biname.first().name );
            
            out.printf("name,");
            if( null != ptable ) {
                formatHeader(out, ptable);
            } else {
                out.printf("param,");
            }
            out.printf("trials,average,deviation%n");
            
            for(BInfoForParam biparam : biname.map.values()) {
                int trials = biparam.list.size();
                double avg = 0;
                double dev = 0;
                for(BInfo info : biparam.list) {
                    avg += info.time;
                }
                avg = avg / trials;
                
                for(BInfo info : biparam.list) {
                    dev += (avg - info.time) * (avg - info.time);
                }
                dev = Math.sqrt(dev);
                
                out.printf("%s,", biparam.list.get(0).name);
                if( null != ptable ) {
                    formatRow(out, ptable, biparam.list.get(0).param);
                } else {
                    out.printf("%d,", biparam.list.get(0).param);
                }
                out.printf("%d,%.0f,%.0f%n", trials, avg, dev);
            }
            out.println();
        }
    }
    
    private static void formatHeader(PrintStream out, Grid<?> grid) {
        for(String c : grid.header()) {
            out.printf("%s,",c);
        }
    }

    private static void formatRow(PrintStream out, Grid<?> grid, int row) {
        for(Object c : grid.rows().get(row)) {
            out.printf("%s,",c);
        }
    }
 
    private static class BInfoCollection {
        
        public final TreeMap<String, BInfoForName> map = new TreeMap<>();
        
        public void add(BInfo info) {
            if( !map.containsKey(info.name) ) {
                map.put(info.name, new BInfoForName());
            }
            map.get(info.name).add(info);
        }
        
        public BInfo first() {
            return map.firstEntry().getValue().first();
        }
                
    }

    private static class BInfoForName {
        
        public final TreeMap<Integer, BInfoForParam> map = new TreeMap<>();
        
        public void add(BInfo info) {
            if( !map.containsKey(info.param) ) {
                map.put(info.param, new BInfoForParam());
            }
            map.get(info.param).add(info);
        }
        
        public BInfo first() {
            return map.firstEntry().getValue().first();
        }
        
    }
    
    private static class BInfoForParam {
        
        public final List<BInfo> list = new ArrayList<>();
        
        public void add(BInfo info) {
            list.add(info);
        }
        
        public BInfo first() {
            return list.get(0);
        }
        
    }
    
    private static class BInfo {
        
        public final String name;
        public final int param;
        public final String trial;
        public final double time;
        public final double timeDev;
        
        public BInfo(Matcher hit) {
            this.name = hit.group(3);
            this.param = Integer.parseInt(Strings.or(hit.group(4),"0"));
            this.trial = hit.group(2);
            this.time = Double.parseDouble(hit.group(5).replace(',', '.'));
            this.timeDev = Double.parseDouble(hit.group(6).replace(',', '.'));
        }
        
        public void print(PrintStream out) {
            out.printf("%s(%s):%s = %s+%s%n", name, trial, param, time, timeDev);
        }
    }

}
