/*
 * 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.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import org.apache.velocity.tools.view.context.ViewContext;
import org.apache.velocity.tools.view.tools.ContextTool;


/**
 * Context tool for easy construction of links in templates
 *
 * It is important to note that instances of this tool are essentially
 * immutable in that methods which would seem to modify its internal
 * representation actually do not.  they simply return a new instance
 * that reflects the 'requested change'.
 *
 * While this does make it difficult to extend this class and 
 * retain functionality, it does allow for cleaner repetitive use 
 * in Velocity templates.
 *
 * So, use of this class should be familiar to those who've used
 * the Turbine project's TemplateLink (which this is loosely modeled after),
 * but this is a fairly significant behavioral difference which I believe
 * offers cleaner, more intuitive use.
 *
 * e.g. 
 * #set( $foolink = $link.setPath('templates/SeeFoo.vm') )
 * <a href="$foolink.addQueryData('foo',42)">Foo 42</a>
 * <a href="$foolink.addQueryData('foo',14)">Foo 14</a>
 *
 * @author <a href="mailto:nathan@esha.com">Nathan Bubna</a>
 * @version $Id: LinkTool.java,v 1.2 2002/02/07 22:31:39 nathan Exp $
 */

public class LinkTool implements ContextTool
{


    public static final String HTTP = "http";
    public static final String HTTPS = "https";
    public static final String TEMPLATE_SUFFIX = ".vm";

    private final HttpServletRequest request;
    private final String path;
    private final ArrayList queryData;


    /**
     * Constructor to obtain a factory. Use
     * method {@link #init(ViewContext context)}
     * to obtain instances of the tool.
     */
    public LinkTool()
    {
        this.request = null;
        this.path = null;
        this.queryData = null;
    }


    /**
     * For internal use.
     *
     * Use method {@link #init(ViewContext context)}
     * to obtain instances of the tool.
     */
    private LinkTool(ViewContext context)
    {
        this.request = context.getRequest();
        this.path = null;
        this.queryData = null;
    }


    /**
     * For internal use.
     *
     * Copies 'that' LinkTool into this one and adds the new query data
     */
    private LinkTool(LinkTool that, QueryPair pair)
    {
        this.request = that.request;
        this.path = that.path;
        if (that.queryData != null)
        {
            //set this query data to a shallow clone of that query data
            this.queryData = (ArrayList)that.queryData.clone();
        }
        else
        {
            this.queryData = new ArrayList();
        }
        //add new pair to this LinkTool's query data
        this.queryData.add(pair);
    }


    /**
     * For internal use.
     *
     * Copies 'that' LinkTool into this one and sets the new path
     */
    private LinkTool(LinkTool that, String path)
    {
        this.request = that.request;
        //set to new path
        this.path = path;
        //we don't need to clone here, this was not changed
        this.queryData = that.queryData;
    }


    /************************* ContextTool interface **************************/

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


    /**
     * This object is immutable, do nothing.
     */
    public void destroy(Object obj)
    {
        //nada!
    }


    /**************************** LinkTool methods ****************************/

    /**
     * Returns a copy of this link with the given path setting
     *
     * NOTE! this will override any previous path, action or template setting!
     */
    public LinkTool setPath(String path)
    {
        return new LinkTool(this, path);
    }


    /**
     * This is a convenience method for setting the path to a template
     * 
     * Returns a copy of this link with the template as the new path
     * if the template does not already end in TEMPLATE_SUFFIX, this will append it
     *
     * NOTE! this will override any previous path, action or template setting!
     */
    public LinkTool setTemplate(String template)
    {
        if (template != null && !template.endsWith(TEMPLATE_SUFFIX))
        {
            template = template.concat(TEMPLATE_SUFFIX);
        }
        return new LinkTool(this, template);
    }


    /**
     * Adds key/value pair to the query data
     * This returns a new LinkTool containing both
     * a copy of this LinkTool's query data and the new data
     * This makes repeated use in velocity templates much easier
     */
    public LinkTool addQueryData(String key, Object value)
    {
        return new LinkTool(this, new QueryPair(key, value));
    }


    /**
     * Returns the current path set for this link
     * e.g. "Index.vm" or "images/spacer.gif"
     */
    public String getPath()
    {
        return path;
    }


    /**
     * Returns this link's query data as an url-encoded string
     * e.g. "key=value&foo=this+is+encoded"
     */
    public String getQueryData()
    {
        if (queryData != null && !queryData.isEmpty())
        {

            StringBuffer out = new StringBuffer();
            for(int i=0; i < queryData.size(); i++)
            {
                out.append(queryData.get(i));
                if (i+1 < queryData.size())
                {
                    out.append('&');
                }
            }
            return out.toString();
        }
        return null;
    }


    /**
     * Returns the server & context url
     * e.g. "http://myserver.net/myapp"
     */
    public String getServerPath()
    {
        String scheme = request.getScheme();
        int port = request.getServerPort();

        StringBuffer out = new StringBuffer();
        out.append(request.getScheme());
        out.append("://");
        out.append(request.getServerName());
        if ((scheme.equals(HTTP) && port != 80) ||
            (scheme.equals(HTTPS) && port != 443))
        {
            out.append(":");
            out.append(port);
        }
        out.append(request.getContextPath());
        return out.toString();
    }


    /**
     * Returns a full reference to the requested
     * uri minus it's query data
     * e.g. "http://myserver.net/myapp/stuff/View.vm"
     *
     * NOTE! this returns that base ref of the last request.
     *     this will NOT represent any path or query data
     *     set for this LinkTool!
     */
    public String getBaseRef()
    {
        StringBuffer out = new StringBuffer();
        out.append(getServerPath());
        out.append(request.getServletPath());
        return out.toString();
    }


    /**
     * Returns the full URI that's been built with this tool
     * e.g. "http://myserver.net/myapp/stuff/View.vm?id=42&type=blue"
     */
    public String toString()
    {
        StringBuffer out = new StringBuffer();
        out.append(getServerPath());

        if (path == null)
        {
            //no path specified, so use current servlet path
            out.append(request.getServletPath());
        } 
        else
        {
            if (!path.startsWith("/"))
            {
                out.append("/");
            }
            out.append(path);
        }

        String query = getQueryData();
        if (query != null)
        {
            out.append("?");
            out.append(query);
        }

        return out.toString();
    }


    /**************************** internal class *********************************/

    /**
     * Internal util class to handle representation and
     * encoding of key/value pairs in the query string
     */
    final class QueryPair
    {

        private final String key;
        private final Object value;

        public QueryPair(String key, Object value) 
        {
            this.key = key;
            this.value = value;
        }


        public String toString()
        {
            StringBuffer out = new StringBuffer();
            out.append(URLEncoder.encode(key));
            out.append('=');
            out.append(URLEncoder.encode(String.valueOf(value)));
            return out.toString();
        }
    }


}
