/*
 * QueryTokenizer.java
 *
 * Copyright (C) 2002-2009 Takis Diakoumis
 *
 * 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 3
 * of the License, or 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, see <http://www.gnu.org/licenses/>.
 *
 */

package org.executequery.sql;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.executequery.Constants;
import org.executequery.gui.text.syntax.Token;
import org.executequery.gui.text.syntax.TokenTypes;

/** 
 * Query tokenizer.
 *
 * @author   Takis Diakoumis
 * @version  $Revision: 1460 $
 * @date     $Date: 2009-01-25 11:06:46 +1100 (Sun, 25 Jan 2009) $
 */
public class QueryTokenizer {

    private static final String NEW_LINE_TRIM_REGEX = "\n{2,}";

    private static final String MULTILINE_COMMENT_REGEX = "/\\*((?>[^\\*/]*+)*+)\\*/|/\\*.*";

    private static final String QUOTE_REGEX = "'((?>[^']*+)(?>'{2}[^']*+)*+)'|'.*";

    private static final String QUERY_DELIMITER = ";";
    
    /** Regex matcher for quote marks */
    private Matcher quoteMatcher;
    
    /** Regex matcher for single-line comments */
    private Matcher singleLineCommentMatcher;

    /** Regex matcher for multi-line comments */
    private Matcher multiLineCommentMatcher;

    /** Regex matcher for final query trim */
    private Matcher trimNewLineMatcher;    

    /**
     * Splits (tokenizes) the specified query block into multiple single 
     * queries using the semi-colon (;) character as the effective delimiter.
     * This will store the queries in their original form in addition to
     * returning a list of individual queries formatted for execution by 
     * removing any multi-line and single-line comments from each query.
     *
     * @param sql - the sql block to be executed
     * @return a list of individual queries derived from the specified block
     */
    public List<DerivedQuery> tokenizeQuery(String query) {

        initMatchers();

        final List<Token> tokens = new ArrayList<Token>();

        addMultiLineCommentTokens(query, tokens);
        addSingleLineCommentTokens(query, tokens);
        addQuotedStringTokens(query, tokens);

        int index = 0;

        // store the delim (;) indexes
        List<Integer> delims = new ArrayList<Integer>();
        while ((index = query.indexOf(QUERY_DELIMITER, index + 1)) != -1) {

            delims.add(Integer.valueOf(index));
        }

        int delimCount = delims.size();
        int tokenCount = tokens.size();
        
        if (tokenCount > 0 && delimCount > 0) {

            // loop through and remove the delim indexes that are 
            // either a comment (single or multi-line) or a quoted string
            
            for (int i = 0; i < delims.size(); i++) {

                // add 1 to this index as the token endIndex 
                // includes the last char in the pattern test
                index = delims.get(i).intValue() + 1;

                for (Token token : tokens) {

                    if (token.contains(index)) {
                        delims.remove(i);
                        i--;
                        break;
                    }

                }

            }
        }
        
        index = 0;

        delimCount = delims.size();

        List<DerivedQuery> queries = new ArrayList<DerivedQuery>(delimCount);
        
        if (delimCount > 0) {

            for (int i = 0; i < delimCount; i++) {

                int delimIndex = delims.get(i).intValue();

                queries.add(new DerivedQuery(query.substring(index, delimIndex)));

                index = delimIndex + 1;
            }

        } else {

            queries.add(new DerivedQuery(query));
        }
        
        // ---------------------------------------------
        // next, store the queries in execution form

        // remove all multi-line comments
        multiLineCommentMatcher.reset();
        String _query = multiLineCommentMatcher.replaceAll(Constants.EMPTY);
        
        // remove all the single-line comments
        singleLineCommentMatcher.reset(_query);
        _query = singleLineCommentMatcher.replaceAll(Constants.EMPTY);
        
        // rescan for quoted text
        tokens.clear();
        quoteMatcher.reset(_query);

        while (quoteMatcher.find()) {

            tokens.add(new Token(quoteMatcher.start(), quoteMatcher.end()));
        }
        
        // rescan for delims in absence of commented strings
        index = 0;
        delims.clear();

        while ((index = _query.indexOf(QUERY_DELIMITER, index + 1)) != -1) {

            delims.add(Integer.valueOf(index));
        }

        delimCount = delims.size();
        tokenCount = tokens.size();

        if (tokenCount > 0 && delimCount > 0) {

            // loop through and remove the delim indexes that are 
            // either a comment (single or multi -line) or a quoted string
            for (int i = 0; i < delims.size(); i++) {

                // add 1 to this index as the token endIndex 
                // includes the last char in the pattern test
                index = delims.get(i).intValue() + 1;

                for (Token token : tokens) {
                    
                    if (token.contains(index)) {
                        delims.remove(i);
                        i--;
                        break;
                    }

                }

            }
        }

        index = 0;
        delimCount = delims.size();

        if (delimCount > 0) {

            for (int i = 0; i < delimCount; i++) {

                int delimIndex = delims.get(i).intValue();
                trimNewLineMatcher.reset(_query.substring(index, delimIndex));
                
                DerivedQuery derivedQuery = queries.get(i);
                
                derivedQuery.setDerivedQuery(trimNewLineMatcher.replaceAll(
                        Constants.NEW_LINE_STRING).trim());
                
                index = delimIndex + 1;
            }

        }
        else {

            trimNewLineMatcher.reset(_query);

            DerivedQuery derivedQuery = queries.get(0);
            
            derivedQuery.setDerivedQuery(trimNewLineMatcher.replaceAll(
                    Constants.NEW_LINE_STRING).trim());
        }

        return queries;
    }
    
    private void addMultiLineCommentTokens(String text, List<Token> tokens) {

        // store the multi-line comments position
        multiLineCommentMatcher.reset(text);

        while (multiLineCommentMatcher.find()) {

            tokens.add(new Token(TokenTypes.COMMENT, 
                    multiLineCommentMatcher.start(), multiLineCommentMatcher.end()));
        }

    }

    private void addQuotedStringTokens(String query, List<Token> tokens) {

        // store the quoted string positions
        quoteMatcher.reset(query);

        while (quoteMatcher.find()) {

            tokens.add(new Token(TokenTypes.STRING, 
                    quoteMatcher.start(), quoteMatcher.end()));
        }

    }

    private void addSingleLineCommentTokens(String query, List<Token> tokens) {

        // store the single-line comments position
        singleLineCommentMatcher.reset(query);

        while (singleLineCommentMatcher.find()) {

            tokens.add(new Token(TokenTypes.SINGLE_LINE_COMMENT, 
                    singleLineCommentMatcher.start(), singleLineCommentMatcher.end()));
        }

    }
    
    /**
     * Intialises the regex matchers.
     */
    private void initMatchers() {
       
        if (quoteMatcher == null) {
            quoteMatcher = Pattern.compile(
                    QUOTE_REGEX).matcher(Constants.EMPTY);            
        }
        
        if (singleLineCommentMatcher == null) {
            singleLineCommentMatcher = Pattern.compile(
                    TokenTypes.SINGLE_LINE_COMMENT_REGEX, Pattern.MULTILINE).
                    matcher(Constants.EMPTY);
        }
        
        if (multiLineCommentMatcher == null) {
            multiLineCommentMatcher = Pattern.compile(
                    MULTILINE_COMMENT_REGEX, Pattern.DOTALL).
                    matcher(Constants.EMPTY);
        }
        
        if (trimNewLineMatcher == null) {
            trimNewLineMatcher = Pattern.compile(
                    NEW_LINE_TRIM_REGEX, Pattern.DOTALL).
                    matcher(Constants.EMPTY);            
        }

    }

    /*
    insert into dummy_table_2 (column1, column2) --jhdsfhs ; kjdshf
    values (
    'ABC12', 
    'f_issueQC=true;f_articleQC=true;f_pdfQC=true;f_hasPDFs=true');

    insert into dummy_table_2 (column1, column2) --jhdsfhs ; kjdshf
    values (

    /* kkjdhgjhdgj 
    jshdkjfs; h * /
    'ABC12', 
    'f_issueQC=true;f_articleQC=true;f_pdfQC=true;f_hasPDFs=true');
    */

}






