/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 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
 *
 * Initial developer(s):
 * Contributor(s):
 *
 * --------------------------------------------------------------------------
 * $Id: MedorFactory.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.ejb21.jorm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.EJBException;

import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.ejb.DeploymentDescEjb2;
import org.ow2.jonas.deployment.ejb.EntityCmp2Desc;
import org.ow2.jonas.deployment.ejb.EntityDesc;
import org.ow2.jonas.deployment.ejb.MethodCmp2Desc;
import org.ow2.jonas.lib.ejb21.JContainer;
import org.ow2.jonas.lib.ejb21.TraceEjb;
import org.ow2.jonas.lib.ejb21.sql.EjbqlLimiterRange;
import org.ow2.jonas.lib.ejb21.sql.EjbqlQueryTreeHolder;


import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.medor.api.EvaluationException;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.eval.api.ConnectionResources;
import org.objectweb.medor.eval.api.QueryEvaluator;
import org.objectweb.medor.eval.lib.BasicEvaluationMetaData;
import org.objectweb.medor.eval.prefetch.api.PrefetchBuffer;
import org.objectweb.medor.eval.prefetch.lib.PrefetchBufferFactoryImpl;
import org.objectweb.medor.expression.api.ParameterOperand;
import org.objectweb.medor.expression.api.TypingException;
import org.objectweb.medor.lib.Log;
import org.objectweb.medor.optim.api.ExecPlanGenerator;
import org.objectweb.medor.optim.api.QueryTransformer;
import org.objectweb.medor.optim.jorm.Jorm2Rdb;
import org.objectweb.medor.optim.lib.BasicQueryRewriter;
import org.objectweb.medor.optim.lib.FlattenQueryTreeRule;
import org.objectweb.medor.optim.lib.IndexesGenerator;
import org.objectweb.medor.query.api.QueryLeaf;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.tuple.api.TupleCollection;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This class does the initialisation of Medor and permits to access to the
 * query. The optimisation of the query tree is done during the first time that
 * it is used.
 * @author S.Chassande-Barrioz
 */
public abstract class MedorFactory extends JormFactory {

    /**
     * This field references the query transformer which must be used to
     * optimize the medor requests.
     */
    protected QueryTransformer queryTransformer = null;

    protected Manager miManager = null;

    protected ExecPlanGenerator indexesGenerator;

    protected boolean optimizeAtInit = false;

    /**
     * This is the prefetch index for the query method CURRENTLY evaluated
     */
    private int prefetchIndex;

    private HashMap qthList = new HashMap();

    public MedorFactory() {
        super();
        if (Log.loggerFactory != TraceEjb.loggerFactory) {
            Log.loggerFactory = TraceEjb.loggerFactory;
        }
    }

    /**
     * Build the EjbqlQueryTreeHolder or just return it if already there.
     * @param methodIndex is method index which permits to find a medor request.
     * @return the QueryTreeHolder for that method
     */
    public EjbqlQueryTreeHolder getQueryTreeHolder(int methodIndex) throws Exception {
        MethodCmp2Desc mcd = (MethodCmp2Desc) dd.getMethodDesc(methodIndex);
        EjbqlQueryTreeHolder qth = (EjbqlQueryTreeHolder) qthList.get(new Integer(methodIndex));
        if (qth == null) {
            qth = new EjbqlQueryTreeHolder(mcd, mcd.getQueryNode(), mapper, miManager);
            qthList.put(new Integer(methodIndex), qth);
        }
        return qth;
    }
    
    /**
     * It evaluate an optimized medor request according to the specified
     * parameters To evalute the medor request the query evaluator is used.
     * @param conn is the connection handle
     * @param methodIndex is method index which permits to find a medor request.
     * @param parameters is the parameters (key=parameter name / value=parameter
     *        value)
     * @return TupleCollection is the result of the request
     */
    public TupleCollection evaluate(Object conn, int methodIndex, ParameterOperand[] parameters) throws MedorException {

        MethodCmp2Desc mcd = (MethodCmp2Desc) dd.getMethodDesc(methodIndex);

        // It retrieves a medor request which is evaluable and optimized. The method
        // index is translate into a request index, then the found request is
        // optimized if it is not already.
        EjbqlLimiterRange[] limiterRanges = null;
        EjbqlQueryTreeHolder qth;
        QueryEvaluator evaluator = null;
        synchronized (this) {
            try {
                qth = getQueryTreeHolder(methodIndex);
            } catch (Exception e) {
                throw new MedorException("Cannot build QueryTree:" + e);
            }
            // set the prefetch index for this method
            setPrefetchIndex(qth.getPrefetchIndex());
            // set the optimizer if not set yet
            if (qth.getQueryOptimizer() == null) {
                if (queryTransformer == null) {
                    ArrayList rules = new ArrayList(2);
                    rules.add(new FlattenQueryTreeRule());
                    rules.add(new Jorm2Rdb());
                    queryTransformer = new BasicQueryRewriter(rules);
                }
                qth.setQueryOptimizer(new QueryTransformer() {

                    public QueryTree transform(QueryTree qt) throws MedorException {
                        QueryTree qt1 = queryTransformer.transform(qt);
                        if (indexesGenerator == null) {
                            throw new Error("getOptimizedRequest: indexesGenerator == null");
                        }
                        return indexesGenerator.transform(qt1);
                    }
                });
            }
            limiterRanges = qth.getLimiterRanges();
            evaluator =  qth.getOptimizedQueryTree();
        }

        // Calculates and gets the required connection resources for this query
        ConnectionResources cr = evaluator.getRequiredConnectionResources();

        // Gets the QueryLeafs that requierd connections
        QueryLeaf[] leaves = cr.getRequiredQueryLeafConnection();

        // Setting QueryLeaf's appropriated connection Object
        // TODO : All jorm classes are not mapped on the same mapper
        // than the current class - should use connections from different
        // datasources.
        if (conn == null) {
            throw new MedorException("invalid connection handle [null]");
        }
        for (int i = 0; (i < leaves.length); i++) {
            cr.setConnection(leaves[i], conn);
        }
        // PrefetchBuffer initialization if prefetch tag setting
        PrefetchBuffer prefetchBuffer = null;
        if (mcd.getPrefetch()) {
            Object tx = null;
            try {
                tx = this.getTransactionManager().getTransaction();
                if (tx != null) {
                    prefetchBuffer = mapper.getPrefetchCache().createPrefetchBuffer(new PrefetchBufferFactoryImpl(),
                            this, tx, getPrefetchIndex(), true);
                }
            } catch (javax.transaction.SystemException e) {
                // TODO : Que fait-on ???
                TraceEjb.logger.log(BasicLevel.ERROR,
                        "Cannot get the current transaction to create the Prefetch buffer:", e);
            }
        }
        // Map of EvaluationMetaData initialization needed for LIMIT clause
        Map evalMDMap = null;
        if (limiterRanges.length > 0) {
            try {
                // There is a LIMIT clause, init the Map of EvaluationMetaData
                evalMDMap = new HashMap();
                BasicEvaluationMetaData evalMD = new BasicEvaluationMetaData();
                EjbqlLimiterRange range = limiterRanges[0];
                if (range.getKind() == EjbqlLimiterRange.KIND_PARAMETER) {
                    evalMD.setLimitedRangeStartAt(parameters[range.getValue()].getInt());
                } else {
                    evalMD.setLimitedRangeStartAt(range.getValue());
                }
                if (TraceEjb.isDebugQuery()) {
                    TraceEjb.query.log(BasicLevel.LEVEL_DEBUG, "Query with LIMITer Range Start At = "
                            + evalMD.getLimitedRangeStartAt() + " for method name " + mcd.getMethod().getName());
                }
                if (limiterRanges.length > 1) {
                    range = limiterRanges[1];
                    if (range.getKind() == EjbqlLimiterRange.KIND_PARAMETER) {
                        System.out.println("parameters[range.getValue()].getInt() = "
                                + parameters[range.getValue()].getInt());
                        evalMD.setLimitedRangeSize(parameters[range.getValue()].getInt());
                    } else {
                        evalMD.setLimitedRangeSize(range.getValue());
                    }
                    if (TraceEjb.isDebugQuery()) {
                        TraceEjb.query.log(BasicLevel.LEVEL_DEBUG, "Query with LIMITer Range Size = "
                                + evalMD.getLimitedRangeSize() + " for method name " + mcd.getMethod().getName());
                    }
                }
                evalMDMap.put(leaves[0], evalMD);
            } catch (TypingException te) {
                throw new MedorException("Invalid parameter for a LIMIT range", te);
            }
        }
        try {
            return evaluator.evaluate(parameters, cr, prefetchBuffer, evalMDMap);
        } catch (EvaluationException e) {
            throw e;
        }
    }

    public void init(EntityDesc ed, JContainer c, String mapperName) {
        super.init(ed, c, mapperName);

        ecd = (EntityCmp2Desc) ed;

        //------------------- JORM META INFORMATION BUILDING ----------------//
        if (TraceEjb.isVerbose()) {
            TraceEjb.logger.log(BasicLevel.DEBUG, "Jorm Meta information building");
        }
        DeploymentDescEjb2 dd = ecd.getDeploymentDescEjb2();
        miManager = c.getJormMapping().getJormMIManager();

        //---------------------- MEDOR INITIALIZATION -----------------------//
        if (TraceEjb.isVerbose()) {
            TraceEjb.logger.log(BasicLevel.DEBUG, "Medor initialisation");
        }
        indexesGenerator = new IndexesGenerator();

    }

    private void setPrefetchIndex(int i) {
        prefetchIndex = i;
    }

    private int getPrefetchIndex() {
        return prefetchIndex;
    }

}