/*  Copyright (C) 2003-2011 JabRef contributors.
    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.search;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import net.sf.jabref.BibtexEntry;
import net.sf.jabref.SearchRule;
import net.sf.jabref.export.layout.format.RemoveLatexCommands;

/**
 * Search rule for simple search.
 */
public class BasicSearch implements SearchRule {
    private boolean caseSensitive;
    private boolean regExp;
    Pattern[] pattern;
    //static RemoveBrackets removeLatexCommands = new RemoveBrackets();
    static RemoveLatexCommands removeBrackets = new RemoveLatexCommands();

    public BasicSearch(boolean caseSensitive, boolean regExp) {

        this.caseSensitive = caseSensitive;
        this.regExp = regExp;
    }

    public int applyRule(String query, BibtexEntry bibtexEntry) {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("1", query);
        return applyRule(map, bibtexEntry);
    }

    public boolean validateSearchStrings(Map<String, String> searchStrings) {
        if (regExp) {
            int flags = 0;
            String searchString = searchStrings.values().iterator().next();
            if (!caseSensitive) {
                searchString = searchString.toLowerCase();
                flags = Pattern.CASE_INSENSITIVE;
            }
            ArrayList<String> words = parseQuery(searchString);
            try {
                pattern = new Pattern[words.size()];
                for (int i = 0; i < pattern.length; i++) {
                    pattern[i] = Pattern.compile(words.get(i), flags);
                }
            } catch (PatternSyntaxException ex) {
                return false;
            }
        }
        return true;
    }

    public int applyRule(Map<String, String> searchStrings, BibtexEntry bibtexEntry) {

        int flags = 0;
        String searchString = searchStrings.values().iterator().next();
        if (!caseSensitive) {
            searchString = searchString.toLowerCase();
            flags = Pattern.CASE_INSENSITIVE;
        }

        ArrayList<String> words = parseQuery(searchString);

        if (regExp)
            try {
                pattern = new Pattern[words.size()];
                for (int i = 0; i < pattern.length; i++) {
                    pattern[i] = Pattern.compile(words.get(i), flags);
                }
            } catch (PatternSyntaxException ex) {
                return 0;
            }

        //print(words);
        // We need match for all words:
        boolean[] matchFound = new boolean[words.size()];

        Object fieldContentAsObject;
        String fieldContent;
        
        for (String field : bibtexEntry.getAllFields()){
            fieldContentAsObject = bibtexEntry.getField(field);
            if (fieldContentAsObject != null) {
                fieldContent = removeBrackets.format(fieldContentAsObject.toString());
                if (!caseSensitive)
                    fieldContent = fieldContent.toLowerCase();
                int index = 0;
                // Check if we have a match for each of the query words, ignoring
                // those words for which we already have a match:
                for (int j=0; j<words.size(); j++) {
                    if (!regExp) {
                        String s = words.get(j);
                        matchFound[index] = matchFound[index]
                            || (fieldContent.indexOf(s) >= 0);
                    } else {
                        if (fieldContent != null) {
                            Matcher m = pattern[j].matcher
                                    (removeBrackets.format(fieldContent));
                            matchFound[index] = matchFound[index]
                                || m.find();
                        }
                    }

                    index++;
                }
            }

        }
        for (int i = 0; i < matchFound.length; i++) {
            if (!matchFound[i])
                return 0; // Didn't match all words.
        }
        return 1; // Matched all words.
    }

    private ArrayList<String> parseQuery(String query) {
        StringBuffer sb = new StringBuffer();
        ArrayList<String> result = new ArrayList<String>();
        int c;
        boolean escaped = false, quoted = false;
        for (int i=0; i<query.length(); i++) {
            c = query.charAt(i);
            // Check if we are entering an escape sequence:
            if (!escaped && (c == '\\'))
                escaped = true;
            else {
                // See if we have reached the end of a word:
                if (!escaped && !quoted && Character.isWhitespace((char)c)) {
                    if (sb.length() > 0) {
                        result.add(sb.toString());
                        sb = new StringBuffer();
                    }
                }
                else if (c == '"') {
                    // Whether it is a start or end quote, store the current
                    // word if any:
                    if (sb.length() > 0) {
                        result.add(sb.toString());
                        sb = new StringBuffer();
                    }
                    quoted = !quoted;
                }
                else {
                    // All other possibilities exhausted, we add the char to
                    // the current word:
                    sb.append((char)c);
                }
                escaped = false;
            }
        }
        // Finished with the loop. If we have a current word, add it:
        if (sb.length() > 0) {
            result.add(sb.toString());
        }

        return result; 
    }
}
