package org.jabref.gui.preferences;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.prefs.BackingStoreException;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import org.jabref.gui.AbstractViewModel;
import org.jabref.gui.DialogService;
import org.jabref.gui.preferences.ai.AiTab;
import org.jabref.gui.preferences.autocompletion.AutoCompletionTab;
import org.jabref.gui.preferences.citationkeypattern.CitationKeyPatternTab;
import org.jabref.gui.preferences.customentrytypes.CustomEntryTypesTab;
import org.jabref.gui.preferences.customexporter.CustomExporterTab;
import org.jabref.gui.preferences.customimporter.CustomImporterTab;
import org.jabref.gui.preferences.entry.EntryTab;
import org.jabref.gui.preferences.entryeditor.EntryEditorTab;
import org.jabref.gui.preferences.export.ExportTab;
import org.jabref.gui.preferences.external.ExternalTab;
import org.jabref.gui.preferences.externalfiletypes.ExternalFileTypesTab;
import org.jabref.gui.preferences.general.GeneralTab;
import org.jabref.gui.preferences.groups.GroupsTab;
import org.jabref.gui.preferences.journals.JournalAbbreviationsTab;
import org.jabref.gui.preferences.keybindings.KeyBindingsTab;
import org.jabref.gui.preferences.linkedfiles.LinkedFilesTab;
import org.jabref.gui.preferences.nameformatter.NameFormatterTab;
import org.jabref.gui.preferences.network.NetworkTab;
import org.jabref.gui.preferences.ocr.OcrTab;
import org.jabref.gui.preferences.openoffice.OpenOfficeTab;
import org.jabref.gui.preferences.preview.PreviewTab;
import org.jabref.gui.preferences.protectedterms.ProtectedTermsTab;
import org.jabref.gui.preferences.table.TableTab;
import org.jabref.gui.preferences.websearch.WebSearchTab;
import org.jabref.gui.preferences.xmp.XmpPrivacyTab;
import org.jabref.gui.util.FileDialogConfiguration;
import org.jabref.logic.JabRefException;
import org.jabref.logic.ai.preferences.AiPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.StandardFileType;
import org.jabref.model.entry.BibEntryTypesManager;

import com.airhacks.afterburner.injection.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PreferencesDialogViewModel extends AbstractViewModel {

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

    /// Memory-stick mode is deliberately not part of any tab: it belongs to the portable-preferences
    /// concern (import/export/reset) and is therefore a footer toggle of the dialog itself,
    /// loaded in [#setValues()] and stored in [#storeAllSettings()] alongside the tabs.
    private final SimpleBooleanProperty memoryStickProperty = new SimpleBooleanProperty();

    private final DialogService dialogService;
    private final GuiPreferences preferences;
    private final ObservableList<PreferencesTab> preferenceTabs;

    public PreferencesDialogViewModel(DialogService dialogService, GuiPreferences preferences) {
        this.dialogService = dialogService;
        this.preferences = preferences;

        // Dialog-scoped working copy of the AI preferences: the AI tab edits it and flushes it to
        // the live preferences on save; the web search tab observes its master switch meanwhile.
        AiPreferences workingAiPreferences = AiPreferences.copyOf(preferences.getAiPreferences());

        preferenceTabs = FXCollections.observableArrayList(
                new GeneralTab(),
                new KeyBindingsTab(),
                new GroupsTab(),
                new WebSearchTab(workingAiPreferences),
                new AiTab(workingAiPreferences),
                new EntryTab(),
                new TableTab(),
                new PreviewTab(),
                new EntryEditorTab(),
                new CustomEntryTypesTab(),
                new CitationKeyPatternTab(),
                new LinkedFilesTab(),
                new OcrTab(),
                new OpenOfficeTab(),
                new ExportTab(),
                new AutoCompletionTab(),
                new ProtectedTermsTab(),
                new ExternalTab(),
                new ExternalFileTypesTab(),
                new JournalAbbreviationsTab(),
                new NameFormatterTab(),
                new XmpPrivacyTab(),
                new CustomImporterTab(),
                new CustomExporterTab(),
                new NetworkTab()
        );
    }

    public ObservableList<PreferencesTab> getPreferenceTabs() {
        return new ReadOnlyListWrapper<>(preferenceTabs);
    }

    public boolean importPreferences() {
        FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
                .addExtensionFilter(StandardFileType.XML)
                .withDefaultExtension(StandardFileType.XML)
                .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath()).build();

        Optional<Path> importFileOpt = dialogService.showFileOpenDialog(fileDialogConfiguration);
        if (importFileOpt.isPresent()) {
            try {
                preferences.importPreferences(importFileOpt.get());
                setValues();

                return true;
            } catch (JabRefException ex) {
                LOGGER.error("Error while importing preferences", ex);
                dialogService.showErrorDialogAndWait(Localization.lang("Import preferences"), ex);
            }
        }

        return false;
    }

    public void exportPreferences() {
        FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
                .addExtensionFilter(StandardFileType.XML)
                .withDefaultExtension(StandardFileType.XML)
                .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath())
                .build();

        dialogService.showFileSaveDialog(fileDialogConfiguration)
                     .ifPresent(exportFile -> {
                         try {
                             storeAllSettings();
                             preferences.exportPreferences(exportFile);
                             preferences.getInternalPreferences().setLastPreferencesExportPath(exportFile);
                         } catch (JabRefException ex) {
                             LOGGER.warn(ex.getMessage(), ex);
                             dialogService.showErrorDialogAndWait(Localization.lang("Export preferences"), ex);
                         }
                     });
    }

    public void showPreferences() {
        dialogService.showCustomDialogAndWait(new PreferencesFilterDialog(new PreferencesFilter(preferences)));
    }

    /// returns true if the preferences have been reset
    public boolean resetPreferences() {
        boolean resetPreferencesConfirmed = dialogService.showConfirmationDialogAndWait(
                Localization.lang("Reset preferences"),
                Localization.lang("Are you sure you want to reset all settings to default values?"),
                Localization.lang("Reset preferences"),
                Localization.lang("Cancel"));
        if (resetPreferencesConfirmed) {
            try {
                preferences.clear();
                setValues();

                return true;
            } catch (BackingStoreException ex) {
                LOGGER.error("Error while resetting preferences", ex);
                dialogService.showErrorDialogAndWait(Localization.lang("Reset preferences"), ex);
            }
        }

        return false;
    }

    /// Checks if all tabs are valid
    private boolean validSettings() {
        for (PreferencesTab tab : preferenceTabs) {
            if (!tab.validateSettings()) {
                return false;
            }
        }
        return true;
    }

    /// @return true if the settings were valid and have been stored
    public boolean storeAllSettings() {
        if (!validSettings()) {
            return false;
        }

        // Store settings
        preferences.getInternalPreferences().setMemoryStickMode(memoryStickProperty.get());
        List<String> restartWarnings = new ArrayList<>();
        for (PreferencesTab tab : preferenceTabs) {
            tab.storeSettings();
            restartWarnings.addAll(tab.getRestartWarnings());
        }
        preferences.flush();

        if (!restartWarnings.isEmpty()) {
            dialogService.showWarningDialogAndWait(Localization.lang("Restart required"),
                    String.join(",\n", restartWarnings)
                            + "\n\n"
                            + Localization.lang("You must restart JabRef for this to come into effect."));
        }

        Injector.setModelOrService(BibEntryTypesManager.class, preferences.getCustomEntryTypesRepository());
        dialogService.notify(Localization.lang("Preferences recorded."));
        return true;
    }

    /// Inserts the preference values into the Properties of the ViewModel
    public void setValues() {
        memoryStickProperty.setValue(preferences.getInternalPreferences().isMemoryStickMode());

        for (PreferencesTab preferencesTab : preferenceTabs) {
            preferencesTab.setValues();
        }
    }

    public BooleanProperty getMemoryStickProperty() {
        return memoryStickProperty;
    }
}
