/**
 * 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.Collection;
import java.util.Iterator;
import java.util.Map;

import org.ow2.jonas.deployment.ejb.DeploymentDescEjb2;
import org.ow2.jonas.deployment.ejb.EntityCmp2Desc;
import org.ow2.jonas.deployment.ejb.MethodCmp2Desc;
import org.ow2.jonas.deployment.ejb.ejbql.ASTEJBQL;
import org.ow2.jonas.lib.ejb21.TraceEjb;
import org.ow2.jonas.lib.ejb21.jorm.RdbMappingBuilder;

import org.objectweb.jorm.api.PMapper;
import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.medor.api.Field;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.eval.api.QueryEvaluator;
import org.objectweb.medor.eval.lib.MedorEvaluator;
import org.objectweb.medor.expression.api.Expression;
import org.objectweb.medor.optim.api.QueryTransformer;
import org.objectweb.medor.query.api.OrderField;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.query.api.QueryTreeField;
import org.objectweb.medor.query.jorm.api.JormExtent;
import org.objectweb.medor.query.jorm.lib.ClassExtent;
import org.objectweb.medor.query.jorm.lib.JormQueryTreeHelper;
import org.objectweb.medor.query.jorm.lib.QueryBuilder;
import org.objectweb.medor.query.lib.QueryTreePrinter;
import org.objectweb.medor.query.lib.SelectProject;
import org.objectweb.util.monolog.api.BasicLevel;


/**
 * Class to hold the query tree of a given EJBQL request. This allows walking the
 * JormExtents of the tree to set the mappers.
 * @author  Christophe Ney - cney@batisseurs.com : Initial developer
 * @author Helene Joanin: Take into account the ORDER BY clause.
 * @author Sebastien Chassande-Barrioz & Helene Joanin: prefetch code
 * @author Helene Joanin: Take into account the aggregate select expression.
 * @author Cyrille Blot: Take into account the LIMIT clause
 */
public class EjbqlQueryTreeHolder {

    QueryTree queryTree = null;
    QueryTransformer queryOptimizer = null;
    QueryEvaluator optimizedQuery = null;
    Field resField = null;
    EjbqlLimiterRange[] limiterRanges = null;

    private String beanName;
    private String methodName;

    /**
     * Indicates the index of the identifier (PName) in the TupleCollection of
     * the SelectProject node (root of of the tree).
     */
    private int prefetchIdentifierIndex;


    /**
     * constructor of a holder for a specific query.
     * @param mDesc JOnAS meta-information for the corresponding finder/select method
     * @param ejbql root of the lexical tree of the query
     * @param mapper mapper to associate at each leaves of the QueryTree.
     *               The mapper may be null in case of the QueryTree is build in the GenIC phase.
     */
    public EjbqlQueryTreeHolder(MethodCmp2Desc mDesc, ASTEJBQL ejbql, PMapper mapper, Manager mgr) throws Exception {

        EntityCmp2Desc eDesc = (EntityCmp2Desc) mDesc.getBeanDesc();
        DeploymentDescEjb2 dd = (DeploymentDescEjb2) eDesc.getDeploymentDesc();

        // create identifiers Map (visit ALL the query)
        QueryBuilder qb = new QueryBuilder();

        EjbqlVariableVisitor visitor = new EjbqlVariableVisitor(ejbql, dd, qb, mgr);
        Map fields = visitor.getFields();

        // Begin PREFETCHING CODE (by Seb)
        SelectProject sp = new SelectProject("");
        if (mDesc.getPrefetch() && (mapper != null)) {
            // Find the QueryTree of the ClassExtend of the bean of the current finder method
            // WARNING: This only works for finder methods, and not for ejbSelect methods!!!
            //          (Indeed the result type of the query must be the bean type in which
            //           the method is defined)
            ClassExtent ce = null;
            QueryTree qt = null;
            String jormClassName = ((EntityCmp2Desc) mDesc.getBeanDesc()).getJormClassName();
            Iterator itFields = fields.values().iterator();
            boolean found = false;
            while (itFields.hasNext() && !found) {
                QueryTree cqt = ((QueryTreeField) itFields.next()).getQueryTree();
                Collection extents = JormQueryTreeHelper.getJormExtents(cqt);
                for (Iterator it = extents.iterator(); it.hasNext() && !found;) {
                    JormExtent je = (JormExtent) it.next();
                    if (jormClassName.equals(je.getJormName())) {
                        found = true;
                        ce = (ClassExtent) je;
                        qt = cqt;
                    }
                }
            }
            if ((ce == null) && (qt == null)) {
                throw new Error("EjbqlQueryTreeHolder: cannot do the prefetch !!!");
            }
            setMapper(qt, mapper);
            // Add fields of the class which the values must be prefetched, at
            // the begin of the the TupleCollection
            JormQueryTreeHelper.addPrefetchFields(ce, qt, sp);
            prefetchIdentifierIndex = sp.getTupleStructure().getSize() + 1;
        }
        // End PREFETCHING CODE

        // create SelectProject (visit the SELECT clause)
        EjbqlSelectVisitor selectVisitor = new EjbqlSelectVisitor(ejbql, fields, sp);

        // create the Filter's expression (visit the WHERE clause)
        Expression qf = new EjbqlQueryFilterVisitor
            (mapper, fields, mDesc.getMethod().getParameterTypes(), ejbql, qb).getQueryFilter();
        sp.setQueryFilter(qf);

        // create the OrderField list (visit the ORDER BY clause)
        OrderField[] ofs = new EjbqlOrderByVisitor(ejbql, fields).getOrderFields();
        if (ofs.length > 0) {
            sp.setOrderBy(ofs);
        }

        // create the EjbqlLimiterRange list (visit the LIMIT clause)
        limiterRanges = new EjbqlLimitVisitor(ejbql, mDesc.getMethod().getParameterTypes()).getLimiterRanges();

        queryTree = selectVisitor.getQueryTree();

        setMapper(queryTree, mapper);

        // Get the field description of the result of the query
        // Nota Bene: TupleStructures of queries non optimized and optimized are differents.
        resField = queryTree.getTupleStructure().getField(queryTree.getTupleStructure().getSize());

        // Just needed later for debug trace
        beanName = eDesc.getJormClassName();
        methodName = mDesc.getMethod().getName();
    }

    private void setMapper(QueryTree tree, PMapper mapper) {
        // set the mapper of each leaves of the QueryTree
        if (mapper != null) {
            Collection extents = JormQueryTreeHelper.getJormExtents(tree);
            for (Iterator it = extents.iterator(); it.hasNext();) {
                JormExtent je = (JormExtent) it.next();
                je.setPMapper(mapper, RdbMappingBuilder.getProjectName());
            }
        }
    }

    public int getPrefetchIndex() {
        return prefetchIdentifierIndex;
    }

    /**
     * get the query tree that was built from visiting the lexical tree
     */
    public QueryTree getQueryTree() {
        return queryTree;
    }

    /**
     * Set the optimizer to be used when optimizing the query tree
     */
    public void setQueryOptimizer(QueryTransformer qtf) {
        optimizedQuery = null;
        queryOptimizer = qtf;
    }

    /**
     * get the current query tree optimizer
     */
    public QueryTransformer getQueryOptimizer() {
        return queryOptimizer;
    }

    /**
     * get the Medor result Field of the query (useful for the ejbSelect method)
     */
    public Field getResField() {
        return resField;
    }
    /**
     * @return returns the limiter ranges of the LIMIT clause.
     * May be 0 element if no LIMIT clause, 1 or 2 elements otherwise.
     */
    public EjbqlLimiterRange[] getLimiterRanges() {
        return limiterRanges;
    }

    /**
     * Get the query evaluator of the optimized query. The query is optimized on first call.
     * All mappers of the query should have been already set with the help of the iterator
     * returned by the <code>getJormExtentIterator</code> method.
     * @throws MedorException
     */
    public QueryEvaluator getOptimizedQueryTree() throws MedorException {
        if (optimizedQuery == null) {
            if (TraceEjb.isDebugQuery()) {
                TraceEjb.query.log(BasicLevel.DEBUG,
                                   "Initial QueryTree of the method " + methodName
                                   + " of the bean " + beanName);
                QueryTreePrinter.printQueryTree(queryTree, TraceEjb.query, BasicLevel.DEBUG);
            }
            QueryTree qt = queryOptimizer.transform(queryTree);
            if (TraceEjb.isDebugQuery()) {
                TraceEjb.query.log(BasicLevel.DEBUG,
                                   "Optimized QueryTree of the method " + methodName
                                   + " of the bean " + beanName);
                QueryTreePrinter.printQueryTree(qt, TraceEjb.query, BasicLevel.DEBUG);
            }
            optimizedQuery = new MedorEvaluator(qt, 0);
        }
        return optimizedQuery;
    }

}
