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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

import kieker.analysis.AnalysisController.STATE;
import kieker.analysis.model.analysisMetaModel.MIProject;
import kieker.webgui.common.exception.AnalysisDisplayReloadException;
import kieker.webgui.common.exception.AnalysisInitializationException;
import kieker.webgui.common.exception.InvalidAnalysisStateException;
import kieker.webgui.common.exception.NewerProjectException;
import kieker.webgui.common.exception.ProjectAlreadyExistingException;
import kieker.webgui.common.exception.ProjectNotExistingException;
import kieker.webgui.common.exception.ProjectStillRunningException;
import kieker.webgui.domain.ComponentListContainer;
import kieker.webgui.domain.DisplayType;
import kieker.webgui.persistence.IProjectDAO;
import kieker.webgui.service.IProjectService;
import kieker.webgui.service.impl.utility.AnalysisManagementService;
import kieker.webgui.service.impl.utility.LockManager;

import org.primefaces.model.UploadedFile;

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

/**
 * This is an implementation of the {@link IProjectService} interface for the project management. It makes sure that accesses to the projects are synchronized. Most
 * of the work is delegated to the underlying layer or to the utility classes.
 * 
 * @author Nils Christian Ehmke
 */
@Service
public final class ProjectServiceImpl implements IProjectService {

	private final List<String> projects = Collections.synchronizedList(new ArrayList<String>());

	private final LockManager fileSystemLockManager = new LockManager();
	private final LockManager analysesLockManager = new LockManager();

	@Autowired
	private IProjectDAO projectDAO;
	@Autowired
	private AnalysisManagementService acManager;

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

	/**
	 * This method does some initialization work after construction. <b>Do not call this method. The method is Spring managed.</b>
	 */
	@PostConstruct
	protected void initialize() { // NOPMD (has to be protected)
		// Load a list with all available projects on the FS
		this.projects.addAll(this.projectDAO.listAllProjects());
	}

	@Override
	public void addProject(final String projectName, final String username) throws ProjectAlreadyExistingException, IOException {
		this.fileSystemLockManager.lock(projectName);

		try {
			this.projectDAO.addProject(projectName, username);
			this.projects.add(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public void deleteProject(final String projectName) throws ProjectNotExistingException, IOException, ProjectStillRunningException {
		this.fileSystemLockManager.lock(projectName);
		this.analysesLockManager.lock(projectName);

		try {
			final STATE state = this.acManager.getCurrentState(projectName);
			if ((state == STATE.RUNNING) || (state == STATE.TERMINATING)) {
				throw new ProjectStillRunningException("The project is still running and cannot be deleted.");
			}
			this.projectDAO.removeProject(projectName);
			this.projects.remove(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
			this.analysesLockManager.unlock(projectName);
		}

	}

	@Override
	public void copyProject(final String originalProjectName, final String newProjectName) throws ProjectNotExistingException, ProjectAlreadyExistingException,
			IOException {
		if (originalProjectName.equals(newProjectName)) {
			throw new ProjectAlreadyExistingException("Source and target are identical");
		}

		this.fileSystemLockManager.lock(originalProjectName);
		this.fileSystemLockManager.lock(newProjectName);

		try {
			this.projectDAO.copyProject(originalProjectName, newProjectName);
			this.projects.add(newProjectName);
		} finally {
			this.fileSystemLockManager.unlock(originalProjectName);
			this.fileSystemLockManager.unlock(newProjectName);
		}
	}

	@Override
	public void renameProject(final String originalProjectName, final String newProjectName) throws ProjectNotExistingException, ProjectAlreadyExistingException,
			IOException {
		if (originalProjectName.equals(newProjectName)) {
			throw new ProjectAlreadyExistingException("Source and target are identical");
		}

		this.fileSystemLockManager.lock(originalProjectName);
		this.fileSystemLockManager.lock(newProjectName);

		try {
			this.projectDAO.copyProject(originalProjectName, newProjectName);
			this.projectDAO.removeProject(originalProjectName);
			synchronized (this.projects) {
				this.projects.add(newProjectName);
				this.projects.remove(originalProjectName);
			}
		} finally {
			this.fileSystemLockManager.unlock(originalProjectName);
			this.fileSystemLockManager.unlock(newProjectName);
		}
	}

	@Override
	public void importProject(final String projectName, final String username, final UploadedFile file) throws ProjectAlreadyExistingException, IOException {
		this.fileSystemLockManager.lock(projectName);

		try {
			this.projectDAO.importProject(projectName, username, file);
			this.projects.add(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public MIProject loadProject(final String projectName) throws ProjectNotExistingException, IOException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.loadProject(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public void saveProject(final String projectName, final MIProject project, final long timeStamp, final boolean overwriteNewerProject, // NOPMD (parameters)
			final String username, final String analysisLayout, final String cockpitLayout) throws ProjectNotExistingException, IOException, NewerProjectException {
		this.fileSystemLockManager.lock(projectName);

		try {
			this.projectDAO.saveProject(projectName, project, timeStamp, overwriteNewerProject, username, analysisLayout, cockpitLayout);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public long getCurrTimeStamp(final String projectName) throws ProjectNotExistingException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getCurrTimeStamp(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public void uploadLibrary(final UploadedFile file, final String projectName) throws ProjectNotExistingException, IOException {
		this.fileSystemLockManager.lock(projectName);

		try {
			this.projectDAO.uploadLibrary(file, projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public ClassLoader getClassLoader(final String projectName, final Object requester) throws ProjectNotExistingException, IOException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getClassLoader(projectName, requester); // NOPMD (ClassLoader)
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public ComponentListContainer getAvailableComponents(final String projectName) {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getAvailableComponents(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public List<String> listAllLibraries(final String projectName) throws ProjectNotExistingException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.listAllLibraries(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public void initializeAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException, AnalysisInitializationException,
			IOException {
		// We have to lock both - the project and the analysis, as a file has to be loaded
		this.fileSystemLockManager.lock(projectName);
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.initializeAnalysis(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public void cleanAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.cleanAnalysis(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public void startAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.startAnalysis(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public void stopAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.stopAnalysis(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public void emergencyShutdownOfAnalysis(final String projectName) throws ProjectNotExistingException, InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.emergencyShutdown(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public Object getDisplay(final String projectName, final String viewName, final String displayName) throws InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			return this.acManager.getDisplay(projectName, viewName, displayName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public STATE getCurrentState(final String projectName) {
		this.analysesLockManager.lock(projectName);

		try {
			return this.acManager.getCurrentState(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public Object[] getLogEntries(final String projectName) throws InvalidAnalysisStateException {
		this.analysesLockManager.lock(projectName);

		try {
			return this.acManager.getLogEntries(projectName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public boolean deleteLibrary(final String projectName, final String libName) throws IOException, ProjectNotExistingException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.deleteLibrary(projectName, libName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public String getOwner(final String projectName) throws ProjectNotExistingException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getOwner(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public String getLastUser(final String projectName) throws ProjectNotExistingException {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getLastUser(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public String getAnalysisLayout(final String projectName) {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getAnalysisLayout(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public String getCockpitLayout(final String projectName) {
		this.fileSystemLockManager.lock(projectName);

		try {
			return this.projectDAO.getCockpitLayout(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
		}
	}

	@Override
	public DisplayType getDisplayType(final String projectName, final String viewName, final String displayConnectorName) {
		this.analysesLockManager.lock(projectName);

		try {
			return this.acManager.getDisplayType(projectName, viewName, displayConnectorName);
		} finally {
			this.analysesLockManager.unlock(projectName);
		}
	}

	@Override
	public List<String> getProjects() {
		synchronized (this.projects) {
			return new ArrayList<String>(this.projects);
		}
	}

	@Override
	public void reloadDisplays(final String projectName) throws AnalysisDisplayReloadException, InvalidAnalysisStateException {
		// We have to lock both - the project and the analysis, as a file has to be loaded
		this.fileSystemLockManager.lock(projectName);
		this.analysesLockManager.lock(projectName);

		try {
			this.acManager.reloadDisplays(projectName);
		} finally {
			this.fileSystemLockManager.unlock(projectName);
			this.analysesLockManager.unlock(projectName);
		}
	}

}
