/***************************************************************************
 * 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.web.beans.view;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import kieker.analysis.AnalysisController;
import kieker.common.logging.Log;
import kieker.common.logging.LogFactory;
import kieker.webgui.common.LogEntry;
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.service.IProjectService;
import kieker.webgui.web.beans.application.GlobalPropertiesBean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * The {@link ControllerBean} contains the necessary data behind an instance of the analysis controller. The class is a Spring managed bean with view scope to
 * make sure that one user (even in one session) can open multiple projects at a time without causing any problems.
 * 
 * @author Nils Christian Ehmke
 */
@Component
@Scope("view")
public final class ControllerBean {

	private static final Log LOG = LogFactory.getLog(ControllerBean.class);

	private final List<LogEntry> logEntries = new ArrayList<LogEntry>();
	private String projectName;

	@Autowired
	private GlobalPropertiesBean globalPropertiesBean;
	@Autowired
	private IProjectService projectService;

	private String currentDetailMessage;

	/**
	 * Creates a new instance of this class. <b>Do not use this constructor. This bean is Spring managed.</b>
	 */
	public ControllerBean() {
		// No code necessary
	}

	public void setProjectName(final String newName) {
		// Remember the given parameters
		this.projectName = newName;

	}

	public String getProjectName() {
		return this.projectName;
	}

	/**
	 * This method starts the current analysis and informs the user about a fail.
	 */
	public void startAnalysis() {
		this.addLogEntry(this.globalPropertiesBean.getLogMsgStartingAnalysis());
		try {
			this.projectService.startAnalysis(this.projectName);
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.info("The analysis has already been started.", ex);
			this.addLogEntry(this.globalPropertiesBean.getLogMsgAnalysisAlreadyStarted());
		} catch (final ProjectNotExistingException ex) {
			ControllerBean.LOG.info("The project does not exist.", ex);
			this.addLogEntry("The project does not exist.");
		}

	}

	/**
	 * Reloads the cockpit displays of the current project.
	 */
	public void reloadCockpit() {
		try {
			this.projectService.reloadDisplays(this.projectName);
		} catch (final AnalysisDisplayReloadException ex) {
			ControllerBean.LOG.warn("The displays could not be reloaded.", ex);
			this.addLogEntry("The displays could not be reloaded.");
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.info("The analysis has not been started yet.", ex);
			this.addLogEntry("The analysis has not been started yet.");
		}
	}

	/**
	 * This method stops the current analysis and informs the user about a fail.
	 */
	public void stopAnalysis() {
		try {
			this.addLogEntry(this.globalPropertiesBean.getLogMsgStoppingAnalysis());
			synchronized (this) {
				this.projectService.stopAnalysis(this.projectName);
			}
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.info("The analysis has not been started yet.", ex);
			this.addLogEntry("The analysis has not been started yet.");
		} catch (final ProjectNotExistingException ex) {
			ControllerBean.LOG.info("The project does not exist.", ex);
			this.addLogEntry("The project does not exist.");
		}
	}

	/**
	 * Initializes an emergency shutdown.
	 */
	public void emergencyShutdown() {
		try {
			this.addLogEntry(this.globalPropertiesBean.getLogMsgEmergencyShutdown());
			synchronized (this) {
				this.projectService.emergencyShutdownOfAnalysis(this.projectName);
			}
		} catch (final ProjectNotExistingException ex) {
			ControllerBean.LOG.info("The project does not exist.", ex);
			this.addLogEntry("The project does not exist.");
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.info("An error occured during the operation.", ex);
			this.addLogEntry(this.globalPropertiesBean.getLogMsgErrorOccured());
		}
	}

	/**
	 * This method initializes the current analysis and informs the user about a fail.
	 */
	public void instantiateAnalysis() {
		this.addLogEntry(this.globalPropertiesBean.getLogMsgInstantiatingAnalysis());
		try {
			this.projectService.initializeAnalysis(this.projectName); // NOPMD (ClassLoader)
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.error("The analysis has already been instantiated.", ex);
			this.addLogEntry("The analysis has already been instantiated.");
		} catch (final ProjectNotExistingException ex) {
			ControllerBean.LOG.info("The project does not exist.", ex);
			this.addLogEntry("The project does not exist.");
		} catch (final IOException ex) {
			ControllerBean.LOG.info("An error occured during the initialization.", ex);
			this.addLogEntry(this.globalPropertiesBean.getLogMsgErrorDuringInitialization(), ex);
		} catch (final AnalysisInitializationException ex) {
			ControllerBean.LOG.info("An error occured during the initialization.", ex);
			this.addLogEntry(this.globalPropertiesBean.getLogMsgErrorDuringInitialization(), ex);
		}
	}

	/**
	 * This method cleans the current analysis instance.
	 */
	public void cleanAnalysis() {
		this.addLogEntry(this.globalPropertiesBean.getLogMsgCleaning());
		try {
			this.projectService.cleanAnalysis(this.projectName);
		} catch (final ProjectNotExistingException ex) {
			ControllerBean.LOG.info("The project does not exist.", ex);
			this.addLogEntry("The project does not exist.");
		} catch (final InvalidAnalysisStateException ex) {
			ControllerBean.LOG.error("The analysis has not been instantiated yet.", ex);
			this.addLogEntry("The analysis has not been instantiated yet.");
		}
	}

	/**
	 * Checks whether the analysis is currently running.
	 * 
	 * @return true if and only if the analysis is running.
	 */
	public boolean isAnalysisRunning() {
		try {
			return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.RUNNING;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return false;
	}

	/**
	 * Checks whether the analysis is currently terminating.
	 * 
	 * @return true if and only if the analysis is currently terminating.
	 */
	public boolean isAnalysisTerminating() {
		try {
			return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.TERMINATING;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return false;
	}

	/**
	 * Checks whether the analysis is currently in the ready state.
	 * 
	 * @return true if and only if the analysis is ready to be started.
	 */
	public boolean isAnalysisReady() {
		try {
			return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.READY;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return false;
	}

	/**
	 * Checks whether the analysis is not available.
	 * 
	 * @return true if and only if the analysis is <b>not</b> available.
	 */
	public boolean isAnalysisNotAvailable() {
		try {
			return this.projectService.getCurrentState(this.projectName) == null;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return true;
	}

	/**
	 * Checks whether the analysis is currently terminated.
	 * 
	 * @return true if and only if the analysis has been terminated.
	 */
	public boolean isAnalysisTerminated() {
		try {
			return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.TERMINATED;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return false;
	}

	/**
	 * Checks whether the analysis is currently in the failed state.
	 * 
	 * @return true if and only if the analysis has failed.
	 */
	public boolean isAnalysisFailed() {
		try {
			return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.FAILED;
		} catch (final NullPointerException ex) {
			// This exception can occur, when the projectsBean has not been initialized
			ControllerBean.LOG.warn("A null pointer exception occured.", ex);
		}
		return false;
	}

	/**
	 * Delivers the available log entries of the current analysis controller instance.
	 * 
	 * @return The current log entries.
	 */
	public Object[] getAnalysisLog() {
		try {
			return this.projectService.getLogEntries(this.projectName);
		} catch (final InvalidAnalysisStateException ex) {
			// Ignore
			return new Object[0];
		} catch (final NullPointerException ex) {
			return new Object[0];
		}
	}

	public void setCurrentDetailMessage(final String detailMessage) {
		this.currentDetailMessage = detailMessage;
	}

	public String getCurrentDetailMessage() {
		return this.currentDetailMessage;
	}

	/**
	 * Delivers the available log entries of the current view.
	 * 
	 * @return The current log entries.
	 */
	public Collection<LogEntry> getViewLog() {
		return this.logEntries;
	}

	/**
	 * Adds the element (with the current time) to the log.
	 * 
	 * @param logMsg
	 *            The log message.
	 */
	private void addLogEntry(final String logMsg) {
		final String finalMsg = new Date().toString() + " : " + logMsg;
		if (this.logEntries.size() > 50) {
			this.logEntries.remove(0);
		}
		this.logEntries.add(new LogEntry(finalMsg));
	}

	/**
	 * Adds the element (with the current time) to the log.
	 * 
	 * @param logMsg
	 *            The log message.
	 * @param ex
	 *            The exception containing details for the log message.
	 */
	private void addLogEntry(final String logMsg, final Exception ex) {
		final String detailMessage = this.exceptionStackTraceToString(ex);
		final String finalMsg = new Date().toString() + " : " + logMsg;
		if (this.logEntries.size() > 50) {
			this.logEntries.remove(0);
		}

		this.logEntries.add(new LogEntry(finalMsg, detailMessage));
	}

	private String exceptionStackTraceToString(final Exception ex) {
		ByteArrayOutputStream outStream = null;
		try {
			outStream = new ByteArrayOutputStream();
			final PrintStream ps = new PrintStream(outStream, true, "UTF-8");
			ex.printStackTrace(ps);
			ps.close();
			return outStream.toString("UTF-8");
		} catch (final UnsupportedEncodingException ex1) {
			LOG.warn("Unsupported encoding", ex1);
		} finally {
			if (outStream != null) {
				try {
					outStream.close();
				} catch (final IOException ex1) {
					LOG.warn("Could not close stream", ex1);
				}
			}
		}

		return null;

	}
}
