/*******************************************************************************
 * Copyright (c) 2014 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Marc-Andre Laperle - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.alltests.perf;

import java.io.IOException;

import org.json.JSONException;
import org.junit.Test;

/**
 * Convert results from the database to JSON suitable for display.
 *
 * Normal charts:
 *
 * Individual charts are generated into JSON files in the form chart#.json where
 * # is incremented for each new chart. A chart contains data points consisting
 * of X and Y values suitable for a line chart. Each point can also have
 * additional data, for example the commit id. This format is compatible with
 * nvd3. For example:
 *
 * <pre>
 * <code>
 * [{
 *   "key": "Experiment Benchmark:84 traces",
 *   "values": [{
 *       "label": {"commit": "fe3c142"},
 *       "x": 1405024320000,
 *       "y": 17592
 *   }]
 * }]
 * </code>
 * </pre>
 *
 * Normal charts metadata:
 *
 * Each chart has an entry in the metada.js file which organizes the charts per
 * component and contains additional information to augment the format expected
 * by nvd3. Each entry contains the combination of OS and JVM, the filename (in
 * JSON format), the title of the chart, the unit (seconds, etc) and the
 * dimension (CPU time, used heap, etc).
 *
 * <pre>
 *  <code>
 *  var MetaData = {
 *     "applicationComponents": {
 *         "Experiment benchmark": {
 *             "name": "Experiment benchmark",
 *             "tests": [
 *                 {
 *                     "dimension": "CPU Time",
 *                     "file": "chart12",
 *                     "jvm": "1.7",
 *                     "os": "linux",
 *                     "title": "Experiment Benchmark:84 traces",
 *                     "unit": "s"
 *                 },
 *                 {
 *                     "dimension": "CPU Time",
 *                     "file": "chart11",
 *                     "jvm": "1.7",
 *                     "os": "linux",
 *                     "title": "Experiment Benchmark:6 traces",
 *                     "unit": "s"
 *                 },
 * ...
 *  </code>
 * </pre>
 *
 * Overview charts:
 *
 * In addition to the normal charts, overview charts are generated. An overview
 * chart presents a summary of the scenarios ran for a given OS and JVM
 * combination. Only scenarios marked as "global" are added to the overview
 * because of space concerns. Overview charts are generated under the
 * chart_overview#.json name and look similar in structure to the normal charts
 * except that they contain more than one series.
 *
 * <pre>
 *   <code>
 * [
 *   {
 *       "key": "CTF Read & Seek Benchmark (500 seeks):tr",
 *       "values": [
 *           {
 *               "label": {"commit": "4d34345"},
 *               "x": 1405436820000,
 *               "y": 5382.5
 *           },
 *           ...
 *       ]
 *   },
 *   {
 *       "key": "CTF Read Benchmark:trace-kernel",
 *       "values": [
 *           {
 *               "label": {"commit": "4d34345"},
 *               "x": 1405436820000,
 *               "y": 1311.5
 *           },
 *           ...
 *       ]
 *   },
 *   ...
 *   </code>
 * </pre>
 *
 * Overview charts metadata:
 *
 * Overview charts also have similar metadata entries to normal charts except
 * they are not organized by component.
 *
 * <pre>
 *   <code>
 * var MetaData = {
 * ...
 *   "overviews": {
 *       "1": {
 *           "dimension": "",
 *           "file": "chart_overview0",
 *           "jvm": "1.7",
 *           "os": "linux",
 *           "title": "linux / 1.7",
 *           "unit": ""
 *       },
 *       "2": {
 *           "dimension": "",
 *           "file": "chart_overview1",
 *           "jvm": "1.7",
 *           "os": "windows",
 *           "title": "windows / 1.7",
 *           "unit": ""
 *       },
 *       ...
 *   </code>
 * </pre>
 *
 * Finally, since we want to be able to filter all the charts by OS/JVM
 * combination, there is a section in the metadata that lists all the
 * combinations:
 *
 * <pre>
 *   <code>
 *     "osjvm": {
 *       "1": {
 *           "description": "linux / 1.7",
 *           "jvm": "1.7",
 *           "os": "linux"
 *       },
 *       "2": {
 *           "description": "windows / 1.7",
 *           "jvm": "1.7",
 *           "os": "windows"
 *       },
 *       "3": {
 *           "description": "mac / 1.7",
 *           "jvm": "1.7",
 *           "os": "mac"
 *       }
 *   },
 *   </code>
 * </pre>
 *
 * All of this data is meant to be view on a website. Specifically, the source
 * code for our implementation is available on GitHub at
 * https://github.com/PSRCode/ITCFYWebsite
 *
 * It makes use of the NVD3 project to display the charts based on the data
 * generated by this class.
 */
public class PerfResultsToJSon {

    /*
     * Labels
     */
//    private static final String APPLICATION_COMPONENTS_LABEL = "applicationComponents";
//    private static final String BUILD_LABEL = "build";
//    private static final String COMMIT_LABEL = "commit";
//    private static final String CONFIG_LABEL = "config";
//    private static final String DESCRIPTION_LABEL = "description";
//    private static final String DIMENSION_LABEL = "dimension";
//    private static final String FILE_LABEL = "file";
//    private static final String HOST_LABEL = "host";
//    private static final String JVM_LABEL = "jvm";
//    private static final String KEY_LABEL = "key";
//    private static final String LABEL_LABEL = "label";
//    private static final String NAME_LABEL = "name";
//    private static final String OS_LABEL = "os";
//    private static final String OSJVM_LABEL = "osjvm";
//    private static final String OVERVIEWS_LABEL = "overviews";
//    private static final String TESTS_LABEL = "tests";
//    private static final String TITLE_LABEL = "title";
//    private static final String UNIT_LABEL = "unit";
//    private static final String VALUES_LABEL = "values";
//    private static final String X_LABEL = "x";
//    private static final String Y_LABEL = "y";
//
//    private static final String BUILD_DATE_FORMAT = "yyyyMMdd-HHmm";
//    private static final String OVERVIEW_CHART_FILE_NAME = "chart_overview";
//    private static final String METADATA_FILE_NAME = "meta";
//    private static final String METADATA_FILE_NAME_EXTENSION = ".js";
//    private static final String CHART_FILE_NAME = "chart";
//    private static final String CHART_FILE_NAME_EXTENSION = ".json";
//    private static final String WILDCARD_PATTERN = "%";
//    private static final @NonNull String COMPONENT_SEPARATOR = "#";
//    private static final String META_DATA_JAVASCRIPT_START = "var MetaData = ";
//
//    private static Pattern BUILD_DATE_PATTERN = Pattern.compile("(\\w+-\\w+)(-\\w+)?");
//    private static Pattern COMMIT_PATTERN = Pattern.compile(".*-.*-(.*)");
//
//    private JSONObject fApplicationComponents = new JSONObject();
//    private JSONObject fOverviews = new JSONObject();
//
//    private int fNumChart = 0;
//    private int fNumOverviewChart = 0;

    /**
     * Convert results from the database to JSON suitable for display
     *
     * <pre>
     * For each variant (os/jvm combination)
     *    - For each summary entry (scenario)
     *      - Generate a chart
     *      - Add it to global summary (if needed)
     *      - Create the metadata for this test
     *    - Create an overview chart for this os/jvm
     * </pre>
     *
     * @throws JSONException
     *             JSON error
     * @throws IOException
     *             IO error
     */
    @Test
    public void parseResults() throws JSONException, IOException {
//        Variations configVariations = PerformanceTestPlugin.getVariations();
//        JSONObject osJvmVariants = createOsJvm();
//
//        Iterator<?> keysIt = osJvmVariants.keys();
//        while (keysIt.hasNext()) {
//            JSONArray overviewSummarySeries = new JSONArray();
//
//            JSONObject variant = osJvmVariants.getJSONObject((String) keysIt.next());
//            String seriesKey = PerformanceTestPlugin.BUILD;
//
//            // Clone the variations from the environment because it might have
//            // extra parameters like host=, etc.
//            Variations buildVariations = (Variations) configVariations.clone();
//            buildVariations.setProperty(JVM_LABEL, variant.getString(JVM_LABEL));
//            buildVariations.setProperty(CONFIG_LABEL, variant.getString(OS_LABEL));
//            buildVariations.setProperty(BUILD_LABEL, WILDCARD_PATTERN);
//
//            Scenario[] scenarios = DB.queryScenarios(buildVariations, WILDCARD_PATTERN, seriesKey, null);
//            SummaryEntry[] summaryEntries = DB.querySummaries(buildVariations, WILDCARD_PATTERN);
//            for (SummaryEntry entry : summaryEntries) {
//                Scenario scenario = getScenario(entry.scenarioName, scenarios);
//                JSONObject scenarioSeries = createScenarioChart(scenario, entry, buildVariations);
//                // Add to global summary
//                if (scenarioSeries != null && entry.isGlobal) {
//                    overviewSummarySeries.put(scenarioSeries);
//                }
//            }
//
//            JSONObject overviewMetadata = createOverviewChart(overviewSummarySeries, buildVariations);
//            fOverviews.put(Integer.toString(fNumOverviewChart), overviewMetadata);
//        }
//
//        // Create the matadata javascript file that includes OS/JVM combinations
//        // (for filtering), application components and overviews (one of OS/JVM
//        // combination)
//        JSONObject rootMetadata = new JSONObject();
//        rootMetadata.put(OSJVM_LABEL, osJvmVariants);
//        rootMetadata.put(APPLICATION_COMPONENTS_LABEL, fApplicationComponents);
//        rootMetadata.put(OVERVIEWS_LABEL, fOverviews);
//        try (FileWriter fw1 = new FileWriter(METADATA_FILE_NAME + METADATA_FILE_NAME_EXTENSION)) {
//            fw1.write(META_DATA_JAVASCRIPT_START + rootMetadata.toString(4));
//        }
    }

    /**
     * Create chart for a scenario instance and add it to the relevant metadatas
     *
     * @param scenario
     *            the scenario. For example,
     *            "CTF Read & Seek Benchmark (500 seeks)".
     * @param entry
     *            an entry from the summary. Only scenarios that are part of the
     *            summary are processed.
     * @param variations
     *            all variations to consider to create the scenario chart. For
     *            example build=%;jvm=1.7;config=linux will generate a chart for
     *            all builds on Linux / JVM 1.7
     *
     * @return
     * @throws JSONException
     *             JSON error
     * @throws IOException
     *             IO error
     */
//    private JSONObject createScenarioChart(Scenario scenario, SummaryEntry entry, Variations variations) throws JSONException, IOException {
//        if (scenario == null) {
//            return null;
//        }
//        String[] split = entry.scenarioName.split(COMPONENT_SEPARATOR);
//        if (split.length < 3) {
//            Activator.logError("Invalid scenario name \"" + entry.scenarioName + "\", it must be in format: org.package.foo#component#test");
//            return null;
//        }
//
//        // Generate individual chart
//        JSONArray rootScenario = new JSONArray();
//        JSONObject series = createSerie(scenario, variations, entry.shortName, entry.dimension);
//        rootScenario.put(series);
//        int numChart = fNumChart++;
//        try (FileWriter fw = new FileWriter(CHART_FILE_NAME + numChart + CHART_FILE_NAME_EXTENSION)) {
//            fw.write(rootScenario.toString(4));
//        }
//
//        // Create the metadata
//        JSONObject testMetadata = new JSONObject();
//        testMetadata.put(TITLE_LABEL, entry.shortName);
//        testMetadata.put(FILE_LABEL, CHART_FILE_NAME + numChart);
//        testMetadata.put(OS_LABEL, variations.getProperty(CONFIG_LABEL));
//        testMetadata.put(JVM_LABEL, variations.getProperty(JVM_LABEL));
//        testMetadata.put(DIMENSION_LABEL, entry.dimension.getName());
//        testMetadata.put(UNIT_LABEL, entry.dimension.getUnit().getShortName());
//
//        // Add the scenario to the metadata, under the correct component
//        String componentName = split[1];
//        JSONObject componentObject = null;
//        if (fApplicationComponents.has(componentName)) {
//            componentObject = fApplicationComponents.getJSONObject(componentName);
//        } else {
//            componentObject = new JSONObject();
//            componentObject.put(NAME_LABEL, componentName);
//            componentObject.put(TESTS_LABEL, new JSONArray());
//            fApplicationComponents.put(componentName, componentObject);
//        }
//        JSONArray tests = componentObject.getJSONArray(TESTS_LABEL);
//        tests.put(testMetadata);
//
//        return series;
//    }

    /**
     * Create an overview chart for this OS / JVM combination. The chart is made
     * of multiple series (scenarios) that were marked as global.
     *
     * @param overviewSummarySeries
     *            an array of series to include in the chart (multiple
     *            scenarios)
     * @param variations
     *            the variations used to generate the series to be included in
     *            this overview chart. For example build=%;jvm=1.7;config=linux
     *            will generate an overview chart for Linux / JVM 1.7
     * @return the overview metadata JSON object
     * @throws JSONException
     *             JSON error
     * @throws IOException
     *             io error
     */
//    private JSONObject createOverviewChart(JSONArray overviewSummarySeries, Variations variations) throws IOException, JSONException {
//        int numOverviewChart = fNumOverviewChart++;
//        try (FileWriter fw = new FileWriter(OVERVIEW_CHART_FILE_NAME + numOverviewChart + CHART_FILE_NAME_EXTENSION)) {
//            fw.write(overviewSummarySeries.toString(4));
//        }
//
//        String os = variations.getProperty(CONFIG_LABEL);
//        String jvm = variations.getProperty(JVM_LABEL);
//
//        // Create the overview metadata
//        JSONObject overviewMetadata = new JSONObject();
//        overviewMetadata.put(TITLE_LABEL, os + " / " + jvm);
//        overviewMetadata.put(FILE_LABEL, OVERVIEW_CHART_FILE_NAME + numOverviewChart);
//        overviewMetadata.put(OS_LABEL, os);
//        overviewMetadata.put(JVM_LABEL, jvm);
//        overviewMetadata.put(DIMENSION_LABEL, StringUtils.EMPTY);
//        overviewMetadata.put(UNIT_LABEL, StringUtils.EMPTY);
//
//        return overviewMetadata;
//    }
//
//    private static Scenario getScenario(String scenarioName, Scenario[] scenarios) {
//        for (int i = 0; i < scenarios.length; i++) {
//            Scenario s = scenarios[i];
//            if (s.getScenarioName().equals(scenarioName)) {
//                return s;
//            }
//
//        }
//        return null;
//    }
//
//    /**
//     * Get all combinations of OS / JVM. This will be used for filtering.
//     *
//     * @return the JSON object containing all the combinations
//     * @throws JSONException
//     *             JSON error
//     */
//    private static JSONObject createOsJvm() throws JSONException {
//        JSONObject osjvm = new JSONObject();
//        List<String> oses = getDistinctOses();
//
//        int osJvmIndex = 1;
//        for (String os : oses) {
//            String key = JVM_LABEL;
//            Variations v = new Variations();
//
//            v.setProperty(BUILD_LABEL, WILDCARD_PATTERN);
//            v.setProperty(HOST_LABEL, WILDCARD_PATTERN);
//            v.setProperty(CONFIG_LABEL, os);
//            v.setProperty(JVM_LABEL, WILDCARD_PATTERN);
//
//            List<String> jvms = new ArrayList<>();
//            DB.queryDistinctValues(jvms, key, v, WILDCARD_PATTERN);
//            for (String jvm : jvms) {
//                JSONObject osjvmItem = new JSONObject();
//                osjvmItem.put(OS_LABEL, os);
//                osjvmItem.put(JVM_LABEL, jvm);
//                osjvmItem.put(DESCRIPTION_LABEL, os + " / " + jvm);
//                osjvm.put(Integer.toString(osJvmIndex), osjvmItem);
//                osJvmIndex++;
//            }
//        }
//
//        return osjvm;
//    }

    /**
     * Get all the distinct OS values
     *
     * @return the distinct OS values
     */
//    private static List<String> getDistinctOses() {
//        List<String> configs = new ArrayList<>();
//        String key = PerformanceTestPlugin.CONFIG;
//        Variations v = new Variations();
//        v.setProperty(WILDCARD_PATTERN, WILDCARD_PATTERN);
//        DB.queryDistinctValues(configs, key, v, WILDCARD_PATTERN);
//        return configs;
//    }

    /**
     * This main can be run from within Eclipse provided everything is on the
     * class path.
     *
     * @param args
     *            the arguments
     * @throws JSONException
     *             JSON error
     * @throws IOException
     *             io error
     */
    public static void main(String[] args) throws JSONException, IOException {
        new PerfResultsToJSon().parseResults();
    }

    /**
     * Create a series of data points for a given scenario through variations
     *
     * @param scenario
     *            the scenario. For example,
     *            "CTF Read & Seek Benchmark (500 seeks)".
     * @param variations
     *            all variations to consider to create the series. For example
     *            build=%;jvm=1.7;config=linux will generate the series for all
     *            builds on Linux / JVM 1.7
     * @param shortName
     *            the short name of the scenario
     * @param dimension
     *            the dimension of interest (CPU time, used java heap, etc).
     * @return the generated JSON object representing a series of data points
     *         for this scenario
     * @throws JSONException
     */
//    private static JSONObject createSerie(Scenario scenario, Variations variations, String shortName, Dim dimension) throws JSONException {
//        JSONObject o = new JSONObject();
//        o.putOpt(KEY_LABEL, shortName);
//        o.putOpt(VALUES_LABEL, createDataPoints(scenario, variations, dimension));
//        return o;
//    }

    /**
     * Create data points for a given scenario and variations.
     *
     * @param s
     *            the scenario. For example,
     *            "CTF Read & Seek Benchmark (500 seeks)".
     * @param variations
     *            all variations to consider to create the data points. For
     *            example build=%;jvm=1.7;config=linux will generate the data
     *            points for all builds on Linux / JVM 1.7
     * @param dimension
     *            the dimension of interest (CPU time, used java heap, etc).
     *
     * @return the generated JSON array of points
     * @throws JSONException
     *             JSON error
     */
//    private static JSONArray createDataPoints(Scenario s, Variations variations, Dim dimension) throws JSONException {
//        // Can be uncommented to see raw dump
//        //s.dump(System.out, PerformanceTestPlugin.BUILD);
//
//        String[] builds = DB.querySeriesValues(s.getScenarioName(), variations, PerformanceTestPlugin.BUILD);
//        Date[] dates = new Date[builds.length];
//        String[] commits = new String[builds.length];
//        for (int i = 0; i < builds.length; i++) {
//            dates[i] = parseBuildDate(builds[i]);
//            commits[i] = parseCommit(builds[i]);
//        }
//
//        TimeSeries timeSeries = s.getTimeSeries(dimension);
//        JSONArray dataPoints = new JSONArray();
//        int length = timeSeries.getLength();
//        for (int i = 0; i < length; i++) {
//            JSONObject point = new JSONObject();
//            if (dates[i] == null) {
//                continue;
//            }
//            point.put(X_LABEL, dates[i].getTime());
//            double value = 0;
//            if (timeSeries.getCount(i) > 0) {
//                value = timeSeries.getValue(i);
//                if (Double.isNaN(value)) {
//                    value = 0;
//                }
//            }
//            point.put(Y_LABEL, value);
//            dataPoints.put(point);
//            point.put(LABEL_LABEL, createLabel(commits[i]));
//        }
//        return dataPoints;
//    }

    /**
     * Create a label JSONObject which is used to attach more information to a
     * data point.
     *
     * @param commit
     *            the commit id for this data point
     * @return the resulting JSON object
     * @throws JSONException
     *             JSON error
     */
//    private static JSONObject createLabel(String commit) throws JSONException {
//        /*
//         * Here we could add more information about this specific data point
//         * like the commit author, the commit message, etc.
//         */
//        JSONObject label = new JSONObject();
//        if (commit != null && !commit.isEmpty()) {
//            label.put(COMMIT_LABEL, commit);
//        }
//        return label;
//    }

    /**
     * Get the commit id out of the build= string
     *
     * @param build
     *            the build string
     * @return the parsed commit id
     */
//    private static String parseCommit(String build) {
//        Matcher matcher = COMMIT_PATTERN.matcher(build);
//        if (matcher.matches()) {
//            return matcher.group(1);
//        }
//        return null;
//    }

    /**
     * Get the Date out of the build= string
     *
     * @param build
     *            the build string
     * @return the parsed Date
     */
//    private static Date parseBuildDate(String build) {
//        Matcher matcher = BUILD_DATE_PATTERN.matcher(build);
//        Date date = null;
//        if (matcher.matches()) {
//            String dateStr = matcher.group(1);
//            SimpleDateFormat f = new SimpleDateFormat(BUILD_DATE_FORMAT);
//            try {
//                date = dateStr.length() > BUILD_DATE_FORMAT.length() ?
//                        f.parse(dateStr.substring(dateStr.length() - BUILD_DATE_FORMAT.length())) :
//                        f.parse(dateStr);
//            } catch (ParseException e) {
//                return null;
//            }
//        }
//        return date;
//    }
}
