/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Struts", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */


package org.apache.velocity.tools.view.tools;


import java.util.Collections;
import java.util.List;
import org.apache.velocity.tools.view.context.ViewContext;


/**
 * Abstract context tool for doing searching and result
 * pagination in Velocity templates
 *
 * In order for this to be useful, developers need only 
 * extend this class and implement the 
 * <code>public List executeQuery(String criteria)</code>
 * method in the subclass.
 *
 * @author <a href="mailto:nathan@esha.com">Nathan Bubna</a>
 * @version $Revision: 1.2 $ $Date: 2002/02/11 16:49:17 $
 */

public abstract class SearchTool extends PullTool {


    public static final String PARAM_CRITERIA       = "find";
    public static final String PARAM_INDEX          = "index";
    public static final String PARAM_SHOW           = "show";
    public static final int    DEFAULT_INDEX        = 0;
    public static final int    DEFAULT_SHOW         = 10;
    public static final String KEY_SEARCH_RESULTS   = "searchResults";

    private List results;
    private String criteria;
    private int index;
    private int next;
    private int prev;
    private int show;


    /**
     * Constructor to obtain a factory. Use method {@link #init(ViewContext context)} to obtain
     * instances of the tool.
     */
    public SearchTool()
    {
        //do nothing
    }


    /**
     * For internal use only! Use method {@link #init(ViewContext context)} to obtain instances of
     * the tool.
     */
    public SearchTool(ViewContext context)
    {
        super(context);

        criteria = getStringParam(PARAM_CRITERIA, null);
        show = getIntParam(PARAM_SHOW, DEFAULT_SHOW);
        if (show < 1)
        {
            show = 1;
        }
        index = getIntParam(PARAM_INDEX, DEFAULT_INDEX);
        if (index < 0)
        {
            index = 0;
        }
    }


    /**************************** PullTool methods ****************************/

    public Object init(ViewContext context)
    {
        return new SearchTool(context);
    }


    public void reset()
    {
        results = null;
        criteria = null;
        index = DEFAULT_INDEX;
        show = DEFAULT_SHOW;
        next = index;
        prev = index;
    }


    public void log(String s)
    {
        super.log("[SearchTool] "+s);
    }



    /**************************** data access methods ****************************/


    /**
     * Performs a search for a given criteria
     */
    private void search()
    {
        if (criteria != null && criteria.length() > 0)
        {

            SearchResults sr = null;
            if (getSession() != null)
            {
                //try to get the results from the session.
                sr = (SearchResults)getSession().getAttribute(KEY_SEARCH_RESULTS);
            }
            if (sr == null || !criteria.equals(sr.getCriteria()))
            {
                log("executing new search for \""+criteria+"\"");

                try
                {
                    results = executeQuery(criteria);
                }
                catch (Exception e)
                {
                    log("an exception occurred executing query for \"" + criteria + "\": " + e.getMessage());
                    results = Collections.EMPTY_LIST;
                }
                
                //try to store results in the session so we don't have to
                //re-execute the query for each page
                if (getSession() != null)
                {
                    getSession().setAttribute(KEY_SEARCH_RESULTS, sr);
                }
            }
            else
            {
                results = sr.getResults();
            }

            next = Math.min(results.size(), index+show);
            prev = Math.max(DEFAULT_INDEX, index-show);
        }
        else
        {
            //no criteria
            results = null;
            next = DEFAULT_INDEX;
            prev = DEFAULT_INDEX;
        }
    }


    /**
     * Subclasses should override this to perform queries
     * e.g.  a UserSearchTool should implement this to return a list of users
     */
    public abstract List executeQuery(String criteria) throws Exception;


    /************************ search data access ********************/


    /**
     * Return criteria string
     */
    public String getCriteria()
    {
        return criteria;
    }


    /**
     * Return first index of current page
     */
    public int getIndex()
    {
        return index;
    }


    /**
     * Return current number of results shown per page
     */
    public int getShow()
    {
        return show;
    }


    /**
     * Return first index of next page
     */
    public int getNext()
    {
        return next;
    }


    /**
     * Return first index of previous page
     */
    public int getPrev()
    {
        return prev;
    }


    /**
     * Return true if there are any results
     */
    public boolean hasResults()
    {
        return (getResults() != null && getResults().size() > 0);
    }


    /**
     * Return List of all results for the criteria
     */
    public List getResults()
    {
        if (results == null)
        {
            search();
        }
        return results;
    }


    /**
     * Return the total number of results for the criteria
     */
    public int getResultCount()
    {
        return hasResults() ? getResults().size() : 0;
    }


    /**
     * Return true if there are any results on the selected page
     */
    public boolean hasPage()
    {
        return (hasResults() && getResults().size() > index);
    }


    /**
     * Return List of all results on the selected page
     */
    public List getPage()
    {
        return hasResults() ? getResults().subList(index, next) : null;
    }



    /**
     * Return number of pages that can be created from
     * these results given the value of show
     */
    public int getPageCount()
    {
        return hasResults() ? (int)Math.ceil(getResults().size()/(double)show) : 0;
    }


    /**
     * Return true if there are results on the next page
     */
    public boolean hasNext()
    {
        return (hasResults() && getResults().size() > next);
    }


    /**
     * Return true if there are results on the previous page
     */
    public boolean hasPrev()
    {
        return (hasResults() && index > DEFAULT_INDEX);
    }


    /************************ inner util class ********************/


    /**
     * Simple utility class to hold a matching
     * criteria and result list
     */
    public class SearchResults
    {

        private String srCriteria;
        private List srResults;


        public SearchResults(String criteria, List results)
        {
            srCriteria = criteria;
            srResults = results;
        }


        public String getCriteria()
        {
            return srCriteria;
        }


        public List getResults()
        {
            return srResults;
        }


    }


}

