/*
 * Copyright (c) 2018 Karl Tauber <karl at jformdesigner dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.markdownwriterfx.projects;

import java.io.File;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import org.markdownwriterfx.FileEditorManager;
import org.markdownwriterfx.Messages;
import org.markdownwriterfx.util.Utils;

/**
 * A combo box that contains recently opened projects and allows switching active project.
 *
 * @author Karl Tauber
 */
class ProjectsComboBox
	extends ComboBox<File>
{
	private static final File OPEN_FOLDER = new File("");
	private static final Comparator<File> PROJECT_COMPARATOR = (f1, f2) -> f1.getPath().compareToIgnoreCase(f2.getPath());

	private boolean inSelectionChange;
	private boolean doNotHidePopupOnce;

	ProjectsComboBox(FileEditorManager fileEditorManager) {
		getStyleClass().add("projects-combo-box");
		setFocusTraversable(false);
		setVisibleRowCount(30);
		setButtonCell(new ProjectButtonCell());
		setCellFactory(listView -> new ProjectListCell());

		// let this control grow horizontally
		setMaxWidth(Double.MAX_VALUE);

		// update tooltip
		valueProperty().addListener((observer, oldProject, newProject) -> {
			setTooltip((newProject != null) ? new Tooltip(newProject.getAbsolutePath()) : null);
		});

		// add items
		ObservableList<File> projects = ProjectManager.getProjects();
		projects.sort(PROJECT_COMPARATOR);
		getItems().add(OPEN_FOLDER);
		getItems().addAll(projects);

		// set active project
		setValue(ProjectManager.getActiveProject());

		// listen to selection changes
		getSelectionModel().selectedItemProperty().addListener((observer, oldProject, newProject) -> {
			if (inSelectionChange)
				return;

			if (newProject == ProjectManager.getActiveProject())
				return;

			Platform.runLater(() -> {
				inSelectionChange = true;
				try {
					if (newProject == OPEN_FOLDER) {
						// closing last active project automatically selects this item
						// --> activate first project
						if (oldProject != null && !getItems().contains(oldProject)) {
							ProjectManager.setActiveProject((getItems().size() > 1) ? getItems().get(1) : null);
							return;
						}

						getSelectionModel().select(oldProject);

						if (fileEditorManager.canOpenAnotherProject())
							ProjectManager.openProject(getScene().getWindow());
					} else {
						if (fileEditorManager.canOpenAnotherProject())
							ProjectManager.setActiveProject(newProject);
						else
							getSelectionModel().select(oldProject);
					}
				} finally {
					inSelectionChange = false;
				}
			});
		});

		// listen to projects changes and update combo box
		projects.addListener((ListChangeListener<File>) change -> {
			while (change.next()) {
				if (change.wasAdded()) {
					for (File addedProject : change.getAddedSubList())
						Utils.addSorted(getItems(), addedProject, PROJECT_COMPARATOR);
				}
				if (change.wasRemoved())
					getItems().removeAll(change.getRemoved());
			}
		});

		// listen to active project change and update combo box value
		ProjectManager.activeProjectProperty().addListener((observer, oldProject, newProject) -> {
			setValue(newProject);
		});
	}

	@Override
	public void hide() {
		if (doNotHidePopupOnce) {
			doNotHidePopupOnce = false;
			return;
		}

		super.hide();
	}

	//---- class ProjectButtonCell --------------------------------------------

	private class ProjectButtonCell
		extends ListCell<File>
	{
		@Override
		protected void updateItem(File item, boolean empty) {
			super.updateItem(item, empty);

			setText((!empty && item != null) ? buildUniqueName(item) : null);
		}

		private String buildUniqueName(File item) {
			String name = item.getName();

			// find other projects with same name
			List<File> projects = getItems().stream()
				.filter(f -> !f.equals(item) && f.getName().equals(name))
				.collect(Collectors.toList());
			if (projects.isEmpty())
				return name;

			// there are more than one project with the same name
			// --> go up folder hierarchy and find a unique name
			File itemParent = item.getParentFile();
			while (itemParent != null) {
				// parent of projects
				projects = projects.stream()
					.map(f -> f.getParentFile())
					.filter(f -> f != null)
					.collect(Collectors.toList());

				String n = itemParent.getName();
				if (n.isEmpty())
					break;

				// find unique parent name
				long equalCount = projects.stream()
					.filter(f -> f.getName().equals(n))
					.count();
				if (equalCount == 0)
					return name + " [" + itemParent.getName() + "]";

				itemParent = itemParent.getParentFile();
			}

			// fallback: append path
			return name + " - " + item.getParent();
		}
	}

	//---- class ProjectListCell ----------------------------------------------

	private class ProjectListCell
		extends ListCell<File>
	{
		private final StackPane closeButton = new StackPane();

		ProjectListCell() {
			closeButton.getStyleClass().add("close-project-button");
			closeButton.setPrefSize(16, 16);
			closeButton.setOnMousePressed( event -> {
				event.consume();
				doNotHidePopupOnce = true;
				ProjectManager.getProjects().remove(getItem());
			});
		}

		@Override
		protected void updateItem(File item, boolean empty) {
			super.updateItem(item, empty);

			// add/remove separator below "open folder" item
			if (!empty && item == OPEN_FOLDER && ProjectsComboBox.this.getItems().size() > 1)
				getStyleClass().add("open-project");
			else
				getStyleClass().remove("open-project");

			String text = null;
			Node graphic = null;
			if (!empty && item != null) {
				text = (item == OPEN_FOLDER)
					? Messages.get("ProjectsComboBox.openProject")
					: item.getAbsolutePath();

				graphic = closeButton;
				closeButton.setVisible(item != OPEN_FOLDER);
			}
			setText(text);
			setGraphic(graphic);
		}
	}
}