/***************************************************************************
 * Copyright 2014 Kieker Project (http://kieker-monitoring.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***************************************************************************/

package kieker.webgui.service.impl.utility;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import kieker.analysis.AnalysisController.STATE;
import kieker.webgui.common.exception.AnalysisDisplayReloadException;
import kieker.webgui.common.exception.AnalysisInitializationException;
import kieker.webgui.common.exception.InvalidAnalysisStateException;
import kieker.webgui.common.exception.ProjectNotExistingException;
import kieker.webgui.domain.DisplayType;
import kieker.webgui.persistence.IProjectDAO;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * This manager is a service which is responsible for the currently used and running instances of {@code AnalysisController}. It provides methods to check the states
 * of analyses, instantiate, start, and to stop them.
 * 
 * @author Nils Christian Ehmke
 */
@Service
public final class AnalysisManagementService {

	private final Map<String, Analysis> analyses = new ConcurrentHashMap<String, Analysis>();

	@Autowired
	private IProjectDAO projectDAO;

	/**
	 * Default constructor. <b>Do not use this constructor. This bean is Spring managed.</b>
	 */
	public AnalysisManagementService() {
		// No code necessary.
	}

	/**
	 * This method initializes the analysis for the given project.
	 * 
	 * @param projectName
	 *            The name of the project to be initialized.
	 * @throws AnalysisInitializationException
	 *             If an error occurred during the initialization of the analysis.
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to be initialized.
	 * @throws IOException
	 *             If something went wrong while loading the file.
	 * @throws ProjectNotExistingException
	 *             If a project with the given name does not exist.
	 */
	public void initializeAnalysis(final String projectName) throws InvalidAnalysisStateException, AnalysisInitializationException, ProjectNotExistingException,
			IOException {
		// The analysis for the given project must not exist!
		if (this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been cleaned yet.");
		}

		final Analysis analysis = new Analysis(projectName, this.projectDAO);
		this.analyses.put(projectName, analysis);
	}

	/**
	 * Reloads the displays of the given project.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * 
	 * @throws AnalysisDisplayReloadException
	 *             If something went wrong while reloading the displays.
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state for the operation.
	 */
	public void reloadDisplays(final String projectName) throws AnalysisDisplayReloadException, InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}

		this.analyses.get(projectName).reloadDisplays(projectName, this.projectDAO);
	}

	/**
	 * This method cleans the analysis for the given project.
	 * 
	 * @param projectName
	 *            The name of the project to be cleaned.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to be cleaned.
	 */
	public void cleanAnalysis(final String projectName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (this.analyses.containsKey(projectName)) {
			final Analysis analysis = this.analyses.get(projectName);

			// The analysis for the given project must not run
			if (analysis.getCurrentState() == STATE.RUNNING) {
				throw new InvalidAnalysisStateException("The analysis is still running.");
			}

			this.analyses.remove(projectName);
		}
	}

	/**
	 * This method starts the analysis for the given project.
	 * 
	 * @param projectName
	 *            The name of the project to be started.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to be started.
	 */
	public void startAnalysis(final String projectName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}

		final Analysis analysis = this.analyses.get(projectName);

		// The analysis for the given project must be ready
		if (analysis.getCurrentState() != STATE.READY) {
			throw new InvalidAnalysisStateException("The analysis is not ready.");
		}

		analysis.start();
	}

	/**
	 * This method stops the analysis for the given project.
	 * 
	 * @param projectName
	 *            The name of the project to be stopped.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to be stopped.
	 */
	public void stopAnalysis(final String projectName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}

		final Analysis analysis = this.analyses.get(projectName);

		// The analysis for the given project must be running
		if (analysis.getCurrentState() != STATE.RUNNING) {
			throw new InvalidAnalysisStateException("The analysis is not running.");
		}

		analysis.stop();
	}

	/**
	 * Initializes an emergency shutdown of the analysis.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state for the operation.
	 */
	public void emergencyShutdown(final String projectName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}

		this.analyses.get(projectName).emergencyShutdown();
	}

	/**
	 * This method delivers the available log entries of the analysis controller of the given project.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * 
	 * @return An array containing the entries of the log.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to deliver the entries.
	 */
	public Object[] getLogEntries(final String projectName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}
		return this.analyses.get(projectName).getLogEntries();
	}

	/**
	 * This method delivers the display object of the (currently running) analysis for the given project and the given parameters. Technically it is an instance of
	 * {@code AbstractDisplay}, but in fact the project specific class loader has been used.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * @param viewName
	 *            The name of the view.
	 * @param displayName
	 *            The name of the display.
	 * 
	 * @return A display object for the given parameters.
	 * 
	 * @throws InvalidAnalysisStateException
	 *             If the analysis is in an invalid state to deliver the displays.
	 */
	public Object getDisplay(final String projectName, final String viewName, final String displayName) throws InvalidAnalysisStateException {
		// The analysis for the given project must exist!
		if (!this.analyses.containsKey(projectName)) {
			throw new InvalidAnalysisStateException("The analysis has not been initialized yet.");
		}

		return this.analyses.get(projectName).getDisplay(viewName, displayName);
	}

	/**
	 * This method delivers the current state of the analysis, if it is available, null otherwise.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * 
	 * @return The state of the given project.
	 */
	public STATE getCurrentState(final String projectName) {
		STATE retState = null;

		// The analysis for the given project must exist!
		if (this.analyses.containsKey(projectName)) {
			final Analysis analysis = this.analyses.get(projectName);
			retState = analysis.getCurrentState();
		}

		return retState;
	}

	/**
	 * Delivers the type of the given display connector.
	 * 
	 * @param projectName
	 *            The name of the project.
	 * @param viewName
	 *            The name of the view.
	 * @param displayConnectorName
	 *            The name of the display connector.
	 * 
	 * @return The type of the display connector.
	 */
	public DisplayType getDisplayType(final String projectName, final String viewName, final String displayConnectorName) {
		if (this.analyses.containsKey(projectName)) {
			return this.analyses.get(projectName).getDisplayType(viewName, displayConnectorName);
		} else {
			return DisplayType.UNKNOWN;
		}
	}

}
