/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/BasicQueryEnvelope.java,v 1.12 2004/11/01 17:58:02 luetzkendorf Exp $
 * $Revision: 1.12 $
 * $Date: 2004/11/01 17:58:02 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */


package org.apache.slide.search.basic;

import org.jdom.Element;
import org.apache.slide.search.PropertyProvider;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.SearchQuery;
import org.apache.slide.search.SlideUri;
import org.apache.slide.search.SearchQueryResult;
import org.apache.slide.search.InvalidScopeException;
import org.apache.slide.search.SearchToken;
import org.apache.slide.common.RequestedProperties ;
import org.apache.slide.common.Scope;
import org.apache.slide.common.Uri;
import org.apache.slide.common.SlideRuntimeException;
import org.apache.slide.common.Namespace;

import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.store.AbstractStore ;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Enumeration;


/**
 * Envelopes all queries that are necessary, if a scope covers several stores.
 * For example: /mycoll is mapped to an SQL store, /mycoll/xml is mapped to an
 * XML database. A query with scope /mycoll must be divided in two different
 * queries, the result must be joined.
 *
 * @version $Revision: 1.12 $
 *
 **/
public class BasicQueryEnvelope extends SearchQuery implements IBasicQuery {
    
    
    /**
     * Should never be called ??
     *
     * @return   an IBasicExpressionFactory
     *
     */
    public IBasicExpressionFactory getContentExpressionFactory()
    {
        if (true) throw new SlideRuntimeException ("method should never be called");
        return null;
    }
    
    /**
     * Should never be called ??
     *
     * @return   an IBasicExpressionFactory
     *
     */
    public IBasicExpressionFactory getPropertiesExpressionFactory()
    {
        if (true) throw new SlideRuntimeException ("method should never be called");
        return null;
    }
    
    private BasicQueryImpl topLevelQuery;
    private QueryScope topLevelQueryScope;
 
    private Map subQueries = new HashMap();
    
    private QueryTree queryTree;
    
    private int queryDepth;
    
    private SlideUri slideUri;
    
    private SearchToken token;
    
    
    /*
     * Constructs a BasicQueryEnvelope
     *
     * @param token  the searchtoken
     * @param queryScope  the scope of this query
     *
     * @throws   InvalidScopeException
     */
    public BasicQueryEnvelope (SearchToken token, QueryScope queryScope)
        throws InvalidScopeException
    {
        this.token = token;
        Namespace namespace = token.getNamespace();
        Enumeration stores = namespace.enumerateScopes();
        this.slideUri = token.getSlideContext();
        this.topLevelQueryScope = queryScope;
        String slideScope = slideUri.getSlidePath(queryScope.getHref());
        Scope topScope = new Scope (slideScope);
        
        Set ex = queryScope.getExcludedScopes();
        int size = ex.size();
        Scope [] excludedScopes = new Scope [size] ;
        Iterator it = ex.iterator();
        
        for (int i = 0; i < size; i++) {
            String href = (String)it.next();
            excludedScopes [i] = new Scope (slideUri.getSlidePath(href));
        }
        
        this.queryTree = new QueryTree (stores, topScope, excludedScopes);
        
        it = queryTree.iterator();
        
        while (it.hasNext()) {
            Scope sScope = (Scope)it.next();
            // System.out.println("createEnvelope for scope " + sScope);
            BasicQueryImpl query = createSubQuery (namespace, sScope.toString(), token);
            subQueries.put (sScope, query);
        }
        
        topLevelQuery = (BasicQueryImpl) subQueries.get (topScope);
    }
    
    
    /**
     * parses all subQueries. The scope must be calculated for each subquery.
     *
     * @param    basicSearchElement  an Element
     * @param    propertyProvider    a  PropertyProvider
     *
     * @throws   BadQueryException
     *
     */
    public void parseQueryElement (Element basicSearchElement,
                                   PropertyProvider propertyProvider)
        throws BadQueryException
    {
        //topLevelQuery.parseQueryElement (basicSearchElement, propertyProvider);
        // queryDepth = topLevelQueryScope.getDepth();

        // WAM restrict depth to what is defined in web.xml
        queryDepth = Math.min (topLevelQueryScope.getDepth(), token.getMaxDepth());
        
        Iterator it = subQueries.keySet().iterator();
        Set scopesToRemove = new HashSet ();
        while (it.hasNext()) {

            Scope scope = (Scope)it.next();
            // System.out.println ("parseQueryElement for scope " + scope);
            QueryScope subQueryScope = calculateSubQueryScope (scope);
            if (subQueryScope.getDepth() >= 0) {
                BasicQueryImpl query = (BasicQueryImpl)subQueries.get (scope);
                query.parseQueryElement (basicSearchElement, propertyProvider, subQueryScope);
            }
            else {
                scopesToRemove.add (scope);
            }
            }
        it = scopesToRemove.iterator();
        while (it.hasNext()) {
            subQueries.remove(it.next());
        }
    }
    
    /**
     * Executes each involved query and merges the results
     *
     * @return   a SearchQueryResult
     *
     * @throws   ServiceAccessException
     *
     */
    public SearchQueryResult execute() throws ServiceAccessException {
        Iterator it = subQueries.keySet().iterator();
        SearchQueryResult result = null;
        
        if (topLevelQuery.orderBy != null) {
            result = new SearchQueryResult (topLevelQuery.orderBy.getComparator());
        }
        else {
            result = new SearchQueryResult ();
        }
        
        
        
        while (it.hasNext()) {
            Scope scope = (Scope)it.next();
            
            BasicQuery query = (BasicQuery)subQueries.get (scope);
            query.setScope (calculateSubQueryScope(scope));
            SearchQueryResult subResult = query.execute();
            result.add (subResult);
            if (subResult.getStatus() != 0) {
                result.setStatus (subResult.getStatus());
                result.setDescription (subResult.getDescription());
            }
        }
        
        return result;
    }
    
    /**
     * calculates a QueryScope for the defined store
     *
     * @param    scope               the scope
     *
     * @return   a QueryScope
     *
     */
    private QueryScope calculateSubQueryScope (Scope scope) {
        int relDepth = queryTree.relativeDepth (scope);
        int subQueryDepth;
//        if (queryTree.hasChildren (scope)) {
//            subQueryDepth = 1;
//        }
//        else 
        if (queryDepth == QueryScope.DEPTH_INFINITY) {
            subQueryDepth = QueryScope.DEPTH_INFINITY;
        }
        else {
            subQueryDepth = queryDepth - relDepth;
        }
        String contextPath = slideUri.getContextPath (scope.toString());
        
        
        Set inclSet = topLevelQueryScope.getIncludeSet();
        Set exclSet = topLevelQueryScope.getExcludeSet();
        
        QueryScope queryScope;
        if (queryTree.hasChildren (scope)) {
            queryScope = new BasicQueryScope(contextPath, subQueryDepth, 
                    inclSet, exclSet, queryTree.getChildren(scope));
        } else {
            queryScope = new BasicQueryScope(contextPath, subQueryDepth, 
                    inclSet, exclSet, Collections.EMPTY_SET);
        }
        
        return queryScope;
    }
    
    
//    /**
//     * Method getDepthOfHRef
//     *
//     * @param    href                a  String
//     *
//     * @return   an int
//     *
//     */
//    private int getDepthOfHRef (String href) {
//        StringTokenizer st = new StringTokenizer (href, "/");
//        return st.countTokens();
//    }
    
    
    /**
     * creates a subquery
     *
     * @param    namespace           a  Namespace
     * @param    slideScope          a  String
     * @param    token               a  SearchToken
     *
     * @return   a BasicQueryImpl
     *
     * @throws   SlideRuntimeException
     *
     */
    private BasicQueryImpl createSubQuery (Namespace namespace,
                                           String slideScope,
                                           SearchToken token)
        throws SlideRuntimeException
    {
        BasicQueryImpl query = null;
        
//      Uri uri = new Uri (namespace, slideScope);
        Uri uri = namespace.getUri(token.getSlideToken(), slideScope);
        
        
        AbstractStore store = (AbstractStore)uri.getStore();
        
        // todo: can we make the query storeindependant?
        String className = (String)store.getParameter
            (BasicSearchLanguage.BASIC_QUERY_CLASS);
        
        if (className != null) {
            try {
                Class queryClass = Class.forName (className);
                query = (BasicQueryImpl) queryClass.newInstance();
                query.init (token);
            }
            catch (Exception e) {
                e.printStackTrace();
                throw new SlideRuntimeException (e.getMessage());
            }
        }
        else {
            query =  new BasicQueryImpl(token);
        }
        
        return query;
    }
    /**
     * Method getStore
     *
     * @return   an AbstractStore
     *
     */
    public AbstractStore getStore() {
        return topLevelQuery.getStore();
    }
    
    /**
     * Method getScope
     *
     * @return   a QueryScope
     *
     */
    public QueryScope getScope() {
        return topLevelQueryScope;
    }
    
    /**
     * Method getPropertyProvider
     *
     * @return   a PropertyProvider
     *
     */
    public PropertyProvider getPropertyProvider() {
        return topLevelQuery.getPropertyProvider();
    }
    
    /**
     * Method isLimitDefined
     *
     * @return   a boolean
     *
     */
    public boolean isLimitDefined() {
        return topLevelQuery.isLimitDefined();
    }
    
    /**
     * Method getLimit
     *
     * @return   an int
     *
     */
    public int getLimit() {
        return topLevelQuery.getLimit();
    }
    
    /**
     * Method getExpression
     *
     * @return   an IBasicExpression
     *
     */
    public IBasicExpression getExpression() {
        return topLevelQuery.getExpression();
    }
    
    /**
     * Method requestedProperties
     *
     * @return   a RequestedProperties
     *
     */
    public RequestedProperties requestedProperties() {
        return topLevelQuery.requestedProperties;
    }
    
    /**
     * Method getSlidePath
     *
     * @return   a String
     *
     * @throws   InvalidScopeException
     *
     */
    public String getSlidePath() throws InvalidScopeException {
        return topLevelQuery.getSlidePath();
    }
    
    /**
     * Method getSearchToken
     *
     * @return   a SearchToken
     *
     */
    public SearchToken getSearchToken() {
        return topLevelQuery.getSearchToken();
    }
    
    public void init (SearchToken token) {
        this.token = token;
    }
}
