/*******************************************************************************
 * Copyright (c) 2013, 2017 Red Hat, Inc. 
 * 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:
 * 		 Red Hat Inc. - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.thym.ui.internal.wizard.imports;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.thym.core.HybridProject;
import org.eclipse.thym.core.config.Widget;
import org.eclipse.thym.core.config.WidgetModel;
import org.eclipse.thym.core.platform.PlatformConstants;
import org.eclipse.thym.ui.HybridUI;
import org.eclipse.thym.ui.internal.status.StatusManager;
import org.eclipse.thym.ui.wizard.project.HybridProjectCreator;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;

public class HybridProjectImportPage extends WizardPage implements IOverwriteQuery{
	
	private class ProjectCandidate {
		private Widget widget;
		File wwwLocation;
		File configLocation;
		boolean conflicts;
		
		
		public ProjectCandidate(File www, File config){
			this.configLocation = config;
			this.wwwLocation = www;
		}
		
		String getLabel(){
			if(getWidget() == null ){
				return NLS.bind("INVALID {0}",wwwLocation.toString()); 
			}
			String appName = getProjectName();
			return NLS.bind("{0} ({1})", new String[]{appName,wwwLocation.toString() });
		}

		String getProjectName() {
			String projectName = getWidget().getId();
			if(widget.getName() != null ){
				projectName = getWidget().getName();
			}
			return projectName;
		}
		
		boolean exists(){
			if(getWidget() == null )
				return true;
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			final String projectName = getProjectName();
			IPath wsPath = root.getLocation();
			IPath localProjectPath = wsPath.append(projectName);
			IProject project = root.getProject(projectName);
			return project.exists() || localProjectPath.toFile().exists();
		}
		
		Widget getWidget(){
			if(widget == null ){
				try {
					widget = WidgetModel.parseToWidget(configLocation);
				} catch (CoreException e) {
					HybridUI.log(IStatus.ERROR, "Error parsing the config.xml for import project", e);
				}
			}
			return widget;
		}
	}
	
	private final class ProjectCandidateLabelProvider extends LabelProvider implements IColorProvider{
		public String getText(Object element) {
			return ((ProjectCandidate) element).getLabel();
		}

		@Override
		public Color getForeground(Object element) {
			ProjectCandidate candie = (ProjectCandidate) element;
			if(candie.conflicts || candie.exists()){
				return getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
			}
			return null;
		}

		@Override
		public Color getBackground(Object element) {
			return null;
		}
	}
	
	private final static String SETTINGSKEY_DIRECTORIES = "HybridProjectImportPage.DIRECTORIES";//$NON-NLS-1$
	private final static String SETTINGSKEY_COPY = "HybridProjectImportPage.COPY";//$NON-NLS-1$
	   
	private Combo directoryPathField;
	private String previouslyBrowsedDirectory ="";
	private ProjectCandidate[] candidates;
	private CheckboxTreeViewer projectList;
	private Button copyCheckbox;
	private boolean copyFiles;

	protected HybridProjectImportPage() {
		super("HybridProjectImportPage");
		setTitle("Cordova Project Import");
		setDescription("Select a directory to search for Cordova projects");
	}

	@Override
	public void createControl(final Composite parent) {
		initializeDialogUnits(parent);
		Composite workArea = new Composite(parent, SWT.NONE);
		setControl(workArea);
		
		GridLayoutFactory.fillDefaults().applyTo(workArea);
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(workArea);
		
		createProjectRoot(workArea);
		createProjectsList(workArea);
		createOptionsGroup(workArea);
		restoreFromHistory();
		setPageComplete(validatePage());
		Dialog.applyDialogFont(workArea);
	}

	private void createProjectsList( final Composite workArea) {
		final Label projectsLabel = new Label(workArea,SWT.NULL);
		projectsLabel.setText("Projects:");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(projectsLabel);
		
		Composite projectListGroup = new Composite(workArea, SWT.NULL);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(projectListGroup);
		GridLayoutFactory.fillDefaults().numColumns(2).applyTo(projectListGroup);
		projectList = new CheckboxTreeViewer(projectListGroup);
		PixelConverter pc = new PixelConverter(projectList.getControl());
		GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL)
			.hint(pc.convertWidthInCharsToPixels(25),pc.convertHeightInCharsToPixels(10)).applyTo(projectList.getControl());
		
		projectList.setLabelProvider(new ProjectCandidateLabelProvider());
		projectList.setContentProvider(new ITreeContentProvider() {
			
			@Override
			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			}
			
			@Override
			public void dispose() {
			}
			
			@Override
			public boolean hasChildren(Object element) {
				return false;
			}
			
			@Override
			public Object getParent(Object element) {
				return null;
			}
			
			@Override
			public Object[] getElements(Object inputElement) {
				if(candidates == null )
					return new ProjectCandidate[0];
				return candidates;
			}
			
			@Override
			public Object[] getChildren(Object parentElement) {
				return null;
			}
		});
		
		projectList.addCheckStateListener(new ICheckStateListener() {
			
			@Override
			public void checkStateChanged(CheckStateChangedEvent event) {
				ProjectCandidate candidate = (ProjectCandidate)event.getElement();
				//Cancel out existing projects
				if(candidate.exists()){
					projectList.setChecked(candidate, false);
					return;
				}
				updateConflicts(candidate, event.getChecked());
				projectList.refresh(true);
				setPageComplete(validatePage());
			}
		});
		
		
		final Composite selectButtonGroup = new Composite(projectListGroup, SWT.NULL);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(selectButtonGroup);
		GridLayoutFactory.fillDefaults().applyTo(selectButtonGroup);
		
		Button selectAll = new Button(selectButtonGroup,SWT.PUSH);
		selectAll.setText("Select All");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(selectAll);
	
		Button deselectAll = new Button(selectButtonGroup, SWT.PUSH);
		deselectAll.setText("Deselect All");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(deselectAll);
		
		Button refresh = new Button(selectButtonGroup, SWT.PUSH);
		refresh.setText("Refresh");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(refresh);
		
		selectAll.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				if(candidates != null ){
					for (ProjectCandidate candie : candidates) {
						if(!candie.conflicts && !candie.exists()){
							projectList.setChecked(candie, true);
							updateConflicts(candie, true);
						}
					}
				}
				setPageComplete(validatePage());
			}
		});
		
		deselectAll.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				if (candidates != null) {
					for (ProjectCandidate candie : candidates) {
						projectList.setChecked(candie, false);
						updateConflicts(candie, false);
					}
				}
				setPageComplete(false);
			}
		});
		
		refresh.addListener(SWT.Selection, new Listener() {
			@Override
			public void handleEvent(Event event) {
				updateProjectsList(directoryPathField.getText());
			}
		});
		
		projectList.setInput(this);
	}

	private void createProjectRoot(final Composite workArea) {
		final Composite projectRootGroup = new Composite(workArea, SWT.NONE);
		GridLayoutFactory.fillDefaults().numColumns(3).margins(0, 10).applyTo(projectRootGroup);
		GridDataFactory.fillDefaults().grab(true,false).align(SWT.FILL,SWT.FILL).applyTo(projectRootGroup);
	
		final Label directoryLabel = new Label(projectRootGroup, SWT.NULL);
		directoryLabel.setText("Select root directory:");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(directoryLabel);
		
		directoryPathField = new Combo(projectRootGroup, SWT.BORDER);
		PixelConverter pixelConverter = new PixelConverter(directoryPathField);
		GridDataFactory.fillDefaults().grab(true, false).hint(pixelConverter.convertWidthInCharsToPixels(25),SWT.DEFAULT).applyTo(directoryPathField);
		
		final Button browseDirectoriesButton = new Button(projectRootGroup, SWT.PUSH);
		browseDirectoriesButton.setText("Browse...");
		GridDataFactory.fillDefaults().applyTo(browseDirectoriesButton);
		browseDirectoriesButton.addListener(SWT.Selection, new Listener() {
			@Override
			public void handleEvent(Event event) {
				handleBrowseButtonPressed();
			}
		});
		
		directoryPathField.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				updateProjectsList(directoryPathField.getText());
			}
		});
	}
	
	private void handleBrowseButtonPressed() {
		final DirectoryDialog dialog = new DirectoryDialog(
				directoryPathField.getShell(), SWT.SHEET);
		dialog.setMessage("Select search directory");

		String dirName = directoryPathField.getText().trim();
		if (dirName.isEmpty()) {
			dirName = previouslyBrowsedDirectory;
		}

		if (dirName.isEmpty()) {
			dialog.setFilterPath(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
		} else {
			File path = new File(dirName);
			if (path.exists()) {
				dialog.setFilterPath(new Path(dirName).toOSString());
			}
		}
		
		String selectedDirectory = dialog.open();
		if (selectedDirectory != null) {
			previouslyBrowsedDirectory = selectedDirectory;
			directoryPathField.setText(previouslyBrowsedDirectory);
			updateProjectsList(selectedDirectory);
		}
	}
	private void updateConflicts(ProjectCandidate candidate, boolean checked){
		for(ProjectCandidate elem: candidates ){
			Widget w1 = elem.getWidget();
			Widget w2 = candidate.getWidget();
			if(w1.getId().equals(w2.getId()) &&
					w1.getName().equals(w2.getName()) &&
					!elem.configLocation.equals(candidate.configLocation)){
				if(projectList.getChecked(elem)){
					projectList.setChecked(candidate, false);
				}else{
					elem.conflicts = checked;
				}
			}
			
		}
		
	}
	private void updateProjectsList(final String selectedDirectory) {
		if(selectedDirectory == null || selectedDirectory.isEmpty()){
			candidates = null;
			projectList.refresh(true);
			return;
		}
		final File directory = new File(selectedDirectory);

		try {
			getContainer().run(true, true, new IRunnableWithProgress() {
				@Override
				public void run(IProgressMonitor monitor) throws InvocationTargetException,
						InterruptedException {
					if(directory.isDirectory()){
						List<ProjectCandidate> candies= new ArrayList<HybridProjectImportPage.ProjectCandidate>();
						collectProjectCandidates(candies, directory, monitor);
						candidates = candies.toArray(new ProjectCandidate[candies.size()]); 
					}
				}
			});
		} catch (InvocationTargetException e) {
			if (e.getTargetException() != null) {
				if(e.getTargetException() instanceof CoreException ){
					StatusManager.handle((CoreException) e.getTargetException());
				}else{
					ErrorDialog.openError(getShell(), "Error finding projects to import",null, 
							new Status(IStatus.ERROR, HybridUI.PLUGIN_ID, "Error while searching for projects to import", e.getTargetException() ));
				}
			}
		} catch (InterruptedException e) {
			HybridUI.log(IStatus.ERROR, "Error searchig projects to import", e);
		}
		projectList.refresh(true);
	}

	protected void collectProjectCandidates(List<ProjectCandidate> candidates,
			File directory, IProgressMonitor monitor) {
		if(monitor.isCanceled()){
			return;
		}
		
		File[] configXMLs = directory.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return PlatformConstants.FILE_XML_CONFIG.equals(name);
			}
		});
		
		if(configXMLs == null){
			return;
		}
	
		for (File config: configXMLs) {
			File parent = config.getParentFile();
			ProjectCandidate candidate = null;
			if (config.isFile()) {
				if (parent.getName().equals(PlatformConstants.DIR_WWW)) {
					candidate = new ProjectCandidate(parent, config);
				} else {
					File sameLevelWWW = new File(parent, PlatformConstants.DIR_WWW);
					if (sameLevelWWW.isDirectory()) {
						candidate = new ProjectCandidate(sameLevelWWW, config);
					}
				}
			}
			if(candidate != null){
				candidates.add(candidate);
				return;
			}
		}
		
		File[] dirs = directory.listFiles();
		for (File dir : dirs) {
			collectProjectCandidates(candidates, dir, monitor);
		}
	}
	
	private void createOptionsGroup(Composite workArea) {
		final Group optionsGroup = new Group(workArea, SWT.NULL);
		optionsGroup.setText("Options:");
		GridLayoutFactory.fillDefaults().margins(10,10).applyTo(optionsGroup);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.FILL).applyTo(optionsGroup);
		
		
		copyCheckbox = new Button(optionsGroup, SWT.CHECK);
		copyCheckbox.setText("Copy into workspace");
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL);
		copyCheckbox.addListener(SWT.Selection, new Listener() {
			@Override
			public void handleEvent(Event event) {
				copyFiles = copyCheckbox.getSelection();
				setPageComplete(validatePage());
			}
		});
	}
	
	private void restoreFromHistory(){
		//Directories
		IDialogSettings settings = getDialogSettings();
		if (settings == null) return;
		String[] sourceNames = settings.getArray(SETTINGSKEY_DIRECTORIES);
		if (sourceNames == null) {
			return; 
		}
		for (String dirname : sourceNames) {
			directoryPathField.add(dirname);
		}
		//copy to workspace
		copyFiles = settings.getBoolean(SETTINGSKEY_COPY);
		copyCheckbox.setSelection(copyFiles);
	}
	
	private void saveInHistroy(){
		IDialogSettings settings = getDialogSettings();
		if (settings != null) {
			// Directories 
			String[] sourceNames = settings.getArray(SETTINGSKEY_DIRECTORIES);
			if (sourceNames == null) {
				sourceNames = new String[0];
			}
			List<String> l = new ArrayList<String>(Arrays.asList(sourceNames));
			l.remove(directoryPathField.getText());
			l.add(0,directoryPathField.getText());
			sourceNames = l.toArray(new String[l.size()]);
			settings.put(SETTINGSKEY_DIRECTORIES, sourceNames);
			
			//Copy to workspace
			settings.put(SETTINGSKEY_COPY, copyCheckbox.getSelection());
		}
	}
	
	boolean createProjects(){
		saveInHistroy();
		
		final Object[] selectedCandidates = projectList.getCheckedElements();
		WorkspaceModifyOperation wop = new WorkspaceModifyOperation() {
			
			@Override
			protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
				monitor.beginTask("", selectedCandidates.length);
				if (monitor.isCanceled()) {
					throw new OperationCanceledException();
				}
				try {
					for (int i = 0; i < selectedCandidates.length; i++) {
						ProjectCandidate pc = (ProjectCandidate) selectedCandidates[i];
						IProject prj = doCreateProject(pc, new SubProgressMonitor(monitor, 1));
						HybridProject project = HybridProject.getHybridProject(prj);
						
						
					}
				}
				catch(CoreException e){
					throw new InvocationTargetException(e);
				}finally{
					monitor.done();
				}
			}
		};
		
		try {
			getContainer().run(true, true, wop);
		} catch (InvocationTargetException e) {
			if (e.getTargetException() != null) {
				if(e.getTargetException() instanceof CoreException ){
					StatusManager.handle((CoreException) e.getTargetException());
				}else{
					ErrorDialog.openError(getShell(), "Error importing project",null, 
							new Status(IStatus.ERROR, HybridUI.PLUGIN_ID, "Project import error", e.getTargetException() ));
				}
			}

		} catch (InterruptedException e) {
			throw new OperationCanceledException();
		}
		return true;
	}
	

	private IProject doCreateProject(ProjectCandidate pc, IProgressMonitor monitor) throws CoreException, InterruptedException {
		HybridProjectCreator projectCreator = new HybridProjectCreator();
		Widget w = pc.getWidget();
		String projectName = pc.getProjectName();
		URI location = null;
		if(!copyFiles){
			location = pc.wwwLocation.getParentFile().toURI();
		}
		IProject project = projectCreator.createProject(projectName, location, w.getName(), w.getId(), null, monitor);
		if(copyFiles){
			ImportOperation operation = new ImportOperation(project
					.getFullPath(), pc.wwwLocation.getParentFile(), FileSystemStructureProvider.INSTANCE
					, this);
			operation.setContext(getShell());
			operation.setOverwriteResources(true); 
			operation.setCreateContainerStructure(false);
			
			try {
				operation.run(monitor);
			} catch (InvocationTargetException e) {
				if(e.getCause() != null  && e.getCause() instanceof CoreException){
					CoreException corex = (CoreException) e.getCause();
					throw corex;
				}
			}
			IStatus status = operation.getStatus();
			if (!status.isOK())
				throw new CoreException(status);
		}
		return project;

	}
	
	private boolean validatePage(){
		final Object[] selectedCandidates = projectList.getCheckedElements();
		if(selectedCandidates.length < 1 ){
			String msg = null;
			if(candidates != null ){
				for (ProjectCandidate candie : candidates){
					if(candie.exists()){
						msg = "Some projects cannot be imported because they already exist in the workspace";
					}
				}
			}
			setMessage(msg, WARNING);
			setErrorMessage(null);
			return false;
		}
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		
		for (int i = 0; i < selectedCandidates.length; i++) {
			ProjectCandidate pc = (ProjectCandidate) selectedCandidates[i];
			
			final IStatus nameStatus= workspace.validateName(pc.getProjectName(), IResource.PROJECT);
			if (!nameStatus.isOK()) {
				setErrorMessage(nameStatus.getMessage());
				return false;
			}
			final IProject handle= workspace.getRoot().getProject(pc.getProjectName());
			if (handle.exists()) {
				setErrorMessage(NLS.bind("Project {0} already exists in the workspace", pc.getProjectName()));
				return false;
			}			
			
			IPath projectLocation= ResourcesPlugin.getWorkspace().getRoot().getLocation().append(pc.getProjectName());
			if (projectLocation.toFile().exists()) {
				try {
					//correct casing
					String canonicalPath= projectLocation.toFile().getCanonicalPath();
					projectLocation= new Path(canonicalPath);
				} catch (IOException e) {
					HybridUI.log(IStatus.WARNING, "Error getting canonical path", e);
				}

				String existingName= projectLocation.lastSegment();
				if (!existingName.equals(pc.getProjectName())) {
					setErrorMessage(NLS.bind("Project name {0} already exists on the workspace", existingName));
					return false;
				}
			}
			
			IPath projectPath = null;
			if(!copyFiles){
				projectPath = new Path(pc.wwwLocation.getParentFile().toString());
			}
			final IStatus locationStatus= workspace.validateProjectLocation(handle, projectPath);
			if (!locationStatus.isOK()) {
				setErrorMessage(locationStatus.getMessage());
				return false;
			}
			//Warn for an existing eclipse project
			File[] files = pc.wwwLocation.getParentFile().listFiles();
			for (File file : files) {
				if(file.isFile() && IProjectDescription.DESCRIPTION_FILE_NAME.equals(file.getName())){
					setMessage(NLS.bind("A project description for {0} already exists and will be replaced, use existing project import to restore the project", pc.getProjectName()),
							IStatus.WARNING);
					return false;
				}
			}
			
		}
		setMessage(null);
		setErrorMessage(null);
		return true;
	}

	@Override
	public String queryOverwrite(String pathString) {
		return IOverwriteQuery.ALL;
	}
	
}