/**************************************************************************
 OmegaT - Computer Assisted Translation (CAT) tool 
          with fuzzy matching, translation memory, keyword search, 
          glossaries, and translation leveraging into updated projects.

 Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk
               2006 Henry Pijffers
               2009 Didier Briel
               2010 Martin Fleurke, Antonio Vilei, Alex Buloichik, Didier Briel
               2012 Thomas Cordonnier
               2013 Aaron Madlon-Kay, Alex Buloichik
               2014 Alex Buloichik, Piotr Kulik, Thomas Cordonnier
               2015 Thomas Cordonnier
               Home page: http://www.omegat.org/
               Support center: http://groups.yahoo.com/group/OmegaT/

 This file is part of OmegaT.

 OmegaT 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.

 OmegaT 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

package org.omegat.core.search;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.omegat.core.Core;
import org.omegat.core.data.EntryKey;
import org.omegat.core.data.ExternalTMX;
import org.omegat.core.data.IProject;
import org.omegat.core.data.PrepareTMXEntry;
import org.omegat.core.data.ProjectTMX;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.TMXEntry;
import org.omegat.gui.glossary.GlossaryEntry;
import org.omegat.gui.search.ProjectSearchWindow;
import org.omegat.util.Language;
import org.omegat.util.OStrings;
import org.omegat.util.TMXProp;

/**
 * Specific searcher for searches in a project
 * 
 * @author Keith Godfrey
 * @author Maxym Mykhalchuk
 * @author Henry Pijffers
 * @author Didier Briel
 * @author Martin Fleurke
 * @author Antonio Vilei
 * @author Alex Buloichik (alex73mail@gmail.com)
 * @author Aaron Madlon-Kay
 * @author Piotr Kulik
 * @author Thomas Cordonnier
 */
public class ProjectSearcher extends Searcher {

    // ----------- Bit fields for search location ------------
    
    public static final int SEARCH_SCOPE_ONGOING 	= 	0x01;
    public static final int SEARCH_SCOPE_SOURCE_FILES 	= 	0x10;

    /**
     * Create new searcher instance.
     * 
     * @param project
     *            Current project
     */
    public ProjectSearcher(final ProjectSearchWindow window, final IProject project, 
        boolean removeDup, int numberOfResults, 
        TranslationStateFilter translationStateFilter, int searchLocation,
        TextExpression searchSource, TextExpression searchTarget, TextExpression searchNotes, boolean andSearch,
        TextExpression author, TextExpression translator,long dateAfter, long dateBefore) {

        super (window, removeDup, numberOfResults);
		
        this.m_project = project; 
        this.m_translationStateFilter = translationStateFilter;		
        this.m_searchLocation = searchLocation;
        this.m_searchSource = searchSource; this.m_searchTarget = searchTarget; this.m_searchNotes = searchNotes;
        this.m_andSearch = andSearch;
        this.m_author = author; m_translator = translator;
        this.m_dateBefore = dateBefore; this.m_dateAfter = dateAfter;
        
        // Avoid unuseful results :
        if ((searchLocation & SEARCH_SCOPE_ONGOING) > 0)
            m_sourceTranslationStateFilter = TranslationStateFilter.TRANSLATED_ONLY;
        else
            m_sourceTranslationStateFilter = m_translationStateFilter;
    }

    // /////////////////////////////////////////////////////////
    // thread main loop
    @Override
    protected void doSearch() {
        // reset the number of search hits
        m_numFinds = 0;

        // search through all project entries
        if ((m_searchLocation & (SEARCH_SCOPE_SOURCE_FILES | SEARCH_SCOPE_ONGOING)) != 0) {	
			int count = m_project.getAllEntries().size(), progress = 0;
            for (SourceTextEntry entry: m_project.getAllEntries()) {
				m_window.displayProgress("ONGOING", progress++, count, m_numFinds);
                // stop searching if the max. nr of hits has been reached
                if (m_numFinds >= m_maxResults) break;
                // get the source and translation of the next entry
                if ((m_searchLocation & SEARCH_SCOPE_SOURCE_FILES) > 0) testSource (entry);
                if ((m_searchLocation & SEARCH_SCOPE_ONGOING) > 0) testOngoing (entry, true);
                checkInterrupted();
            }
        }
    }
    
    protected void testSource(SourceTextEntry ste) {
        if (! m_sourceTranslationStateFilter.isValidEntry(ste)) return;
		// Source entry does not store author/date, so skip this search if required
		if ((m_author != null) || (m_dateAfter < Long.MAX_VALUE) || (m_dateBefore > Long.MIN_VALUE)) return;

        List<SearchMatch> srcMatches = null, targetMatches = null, noteMatches = null;
        if (m_searchSource != null) srcMatches = m_searchSource.searchString(ste.getSrcText());
        if ((m_searchTarget != null) && (ste.getSourceTranslation() != null)) targetMatches = m_searchTarget.searchString(ste.getSourceTranslation());
        if ((m_searchNotes != null) && (ste.getComment() != null)) noteMatches = m_searchNotes.searchString(ste.getComment());
                                
        if (checkMatches(srcMatches,targetMatches,noteMatches)) addEntry (new SourceSearchResultEntry(ste, srcMatches, targetMatches, noteMatches));
    }	
    
    public OngoingSearchResultEntry testOngoing(SourceTextEntry ste, boolean add) {
        TMXEntry tmxEntry = m_project.getTranslationInfo(ste);
        if (! m_translationStateFilter.isValidEntry (tmxEntry)) return null;
        if (! checkFilters (tmxEntry)) return null;

        List<SearchMatch> srcMatches = null, targetMatches = null, noteMatches = null;
        if (m_searchSource != null) srcMatches = m_searchSource.searchString(ste.getSrcText());
        if ((m_searchTarget != null) && (tmxEntry.translation != null)) targetMatches = m_searchTarget.searchString(tmxEntry.translation);
        if ((m_searchNotes != null) && (tmxEntry.note != null)) noteMatches = m_searchNotes.searchString(tmxEntry.note);                                
        if (checkMatches(srcMatches, targetMatches, noteMatches)) {
			OngoingSearchResultEntry entry = buildOngoingEntry(ste,tmxEntry, srcMatches, targetMatches, noteMatches);
			if (add && (entry != null)) addEntry (entry); return entry;
		}
		return null;
    }
	
	protected OngoingSearchResultEntry buildOngoingEntry (SourceTextEntry ste, TMXEntry tmxEntry, List<SearchMatch> srcMatches, List<SearchMatch> targetMatches, List<SearchMatch> noteMatches) {
		return new OngoingSearchResultEntry(ste, tmxEntry, srcMatches, targetMatches, noteMatches);
	}
	
	/** 
	 * Check that matches contains something, then apply and/or operator.
	 * Contrarily to check filters, can be only called after the matches have been searched
	 **/
	protected boolean checkMatches(List<SearchMatch> srcMatches, List<SearchMatch> targetMatches, List<SearchMatch> noteMatches) {
        if (m_andSearch) {
            if ((m_searchSource != null) && (srcMatches == null)) return false;
            if ((m_searchTarget != null) && (targetMatches == null)) return false;
            if ((m_searchNotes != null) && (noteMatches == null)) return false;
        } else {
            if ((srcMatches == null) && (targetMatches == null) && (noteMatches == null)) return false;
        }
        return true;
    }
	
    /** Check specified entry against author/date. Called before text filters, because it is faster and does not imply SearchMatch creations **/
    public boolean checkFilters(PrepareTMXEntry tmxEntry) {
        if ((m_author != null) && (! searchAuthor (tmxEntry))) return false;
        if ((m_translator != null) && (! searchTranslator (tmxEntry))) return false;
        if ((m_dateBefore > Long.MIN_VALUE) && (tmxEntry.changeDate != 0) && (tmxEntry.changeDate >= m_dateBefore)) return false;
        if ((m_dateAfter < Long.MAX_VALUE) && (tmxEntry.changeDate != 0) && (tmxEntry.changeDate <= m_dateAfter)) return false;
        return true;
    }	

    /** Check specified entry against author/date. Called before text filters, because it is faster and does not imply SearchMatch creations **/
    public boolean checkFilters(TMXEntry tmxEntry) {
        if ((m_author != null) && (! searchAuthor (tmxEntry))) return false;
        if ((m_dateBefore > Long.MIN_VALUE) && (tmxEntry.changeDate != 0) && (tmxEntry.changeDate >= m_dateBefore)) return false;
        if ((m_dateAfter < Long.MAX_VALUE) && (tmxEntry.changeDate != 0) && (tmxEntry.changeDate <= m_dateAfter)) return false;
        return true;
    }
	
    /**
     * Looks for an occurrence of the author search string in the supplied text string.
     * 
     * @param author
     *            The text string to search in
     * 
     * @return True if the text string contains the search string
     */
    protected boolean searchAuthor(PrepareTMXEntry te) {
        if (m_author == null) return true;
        if (te == null) return false;
        
        String author = te.changer; if (author == null) author = te.creator; // changer has priority over creator
        if (author != null)
            if (m_author.searchString(author) != null) return true;
        
        //None found: we can use 'Txt::Stored by' (note: DGT only) as alternative.
        author = te.getPropValue("Txt::Stored by");
        if (author != null) return (m_author.searchString(author) != null);
        else return false;
    }
	
    /**
     * Looks for an occurrence of the author search string in the supplied text string.
     * 
     * @param author
     *            The text string to search in
     * 
     * @return True if the text string contains the search string
     */
    protected boolean searchAuthor(TMXEntry te) {
        if (m_author == null) return true;
        if (te == null) return false;
        
        String author = te.changer; if (author == null) author = te.creator; // changer has priority over creator
        if (author != null)
            return (m_author.searchString(author) != null);
        else
            return false; // user required a specific author, so we exclude entries with no author
    }

    // Used for replacements
    public TranslationStateFilter getTranslationStateFilter() {
        return m_translationStateFilter;
    }
    
    private boolean searchTranslator(PrepareTMXEntry te) {
        if (te == null) return false;
        if (m_translator == null) return true;
        
        String author = te.getPropValue("Txt::Translator");
        if (author != null) return (m_translator.searchString(author) != null);
        else return false;
    }
    
    // Used for replacements
    public List<SearchMatch> searchInTranslation(String text) {
        return m_searchTarget == null ? null : m_searchTarget.searchString(text);
    }

    public List<SearchMatch> searchInSource(String text) {
        return m_searchSource == null ? null : m_searchSource.searchString(text);
    }
    
    public <T> Iterable<T> searchInProvider (ISearchable<T> provider) throws Exception {
        return provider.search(m_maxResults,
            m_searchSource, m_searchTarget, m_searchNotes, m_andSearch,
            m_author, m_dateAfter, m_dateBefore);
    }	
    
    protected final IProject m_project;
    protected int m_searchLocation;
    protected boolean m_andSearch;
    protected TranslationStateFilter m_translationStateFilter, m_sourceTranslationStateFilter;
    protected TextExpression m_searchSource, m_searchTarget, m_searchNotes;
    protected TextExpression m_author, m_translator;
    protected long m_dateBefore, m_dateAfter;
}
