package org.keycloak.testsuite.performance;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.keycloak.testsuite.performance.statistics.SimpleStatistics;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import static org.keycloak.testsuite.performance.PerformanceTest.LOG;
import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;

/**
 *
 * @author tkyjovsk
 */
public class PerformanceMeasurement {

    private final Date started;
    private final int load;
    private long durationMillis;
    private SimpleStatistics statistics;
    private SimpleStatistics timeoutStatistics;

    public static final DateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXX"); // should be compatible with `date --iso-8601=seconds`
    public static final DateFormat RFC3339_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ"); // should be compatible with `date --rfc-3339=seconds`

    public PerformanceMeasurement(int load) {
        this.started = new Date();
        this.load = load;
    }

    public SimpleStatistics getStatistics() {
        return this.statistics;
    }

    public SimpleStatistics getTimeoutStatistics() {
        return this.timeoutStatistics;
    }

    public void setStatistics(SimpleStatistics statistics, SimpleStatistics timeoutStatistics) {
        this.durationMillis = new Date().getTime() - started.getTime();
        if (durationMillis < 0) {
            throw new IllegalStateException("Cannot set a negative duration.");
        }
        this.statistics = statistics;
        this.timeoutStatistics = timeoutStatistics;
    }

    private void checkStatisticsNotNull() {
        if (statistics == null || timeoutStatistics == null) {
            throw new IllegalStateException("Iteration doesn't have any statistics set.");
        }
    }

    public double getThroughput(String statistic) {
        checkStatisticsNotNull();
        return (double) statistics.get(statistic).getCount() / durationMillis * 1000;
    }

    public double getTimeoutPercentage(String statistic) {
        checkStatisticsNotNull();
        long timeouts = timeoutStatistics.containsKey(statistic) ? timeoutStatistics.get(statistic).getCount() : 0;
        return (double) timeouts / statistics.get(statistic).getCount();
    }

    public static final Object[] HEADER = new String[]{
        "Timestamp",
        "Load",
        "Duration",
        "Count",
        "Min",
        "Max",
        "Average",
        "Standard Deviation",
        "Timeout Percentage",
        "Throughput",};

    public List toRecord(String statistic) {
        checkStatisticsNotNull();
        List record = new ArrayList();
        record.add(ISO8601_DATE_FORMAT.format(started));
        record.add(load);
        record.add(durationMillis);
        record.add(statistics.get(statistic).getCount());
        record.add(statistics.get(statistic).getMin());
        record.add(statistics.get(statistic).getMax());
        record.add(statistics.get(statistic).getAverage());
        record.add(statistics.get(statistic).getStandardDeviation());
        record.add(getTimeoutPercentage(statistic));
        record.add(getThroughput(statistic));
        return record;
    }

    public void printToCSV() {
        printToCSV(null);
    }

    public void printToCSV(String testName) {
        checkStatisticsNotNull();
        for (String statistic : statistics.keySet()) {

            File csvFile = new File(PROJECT_BUILD_DIRECTORY + "/measurements" + (testName == null ? "" : "/" + testName),
                    statistic + ".csv");
            boolean csvFileCreated = false;
            if (!csvFile.exists()) {
                try {
                    csvFile.getParentFile().mkdirs();
                    csvFileCreated = csvFile.createNewFile();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }

            try (BufferedWriter writer = new BufferedWriter(new FileWriter(csvFile, true))) {

                CSVPrinter printer = new CSVPrinter(writer, CSVFormat.RFC4180);

                if (csvFileCreated) {
                    printer.printRecord(HEADER);
                }
                printer.printRecord(toRecord(statistic));

                printer.flush();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public void printToLog() {
        LOG.info("Measurement results:");
        LOG.info("Operation " + Arrays.toString(HEADER));
        for (String statistic : statistics.keySet()) {
            LOG.info(statistic + " " + toRecord(statistic));
        }
    }

}
