/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.extension.jdbc.util;

import org.seasar.extension.jdbc.exception.SingleQuoteNotCloseRuntimeException;

/**
 * トークンを認識するクラスです。
 * 
 * @author higa
 * 
 */
public class QueryTokenizer {

    /**
     * EOFをあらわします。
     */
    public static final int TT_EOF = -1;

    /**
     * 単語をあらわします。
     */
    public static final int TT_WORD = -2;

    /**
     * 引用文をあらわします。
     */
    public static final int TT_QUOTE = -3;

    /**
     * 単語、引用文以外をあらわします。
     */
    public static final int TT_OTHER = -4;

    private String str;

    private int pos;

    private int nextPos;

    private int length;

    private String token;

    private int type = TT_EOF;

    /**
     * {@link QueryTokenizer}を作成します。
     * 
     * @param str
     *            ソースの文字列
     */
    public QueryTokenizer(String str) {
        this.str = str;
        length = str.length();
        peek(0);
    }

    /**
     * 次のトークンを前もって調べます。
     * 
     * @param index
     *            インデックス
     */
    protected void peek(int index) {
        if (index < length) {
            char c = str.charAt(index);
            if (Character.isWhitespace(c) || isOrdinary(c)) {
                type = TT_OTHER;
            } else if (c == '\'') {
                type = TT_QUOTE;
            } else {
                type = TT_WORD;
            }
            pos = index;
            nextPos = index + 1;
        } else {
            type = TT_EOF;
        }
    }

    /**
     * トークンのタイプを返します。
     * 
     * @return トークンのタイプ
     */
    public int nextToken() {
        if (type == TT_EOF) {
            return TT_EOF;
        }
        if (type == TT_QUOTE) {
            for (int i = nextPos; i < length; i++) {
                char c = str.charAt(i);
                if (c == '\'') {
                    i++;
                    if (i >= length) {
                        token = str.substring(pos, i);
                        type = TT_EOF;
                        return TT_QUOTE;
                    } else if (str.charAt(i) != '\'') {
                        token = str.substring(pos, i);
                        peek(i);
                        return TT_QUOTE;
                    }

                }
            }
            throw new SingleQuoteNotCloseRuntimeException(str);
        }
        for (int i = nextPos; i < length; i++) {
            char c = str.charAt(i);
            if (isOther(c)) {
                if (type == TT_WORD) {
                    token = str.substring(pos, i);
                    pos = i;
                    nextPos = pos + 1;
                    type = TT_OTHER;
                    return TT_WORD;
                }
            } else if (c == '\'') {
                if (type == TT_WORD) {
                    token = str.substring(pos, i);
                    pos = i;
                    nextPos = pos + 1;
                    type = TT_QUOTE;
                    return TT_WORD;
                }
                if (type == TT_OTHER) {
                    token = str.substring(pos, i);
                    pos = i;
                    nextPos = pos + 1;
                    type = TT_QUOTE;
                    return TT_OTHER;
                }
            } else {
                if (type == TT_OTHER) {
                    token = str.substring(pos, i);
                    pos = i;
                    nextPos = pos + 1;
                    type = TT_WORD;
                    return TT_OTHER;
                }
            }
        }
        token = str.substring(pos, length);
        if (type == TT_WORD) {
            type = TT_EOF;
            return TT_WORD;
        }
        type = TT_EOF;
        return TT_OTHER;
    }

    /**
     * 単語以外かどうかを返します。
     * 
     * @param c
     *            文字
     * @return 単語以外かどうか
     */
    protected boolean isOther(char c) {
        return Character.isWhitespace(c) || isOrdinary(c);
    }

    /**
     * 単独で文字として認識するかどうかを返します。
     * 
     * @param c
     *            文字
     * @return 単独で文字として認識するかどうか
     */
    protected boolean isOrdinary(char c) {
        return c == '=' || c == '?' || c == '<' || c == '>' || c == '('
                || c == ')' || c == '!' || c == '*' || c == '-' || c == ',';
    }

    /**
     * トークンを返します。
     * 
     * @return トークン
     */
    public String getToken() {
        return token;
    }
}