/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sdo.helper.extension;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * Utility class used for processing an XPath Expression into postfix
 * notation.  For example, consider the following expression:
 *
 *     quantity=5 and (specialOrder='false' or productName='Lawnmower')
 *
 * The operations performed to create a postfix expression are:
 *     1) add "quantity" to the list
 *     2) push "=" onto the stack
 *     3) add "5" to the list
 *     4) pop "=" off the stack, and add to the list
 *     5) push "and" onto the stack
 *     6) push "(" onto the stack
 *     7) add "specialOrder" to the list
 *     8) push "=" onto the stack
 *     9) add "'false'" to the list
 *    10) pop "=" off the stack, and add to the list
 *    11) push "or" onto the stack
 *    12) add "productName" to the list
 *    13) push "=" onto the stack
 *    14) add "'Lawnmower'" to the list
 *    15) pop "=" off the stack, and add to the list
 *    16) pop "or" off the stack, and add to the list
 *    17) push ")" onto the stack
 *    18) pop "(" ")" pair off the stack
 *    19) pop "and" off the stack, and add to the list
 *
 * The resulting postfix expression is:
 *
 *     quantity 5 = specialOrder 'false' = productName 'Lawnmower' = or and
 */
public class OPStack {

    /** Visibility reduced from [public] in 2.1.0. May 15 2007 */
    private int state;

    /** Visibility reduced from [public] in 2.1.0. May 15 2007 */
    private Stack stack;

    /** Visibility reduced from [public] in 2.1.0. May 15 2007 */

    //private String expression;

    /** Visibility reduced from [public] in 2.1.0. May 15 2007 */
    private ArrayList tokens;

    /** Visibility reduced from [public] in 2.1.0. May 15 2007 */
    private List out;

    /**
     * The default constructor initializes the stack and
     * Token list.
     */
    public OPStack() {
        stack = new Stack();
        tokens = new ArrayList();
    }

    /**
     * Removes a '(' ')' pair from the stack.
     */
    private void removeOCpair() {
        stack.pop();
        stack.pop();
    }

    /**
     * Removes lower priority tokens from the stack (adding them to the
     * List that will be returned), then push the new token onto the
     * stack.
     *
     * @param out
     * @param t
     */
    private void pushSpew(List outList, Token t) {
        while (!stack.empty() && (((Token)stack.peek()).getPriority() >= t.getPriority())) {
            outList.add(stack.pop());
        }
        stack.push(t);
    }

    /**
     * Returns the next Token in the list.
     *
     * @return
     */
    private Token getNextToken() {
        if (tokens.size() > 0) {
            Token tok = (Token)tokens.get(0);
            tokens.remove(0);
            return tok;
        }
        return null;
    }

    /**
     * Parse an XPath Expression into Token objects.
     *
     * @param exp
     */
    private void parseExpression(String exp) {
        StringBuffer sbuf = new StringBuffer();
        char ch;
        Token tok;
        for (int i = 0; i < exp.length(); i++) {
            ch = exp.charAt(i);
            // 'and' and 'or' should be preceded by whitespace
            if (ch == ' ') {
                if (((i + 2) < exp.length()) && ((exp.charAt(i + 1) == 'o') && (exp.charAt(i + 2) == 'r'))) {
                    tok = new Token(sbuf.toString(), Token.ARG);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                    sbuf.append(exp.charAt(++i));
                    sbuf.append(exp.charAt(++i));
                    tok = new Token(sbuf.toString(), Token.DI, Token.OR_PR);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                } else if (((i + 3) < exp.length()) && ((exp.charAt(i + 1) == 'a') && (exp.charAt(i + 2) == 'n') && (exp.charAt(i + 3) == 'd'))) {
                    tok = new Token(sbuf.toString(), Token.ARG);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                    sbuf.append(exp.charAt(++i));
                    sbuf.append(exp.charAt(++i));
                    sbuf.append(exp.charAt(++i));
                    tok = new Token(sbuf.toString(), Token.DI, Token.AND_PR);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                }
            } else if (ch == '=') {
                tok = new Token(sbuf.toString(), Token.ARG);
                tokens.add(tok);
                tok = new Token(Character.toString(ch), Token.DI, Token.EQ_PR);
                tokens.add(tok);
                sbuf = new StringBuffer();
            } else if ((ch == '!') && (((i + 1) < exp.length()) && (exp.charAt(i + 1) == '='))) {
                tok = new Token(sbuf.toString(), Token.ARG);
                tokens.add(tok);
                sbuf = new StringBuffer();
                sbuf.append(Character.toString(ch));
                sbuf.append(exp.charAt(++i));
                tok = new Token(sbuf.toString().trim(), Token.DI, Token.NEQ_PR);
                tokens.add(tok);
                sbuf = new StringBuffer();
            } else if (ch == '<') {
                tok = new Token(sbuf.toString(), Token.ARG);
                tokens.add(tok);
                sbuf = new StringBuffer();
                sbuf.append(Character.toString(ch));
                if (((i + 1) < exp.length()) && (exp.charAt(i + 1) == '=')) {
                    sbuf.append(exp.charAt(++i));
                    tok = new Token(sbuf.toString().trim(), Token.DI, Token.LEQ_PR);
                } else {
                    tok = new Token(sbuf.toString().trim(), Token.DI, Token.LT_PR);
                }
                tokens.add(tok);
                sbuf = new StringBuffer();
            } else if (ch == '>') {
                tok = new Token(sbuf.toString(), Token.ARG);
                tokens.add(tok);
                sbuf = new StringBuffer();
                sbuf.append(Character.toString(ch));
                if (((i + 1) < exp.length()) && (exp.charAt(i + 1) == '=')) {
                    sbuf.append(exp.charAt(++i));
                    tok = new Token(sbuf.toString().trim(), Token.DI, Token.GEQ_PR);
                } else {
                    tok = new Token(sbuf.toString().trim(), Token.DI, Token.GT_PR);
                }
                tokens.add(tok);
                sbuf = new StringBuffer();
            } else if (ch == '(') {
                if (sbuf.length() > 0) {
                    tok = new Token(sbuf.toString(), Token.ARG);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                }
                tok = new Token(Character.toString(ch), Token.OPEN);
                tokens.add(tok);
            } else if (ch == ')') {
                if (sbuf.length() > 0) {
                    tok = new Token(sbuf.toString(), Token.ARG);
                    tokens.add(tok);
                    sbuf = new StringBuffer();
                }
                tok = new Token(Character.toString(ch), Token.CLOSE);
                tokens.add(tok);
            } else {
                if (ch != ' ') {
                    sbuf.append(ch);
                }
            }
        }
        if (sbuf.length() > 0) {
            tok = new Token(sbuf.toString(), Token.ARG);
            tokens.add(tok);
            sbuf = new StringBuffer();
        }
    }

    /**
     * Process an XPath Expression into postfix notation.
     *
     * @param exp
     * @return
     */
    public List processExpression(String exp) {
        parseExpression(exp);
        out = new ArrayList();
        state = 0;
        stack.push(Token.OpenExp);// start off with an open of the expression
        Token t;
        while ((t = getNextToken()) != null) {
            switch (t.getType()) {
            case Token.ARG:
                if (state != 0) {
                    out.clear();
                    return out;
                }
                out.add(t);
                state = 1;
                break;
            case Token.PRE:
                if (state != 0) {
                    out.clear();
                    return out;
                }
                stack.push(t);
                break;
            case Token.OPEN:
                stack.push(t);
                break;
            case Token.DI:
                if (state != 1) {
                    out.clear();
                    return out;
                }
                pushSpew(out, t);
                state = 0;
                break;
            case Token.POST:
                if (state != 1) {
                    out.clear();
                    return out;
                }
                pushSpew(out, t);
                out.add(stack.pop());
                break;
            case Token.CLOSE:
                pushSpew(out, t);
                removeOCpair();
            }
        }
        pushSpew(out, Token.CloseExp);
        removeOCpair();
        if (state != 1) {
            out.clear();
        }
        return out;
    }
}
