/*******************************************************************************
 * Copyright (c) 2013 Red Hat Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.perf;


import java.io.BufferedReader;
import java.io.FileNotFoundException;
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.
	 */
	public 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<PMStatEntry>();

		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.
	 */
	public static ArrayList<PMStatEntry> collectStats(IPath file) {
		ArrayList<PMStatEntry> result = new ArrayList<PMStatEntry>();
		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(6);
					units = match.group(7);
					delta = match.group(9);
					scale = match.group(13);

					// 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 (FileNotFoundException e) {
			PerfPlugin.getDefault().openError(e, Messages.MsgError);
		} catch (IOException e) {
			PerfPlugin.getDefault().openError(e, Messages.MsgError);
		} catch (CoreException e) {
			PerfPlugin.getDefault().openError(e, Messages.MsgError);
		} catch (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.
	 */
	public 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<Integer>();
		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;
		}
	}
}
