/*******************************************************************************
 * Copyright (c) 2015 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bruno Medeiros - initial API and implementation
 *******************************************************************************/
package melnorme.lang.ide.ui.dialogs;

import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;

import java.util.ArrayList;
import java.util.Collections;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.swt.widgets.Display;

import melnorme.lang.ide.core.LangNature;
import melnorme.lang.ide.core.utils.EclipseUtils;
import melnorme.lang.ide.core.utils.ResourceUtils;
import melnorme.lang.ide.core.utils.ResourceUtils.CoreOperation;
import melnorme.lang.ide.ui.utils.UIOperationsStatusHandler;
import melnorme.lang.ide.ui.utils.operations.RunnableContextUtils;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.concurrency.OperationCancellation;
import melnorme.utilbox.core.CommonException;

/**
 * Component to create (and removed) new Lang projects, usually used by New Project wizards.
 */
public abstract class ProjectCreationOperation {
	
	public static interface IRevertAction {
		
		public void run(IProgressMonitor monitor) throws CoreException;
		
	}
	
	protected final IRunnableContext context;
	
	protected final ArrayList2<IRevertAction> revertActions = new ArrayList2<>();
	protected final ArrayList2<Runnable> finishActions = new ArrayList2<>();
	
	protected IProject createdProject;
	
	public ProjectCreationOperation(IRunnableContext context) {
		this.context = context;
	}
	
	protected IRunnableContext getRunnableContext() {
		return context;
	}
	
	public IProject getCreatedProject() {
		return createdProject;
	}
	
	public abstract IProject getProject() throws CoreException;
	
	public abstract IPath getProjectLocation2();
	
	protected boolean runOperation(boolean isCancellabe, String errorTitle, CoreOperation operation) {
		try {
			RunnableContextUtils.runOperationInWorkspace(getRunnableContext(), isCancellabe, operation);
			return true;
		} catch(OperationCancellation e) {
			return false;
		} catch(CommonException e) {
			UIOperationsStatusHandler.handleStatus(true, errorTitle, e);
		}
		return false;
	}
	
	public boolean performCreateProject() {
		boolean success = runOperation(true, 
			WizardMessages.LangNewProject_createProjectError_title, this::doCreateProject);
		
		if(!success) {
			revertProjectCreation();
		}
		return success;
	}
	
	protected void doCreateProject(IProgressMonitor monitor) 
			throws CoreException, CommonException, OperationCancellation {
		createdProject = null;
		
		if(getProject() == null) {
			throw new CommonException("No project name specified.");
		}
		
		if(!getProject().exists()) {
			String projectName = getProject().getName();
			ResourceUtils.createAndOpenProject(projectName, getProjectLocation2(), false, monitor);
			
			revertActions.add(new IRevertAction() {
				@Override
				public void run(IProgressMonitor pm) throws CoreException {
					getProject().delete(true, pm);
				}
			});
		}
		if(!getProject().hasNature(LangNature.NATURE_ID)) {
			EclipseUtils.addNature(getProject(), LangNature.NATURE_ID);
			
			revertActions.add(new IRevertAction() {
				@Override
				public void run(IProgressMonitor pm) throws CoreException {
					EclipseUtils.removeNature(getProject(), LangNature.NATURE_ID);
				}
			});
		}
		createdProject = getProject();
		configureCreatedProject(monitor);
		ResourceUtils.refresh(getProject(), EclipseUtils.om(monitor));
	}
	
	/**
	 * Configure created Lang project. Don't forget to add revert actions.
	 */
	protected abstract void configureCreatedProject(IProgressMonitor monitor) 
			throws CommonException, OperationCancellation, CoreException;
	
	public boolean revertProjectCreation() {
		return runOperation(true, WizardMessages.LangNewProject_removeProjectError_title, 
			this::doRevertProjectCreation);
	}
	
	protected void doRevertProjectCreation(IProgressMonitor monitor) throws CoreException {
		ArrayList<IRevertAction> revertActionsToRun = new ArrayList<>(revertActions);
		
		revertActions.clear(); // Clear this before running any code that can throw exceptions.
		
		Collections.reverse(revertActionsToRun); // Run in reverse order.
		
		for(IRevertAction revertAction : revertActionsToRun) {
			revertAction.run(monitor);
		}
	}
	
	
	public void performFinishActions() {
		assertTrue(Display.getCurrent() != null);
		// Runs in UI thread.
		for(Runnable runnable : finishActions) {
			runnable.run();
		}
		finishActions.clear();
	}
	
}