/*******************************************************************************
 * Copyright (c) 2013, 2018 Red Hat Inc.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.perf;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.linuxtools.internal.perf.handlers.Messages;
import org.eclipse.linuxtools.internal.perf.model.PMStatEntry;
import org.eclipse.linuxtools.internal.perf.model.PMStatEntry.Type;
import org.eclipse.linuxtools.profiling.launch.IRemoteFileProxy;
import org.eclipse.linuxtools.profiling.launch.RemoteProxyManager;

/**
 * Class containing all functionality for comparting perf statistics data.
 */
public class StatComparisonData extends BaseDataManipulator implements IPerfData {
    // Old stats file.
    private IPath oldFile;

    // New stats file.
    private IPath newFile;

    // Comparison result string.
    private String result = ""; //$NON-NLS-1$

    // Title for this comparison run.
    private String title;

    // Unique data identifier.
    private String dataID;

    public StatComparisonData(String title, IPath oldFile, IPath newFile) {
        this.title = title;
        this.oldFile = oldFile;
        this.newFile = newFile;
        this.dataID = String.valueOf(((new Date().getTime())));
    }

    @Override
    public String getPerfData() {
        return result;
    }

    @Override
    public String getTitle() {
        return title;
    }

    /**
     * Get unique identifier for this data object.
     *
     * @return String unique identifier based on this object's creation time.
     */
    public String getDataID(){
        return dataID;
    }

    /**
     * Generate a unique identifier based on the given file. The generation is a
     * simple concatenation between the file path and the time of this object's
     * creation.
     *
     * @param file File to generate uniqure id from.
     * @return String unique id for specified file.
     */
    private String generateFileID(IPath file) {
        return file.toOSString() + dataID;
    }

    /**
     * Get path to old perf data file.
     *
     * @return String path corresponding to old perf data.
     */
    public String getOldDataPath() {
        return oldFile.toPortableString();
    }

    /**
     * Get path to new perf data file.
     *
     * @return String path corresponding to new perf data.
     */
    public String getNewDataPath() {
        return newFile.toOSString();
    }

    /**
     * Get a unique to for the old perf data file.
     *
     * @return String unique id.
     */
    public String getOldDataID() {
        return generateFileID(oldFile);
    }

    /**
     * Get a unique to for the old perf data file.
     *
     * @return String unique id.
     */
    public String getNewDataID() {
        return generateFileID(newFile);
    }

    /**
     * Compare stat data files and store the result in the result field.
     */
    public void runComparison() {
        ArrayList<PMStatEntry> statsDiff = getComparisonStats();

        if (!statsDiff.isEmpty()) {
            String[][] statsDiffStr = new String[statsDiff.size()][];
            int currentRow = 0;

            // gather comparison results in a string array
            for (PMStatEntry statEntry : statsDiff) {
                statsDiffStr[currentRow] = statEntry.toStringArray();
                currentRow++;
            }

            // apply format to each entry and set the result
            String format = getFormat(statsDiffStr);
            String curLine;
            for (String[] statEntry : statsDiffStr) {
                curLine = String.format(format, (Object[]) statEntry);
                curLine = curLine.contains(PMStatEntry.TIME) ? "\n" + curLine //$NON-NLS-1$
                        : curLine;
                result += curLine;
            }
        } else{

        }
    }

    /**
     * Return a PMStatEntry array with the result of the comparison between the
     * old and new stat data files
     * @return
     */
    public ArrayList<PMStatEntry> getComparisonStats() {
        ArrayList<PMStatEntry> oldStats = collectStats(oldFile);
        ArrayList<PMStatEntry> newStats = collectStats(newFile);
        ArrayList<PMStatEntry> result = new ArrayList<>();

        for (PMStatEntry oldEntry : oldStats) {
            for (PMStatEntry newEntry : newStats) {
                if (oldEntry.equalEvents(newEntry)) {
                    result.add(oldEntry.compare(newEntry));
                    continue;
                }
            }
        }

        return result;
    }

    /**
     * Collect statistics entries from the specified stat data file.
     *
     * @param file file to collect from
     * @return List containing statistics entries from the given file.
     */
    private static ArrayList<PMStatEntry> collectStats(IPath file) {
        ArrayList<PMStatEntry> result = new ArrayList<>();
        BufferedReader statReader = null;
        URI fileURI = null;
        try {
            fileURI = new URI(file.toPortableString());
            IRemoteFileProxy proxy = null;
            proxy = RemoteProxyManager.getInstance().getFileProxy(fileURI);
            IFileStore newDataFileStore = proxy.getResource(fileURI.getPath());
            statReader = new BufferedReader(new InputStreamReader(newDataFileStore.openInputStream(EFS.NONE, null)));

            // pattern for a valid perf stat entry
            Pattern entryPattern = Pattern.compile(PMStatEntry.getString(Type.ENTRY_PATTERN));

            // pattern for last stat entry (seconds elapsed):
            Pattern totalTimePattern = Pattern.compile(PMStatEntry.getString(Type.TIME_PATTERN));

            String line;
            while((line = statReader.readLine()) != null ){
                line = line.trim();
                Matcher match = entryPattern.matcher(line);
                String samples, event, usage, units, delta, scale;
                PMStatEntry statEntry;

                if(match.find()){

                    // extract information from groups
                    samples = match.group(1);
                    event = match.group(2);
                    usage = match.group(7);
                    units = match.group(8);
                    delta = match.group(10);
                    scale = match.group(14);

                    // create stat entry
                    statEntry = new PMStatEntry(toFloat(samples), event,
                            toFloat(usage), units, toFloat(delta),
                            toFloat(scale));

                    // add stat entry to results list
                    result.add(statEntry);

                } else if(line.contains(PMStatEntry.TIME)){

                    // match seconds elapsed pattern
                    match = totalTimePattern.matcher(line);
                    if(match.find()){
                        samples = match.group(1);
                        event = match.group(2);
                        delta = match.group(4);

                        // create stat entry
                        statEntry = new PMStatEntry(toFloat(samples),
                                event, 0, null, toFloat(delta), 0);

                        result.add(statEntry);
                    }
                }
            }
            return result;
        } catch (IOException|CoreException|URISyntaxException e) {
            PerfPlugin.getDefault().openError(e, Messages.MsgError);
        } finally {
            try {
                if (statReader != null) {
                    statReader.close();
                }
            } catch (IOException e) {
                PerfPlugin.getDefault().openError(e, Messages.PerfResourceLeak_title);
            }
        }

        return result;
    }

    /**
     * Get formatting string from unformatted table.
     *
     * @param table array to construct formatting for.
     * @return Formatting string representing the proper way to format the given
     *         table.
     */
    private String getFormat(String[][] table) {
        // all entries have the same number of columns
        int[] maxCharLen = new int[table[0].length];

        // collect max number of characters per column
        for (int i = 0; i < table.length; i++) {
            for (int j = 0; j < table[i].length; j++) {
                maxCharLen[j] = Math.max(maxCharLen[j], table[i][j].length());
            }
        }

        // prepare format arguments
        ArrayList<Integer> arguments = new ArrayList<>();
        for (int length : maxCharLen) {
            arguments.add(length);
        }

        // generate format string
        String entryFormat = String.format(
                PMStatEntry.getString(Type.ENTRY_FORMAT),
                arguments.toArray());

        return entryFormat;
    }

    /**
     * Get float representation of specified string.
     *
     * @param str String convert
     * @return Float representation of string.
     */
    public static float toFloat(String str) {
        try {
            // remove commas from number string representation
            return (str == null) ? 0
                    : Float.parseFloat(str.replace(",", "")); //$NON-NLS-1$//$NON-NLS-2$
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}
