package org.jabref.gui.exporter;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import javafx.stage.FileChooser;

import org.jabref.gui.DialogService;
import org.jabref.gui.Notifications;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.ActionHelper;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.util.FileDialogConfiguration;
import org.jabref.gui.util.FileFilterConverter;
import org.jabref.logic.exporter.Exporter;
import org.jabref.logic.exporter.ExporterFactory;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

import com.dlsc.gemsfx.infocenter.NotificationAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/// Performs an export action
public class ExportCommand extends SimpleCommand {

    public enum ExportMethod { EXPORT_ALL, EXPORT_SELECTED }

    private static final Logger LOGGER = LoggerFactory.getLogger(ExportCommand.class);

    private final ExportMethod exportMethod;
    private final StateManager stateManager;
    private final GuiPreferences preferences;
    private final DialogService dialogService;
    private final JournalAbbreviationRepository abbreviationRepository;
    private final TaskExecutor taskExecutor;

    public ExportCommand(ExportMethod exportMethod,
                         StateManager stateManager,
                         DialogService dialogService,
                         GuiPreferences preferences,
                         JournalAbbreviationRepository abbreviationRepository,
                         TaskExecutor taskExecutor) {
        this.exportMethod = exportMethod;
        this.stateManager = stateManager;
        this.preferences = preferences;
        this.dialogService = dialogService;
        this.abbreviationRepository = abbreviationRepository;
        this.taskExecutor = taskExecutor;

        this.executable.bind(exportMethod == ExportMethod.EXPORT_SELECTED
                             ? ActionHelper.needsEntriesSelected(stateManager)
                             : ActionHelper.needsDatabase(stateManager));
    }

    @Override
    public void execute() {
        // Get list of exporters and sort before adding to file dialog
        ExporterFactory exporterFactory = ExporterFactory.create(preferences);
        List<Exporter> exporters = exporterFactory.getExporters().stream()
                                                  .sorted(Comparator.comparing(Exporter::getName))
                                                  .collect(Collectors.toList());

        FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
                .addExtensionFilter(FileFilterConverter.exporterToExtensionFilter(exporters))
                .withDefaultExtension(preferences.getExportPreferences().getLastExportExtension())
                .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory())
                .build();
        dialogService.showFileSaveDialog(fileDialogConfiguration)
                     .ifPresent(path -> export(path, fileDialogConfiguration.getSelectedExtensionFilter(), exporters));
    }

    private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilter, List<Exporter> exporters) {
        String selectedExtension = selectedExtensionFilter.getExtensions().getFirst().replace("*", "");
        if (!file.endsWith(selectedExtension)) {
            FileUtil.addExtension(file, selectedExtension);
        }

        final Exporter format = FileFilterConverter.getExporter(selectedExtensionFilter, exporters)
                                                   .orElseThrow(() -> new IllegalStateException("User didn't selected a file type for the extension"));
        List<BibEntry> entries;
        if (exportMethod == ExportMethod.EXPORT_SELECTED) {
            // Selected entries
            entries = stateManager.getSelectedEntries();
        } else {
            // All entries
            entries = stateManager.getActiveDatabase()
                                  .map(BibDatabaseContext::getEntries)
                                  .orElse(List.of());
        }

        List<Path> fileDirForDatabase = stateManager.getActiveDatabase()
                                                    .map(db -> db.getFileDirectories(preferences.getFilePreferences()))
                                                    .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory()));

        // Make sure we remember which filter was used, to set
        // the default for next time:
        preferences.getExportPreferences().setLastExportExtension(format.getName());
        preferences.getExportPreferences().setExportWorkingDirectory(file.getParent());

        final List<BibEntry> finEntries = entries;

        BackgroundTask
                .wrap(() -> {
                    format.export(stateManager.getActiveDatabase().get(),
                            file,
                            finEntries,
                            fileDirForDatabase,
                            abbreviationRepository);
                    return null; // cannot use BackgroundTask.wrap(Runnable) because Runnable.run() can't throw Exceptions
                })
                .onSuccess(_ -> dialogService.notify(new ExportSuccessNotification(file)))
                .onFailure(this::handleError)
                .executeWith(taskExecutor);
    }

    private void handleError(Exception ex) {
        LOGGER.warn("Problem exporting", ex);
        dialogService.notify(Localization.lang("Could not save file."));
        // Need to warn the user that saving failed!
        dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex);
    }

    private class ExportSuccessNotification extends Notifications.FileNotification {
        public ExportSuccessNotification(Path path) {
            super(Localization.lang("Export operation finished successfully."), path.toString());

            NotificationAction<Path> action = new NotificationAction<>(Localization.lang("Reveal"), _ -> {
                try {
                    NativeDesktop.openFolderAndSelectFile(path, preferences.getExternalApplicationsPreferences(), dialogService);
                } catch (IOException e) {
                    LOGGER.error("Could not open export folder.", e);
                }
                return OnClickBehaviour.REMOVE;
            });

            getActions().add(action);
        }
    }
}
