/***************************************************************************
 * 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.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import kieker.analysis.AnalysisController.STATE;
import kieker.common.logging.Log;
import kieker.common.logging.LogFactory;
import kieker.webgui.common.exception.ProjectAlreadyExistingException;
import kieker.webgui.common.exception.ProjectNotExistingException;
import kieker.webgui.common.exception.ProjectStillRunningException;
import kieker.webgui.service.IProjectService;
import kieker.webgui.web.beans.application.GlobalPropertiesBean;
import kieker.webgui.web.beans.request.project.CopyProjectBean;
import kieker.webgui.web.beans.request.project.ImportProjectBean;
import kieker.webgui.web.beans.request.project.NewProjectBean;
import kieker.webgui.web.beans.request.project.RenameProjectBean;
import kieker.webgui.web.beans.session.UserBean;

import org.primefaces.context.RequestContext;
import org.primefaces.event.SelectEvent;

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

/**
 * The {@link ProjectOverviewBean} contains the necessary data behind an instance of the project overview.<br>
 * </br>
 * 
 * The class is a Spring managed bean with view scope.
 * 
 * @author Nils Christian Ehmke
 */
@Component
@Scope("view")
public final class ProjectOverviewBean {

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

	private List<String> availableProjects;
	private String selectedProjectName;

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

	/**
	 * Creates a new instance of this class. <b>Do not use this constructor. This bean is Spring managed.</b>
	 */
	public ProjectOverviewBean() {
		this.availableProjects = Collections.emptyList();
	}

	/**
	 * This method initializes the bean. <b>Do not call this method manually. It will only be accessed by Spring.</b>
	 */
	@PostConstruct
	protected void initialialize() { // NOPMD (has to be protected)
		// Make sure that the initialization will only be done for the init request.
		if (!FacesContext.getCurrentInstance().isPostback()) {
			this.updateAvailableProjects();
		}
	}

	/**
	 * Adds a project to the application, using the given project bean.
	 * 
	 * @param newProjectBean
	 *            The bean containing the necessary data to create the project.
	 */
	public void addProject(final NewProjectBean newProjectBean) {
		try {
			final String projectName = newProjectBean.getProjectName();
			// Try and use the project service to create the project atomically.
			this.projectService.addProject(projectName, this.userBean.getUsername());
			// Inform the user
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectCreated());
			// Update the list of the "calling" bean
			this.updateAvailableProjects();
		} catch (final IOException ex) {
			LOG.error("An error occured while creating the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectCreationException());
		} catch (final ProjectAlreadyExistingException ex) {
			LOG.error("A project with the same name exists already.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectAlreadyExistingException(),
					newProjectBean.getMessageTarget().getClientId());
			RequestContext.getCurrentInstance().addCallbackParam("fail", true);
		}
	}

	/**
	 * This method renames the current project.
	 * 
	 * @param renameProjectBean
	 *            The bean containing the necessary data to rename the current project.
	 */
	public void renameProject(final RenameProjectBean renameProjectBean) {
		try {
			// Try and use the service to copy the project atomically.
			this.projectService.renameProject(this.selectedProjectName, renameProjectBean.getProjectName());
			// Inform the user
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectRenamed());
			// Update the list of the "calling" bean
			this.updateAvailableProjects();
		} catch (final IOException ex) {
			LOG.error("An error occured while creating the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectCreationException());
		} catch (final ProjectAlreadyExistingException ex) {
			LOG.error("A project with the same name exists already.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectAlreadyExistingException(),
					renameProjectBean.getMessageTarget().getClientId());
			RequestContext.getCurrentInstance().addCallbackParam("fail", true);
		} catch (final ProjectNotExistingException ex) {
			LOG.info("A project with the given name does not exist.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_WARN, this.globalPropertiesBean.getMsgProjectNotExistingException());
		}
	}

	/**
	 * This method deletes the current project.
	 */
	public void delProject() {
		try {
			// Try and use the project service to delete the project atomically.
			this.projectService.deleteProject(this.selectedProjectName);
			// Inform the user
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectDeleted());
			// Update the list of the "calling" bean
			this.updateAvailableProjects();
		} catch (final IOException ex) {
			LOG.error("An error occured while deleting the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectDeleteException());
		} catch (final ProjectNotExistingException ex) {
			LOG.error("An error occured while deleting the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectDeleteException());
		} catch (final ProjectStillRunningException ex) {
			LOG.warn("The analysis is still running and cannot be deleted.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_WARN, this.globalPropertiesBean.getMsgProjectStillRunningException());
		}
	}

	/**
	 * This method copies the current project.
	 * 
	 * @param copyProjectBean
	 *            The bean containing the necessary data to copy the current project.
	 */
	public void copyProject(final CopyProjectBean copyProjectBean) {
		try {
			// Try and use the service to copy the project atomically.
			this.projectService.copyProject(this.selectedProjectName, copyProjectBean.getProjectName());
			// Inform the user
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectCreated());
			// Update the list of the "calling" bean
			this.updateAvailableProjects();
		} catch (final IOException ex) {
			LOG.error("An error occured while creating the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectCreationException());
			ex.printStackTrace();
		} catch (final ProjectAlreadyExistingException ex) {
			LOG.error("A project with the same name exists already.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectAlreadyExistingException(),
					copyProjectBean.getMessageTarget().getClientId());
			RequestContext.getCurrentInstance().addCallbackParam("fail", true);
		} catch (final ProjectNotExistingException ex) {
			LOG.info("A project with the given name does not exist.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_WARN, this.globalPropertiesBean.getMsgProjectNotExistingException());
		}
	}

	/**
	 * This method uploads a project.
	 * 
	 * @param importProjectBean
	 *            The bean containing the necessary data to upload the project.
	 */
	public void uploadProject(final ImportProjectBean importProjectBean) {
		try {
			if (importProjectBean.getUploadedFile() == null) {
				GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "Please provide a valid file.");
				return;
			}
			final String projectName = importProjectBean.getProjectName();
			// Try and use the FS-Manager to create the project atomically.
			this.projectService.importProject(projectName, this.userBean.getUsername(), importProjectBean.getUploadedFile());
			// Inform the user
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectUploaded());
			// Update the list of the "calling" bean
			this.updateAvailableProjects();
		} catch (final IOException ex) {
			LOG.error("An error occured while importing the project.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while importing the project.");
		} catch (final ProjectAlreadyExistingException ex) {
			LOG.error("A project with the same name exists already.", ex);
			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectAlreadyExistingException());
		}
	}

	/**
	 * This is the event handler used to select a row instantaneously.
	 * 
	 * @param event
	 *            The select event.
	 */
	public void rowSelectHandler(final SelectEvent event) {
		this.setProjectName((String) event.getObject());
	}

	/**
	 * Updates the list with the available availableProjects.
	 */
	public void updateAvailableProjects() {
		this.availableProjects = this.projectService.getProjects();
	}

	public List<String> getProjects() {
		return this.availableProjects;
	}

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

	public void setProjectName(final String projectName) {
		this.selectedProjectName = projectName;
	}

	/**
	 * This method can be used to get the current time stamp of a given project as a human readable date. If the project doesn't exist or the time stamp would be
	 * invalid "N/A" will be returned.
	 * 
	 * @param project
	 *            The project whose time stamp should be collected.
	 * @return The human readable time stamp of the project.
	 */
	public String getCurrTimeStamp(final String project) {
		try {
			// Get the current time stamp of the project
			final long timeStamp = this.projectService.getCurrTimeStamp(project);
			// Convert the stamp into a human readable string.
			return new Date(timeStamp).toString();
		} catch (final ProjectNotExistingException ex) {
			LOG.info("A project with the given name does not exist.", ex);
			// We can assume that something went wrong
			return "N/A";
		}
	}

	/**
	 * Delivers the owner of the given project or a substitution if the meta data is corrupt or missing.
	 * 
	 * @param project
	 *            The name of the project whose owner should be delivered.
	 * 
	 * @return The owner (creator) of the project.
	 */
	public String getOwner(final String project) {
		try {
			return this.projectService.getOwner(project);
		} catch (final ProjectNotExistingException ex) {
			LOG.info("A project with the given name does not exist.", ex);
			// We can assume that something went wrong
			return "N/A";
		}
	}

	/**
	 * Delivers the last user of the given project or a substitution if the meta data is corrupt or missing. More precisely: It will be the user who saves the
	 * project the last time.
	 * 
	 * @param project
	 *            The name of the project whose last user should be delivered.
	 * 
	 * @return The last user of the project.
	 */
	public String getLastUser(final String project) {
		try {
			return this.projectService.getLastUser(project);
		} catch (final ProjectNotExistingException ex) {
			LOG.info("A project with the given name does not exist.", ex);
			// We can assume that something went wrong
			return "N/A";
		}
	}

	/**
	 * This method can be used to deliver the state of the analysis controller of the given project as a human readable string.
	 * 
	 * @param project
	 *            The project whose state should be delivered.
	 * @return The current state of the corresponding AnalysisController.
	 */
	public String getAnalysisControllerState(final String project) {
		STATE state;
		state = this.projectService.getCurrentState(project);
		if (state != null) {
			return state.toString();
		} else {
			return null;
		}
	}

}
