/*******************************************************************************
 * Copyright (c) 2004, 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:
 *    Keith Seitz <keiths@redhat.com> - initial API and implementation
 *    Kent Sebastian <ksebasti@redhat.com> -
 *******************************************************************************/

package org.eclipse.linuxtools.internal.oprofile.core;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.linuxtools.internal.oprofile.core.daemon.OpEvent;
import org.eclipse.linuxtools.internal.oprofile.core.daemon.OpInfo;
import org.eclipse.linuxtools.internal.oprofile.core.model.OpModelImage;
import org.eclipse.linuxtools.internal.oprofile.core.model.OpModelSession;
import org.eclipse.linuxtools.internal.oprofile.core.opxml.checkevent.CheckEventsProcessor;
import org.eclipse.linuxtools.profiling.launch.IRemoteFileProxy;
import org.eclipse.linuxtools.profiling.launch.RemoteProxyManager;
import org.eclipse.linuxtools.tools.launch.core.factory.RuntimeProcessFactory;

/**
 * Common class wrapper for all things Oprofile.
 */
public class Oprofile {
	// Ugh. Need to know whether the module is loaded without running oprofile
	// commands...
	private static final String[] OPROFILE_CPU_TYPE_FILES = { "/dev/oprofile/cpu_type", //$NON-NLS-1$
			"/proc/sys/dev/oprofile/cpu_type" //$NON-NLS-1$
	};

	/**
	 * Oprofile information
	 */
	private static OpInfo info;

	/**
	 * Make sure that oprofile is ready to go
	 */
	static {
		initializeOprofileModule();
	}

	/**
	 * Initialize the oprofile module
	 *
	 * This function will check if the kernel module is loaded. If it is not, it
	 * will attempt to load it (which will cause the system to prompt the user for
	 * root access).
	 */
	static private void initializeOprofileModule() {
		if (OprofileProject.getProfilingBinary().equals(OprofileProject.OPERF_BINARY) || isKernelModuleLoaded()) {
			initializeOprofileCore();
		}
	}

	// This requires more inside knowledge about Oprofile than one would like,
	// but it is the only way of knowing whether the module is loaded (and we can
	// succesfully call into the oprofile wrapper library without causing it to
	// print out
	// a lot of warnings).
	/**
	 * Check whether oprofile kernel module is loaded
	 * 
	 * @return true if the module is loaded, otherwise false
	 */
	private static boolean isKernelModuleLoaded() {
		IRemoteFileProxy proxy = null;
		try {
			proxy = RemoteProxyManager.getInstance().getFileProxy(Oprofile.OprofileProject.getProject());
		} catch (CoreException e) {
			e.printStackTrace();
		}

		for (int i = 0; i < OPROFILE_CPU_TYPE_FILES.length; ++i) {
			IFileStore f = proxy.getResource(OPROFILE_CPU_TYPE_FILES[i]);
			if (f.fetchInfo().exists()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Initializes static data for oprofile.
	 */
	private static void initializeOprofileCore() {
		info = OpInfo.getInfo();

		if (info == null) {
			throw new ExceptionInInitializerError(OprofileProperties.getString("fatal.opinfoNotParsed")); //$NON-NLS-1$
		}
	}

	/**
	 * Queries oprofile for the number of counters on the current CPU. Used only in
	 * launch config tabs.
	 * 
	 * @return the number of counters
	 */
	public static int getNumberOfCounters() {
		// If operf is not found, set no counters
		try {
			Process p = RuntimeProcessFactory.getFactory().exec(new String[] { "operf", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
					OprofileProject.getProject());
			if (p == null) {
				return 0;
			}
		} catch (IOException e) {
			return 0;
		}
		return info.getNrCounters();
	}

	/**
	 * Returns the CPU speed of the current configuration.
	 * 
	 * @return the cpu speed in MHz
	 */
	public static double getCpuFrequency() {
		return info.getCPUSpeed();
	}

	/**
	 * Get all the events that may be collected on the given counter.
	 * 
	 * @param num the counter number
	 * @return an array of all valid events -- NEVER RETURNS NULL!
	 */
	public static OpEvent[] getEvents(int num) {
		return info.getEvents(num);
	}

	/**
	 * Returns the default location of the opcontrol samples directory or the
	 * project directory if the profiler is operf.
	 * 
	 * @return the default samples directory
	 */
	public static String getDefaultSamplesDirectory() {
		return info.getDefault(OpInfo.DEFAULT_SAMPLE_DIR);
	}

	/**
	 * Returns the oprofile daemon log file.
	 * 
	 * @return the log file (absolute pathname)
	 */
	public static String getLogFile() {
		return info.getDefault(OpInfo.DEFAULT_LOG_FILE);
	}

	/**
	 * Returns whether or not oprofile is in timer mode.
	 * 
	 * @return true if oprofile is in timer mode, false otherwise
	 */
	public static boolean getTimerMode() {
		return info.getTimerMode();
	}

	/**
	 * Checks the requested counter, event, and unit mask for validity.
	 * 
	 * @param ctr   the counter
	 * @param event the event name
	 * @param um    the unit mask
	 * @return whether the requested event is valid
	 */
	public static Boolean checkEvent(int ctr, String event, int um) {
		int[] validResult = new int[1];
		try {
			IRunnableWithProgress opxml = OprofileCorePlugin.getDefault().getOpxmlProvider().checkEvents(ctr, event, um,
					validResult);
			opxml.run(null);
		} catch (InvocationTargetException | InterruptedException e) {
		}

		return (validResult[0] == CheckEventsProcessor.EVENT_OK);
	}

	/**
	 * Returns a list of all the session collected on the system, as well as the
	 * event under each of them.
	 * 
	 * @since 3.0
	 * @returns a list of all collected events
	 */
	public static OpModelSession[] getSessions() {
		OpModelSession[] events = null;

		ArrayList<OpModelSession> sessionList = new ArrayList<>();
		try {
			IRunnableWithProgress opxml = OprofileCorePlugin.getDefault().getOpxmlProvider().sessions(sessionList);
			opxml.run(null);
			events = new OpModelSession[sessionList.size()];
			sessionList.toArray(events);
		} catch (InvocationTargetException | InterruptedException e) {
		}
		return events;
	}

	/**
	 * Return a list of all the Samples in the given session.
	 * 
	 * @param session the session for which to get samples
	 * @param shell   the composite shell to use for the progress dialog
	 */
	public static OpModelImage getModelData(String eventName, String sessionName) {
		OpModelImage image = new OpModelImage();

		final IRunnableWithProgress opxml;
		try {
			opxml = OprofileCorePlugin.getDefault().getOpxmlProvider().modelData(eventName, sessionName, image);
			opxml.run(null);
		} catch (InvocationTargetException | InterruptedException e) {
		}

		return image;
	}

	/**
	 * Check if oprofile kernel module is loaded and update Oprofile's information.
	 * 
	 * @since 1.1
	 */
	public static void updateInfo() {
		info = OpInfo.getInfo();
	}

	// Oprofile class has a static initializer and the code inside it needs to know
	// which project
	// is being profiled in order to get the path for the Linux Tools' binaries set
	// for that project.
	// For this reason the project property has to be set outside the Oprofile class
	/**
	 * OProfileProject class to set/get which project is being profiled
	 * 
	 * @since 1.1
	 */
	public static class OprofileProject {
		private static IProject project;
		public final static String OPERF_BINARY = "operf"; //$NON-NLS-1$
		public final static String OCOUNT_BINARY = "ocount"; //$NON-NLS-1$
		private static String binary = OPERF_BINARY;
		public final static String OPERF_DATA = "oprofile_data"; //$NON-NLS-1$

		/**
		 * Set the project to be profiled
		 * 
		 * @param project
		 */
		public static void setProject(IProject project) {
			OprofileProject.project = project;

		}

		/**
		 * Get the project to be profiled
		 * 
		 * @return project
		 */
		public static IProject getProject() {
			return project;
		}

		/**
		 * Set the profiling binary to be used (operf or opcontrol)
		 * 
		 * @param binary
		 * @since 2.1
		 */
		public static void setProfilingBinary(String binary) {
			OprofileProject.binary = binary;

		}

		/**
		 * Get the profiling binary (operf or opcontrol)
		 * 
		 * @return binary
		 * @since 2.1
		 */
		public static String getProfilingBinary() {
			return binary;
		}

	}

}
