package br.ufrgs.inf.prosoft.tigris.metrics;

import br.ufrgs.inf.prosoft.utils.StatisticalTest;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Map;

/**
 * The type Data filtering.
 */
public class DataFiltering {

    private final StaticMetrics staticMetrics;
    private final Map<String, LightweightMetrics> metrics;

    public DataFiltering(StaticMetrics staticMetrics, Map<String, LightweightMetrics> metrics) {
        this.staticMetrics = staticMetrics;
        this.metrics = metrics;
    }

    public boolean extendedFilter(LightweightMetrics methodMetrics) {
        DescriptiveStatistics frequencies = new DescriptiveStatistics();
        DescriptiveStatistics changeabilities = new DescriptiveStatistics();
        DescriptiveStatistics errorprones = new DescriptiveStatistics();
        DescriptiveStatistics latencies = new DescriptiveStatistics();
        DescriptiveStatistics maintainabilities = new DescriptiveStatistics();
        DescriptiveStatistics userBehaviors = new DescriptiveStatistics();
        DescriptiveStatistics concurrencies = new DescriptiveStatistics();
        DescriptiveStatistics expensiveness = new DescriptiveStatistics();
        DescriptiveStatistics globalimpacts = new DescriptiveStatistics();
        System.out.println("---------------------------");
        for (String method : metrics.keySet()) {
            System.out.println(method);
            LightweightMetrics sc = metrics.get(method);
            StaticMetrics.StaticMetric staticMetric = staticMetrics.getMetrics(sc.getName());
            if (staticMetric == null)
                continue;
            frequencies.addValue(sc.getFrequency());
            changeabilities.addValue(sc.getChangeability());
            errorprones.addValue(staticMetric.maxNesting);
            latencies.addValue(sc.getLatency());
            maintainabilities.addValue(staticMetric.cyclomatic);
            userBehaviors.addValue(sc.getUserBehavior());
            concurrencies.addValue(sc.getConcurrency());
            expensiveness.addValue(sc.getExpensiveness());
            globalimpacts.addValue(staticMetric.countOutput);
        }

        System.out.println("---------------------------");
        System.out.println("Metric: frequency; Distribution: " +
                ((StatisticalTest.isNormalDistribution(frequencies.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: maintainability; Distribution: " +
                ((StatisticalTest.isNormalDistribution(maintainabilities.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: changeability; Distribution: " +
                ((StatisticalTest.isNormalDistribution(changeabilities.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: userBehavior; Distribution: " +
                ((StatisticalTest.isNormalDistribution(userBehaviors.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: concurrency; Distribution: " +
                ((StatisticalTest.isNormalDistribution(concurrencies.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: errorprone; Distribution: " +
                ((StatisticalTest.isNormalDistribution(errorprones.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: expensiveness; Distribution: " +
                ((StatisticalTest.isNormalDistribution(expensiveness.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: globalimpact; Distribution: " +
                ((StatisticalTest.isNormalDistribution(globalimpacts.getValues(), 0.05)) ? "normal" : "not_normal"));
        System.out.println("Metric: latency; Distribution: " +
                ((StatisticalTest.isNormalDistribution(latencies.getValues(), 0.05)) ? "normal" : "not_normal"));

        try {
            final PrintWriter pw = new PrintWriter(new File("lightweightanalysis.csv"));
            pw.write("method,frequency,maintainability,changeability,userBehavior,concurrency,errorprone,expensiveness,globalimpact,latency,vfrequency,vmaintainability,vchangeability,vuserBehavior,vconcurrency,verrorprone,vexpensiveness,vglobalimpact,vlatency\n");
            for (String name : metrics.keySet()) {
                LightweightMetrics lm = metrics.get(name);
                StaticMetrics.StaticMetric staticMetric = staticMetrics.getMetrics(lm.getName());
                if (staticMetric == null)
                    continue;
                pw.write(metrics.get(name).getName() + "," +
                        DataFiltering.getGroup(frequencies, metrics.get(name).getFrequency()) +
                        "," + DataFiltering.getGroup(maintainabilities, staticMetric.cyclomatic) +
                        "," + DataFiltering.getGroup(changeabilities, lm.getChangeability()) +
                        "," + DataFiltering.getGroup(userBehaviors, lm.getUserBehavior()) +
                        "," + DataFiltering.getGroup(concurrencies, lm.getConcurrency()) +
                        "," + DataFiltering.getGroup(errorprones, staticMetric.maxNesting) +
                        "," + DataFiltering.getGroup(expensiveness, lm.getExpensiveness()) +
                        "," + DataFiltering.getGroup(globalimpacts, staticMetric.countOutput) +
                        "," + DataFiltering.getGroup(latencies, metrics.get(name).getLatency()) +
                        "," +
                        DataFiltering.allMetricsToString(lm, staticMetrics) + '\n');
            }
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return
                //extended filter: less changeable C more frequent C (more user_behavior U (less user_behavior C more expensive))
                ((DataFiltering.isGroup("less", changeabilities, methodMetrics.getChangeability())
                        || DataFiltering.isGroup("normal", changeabilities, methodMetrics.getChangeability()))
                        && (DataFiltering.isGroup("more", frequencies, methodMetrics.getFrequency())
                        || DataFiltering.isGroup("normal", frequencies, methodMetrics.getFrequency()))
                        && ((DataFiltering.isGroup("more", userBehaviors, methodMetrics.getUserBehavior())
                        || DataFiltering.isGroup("normal", userBehaviors, methodMetrics.getUserBehavior()))
                        || ((DataFiltering.isGroup("less", userBehaviors, methodMetrics.getUserBehavior())
                        || DataFiltering.isGroup("normal", userBehaviors, methodMetrics.getUserBehavior()))
                        && (DataFiltering.isGroup("normal", expensiveness, methodMetrics.getExpensiveness())
                        || DataFiltering.isGroup("more", expensiveness, methodMetrics.getExpensiveness())))));

        //restricted filter: less changeable C more frequent C (more user_behavior U (less user_behavior C more expensive))
//            (DataFiltering.isGroup("less", changeabilities, sc.getChangeability())
//                && DataFiltering.isGroup("more", frequencies, sc.getFrequency())
//                && (DataFiltering.isGroup("more", userBehaviors, sc.getUserBehavior())
//                    || (DataFiltering.isGroup("less", userBehaviors, sc.getUserBehavior())
//                        && DataFiltering.isGroup("more", expensiveness, sc.getExpensiveness()))))

//            if (DataFiltering.isGroup("least", changeabilities, sc.getChangeability())
//                && DataFiltering.isGroup("most", frequencies, sc.getFrequency())
//                && (DataFiltering.isGroup("most", userBehaviors, sc.getUserBehavior())
//                    || (DataFiltering.isGroup("least", userBehaviors, sc.getUserBehavior())
//                        && DataFiltering.isGroup("most", expensiveness, sc.getExpensiveness()))))

//            if (DataFiltering.isGroup("less", changeabilities, sc.getChangeability())
//                    && DataFiltering.isGroup("most", frequencies, sc.getFrequency())
//                    && DataFiltering.isGroup("most", expensiveness, sc.getExpensiveness()))
    }

    /**
     * Is group boolean.
     *
     * @param group    the group
     * @param criteria the criteria
     * @param value    the value
     * @return the boolean
     */
    public static boolean isGroup(String group, DescriptiveStatistics criteria, double value) {

        boolean isNormal = StatisticalTest.isNormalDistribution(criteria.getValues(), 0.05);
        DescriptiveStatistics half = new DescriptiveStatistics();
        if (!isNormal && (group.equals("most") || group.equals("least"))) {
            for (double val : criteria.getValues())
                if (group.equals("most") && val >= criteria.getPercentile(75))
                    half.addValue(val);
                else if (group.equals("least") && val <= criteria.getPercentile(25))
                    half.addValue(val);
        }

        switch (group) {
            case "normal":
                if (isNormal)
                    return (value > (criteria.getMean() - criteria.getStandardDeviation()) &&
                            value < (criteria.getMean() + criteria.getStandardDeviation()));
                else
                    return (value > criteria.getPercentile(25) &&
                            value < criteria.getPercentile(75));
            case "most":
                if (isNormal)
                    return value >= (criteria.getMean() + (2 * criteria.getStandardDeviation()));
                else
                    return value >= half.getPercentile(50);
            case "least":
                if (isNormal)
                    return value <= (criteria.getMean() + (2 * criteria.getStandardDeviation()));
                else
                    return value <= half.getPercentile(50);
            case "more":
                if (isNormal)
                    return value >= (criteria.getMean() + criteria.getStandardDeviation());
                else
                    return value >= criteria.getPercentile(75);
            case "less":
                if (isNormal)
                    return value <= (criteria.getMean() - criteria.getStandardDeviation());
                else
                    return value <= criteria.getPercentile(25);
        }

        throw new RuntimeException("Não foi possível classificar...");
    }

    /**
     * Get group string.
     *
     * @param criteria the criteria
     * @param value    the value
     * @return the string
     */
    public static String getGroup(DescriptiveStatistics criteria, double value) {

        boolean isNormal = StatisticalTest.isNormalDistribution(criteria.getValues(), 0.05);
        DescriptiveStatistics upperHalf = new DescriptiveStatistics();
        DescriptiveStatistics lowerHalf = new DescriptiveStatistics();
        if (!isNormal) {
            for (double val : criteria.getValues())
                if (val >= criteria.getPercentile(75))
                    upperHalf.addValue(val);
                else if (val <= criteria.getPercentile(25))
                    lowerHalf.addValue(val);
        }

        if (isNormal) {
            if (value > (criteria.getMean() - criteria.getStandardDeviation()) &&
                    value < (criteria.getMean() + criteria.getStandardDeviation()))
                return "normal";
            else if (value >= (criteria.getMean() + (2 * criteria.getStandardDeviation())))
                return "most";
            else if (value <= (criteria.getMean() + (2 * criteria.getStandardDeviation())))
                return "least";
            else if (value >= (criteria.getMean() + criteria.getStandardDeviation()))
                return "more";
            else if (value <= (criteria.getMean() - criteria.getStandardDeviation()))
                return "less";
        } else {
            if (value > criteria.getPercentile(25) &&
                    value < criteria.getPercentile(75))
                return "normal";
            else if (value >= upperHalf.getPercentile(50))
                return "most";
            else if (value <= lowerHalf.getPercentile(50))
                return "least";
            else if (value >= criteria.getPercentile(75))
                return "more";
            else if (value <= criteria.getPercentile(25))
                return "less";
        }

        throw new RuntimeException("Não foi possível classificar...");
    }

    /**
     * All metrics to string string.
     *
     * @param lightweightMetrics the lightweight metrics
     * @param staticMetrics      the static metrics
     * @return the string
     */
    public static String allMetricsToString(LightweightMetrics lightweightMetrics, StaticMetrics staticMetrics) {
        //frequency,maintainability,changeability,userBehavior,concurrency,errorprone,expensiveness,globalimpact,latency
        return lightweightMetrics.getFrequency() +
                "," + staticMetrics.getMetrics(lightweightMetrics.getName()).cyclomatic +
                "," + lightweightMetrics.getChangeability() +
                "," + lightweightMetrics.getUserBehavior() +
                "," + lightweightMetrics.getConcurrency() +
                "," + staticMetrics.getMetrics(lightweightMetrics.getName()).maxNesting +
                "," + lightweightMetrics.getExpensiveness() +
                "," + staticMetrics.getMetrics(lightweightMetrics.getName()).countOutput +
                "," + lightweightMetrics.getLatency();
    }
}
