/*******************************************************************************
 * Copyright (c) 2010, 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 - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.oprofile.core.opxml.sessions;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.linuxtools.internal.oprofile.core.OprofileCorePlugin;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This class manipulates the XML data output from the command 'opxml sessions'
 */
public class SessionManager {
	public final static String PLUGIN_LOC = OprofileCorePlugin.getDefault().getStateLocation().toOSString();
	public final static String SESSIONS = "sessions"; //$NON-NLS-1$
	public final static String EVENT = "event"; //$NON-NLS-1$
	public final static String SESSION = "session"; //$NON-NLS-1$
	public final static String NAME = "name"; //$NON-NLS-1$
	public final static String CURRENT = "current"; //$NON-NLS-1$
	public final static String OPXML_PREFIX = PLUGIN_LOC + "/opxml_"; //$NON-NLS-1$
	public final static String SESSION_LOCATION = OPXML_PREFIX + SESSIONS;
	public final static String MODEL_DATA = "model-data"; //$NON-NLS-1$

	public Document doc;
	public Element root;
	public String absfilePath;

	public SessionManager(String filePath) {
		this(new File(filePath));
		absfilePath = filePath;
		write();
	}

	/**
	 * Session manager class constructor to manipulate the XML data
	 *
	 * @param file the xml file
	 */
	public SessionManager(File file) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		try {
			builder = factory.newDocumentBuilder();
			if (!file.exists()) {
				file.createNewFile();
				doc = builder.newDocument();
				root = doc.createElement(SESSIONS);
				doc.appendChild(root);
			} else {
				InputStream is = new FileInputStream(file);
				doc = builder.parse(is);
				Element elem = (Element) doc.getElementsByTagName(SESSIONS).item(0);
				root = elem;
			}
		} catch (ParserConfigurationException | SAXException | IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Add a session to the specified event element if it does not exist.
	 *
	 * @param sessionName the name of the session
	 * @param eventName   the name of the event
	 */
	public void addSession(String sessionName, String eventName) {
		Element event = null;
		Element session = find(root, SESSION, NAME, sessionName);
		if (session == null) {
			session = doc.createElement(SESSION);
			session.setAttribute(NAME, sessionName);
			root.appendChild(session);
		} else {
			event = find(session, EVENT, NAME, eventName);
		}

		if (event == null) {
			event = doc.createElement(EVENT);
			event.setAttribute(NAME, eventName);
			session.appendChild(event);
		}
	}

	/**
	 * Check if a session exists
	 *
	 * @param sessionName the name of the session
	 * @return true if the session exists, otherwise false
	 */
	public boolean existsSession(String sessionName) {
		return find(root, SESSION, NAME, sessionName) != null ? true : false;
	}

	/**
	 * Find an element in the XML
	 *
	 * @param elem    the element to look under
	 * @param tagName the name of the tag
	 * @param attName the name of the attribute
	 * @param attVal  the name of the attribute value
	 * @return the element that matches the search, or null if none is found.
	 */
	private Element find(Element elem, String tagName, String attName, String attVal) {
		NodeList list = elem.getElementsByTagName(tagName);
		for (int i = 0; i < list.getLength(); i++) {
			if (list.item(i) instanceof Element) {
				Element e = (Element) list.item(i);
				if (e.getAttribute(attName).equals(attVal)) {
					return e;
				}
			}
		}
		return null;
	}

	/**
	 * Remove all sessions under any event that have the name 'current'
	 */
	public void removeAllCurrentSessions() {
		NodeList sessionList = root.getElementsByTagName(SESSION);
		for (int i = 0; i < sessionList.getLength(); i++) {
			Element session = (Element) sessionList.item(i);
			String sessionName = session.getAttribute(NAME);
			if (CURRENT.equals(sessionName)) {
				NodeList eventList = session.getElementsByTagName(EVENT);
				for (int j = 0; j < eventList.getLength(); j++) {
					Element event = (Element) eventList.item(j);
					String eventName = event.getAttribute(NAME);
					session.removeChild(event);
					File file = new File(SessionManager.OPXML_PREFIX + SessionManager.MODEL_DATA + eventName
							+ SessionManager.CURRENT);
					file.delete();
					if (sessionList.getLength() == 0) {
						root.removeChild(session);
					}
				}
			}
		}
	}

	/**
	 * Remove a session named sessionName that is under eventName if it exists.
	 *
	 * @param sessionName
	 * @param eventName
	 */
	public void removeSession(String sessionName, String eventName) {
		NodeList list = root.getElementsByTagName(SESSION);
		for (int i = 0; i < list.getLength(); i++) {

			if (list.item(i) instanceof Element) {
				Element session = (Element) list.item(i);
				if (session.getAttribute(NAME).equals(sessionName)) {
					Element event = find((Element) list.item(i), EVENT, NAME, eventName);
					if (event != null) {
						removeSessionHelper(sessionName, event);
					}
				}
			}
		}
	}

	/**
	 * remove event elem for given session sessionName , also remove session if
	 * there is no event under it.
	 *
	 * @param sessionName
	 * @param elem
	 *
	 */
	private void removeSessionHelper(String sessionName, Element elem) {
		NodeList list = root.getElementsByTagName(SESSION);
		for (int i = 0; i < list.getLength(); i++) {
			if (list.item(i) instanceof Element) {
				Element e = (Element) list.item(i);
				if (e.getAttribute(NAME).equals(sessionName)) {
					NodeList events = e.getElementsByTagName(EVENT);
					for (int j = 0; j < events.getLength(); j++) {
						Element event = (Element) events.item(j);
						if (event.getAttribute(NAME).equals(elem.getAttribute(NAME))) {
							e.removeChild(event);
						}
						if (events.getLength() == 0) {
							root.removeChild(e);
						}
					}
				}
			}
		}
	}

	/**
	 * Return a list of the events run with the given session
	 *
	 * @param sessionName the name of the session
	 * @return A String ArrayList of event names that were run with the given
	 *         session.
	 */
	public ArrayList<String> getSessionEvents(String sessionName) {
		ArrayList<String> ret = new ArrayList<>();
		NodeList eventList = root.getElementsByTagName(SESSION);

		for (int i = 0; i < eventList.getLength(); i++) {
			if (eventList.item(i) instanceof Element) {
				Element event = ((Element) eventList.item(i));
				if (event.getAttribute(NAME).equals(sessionName)) {

					NodeList sessionList = event.getElementsByTagName(EVENT);
					for (int j = 0; j < sessionList.getLength(); j++) {
						if (sessionList.item(j) instanceof Element) {
							Element session = ((Element) sessionList.item(j));
							ret.add(session.getAttribute(NAME));
						}
					}
				}
			}
		}
		return ret;
	}

	/**
	 * Write back to the same file, that the data was loaded from.
	 */
	public void write() {
		writeToFile(absfilePath);
	}

	/**
	 * Write the contents of the given Document to a file.
	 */
	private void writeToFile(String filePath) {
		Source source = new DOMSource(doc);
		Result result = new StreamResult(new File(filePath));
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer xformer;
		try {
			xformer = factory.newTransformer();
			xformer.transform(source, result);
		} catch (TransformerException e) {
			e.printStackTrace();
		}
	}

	public static void saveSession(String name) {
		SessionManager sessMan;
		try {
			sessMan = new SessionManager(SessionManager.SESSION_LOCATION);
			for (String event : sessMan.getSessionEvents(SessionManager.CURRENT)) {
				sessMan.addSession(name, event);
				String oldFile = SessionManager.OPXML_PREFIX + SessionManager.MODEL_DATA + event
						+ SessionManager.CURRENT;
				String newFile = SessionManager.OPXML_PREFIX + SessionManager.MODEL_DATA + event + name;
				Process p = Runtime.getRuntime().exec("cp " + oldFile + " " + newFile); //$NON-NLS-1$ //$NON-NLS-2$
				p.waitFor();
			}
			sessMan.write();
		} catch (FileNotFoundException e) {
			// intentionally blank
			// during a save, the session file will exist
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void deleteSession(String sessionName, String eventName) {
		File file = new File(SessionManager.OPXML_PREFIX + SessionManager.MODEL_DATA + eventName + sessionName);
		file.delete();
		SessionManager sessMan = new SessionManager(SessionManager.SESSION_LOCATION);
		sessMan.removeSession(sessionName, eventName);
		sessMan.write();
	}
}
