/**************************************************************************
 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-2020 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.Iterator;
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.ProjectTMX;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.ITMXEntry;
import org.omegat.core.data.TMXEntry;
import org.omegat.gui.search.ProjectSearchWindow;
import org.omegat.util.Language;
import org.omegat.util.OStrings;

/**
 * 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 abstract class ProjectSearcher extends Searcher {

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

    /**
     * Create new searcher instance.
     * 
     * @param project
     *            Current project
     */
    protected ProjectSearcher(final ProjectSearchWindow window, final IProject project, 
        boolean removeDup, boolean trueMissing, int numberOfResults, 
        TextExpression author, TextExpression translator,long dateAfter, long dateBefore) {

        super (window, removeDup, numberOfResults);
		
        this.m_project = project; 
        this.m_author = author; m_translator = translator;
        this.m_dateBefore = dateBefore; this.m_dateAfter = dateAfter;
        this.m_trueMissing = trueMissing;
    }

    // /////////////////////////////////////////////////////////
    // thread main loop
    @Override
    protected void doSearch() {

        // reset the number of search hits
        m_numFinds = 0;

        // search through all project entries
        if (searchOn (SEARCH_SCOPE_SOURCE_FILES | SEARCH_SCOPE_ONGOING)) {	
            int count = m_project.getAllEntries().size(), progress = 0;
            for (Iterator<SourceTextEntry> iter = projectEntries(); iter.hasNext(); ) {
                SourceTextEntry entry = iter.next();
                m_window.displayProgress("ONGOING:" + entry.getKey().file, 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 (searchOn (SEARCH_SCOPE_SOURCE_FILES)) testSourceDocument (entry);
                if (searchOn (SEARCH_SCOPE_ONGOING)) testOngoing (entry);
                checkInterrupted();
            }
        }
        
        // search the Memory, if requested
        if (searchOn (SEARCH_SCOPE_ORPHANS)) {
            final String localOrphans = OStrings.getString("CT_ORPHAN_STRINGS"), distantOrphans = OStrings.getString("CT_DISTANT_ORPHAN_STRINGS");
            m_window.displayProgress("ORPHAN", 1, 2, m_numFinds);

            // search in orphaned
            m_project.iterateByDefaultTranslations((String source, TMXEntry en) -> {
                    // stop searching if the max. nr of hits has been reached
                    if (m_numFinds >= m_maxResults) return;
                    
                    checkInterrupted();
                    if (! m_project.isLocallyTranslated(source))
                        testInternalEntry(distantOrphans, en);
                    else if (m_project.isOrphaned(source))
                        testInternalEntry(localOrphans, en);
                });

            m_window.displayProgress("ORPHAN", 2, 2, m_numFinds);
            m_project.iterateByMultipleTranslations((EntryKey source, TMXEntry en) -> {
                    // stop searching if the max. nr of hits has been reached
                    if (m_numFinds >= m_maxResults) return;
                    
                    checkInterrupted();
                    if (! m_project.isLocallyTranslated(source))
                        testInternalEntry(distantOrphans, en);
                    else if (m_project.isOrphaned(source))
                        testInternalEntry(localOrphans, en);
                });
        }        
    }
    
    protected Iterator<SourceTextEntry> projectEntries() { return m_project.getAllEntries().iterator(); }
    
    protected abstract void testOngoing(SourceTextEntry ste);
    
    protected boolean testSourceDocument(SourceTextEntry ste) {
        // Source entry does not store author/date except for some formats
        try {
            SourceTextEntry.SourceTranslationEntry info = (SourceTextEntry.SourceTranslationEntry) ste.getSourceTranslationInfo();
            if (m_author != null) {
                String author = info.changer; if (author == null) author = info.creator;
                if (author == null) return m_trueMissing; else return m_author.matchesString(author);
            }
            long date = info.changeDate; if (date == 0) date = info.creationDate;
            if (date == 0) {
                if (m_trueMissing) return true; else return (m_dateBefore == Long.MIN_VALUE) && (m_dateAfter == Long.MAX_VALUE);
            } else {
                if ((m_dateBefore > Long.MIN_VALUE) && (date >= m_dateBefore)) return false;
                if ((m_dateAfter < Long.MAX_VALUE) && (date <= m_dateAfter)) return false;
            }
        } catch (Exception e) { // not stored: use option 'true missing'
            if ((m_author != null) || (m_dateAfter < Long.MAX_VALUE) || (m_dateBefore > Long.MIN_VALUE)) return m_trueMissing;
        }
        return true;
    }
    
    protected boolean testInternalEntry (String fileName, TMXEntry entry) {
        return checkFilters (entry);
    }    
	    
    /** Check specified entry against author/date. Called before text filters, because it is faster and does not imply SearchMatch creations **/
    public boolean checkFilters(ITMXEntry tmxEntry) {
        if ((m_author != null) && (! searchAuthor (tmxEntry))) return false;
        if ((m_translator != null) && (! searchTranslator (tmxEntry))) return false;
        long date = tmxEntry.getChangeDate(); if (date == 0) date = tmxEntry.getCreationDate();
        if (date == 0) {
            if (m_trueMissing) return true; else return (m_dateBefore == Long.MIN_VALUE) && (m_dateAfter == Long.MAX_VALUE);
        } else {
            if ((m_dateBefore > Long.MIN_VALUE) && (date >= m_dateBefore)) return false;
            if ((m_dateAfter < Long.MAX_VALUE) && (date <= 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(ITMXEntry te) {
        if (m_author == null) return true;
        if (te == null) return false;
        
        String author = te.getChanger(); if (author == null) author = te.getCreator(); // 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 m_trueMissing;
    }
	
    private boolean searchTranslator(ITMXEntry 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 m_trueMissing;
    }

    // Used for replacements
    public abstract TranslationStateFilter getTranslationStateFilter();
    protected abstract boolean searchOn(int location);
    
    protected final IProject m_project;
    protected TextExpression m_author, m_translator;
    protected long m_dateBefore, m_dateAfter;
    protected boolean m_trueMissing;
}
