/*******************************************************************************
 * 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.daemon;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;

import org.eclipse.linuxtools.internal.oprofile.core.OprofileCorePlugin;
import org.eclipse.linuxtools.internal.oprofile.core.linux.LinuxOpxmlProvider.OpInfoRunner;
import org.eclipse.linuxtools.internal.oprofile.core.opxml.info.DefaultsProcessor;

/**
 * A class to hold generic information about Oprofile.
 */
public class OpInfo {
	// Oprofile defaults
	public static final String DEFAULT_SAMPLE_DIR = DefaultsProcessor.SAMPLE_DIR;
	public static final String DEFAULT_LOCK_FILE = DefaultsProcessor.LOCK_FILE;
	public static final String DEFAULT_LOG_FILE = DefaultsProcessor.LOG_FILE;
	public static final String DEFAULT_DUMP_STATUS = DefaultsProcessor.DUMP_STATUS;

	/**
	 * A comparator class used when sorting events (sorting by event name)
	 */
	private static class SortEventComparator implements Comparator<OpEvent> {
		@Override
		public int compare(OpEvent o1, OpEvent o2) {
			return o1.getText().compareTo(o2.getText());
		}
	}

	/**
	 * A comparator class used when searching events (searching by event name)
	 */
	private static class SearchEventComparator implements Comparator<Object> {
		@Override
		public int compare(Object a, Object b) {
			String astr, bstr;
			if (a instanceof String) {
				astr = (String) a;
				bstr = ((OpEvent) b).getText();
			} else {
				astr = ((OpEvent) a).getText();
				bstr = (String) b;
			}
			return astr.compareTo(bstr);
		}
	}

	/**
	 * The number of counters supported by this configuration
	 */
	private int nrCounters;

	/**
	 * A HashMap of Oprofile defaults
	 */
	private HashMap<String, String> defaults;

	/**
	 * The permanent list of events indexed by counter
	 */
	private OpEvent[][] eventList;

	/**
	 * The CPU frequency of this CPU in MHz
	 */
	private double cpuSpeed;

	/**
	 * Whether or not oprofile is running in timer mode
	 */
	private boolean timerMode;

	/**
	 * Return all of Oprofile's generic information.
	 * 
	 * @return a class containing the information
	 */
	public static OpInfo getInfo() {
		// Run opmxl and get the static information
		OpInfo info = new OpInfo();

		OpInfoRunner opxml = (OpInfoRunner) OprofileCorePlugin.getDefault().getOpxmlProvider().info(info);
		boolean ret = opxml.run0(null);
		if (!ret) {
			info = null;
		}

		return info;
	}

	/**
	 * Sets the number of counters allowed by Oprofile. This method is called after
	 * this object is contstructed, while opxml is run (the first tag output is
	 * num-counters). Only called from XML parsers.
	 * 
	 * @param ctrs the number of counters
	 */
	public void setNrCounters(int ctrs) {
		nrCounters = ctrs;

		// Allocate room for event lists for the counters
		eventList = new OpEvent[nrCounters][];
	}

	/**
	 * Set the CPU frequency (in MHz). Only called from the XML parsers.
	 * 
	 * @param freq the frequency
	 */
	public void setCPUSpeed(double freq) {
		cpuSpeed = freq;
	}

	/**
	 * Sets the defaults associated with this configuration of Oprofile. Only called
	 * from XML parsers.
	 * 
	 * @param map the <code>HashMap</code> containing the defaults
	 */
	public void setDefaults(HashMap<String, String> map) {
		defaults = map;
	}

	/**
	 * Adds the events of the counter counterNum into the list of all events. Note
	 * they are sorted here. Only called from XML parsers.
	 * 
	 * @param counterNum the counter with the events
	 * @param events     an array of OpEvent events belonging to this counter
	 */
	public void setEvents(int counterNum, OpEvent[] events) {
		if (counterNum < eventList.length) {
			eventList[counterNum] = events;
			Arrays.sort(eventList[counterNum], new SortEventComparator());
		}
	}

	/**
	 * Sets whether or not oprofile is operating in timer mode. Only called from XML
	 * parsers.
	 * 
	 * @param timerMode true if oprofile is in timer mode, false if not
	 */
	public void setTimerMode(boolean timerMode) {
		this.timerMode = timerMode;
	}

	/**
	 * Returns the number of counters allowed by Oprofile
	 * 
	 * @return the number of counters
	 */
	public int getNrCounters() {
		return nrCounters;
	}

	/**
	 * Returns the CPU's speed in MHz
	 * 
	 * @return the speed
	 */
	public double getCPUSpeed() {
		return cpuSpeed;
	}

	/**
	 * Returns the requested default. Valid defaults are
	 * <code>DEFAULT_DUMP_STATUS</code>, <code>DEFAULT_LOCK_FILE</code>,
	 * <code>DEFAULT_LOG_FILE</code>, and <code>DEFAULT_SAMPLE_DIR</code>.
	 * 
	 * @param what which default to return
	 * @return the requested default or <code>null</code> if not known
	 */
	public String getDefault(String what) {
		return defaults.get(what);
	}

	/**
	 * Returns an array of events valid for the given counter number.
	 * 
	 * @param num the counter number
	 * @return an array of valid events
	 */
	public OpEvent[] getEvents(int num) {
		if (num >= 0 && num < eventList.length) {
			return eventList[num];
		}

		return new OpEvent[0];
	}

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

	/**
	 * Searches the for the event with the given name
	 * 
	 * @param name the name of the event (e.g., CPU_CLK_UNHALTED)
	 * @return the event or <code>null</code> if not found
	 */
	public OpEvent findEvent(String name) {
		// Search through all counters
		for (int counter = 0; counter < getNrCounters(); ++counter) {
			int idx = Arrays.binarySearch(getEvents(counter), name, new SearchEventComparator());
			if (idx >= 0) {
				return eventList[counter][idx];
			}
		}

		return null;
	}
}
