/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.ejb21.sql;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;

import org.ow2.jonas.deployment.ejb.DeploymentDescEjb2;
import org.ow2.jonas.deployment.ejb.ejbql.ASTAbstractSchemaName;
import org.ow2.jonas.deployment.ejb.ejbql.ASTAggregateSelectExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticFactor;
import org.ow2.jonas.deployment.ejb.ejbql.ASTArithmeticTerm;
import org.ow2.jonas.deployment.ejb.ejbql.ASTBetweenExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTBooleanExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCmpPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCollectionMemberDeclaration;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCollectionMemberExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTCollectionValuedPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalFactor;
import org.ow2.jonas.deployment.ejb.ejbql.ASTConditionalTerm;
import org.ow2.jonas.deployment.ejb.ejbql.ASTDatetimeExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEJBQL;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEmptyCollectionComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEntityBeanExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFromClause;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFunctionsReturningNumerics;
import org.ow2.jonas.deployment.ejb.ejbql.ASTFunctionsReturningStrings;
import org.ow2.jonas.deployment.ejb.ejbql.ASTIdentificationVariable;
import org.ow2.jonas.deployment.ejb.ejbql.ASTIdentifier;
import org.ow2.jonas.deployment.ejb.ejbql.ASTInExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTLikeExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTNullComparisonExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTOrderByClause;
import org.ow2.jonas.deployment.ejb.ejbql.ASTOrderByItem;
import org.ow2.jonas.deployment.ejb.ejbql.ASTPath;
import org.ow2.jonas.deployment.ejb.ejbql.ASTRangeVariableDeclaration;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSelectClause;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSelectExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSingleValuedCmrPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTSingleValuedPathExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTStringExpression;
import org.ow2.jonas.deployment.ejb.ejbql.ASTWhereClause;
import org.ow2.jonas.deployment.ejb.ejbql.ParseException;
import org.ow2.jonas.deployment.ejb.ejbql.SimpleNode;
import org.ow2.jonas.lib.ejb21.TraceEjb;

import org.objectweb.jorm.metainfo.api.Class;
import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.medor.api.Field;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.query.api.QueryTreeField;
import org.objectweb.medor.query.jorm.lib.ClassExtent;
import org.objectweb.medor.query.jorm.lib.PNameField;
import org.objectweb.medor.query.jorm.lib.QueryBuilder;
import org.objectweb.medor.query.lib.QueryTreePrinter;
import org.objectweb.medor.filter.lib.BasicFieldOperand;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * Implementation of a visitor that creates a map of pairs [name,QueryTreeField]
 * for all defined identifiers of the query.
 * @author Christophe Ney [cney@batisseurs.com] Initial developper
 * @author Helene Joanin:
 *         <ul>
 *         <li>In the visit of the SELECT clause, do not forget to add the id
 *         in the ids structure if its not present. Indeed, the definition of
 *         this id is done in the FROM clause which is visited later. This is
 *         needed for ejbSelect query which allows path_expression in the SELECT
 *         clause.</li>
 *         <li>Use of the NavigatorNodeFactory.getIn() for IN in the FROM part
 *         instead of NavigatorNodeFactory.getNavigatorNode()</li>
 *         <li>Some modifications needed to take into account CollectionValuedPathExpression
 *         for the member of and is empty operators.</li>
 *         <li>For the MemberOf implementation, do not more build an unique
 *         QueryTree, but a Map of pairs [name,QueryTreeField]</li>
 *         <li>Add the ORDER BY clause.</li>
 *         <li>Take into account the new interface of NavigatorNodeFactory.</li>
 *         <li>IS EMPTY / IS NOT EMPTY</li>
 *         <li>Take into account the EJBQL version 2.1 syntax.</li>
 *         </ul>
 */
public class EjbqlVariableVisitor extends EjbqlAbstractVisitor {

    /**
     * The JORM Manager
     */
    private Manager manager;

    /**
     * The Deployment Descriptors information
     */
    private DeploymentDescEjb2 dd;

    /**
     * ids structure for all the defined identifiers of the query,.
     */
    private HashMap ids = new HashMap();

    /**
     * field for each defined identifiers of the query.
     */
    private HashMap fields = new HashMap();

    private QueryBuilder qb = new QueryBuilder();

    /**
     * track which paths are collections
     */
    private HashSet collections = new HashSet();

    /**
     * constructor
     * @param ejbql root of the lexical tree
     * @param dd Deployment Descriptor
     * @param qb query builder used to navigate paths
     * @throws Exception when error parsing
     */

    public EjbqlVariableVisitor(ASTEJBQL ejbql, DeploymentDescEjb2 dd, QueryBuilder qb, Manager mgr) throws Exception {
        this.dd = dd;
        this.qb = qb;
        manager = mgr;

        // parse the ejbql and build the ids structure
        visit(ejbql);

        for (Iterator it = ids.keySet().iterator(); it.hasNext(); ) {
            String id = (String) it.next();
            IdValue idv = (IdValue) ids.get(id);
            fields.put(id + "." + Field.PNAMENAME, qb.project(define(id)));
            for (int i = 0; i < idv.getDeclaredPathLength(); i++) {
                String path = idv.getMergedPath(i);
                if (!collections.contains(path)) {
                    fields.put(path, qb.project(define(path)));
                }
            }
        }
    }

    private QueryTreeField define(String id) throws ParseException, MedorException {
        String[] path = splitPath(id);

        if (!qb.contains(path[0])) {
            IdValue idv = (IdValue) ids.get(path[0]);
            String[] name = idv.getName();

            PNameField pnf;
            if (name.length == 1) {
                pnf = extent(name[0], path[0]);
            } else {
                pnf = (PNameField) define(mergePath(name));
            }
            qb.define(path[0], pnf);
        }

        return qb.navigate(path);
    }

    private PNameField extent(String schema, String alias) throws ParseException, MedorException {
        if (schema == null) { throw new NullPointerException("schema"); }
        String cn = dd.asn2BeanDesc(schema).getJormClassName();
        Class theClass = manager.getClass(cn);
        if (theClass == null) {
            throw new ParseException
                ("Abstract schema name \"" + schema +
                 "\" has not been declared in the jorm meta information");
        }
        ClassExtent ext = new ClassExtent(theClass, alias, Field.PNAMENAME, false);
        return (PNameField) ext.getField(ext.getPNameFieldName());
    }

    /**
     * get the Map that was built from visiting the lexical query tree This map
     * allows to get the org.objectweb.medor.api.Field from its name (ident or
     * path).
     * @return the Fields map
     */
    public Map getFields() {
        return fields;
    }

    /**
     * Trace the given ids structure
     * @param idsM identifiocators Map
     */
    public void traceIds(HashMap idsM) {
        if (TraceEjb.isDebugQuery()) {
            TraceEjb.query.log(BasicLevel.DEBUG, "Begin of IDS structure:");
            // trace the ids structure
            for (Iterator i = idsM.keySet().iterator(); i.hasNext();) {
                String id = (String) i.next();
                IdValue idv = (IdValue) idsM.get(id);
                TraceEjb.query.log(BasicLevel.DEBUG, "ids[" + id + "]=" + idv);
                if (idv.getQueryTree() != null) {
                    QueryTreePrinter.printQueryTree(idv.getQueryTree(), TraceEjb.query, BasicLevel.DEBUG);
                }
            }
            TraceEjb.query.log(BasicLevel.DEBUG, "End of IDS structure:");
        }
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTFromClause node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTCollectionMemberDeclaration node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTRangeVariableDeclaration node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTCollectionValuedPathExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * Push the Node to the stack
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTAbstractSchemaName node, Object data) {
        ((Stack) data).push(node.value);
        return null;
    }

    /**
     * Store the pair identifier,Node from the Stack in HashMap
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTIdentifier node, Object data) {
        String id = (String) node.value;
        String name = (String) ((Stack) data).pop();
        IdValue iv = (IdValue) ids.get(id);
        if (iv == null) {
            iv = new IdValue(name);
            ids.put(id, iv);
        } else {
            iv.setName(splitPath(name));
        }
        return null;
    }

    /**
     * Push the Node to the stack
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTPath node, Object data) {
        ((Stack) data).push(node.value);
        return null;
    }

    /**
     * Process the variable ie create the IdValue if not already done and add the path
     * @param path of the variable
     */
    private void processVariable(String path) {
        // Common to select, where and orderby clauses
        String id = splitPath(path)[0];
        IdValue iv = (IdValue) ids.get(id);
        if (iv == null) {
            iv = new IdValue();
            ids.put(id, iv);
        }
        iv.addPath(path);
    }

    // -------------- select ---------------//
    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTSelectClause node, Object data) {
        visit((SimpleNode) node, data);
        Stack st = (Stack) data;
        if (!st.empty()) {
            processVariable((String) st.pop());
        }
        return null;
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTSelectExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTAggregateSelectExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTIdentificationVariable node, Object data) {
    	// Identification variables are case insensitive
    	String vName = ((String) node.value).toLowerCase();
    	node.value = vName;
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTSingleValuedPathExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTSingleValuedCmrPathExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTCmpPathExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    // ----------- Where -----------------//
    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTWhereClause node, Object data) {
        visit((SimpleNode) node, data);
        Stack st = (Stack) data;
        while (!st.empty()) {
            processVariable((String) st.pop());
        }
        return null;
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTConditionalExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTConditionalTerm node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * Visit child nodes and count the number of the unary operator NOT.
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTConditionalFactor node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTBetweenExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTInExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTLikeExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTNullComparisonExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes and see if it's IS EMPTY or IS NOT EMPTY
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTEmptyCollectionComparisonExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack st = (Stack) data;
        String path = (String) st.peek();
        collections.add(path);
        return null;
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTCollectionMemberExpression node, Object data) {
        visit((SimpleNode) node, data);
        Stack st = (Stack) data;
        String path = (String) st.peek();
        collections.add(path);
        return null;
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTComparisonExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTArithmeticExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTArithmeticTerm node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTArithmeticFactor node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTStringExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTDatetimeExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTBooleanExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTEntityBeanExpression node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTFunctionsReturningStrings node, Object data) {
        return visit((SimpleNode) node, data);
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTFunctionsReturningNumerics node, Object data) {
        return visit((SimpleNode) node, data);
    }

    // ----------- Order by -----------------//
    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTOrderByClause node, Object data) {
        visit((SimpleNode) node, data);
        Stack st = (Stack) data;
        while (!st.empty()) {
            processVariable((String) st.pop());
        }
        return null;
    }

    /**
     * visit child nodes
     * @param node the node to visit
     * @param data the current stack
     * @return the stack
     */
    public Object visit(ASTOrderByItem node, Object data) {
        return visit((SimpleNode) node, data);
    }

}
