package org.postgresql.util;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Extracts keywords from a query with regard to quotes
 *
 */
public class QueryParser {

    /**
     * somewhere in between
     */
    public static final int SPACE = 0;

    /**
     * within a word
     */
    public static final int WORD = 1;

    /**
     * within a word quoted with '
     */
    public static final int QA_WORD = 2;

    /**
     * within a word quoted with "
     */
    public static final int QB_WORD = 3;

    /**
     * if one of these items occurs after from, it marks the end of the from clause
     */
    public static final String[] endsfromitem = {"WHERE", "GROUP", "HAVING", "UNION", "INTERSECT", "EXCEPT", "ORDER", "LIMIT", "OFFSET"};
    /**
     * quote character A
     */
    public static final char QA = '\'';

    /**
     * quote character B
     */
    public static final char QB = '"';

    /**
     * considered as space
     */
    public static final char D1 = ' ';

    /**
     * considered as space
     */
    public static final char D2 = '\n';

    /**
     * considered as space
     */
    public static final char D3 = '\t';

    /**
     * marks end of a word and start of a new one
     */
    public static final char COMMA = ',';

    /**
     * saves the position of the from keyword in the last run of parseQuery
     */
    private int frompos;

    /**
     * holds the "words" determined by parseQuery
     */
    private String[] words;

    /**
     * holds the tableName determined by parseQuery
     */
    private String tableName;


    /**
     * saves whether this is a query to a single table
     */
    private boolean nonsingle;

    /**
     * Parses the given string and outputs unquoted fractions
     */
    private String[] parseQuery(String input) {
        // current position
        int pos = 0;

        // current state
        int state = SPACE;

        // words are added to this list
        ArrayList output = new ArrayList();

        // already read fraction of a word
        String word = "";

        // holds current and next character respectively
        char cur, next;

        // go ones through the string
        while (pos < input.length()) {
            cur = input.charAt(pos);
            next = (pos + 1) == input.length() ? ' ' : input.charAt(pos + 1);

            // decides new state upon current state and current character
            switch (state) {
                case SPACE:
                    switch (cur) {
                        case QA:
                            state = QA_WORD;
                            break;
                        case QB:
                            state = QB_WORD;
                            break;
                        case D1:
                        case D2:
                        case D3:
                            break;
                        case COMMA:
                            output.add(",");
                            break;
                        default:
                            state = WORD;
                            break;
                    }
                    ;
                    break;
                case WORD:
                    switch (cur) {
                        case QA:
                            state = QA_WORD;
                            break;
                        case QB:
                            state = QB_WORD;
                            break;
                        case D1:
                        case D2:
                        case D3:
                            output.add(word);
                            word = "";
                            state = SPACE;
                            break;
                        case COMMA:
                            output.add(word);
                            output.add(",");
                            word = "";
                            state = SPACE;
                            break;
                        default:
                            break;
                    }
                    ;
                    break;
                case QA_WORD:
                    if (cur == QA) {
                        if (next == QA) {
                            word += cur;
                            pos++;
                        } else {
                            state = WORD;
                        }
                    }
                    ;
                    break;
                case QB_WORD:
                    if (cur == QB) {
                        if (next == QB) {
                            word += cur;
                            pos++;
                        } else {
                            state = WORD;
                        }
                    }
                    ;
                    break;
            }

            // if we read a word character, append it
            if (state != SPACE) word += cur;
            pos++;
        }

        // save last word if there is one
        if (state == WORD) output.add(word);

        // convert to array and check for from keyword and nonsingle
        String[] ret = new String[output.size()];
        frompos = -1;

        // not a single table
        nonsingle = false;

        // are we in a from clause?
        boolean fromitem = false;

        // did we encounter the from keyword before?
        boolean hadfrom = false;

        for (int i = 0; i < output.size(); i++) {
            ret[i] = (String) output.get(i);
            // check for join and comma
            if (fromitem) {
                if (ret[i].equals(",") || ret[i].equals("join"))
                    nonsingle = true;
                else
                    for (int k = 0; k < endsfromitem.length; k++) {
                        if (ret[i].equalsIgnoreCase(endsfromitem[k])) {
                            fromitem = false;
                            break;
                        }
                    }

            }
            if (ret[i].equals("from")) {
                frompos = i;

                // second from means non single
                if (hadfrom) nonsingle = true;
                fromitem = true;hadfrom=true;

                // save tableName in word after from if it's not only
                String nextword = (String) output.get(i + 1);
                if (nextword.equalsIgnoreCase("only"))
                    tableName = (String) output.get(i + 2);
                else
                    tableName = (String) output.get(i + 1);
            }

        }
        return words = ret;
    }

    public boolean singleTable() {
        return !nonsingle;
    }

    public String getTableName() {
        return tableName;
    }

    public static void main(String[] args) {
        byte[] bytes = new byte[100];
        while (true) {
            System.out.print("Your Query Please: ");
            try {
                int count = System.in.read(bytes);
                if (count <= 2) System.exit(0);
                String input = new String(bytes, 0, count);
                QueryParser qp = new QueryParser();
                String[] words = qp.parseQuery(input);
                System.out.println("Table : " + qp.getTableName() + " Single: " + qp.singleTable());
                for (int i = 0; i < words.length; i++) System.out.println(words[i]);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
