package net.sf.jabref.gui.search;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import javax.swing.SwingWorker;

import net.sf.jabref.JabRefGUI;
import net.sf.jabref.gui.BasePanel;
import net.sf.jabref.gui.BasePanelMode;
import net.sf.jabref.gui.maintable.MainTableDataModel;
import net.sf.jabref.logic.search.SearchQuery;
import net.sf.jabref.model.database.BibDatabase;
import net.sf.jabref.model.entry.BibEntry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Not reusable. Always create a new instance for each search!
 */
class SearchWorker extends SwingWorker<List<BibEntry>, Void> {

    private static final Log LOGGER = LogFactory.getLog(SearchWorker.class);

    private final BasePanel basePanel;
    private final BibDatabase database;

    private final SearchQuery searchQuery;
    private final SearchDisplayMode searchDisplayMode;

    public SearchWorker(BasePanel basePanel, SearchQuery searchQuery, SearchDisplayMode searchDisplayMode) {
        this.basePanel = Objects.requireNonNull(basePanel);
        this.database = Objects.requireNonNull(basePanel.getDatabase());
        this.searchQuery = Objects.requireNonNull(searchQuery);
        this.searchDisplayMode = Objects.requireNonNull(searchDisplayMode);
        LOGGER.debug("Search (" + this.searchDisplayMode.getDisplayName() + "): " + this.searchQuery);
    }

    @Override
    protected List<BibEntry> doInBackground() throws Exception {
        return database.getEntries().parallelStream()
                .filter(searchQuery::isMatch)
                .collect(Collectors.toList());
    }

    @Override
    protected void done() {
        if (isCancelled()) {
            return;
        }

        try {
            updateUIWithSearchResult(get());
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.error("something went wrong during the search", e);
        }
    }

    private void updateUIWithSearchResult(List<BibEntry> matchedEntries) {
        GlobalSearchBar globalSearchBar = JabRefGUI.getMainFrame().getGlobalSearchBar();

        // check if still the current query
        if (!globalSearchBar.isStillValidQuery(searchQuery)) {
            // do not update - another search was already issued
            return;
        }

        // clear
        for (BibEntry entry : basePanel.getDatabase().getEntries()) {
            entry.setSearchHit(false);
        }
        // and mark
        for (BibEntry entry : matchedEntries) {
            entry.setSearchHit(true);
        }

        basePanel.getMainTable().getTableModel().updateSearchState(MainTableDataModel.DisplayOption.DISABLED);
        // Show the result in the chosen way:
        switch (searchDisplayMode) {
            case FLOAT:
                basePanel.getMainTable().getTableModel().updateSearchState(MainTableDataModel.DisplayOption.FLOAT);
                break;
            case FILTER:
                basePanel.getMainTable().getTableModel().updateSearchState(MainTableDataModel.DisplayOption.FILTER);
                break;
            default:
                LOGGER.error("Following searchDisplayMode was not defined: " + searchDisplayMode);
                break;
        }

        // only selects the first match if the selected entries are no hits or no entry is selected
        // and no editor is open (to avoid jumping around when editing an entry)
        if (basePanel.getMode() != BasePanelMode.SHOWING_EDITOR && basePanel.getMode() != BasePanelMode.WILL_SHOW_EDITOR) {
            List<BibEntry> selectedEntries = basePanel.getSelectedEntries();
            boolean isHitSelected = selectedEntries.stream().anyMatch(BibEntry::isSearchHit);
            if (!isHitSelected && !matchedEntries.isEmpty()) {
                for (int i = 0; i < basePanel.getMainTable().getRowCount(); i++) {
                    BibEntry entry = basePanel.getMainTable().getEntryAt(i);
                    if (entry.isSearchHit()) {
                        basePanel.getMainTable().setSelected(i);
                        break;
                    }
                }
            }
        }

        globalSearchBar.updateResults(matchedEntries.size(), searchQuery.getDescription(), searchQuery.isGrammarBasedSearch());
        globalSearchBar.getSearchQueryHighlightObservable().fireSearchlistenerEvent(searchQuery);
    }

}
