/*
 * Copyright 2015-2016 Todd Kulesza <[email protected]>.
 *
 * This file is part of Archivo.
 *
 * Archivo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Archivo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Archivo.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.straylightlabs.archivo.view;

import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.StageStyle;
import net.straylightlabs.archivo.Archivo;
import net.straylightlabs.archivo.model.Recording;
import net.straylightlabs.archivo.model.UserPrefs;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * Ask user whether to overwrite or rename files that already exist
 */
public class RecordingsExistDialog {
    private final Dialog<ButtonType> dialog;
    private final List<Recording> recordings;
    private final List<Recording> recordingsToDisplay;
    private final UserPrefs userPrefs;
    private final Archivo mainApp;

    private static final int STATUS_MIN_WIDTH = 30;
    private static final int TITLE_MIN_WIDTH = 200;
    private static final int ACTION_BAR_MIN_WIDTH = 200;

    @SuppressWarnings("unused")
    private final static Logger logger = LoggerFactory.getLogger(RecordingsExistDialog.class);

    public RecordingsExistDialog(Archivo mainApp, List<Recording> recordings, List<Recording> toDisplay, UserPrefs userPrefs) {
        dialog = new Dialog<>();
        this.mainApp = mainApp;
        this.recordings = recordings;
        this.recordingsToDisplay = toDisplay;
        this.userPrefs = userPrefs;
        initDialog();
    }

    private void initDialog() {
        dialog.initOwner(mainApp.getPrimaryStage());
        dialog.initModality(Modality.APPLICATION_MODAL);
        dialog.initStyle(StageStyle.DECORATED);
        dialog.setResizable(true);
        dialog.setTitle("Recordings Already Exist");
        dialog.setHeaderText("Replace or rename recordings?");
        dialog.getDialogPane().setPadding(new Insets(10));

        VBox vbox = new VBox();
        vbox.setSpacing(10);
        dialog.getDialogPane().setContent(vbox);

        Label label = new Label("Archiving the following recordings will replace files on your computer unless you rename them:");
        vbox.getChildren().add(label);

        VBox recordingBox = new VBox();
        recordingBox.setSpacing(20);
        for (Recording recording : recordingsToDisplay) {
            recording.setFileExistsAction(Recording.FileExistsAction.REPLACE);
            recordingBox.getChildren().add(buildRecordingGrid(recording));
        }

        ScrollPane scrollPane = new ScrollPane();
        scrollPane.getStyleClass().add("recording-exists-list");
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
        scrollPane.setFitToWidth(true);
        scrollPane.setFitToHeight(true);
        scrollPane.setContent(recordingBox);
        scrollPane.setPadding(new Insets(10));
        vbox.getChildren().add(scrollPane);

        dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
    }

    private GridPane buildRecordingGrid(Recording recording) {
        GridPane grid = new GridPane();
        grid.setHgap(5);
        grid.setVgap(5);

        Label status = new Label();
        status.setAlignment(Pos.CENTER);
        status.setMinWidth(STATUS_MIN_WIDTH);
        updateStatusIcon(recording, status);
        recording.fileExistsActionProperty().addListener(observable -> updateStatusIcon(recording, status));

        Label title = new Label();
        title.setMinWidth(TITLE_MIN_WIDTH);
        title.textProperty().bind(recording.fullTitleProperty());

        Label destination = new Label();
        destination.getStyleClass().add("destination");
        destination.textProperty().bind(recording.destinationProperty().asString());
        LocalDate dateArchived = recording.getDateArchived();
        if (dateArchived != null) {
            String dateArchivedText = String.format("Archived %s", DateUtils.formatArchivedOnDate(dateArchived));
            Tooltip tooltip = new Tooltip(dateArchivedText);
            title.setTooltip(tooltip);
            destination.setTooltip(tooltip);
        }

        ReplaceOrRenameActionBar actionBar = new ReplaceOrRenameActionBar(recording, userPrefs);
        actionBar.setMinWidth(ACTION_BAR_MIN_WIDTH);
        GridPane.setHalignment(actionBar, HPos.RIGHT);
        GridPane.setHgrow(actionBar, Priority.ALWAYS);
        GridPane.setMargin(actionBar, new Insets(0, 0, 0, 10));

        grid.add(status, 0, 0, 1, 2);
        grid.add(title, 1, 0);
        grid.add(destination, 1, 1);
        grid.add(actionBar, 2, 0, 1, 2);

        return grid;
    }

    private void updateStatusIcon(Recording recording, Label status) {
        switch (recording.getFileExistsAction()) {
            case OK:
                status.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.CHECK));
                break;
            case REPLACE:
                status.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.EXCLAMATION_CIRCLE));
                break;
            case CANCEL:
                status.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.CLOSE));
                break;
        }
    }

    public boolean showAndWait() {
        Optional<ButtonType> response = dialog.showAndWait();
        response.filter(r -> r == ButtonType.CANCEL).ifPresent(r -> clearRecordingList());
        response.filter(r -> r == ButtonType.OK).ifPresent(r -> clearCanceledRecordings());
        return (response.isPresent() && response.get() == ButtonType.OK);
    }

    private void clearRecordingList() {
        recordings.clear();
    }

    private void clearCanceledRecordings() {
        List<Recording> toCancel = recordings.stream().filter(
                r -> r.getFileExistsAction() == Recording.FileExistsAction.CANCEL
        ).collect(Collectors.toList());
        toCancel.forEach(recordings::remove);
    }

    public static List<Recording> getRecordingsWithDuplicateDestination(List<Recording> recordings) {
        Set<Path> seen = new HashSet<>();
        Set<Path> duplicatePaths = new HashSet<>();
        for (Recording recording : recordings) {
            Path destination = recording.getDestination();
            if (seen.contains(destination)) {
                duplicatePaths.add(destination);
            } else {
                seen.add(destination);
            }
        }

        return recordings.stream().filter(
                recording -> duplicatePaths.contains(recording.getDestination())).collect(Collectors.toList()
        );
    }

    private class ReplaceOrRenameActionBar extends SegmentedButton {
        private final ToggleButton replace;
        private final ToggleButton rename;
        private final ToggleButton cancel;
        private final ToggleGroup toggleGroup;
        private final UserPrefs userPrefs;
        private Recording recording;

        public ReplaceOrRenameActionBar(Recording recording, UserPrefs userPrefs) {
            this.recording = recording;
            this.userPrefs = userPrefs;
            replace = new ToggleButton("Replace");
            replace.setSelected(true);
            rename = new ToggleButton("Rename");
            cancel = new ToggleButton("Don't Archive");
            toggleGroup = new ToggleGroup();
            toggleGroup.getToggles().setAll(replace, rename, cancel);
            this.getButtons().addAll(replace, rename, cancel);
            replace.setOnAction(event -> replace());
            rename.setOnAction(event -> renameDestination());
            cancel.setOnAction(event -> cancelArchive());
        }

        private void replace() {
            recording.setFileExistsAction(Recording.FileExistsAction.REPLACE);
            replace.setSelected(true);
        }

        private void renameDestination() {
            SaveFileDialog dialog = new SaveFileDialog(mainApp.getPrimaryStage(), recording, userPrefs);
            if (dialog.showAndWait()) {
                if (destinationExistsOrIsDuplicate(recording)) {
                    recording.setFileExistsAction(Recording.FileExistsAction.REPLACE);
                } else {
                    recording.setFileExistsAction(Recording.FileExistsAction.OK);
                }
            }
            setActiveButton();
        }

        /**
         * Return true if @recording's destination already exists or is a duplicate of another queued recording
         */
        private boolean destinationExistsOrIsDuplicate(Recording recording) {
            if (Files.exists(recording.getDestination())) {
                return true;
            }

            for (Recording earlierRecording : recordings) {
                if (earlierRecording == recording) {
                    break;
                } else {
                    if (recording.getDestination().equals(earlierRecording.getDestination())) {
                        return true;
                    }
                }
            }

            return false;
        }

        private void cancelArchive() {
            recording.setFileExistsAction(Recording.FileExistsAction.CANCEL);
            cancel.setSelected(true);
        }

        private void setActiveButton() {
            switch (recording.getFileExistsAction()) {
                case OK:
                    rename.setSelected(true);
                    break;
                case REPLACE:
                    replace.setSelected(true);
                    break;
                case CANCEL:
                    cancel.setSelected(true);
                    break;
            }
        }
    }
}