/*******************************************************************************
 *  Copyright (c) 2011 GitHub 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:
 *    Kevin Sawicki (GitHub Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.mylyn.internal.github.ui;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.User;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.OrganizationService;
import org.eclipse.egit.github.core.service.RepositoryService;
import org.eclipse.egit.ui.internal.FilteredCheckboxTree;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mylyn.internal.github.core.GitHub;
import org.eclipse.mylyn.internal.github.core.GitHubException;
import org.eclipse.mylyn.internal.github.core.gist.GistConnector;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.model.WorkbenchAdapter;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * Repository selection wizard page.
 */
public class RepositorySelectionWizardPage extends WizardPage {

	private static class RepositoryStyledLabelProvider implements
			IStyledLabelProvider, ILabelProvider {
		private final WorkbenchLabelProvider wrapped = new WorkbenchLabelProvider();

		public String getText(Object element) {
			return wrapped.getText(element);
		}

		public void addListener(ILabelProviderListener listener) {
		}

		public void dispose() {
			wrapped.dispose();
		}

		public boolean isLabelProperty(Object element, String property) {
			return false;
		}

		public void removeListener(ILabelProviderListener listener) {
		}

		public StyledString getStyledText(Object element) {
			// TODO Replace with use of IWorkbenchAdapter3 when 3.6 is no longer
			// supported
			if (element instanceof RepositoryAdapter)
				return ((RepositoryAdapter) element).getStyledText(element);
			if (element instanceof OrganizationAdapter)
				return ((OrganizationAdapter) element).getStyledText(element);

			return new StyledString(wrapped.getText(element));
		}

		public Image getImage(Object element) {
			return wrapped.getImage(element);
		}

	}

	private static class RepositoryLabelProvider extends
			DelegatingStyledCellLabelProvider implements ILabelProvider {

		private final RepositoryStyledLabelProvider wrapped;

		public RepositoryLabelProvider() {
			super(new RepositoryStyledLabelProvider());
			wrapped = (RepositoryStyledLabelProvider) getStyledStringProvider();
		}

		public String getText(Object element) {
			return wrapped.getText(element);
		}

		public void dispose() {
			super.dispose();
			wrapped.dispose();
		}
	}

	private static class RepositoryAdapter extends WorkbenchAdapter {

		private final Repository repo;

		RepositoryAdapter(Repository repo) {
			this.repo = repo;
		}

		public String getLabel(Object object) {
			String label = this.repo.generateId();
			return label != null ? label : "";
		}

		public ImageDescriptor getImageDescriptor(Object object) {
			return TasksUiImages.REPOSITORY;
		}

	}

	private static class OrganizationAdapter extends WorkbenchAdapter {

		private final User org;

		private final RepositoryAdapter[] repos;

		OrganizationAdapter(User org, List<Repository> repos) {
			this.org = org;
			this.repos = new RepositoryAdapter[repos.size()];
			final int length = this.repos.length;
			for (int i = 0; i < length; i++)
				this.repos[i] = new RepositoryAdapter(repos.get(i));
		}

		public ImageDescriptor getImageDescriptor(Object object) {
			return GitHubImages.DESC_GITHUB_ORG;
		}

		public Object[] getChildren(Object object) {
			return repos;
		}

		public StyledString getStyledText(Object object) {
			StyledString styled = new StyledString(getLabel(object));
			styled.append(MessageFormat.format(" ({0})", repos.length),
					StyledString.COUNTER_STYLER);
			return styled;
		}

		public String getLabel(Object object) {
			return org.getLogin();
		}

	}

	private Button addGistRepoButton;
	private Label selectedLabel;
	private FilteredCheckboxTree tree;
	private int repoCount = 0;
	private String user;
	private String password;

	/**
	 * Create repository selection wizard page
	 */
	public RepositorySelectionWizardPage() {
		super(
				"repositoriesPage", Messages.RepositorySelectionWizardPage_Title, null); //$NON-NLS-1$
		setDescription(Messages.RepositorySelectionWizardPage_Description);
	}

	/** @param user */
	public void setUser(String user) {
		this.user = user;
	}

	/** @param password */
	public void setPassword(String password) {
		this.password = password;
	}

	/** @return true to create giste repository, false otherwise */
	public boolean createGistRepository() {
		return this.addGistRepoButton.getSelection()
				&& this.addGistRepoButton.isVisible();
	}

	/** @return array of selected repositories */
	public Repository[] getRepositories() {
		Object[] checked = tree.getCheckboxTreeViewer()
				.getCheckedLeafElements();
		Repository[] repos = new Repository[checked.length];
		for (int i = 0; i < repos.length; i++)
			repos[i] = ((RepositoryAdapter) checked[i]).repo;
		return repos;
	}

	/** @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) */
	public void createControl(Composite parent) {
		Composite displayArea = new Composite(parent, SWT.NONE);
		GridLayoutFactory.swtDefaults().numColumns(2).applyTo(displayArea);

		Label repoLabel = new Label(displayArea, SWT.NONE);
		repoLabel.setText(Messages.RepositorySelectionWizardPage_LabelRepos);
		GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
				.applyTo(repoLabel);

		tree = new FilteredCheckboxTree(displayArea, null, SWT.V_SCROLL
				| SWT.H_SCROLL | SWT.BORDER, new PatternFilter());
		CheckboxTreeViewer viewer = tree.getCheckboxTreeViewer();
		viewer.setContentProvider(new WorkbenchContentProvider());
		viewer.setLabelProvider(new RepositoryLabelProvider());
		viewer.setSorter(new ViewerSorter() {

			public int compare(Viewer viewer, Object e1, Object e2) {
				if (e1 instanceof OrganizationAdapter)
					if (e2 instanceof OrganizationAdapter)
						return ((OrganizationAdapter) e1)
								.getLabel(e1)
								.compareToIgnoreCase(
										((OrganizationAdapter) e2).getLabel(e2));
					else if (e2 instanceof RepositoryAdapter)
						return 1;
				if (e1 instanceof RepositoryAdapter)
					if (e2 instanceof RepositoryAdapter)
						return ((RepositoryAdapter) e1).getLabel(e1)
								.compareToIgnoreCase(
										((RepositoryAdapter) e2).getLabel(e2));
					else if (e2 instanceof OrganizationAdapter)
						return -1;
				return super.compare(viewer, e1, e2);
			}

		});
		viewer.addCheckStateListener(new ICheckStateListener() {

			public void checkStateChanged(CheckStateChangedEvent event) {
				updateSelectionLabel();
			}
		});
		GridDataFactory.fillDefaults().grab(true, true).applyTo(tree);

		ToolBar toolbar = new ToolBar(displayArea, SWT.FLAT | SWT.VERTICAL);
		GridDataFactory.fillDefaults().grab(false, true).applyTo(toolbar);
		ToolItem checkItem = new ToolItem(toolbar, SWT.PUSH);
		checkItem.setImage(GitHubImages.get(GitHubImages.GITHUB_CHECKALL_OBJ));
		checkItem
				.setToolTipText(Messages.RepositorySelectionWizardPage_TooltipCheckAll);
		checkItem.addSelectionListener(new SelectionAdapter() {

			public void widgetSelected(SelectionEvent e) {
				tree.getCheckboxTreeViewer().setAllChecked(true);
				for (Object leaf : tree.getCheckboxTreeViewer()
						.getCheckedLeafElements())
					tree.getCheckboxTreeViewer().setChecked(leaf, true);
				updateSelectionLabel();
			}
		});
		ToolItem uncheckItem = new ToolItem(toolbar, SWT.PUSH);
		uncheckItem.setImage(GitHubImages
				.get(GitHubImages.GITHUB_UNCHECKALL_OBJ));
		uncheckItem
				.setToolTipText(Messages.RepositorySelectionWizardPage_TooltipUncheckAll);
		uncheckItem.addSelectionListener(new SelectionAdapter() {

			public void widgetSelected(SelectionEvent e) {
				tree.getCheckboxTreeViewer().setCheckedElements(new Object[0]);
				updateSelectionLabel();
			}
		});

		selectedLabel = new Label(displayArea, SWT.NONE);
		GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
				.applyTo(selectedLabel);

		addGistRepoButton = new Button(displayArea, SWT.CHECK);
		addGistRepoButton
				.setText(Messages.RepositorySelectionWizardPage_LabelAddGist);
		addGistRepoButton.setSelection(true);
		GridDataFactory.swtDefaults().span(2, 1).applyTo(addGistRepoButton);
		addGistRepoButton.addSelectionListener(new SelectionAdapter() {

			public void widgetSelected(SelectionEvent e) {
				validatePage();
			}

		});

		setControl(displayArea);
		setPageComplete(false);
	}

	private void updateSelectionLabel() {
		selectedLabel.setText(MessageFormat.format(
				Messages.RepositorySelectionWizardPage_LabelSelectionCount,
				tree.getCheckboxTreeViewer().getCheckedLeafCount(), repoCount));
		selectedLabel.getParent().layout(true, true);
		validatePage();
	}

	private void validatePage() {
		setPageComplete(getRepositories().length > 0 || createGistRepository());
		if (isPageComplete())
			setErrorMessage(null);
	}

	private void updateInput(final List<Object> repos) {
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {

			public void run() {
				if (getControl().isDisposed())
					return;
				tree.getCheckboxTreeViewer().setCheckedElements(new Object[0]);
				tree.getViewer().setInput(new WorkbenchAdapter() {

					public Object[] getChildren(Object object) {
						return repos.toArray();
					}

				});
				updateSelectionLabel();
			}
		});
	}

	private void removeExisting(List<Repository> repos, List<String> existing) {
		Iterator<Repository> iter = repos.iterator();
		while (iter.hasNext()) {
			String id = iter.next().generateId();
			if (id == null || existing.contains(id))
				iter.remove();
		}
	}

	/** @see org.eclipse.jface.dialogs.DialogPage#setVisible(boolean) */
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (!visible)
			return;
		addGistRepoButton.setVisible(TasksUi.getRepositoryManager()
				.getRepositories(GistConnector.KIND).isEmpty());
		try {
			getContainer().run(true, true, new IRunnableWithProgress() {

				public void run(IProgressMonitor monitor)
						throws InvocationTargetException, InterruptedException {
					GitHubClient client = GitHub
							.configureClient(new GitHubClient());
					client.setCredentials(user, password);
					RepositoryService service = new RepositoryService(client);
					OrganizationService orgs = new OrganizationService(client);
					repoCount = 0;
					List<Object> repos = new ArrayList<Object>();
					List<String> existing = new ArrayList<String>();
					for (TaskRepository repo : TasksUi.getRepositoryManager()
							.getRepositories(GitHub.CONNECTOR_KIND)) {
						String id = GitHub.getRepository(
								repo.getRepositoryUrl()).generateId();
						if (id != null)
							existing.add(id);
					}
					try {
						monitor.beginTask("", 2); //$NON-NLS-1$
						monitor.setTaskName(Messages.RepositorySelectionWizardPage_TaskFetchingRepositories);
						List<Repository> userRepos = service.getRepositories();
						removeExisting(userRepos, existing);
						repoCount += userRepos.size();
						for (Repository repo : userRepos)
							repos.add(new RepositoryAdapter(repo));
						monitor.worked(1);
						monitor.setTaskName(Messages.RepositorySelectionWizardPage_TaskFetchingOrganizationRepositories);
						for (User org : orgs.getOrganizations()) {
							List<Repository> orgRepos = service
									.getOrgRepositories(org.getLogin());
							removeExisting(orgRepos, existing);
							repoCount += orgRepos.size();
							repos.add(new OrganizationAdapter(org, orgRepos));
						}
						updateInput(repos);
					} catch (IOException e) {
						throw new InvocationTargetException(GitHubException
								.wrap(e));
					}
				}
			});
			setErrorMessage(null);
		} catch (InvocationTargetException e) {
			updateInput(Collections.emptyList());
			Throwable cause = e.getCause();
			if (cause == null)
				cause = e;
			setErrorMessage(MessageFormat.format(
					Messages.RepositorySelectionWizardPage_ErrorLoading,
					cause.getLocalizedMessage()));
		} catch (InterruptedException ignored) {
			// Ignored
		}
	}
}
