/* ======================================================================
The Bodington System Software License, Version 1.0

Copyright (c) 2001 The University of Leeds.  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 acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS 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 was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/

====================================================================== */

package org.bodington.util;

import java.util.Enumeration;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import org.bodington.database.PersistentObject;
import org.bodington.database.PrimaryKey;
import org.bodington.server.BuildingServerException;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.ResourceTreeManager;
import org.bodington.server.resources.UploadedFile;

/**
 * Utility class for parsing URLs pointing to Bodington resources. Provides
 * methods for getting <code>resource_id</code> and
 * <code>uploadedFile_id</code> from a URL.
 * @author Antony Corfield
 * @author Matthew Buckett
 */
public class BodingtonURL
{
    private String hostname, servlet, protocol;
    private int port;
    private String href = "href", equal = "=", quote = "\"";
    private Vector parseWarning;

    /**
     * Create a new BodingtonURL using the properties from the BuildingServer.
     */
    private BodingtonURL()
    {
    }

    public BodingtonURL( HttpServletRequest request )
    {
        this( (request.isSecure()) ? "https" : "http", request.getServerName(),
            request.getServerPort(), request.getContextPath()
                + request.getServletPath() + "/" );
    }

    public BodingtonURL( String protocol, String hostname, int port,
        String servlet )
    {
        this.protocol = protocol;
        this.hostname = hostname;
        this.port = port;
        this.servlet = servlet;
    }

    /**
     * Checks for any warnings after parsing html/text href attributes pointing
     * to Bodington resources.
     * @return true if warnings.
     * @see #parseHtml(String)
     */
    public boolean isParseWarning()
    {
        return parseWarning != null && parseWarning.size() > 0;
    }

    /**
     * Get parse warning.
     * @return parseWarning a (String)vector containing warnings.
     * @see #parseHtml(String)
     */
    public Vector getParseWarning()
    {
        return parseWarning;
    }

    /**
     * Checks if resource has been placed in the recycling building.
     * @param resource the resource to be checked.
     * @return true if resource has been deleted.
     * @throws BuildingServerException if recycling building not found.
     */
    public boolean isResourceDeleted( Resource resource )
        throws BuildingServerException
    {
        Resource recycler;
        Enumeration enumeration = null;

        if ( resource == null )
            throw new BuildingServerException( "Resource is null" );

        enumeration = Resource.findResources( "http_facility_no = 30",
            "left_index" );
        if ( !enumeration.hasMoreElements()
            || (recycler = (Resource)enumeration.nextElement()) == null )
            throw new BuildingServerException(
                "Unable to locate the recycling building" );
        return resource.isInside( recycler );
    }

    /**
     * Parses URLs that contain a reference to a Bodington resource or
     * UploadedFile. If the URL contains a query with <code>res_id</code> or
     * <code>file_id</code> (e.g. <code>?res_id=1234</code>), then the URL
     * is updated if the resource has been moved. If a Bodington URL does not
     * contain the query part, then the res_id or file_id is found and inserted
     * into the URL. Parse errors resulting from bad href attribute syntax or
     * invalid/deleted resources are stored in a (String)Vector so that they can
     * be retrieved and reported.
     * @param in String to be parsed.
     * @return String containing updated Bodington URls, if any.
     */
    public String parseHtml( String in )
    {

        PrimaryKey resId;
        String url, s;
        int i, j = 0;

        parseWarning = new Vector();

        if ( in == null )
            return null;
        if ( in.trim().length() == 0 )
            return in;

        StringBuffer out = new StringBuffer();

        i = hrefStartIndex( in, j );
        while ( i > -1 )
        {
            out.append( in.substring( j, i ) );
            j = hrefEndIndex( in, i );
            if ( j > -1 )
            {
                s = in.substring( i, j ).trim();
                url = getResourceUrl( s );
                if ( url != null )
                    s = url; // updates url by lookup via id
                else
                {
                    url = getUploadedFileUrl( s );
                    if ( url != null )
                        s = url; // updates url by lookup via id
                    else if ( isBodingtonUrl( s ) )
                    {
                        resId = getResourceId( s );
                        if ( resId != null ) // get resource id if bodington
                                                // resource
                            s += "?res_id=" + resId.toString();
                        else
                        {
                            resId = getUploadedFileId( s );
                            if ( resId != null ) // get file id if uploaded
                                                    // file
                                s += "?file_id=" + resId.toString();
                            else
                            {
                                parseWarning.add( s
                                    + " - cannot find this Bodington resource" );
                            }
                        }
                    }
                }
            }
            else
                break;
            out.append( s );
            i = hrefStartIndex( in, j + quote.length() );
        }
        if ( i < 0 )
            out.append( in.substring( j ) );
        else if ( j < 0 )
        {
            out.append( in.substring( i ) );
            parseWarning.add( in.substring( i, i + 50 )
                + " - error parsing HREF attribute" );
        }
        return out.toString();
    }

    private int hrefStartIndex( String in, int fromIndex )
    {

        String s;
        int i, j, k, m, n;
        if ( in == null || fromIndex < 0 || fromIndex > in.length() - 1 )
        {
            parseWarning.add( "Error parsing html/text" );
            return -1;
        }
        i = in.indexOf( href, fromIndex );
        if ( i < 0 )
            return -1;
        j = in.indexOf( quote, i + href.length() );
        if ( j < 0 )
        {
            parseWarning.add( in.substring( i, i + 50 )
                + " - error parsing HREF attribute" );
            return -1;
        }
        k = i + href.length();
        if ( !in.substring( k, j ).trim().equals( equal ) )
        {
            parseWarning.add( in.substring( i, i + 50 )
                + " - error parsing HREF attribute" );
            return -1;
        }
        k = j + quote.length();
        m = in.indexOf( quote, k );
        if ( m < 0 )
        {
            parseWarning.add( in.substring( i, i + 50 )
                + " - error parsing HREF attribute" );
            return -1;
        }
        n = in.indexOf( " ", k );
        if ( n > -1 && n < m )
        {
            parseWarning.add( in.substring( i, i + 50 )
                + " - error parsing HREF attribute" );
            return -1;
        }

        return k;
    }

    private int hrefEndIndex( String in, int fromIndex )
    {
        return in.indexOf( quote, fromIndex );
    }

    private String getResourceUrl( String s )
    {
        Resource res;
        String url;
        int i, id;
        boolean deleted = false;

        i = s.indexOf( "?res_id=" );
        if ( i < 0 )
            return null;

        try
        {
            id = Integer.parseInt( s.substring( i + 8 ) );
        }
        catch ( NumberFormatException ex )
        {
            parseWarning.add( s + " - the res_id is not a valid integer" );
            return null;
        }

        res = getResource( new PrimaryKey( id ) );
        if ( res == null )
        {
            parseWarning.add( s + " - resource not found" );
            return null;
        }

        try
        {
            deleted = isResourceDeleted( res );
        }
        catch ( BuildingServerException ex1 )
        {
        }
        if ( deleted )
        {
            parseWarning.add( s + " - resource deleted" );
            return null;
        }

        url = getResourceUrl( res );
        if ( url == null )
        {
            parseWarning.add( s + " - cannot find the name for this resource" );
            return null;
        }
        return url + s.substring( i );
    }

    private String getUploadedFileUrl( String s )
    {
        Resource res;
        UploadedFile ulf;
        String url;
        int i, id;
        boolean deleted = false;

        i = s.indexOf( "?file_id=" );
        if ( i < 0 )
            return null;

        try
        {
            id = Integer.parseInt( s.substring( i + 9 ) );
        }
        catch ( NumberFormatException ex )
        {
            parseWarning.add( s + " - the file_id is not a valid integer" );
            return null;
        }

        ulf = getUploadedFile( new PrimaryKey( id ) );
        if ( ulf == null )
        {
            parseWarning.add( s + " - uploaded file not found" );
            return null;
        }

        if ( ulf.isDeleted() )
        {
            parseWarning.add( s + " - uploaded file deleted" );
            return null;
        }

        res = getResource( ulf.getResourceId() );
        try
        {
            deleted = isResourceDeleted( res );
        }
        catch ( BuildingServerException ex1 )
        {
        }
        if ( deleted )
        {
            parseWarning.add( s + " - uploadedFile parent resource deleted" );
            return null;
        }

        url = getUploadedFileUrl( res, ulf );
        if ( url == null )
        {
            parseWarning.add( s
                + " - cannot find the name for this uploaded file" );
            return null;
        }
        return url + s.substring( i );
    }

    /**
     * Gets a Resource from a Bodington URL.
     * @param url pointing to Bodington resource.
     * @return Resource or Null if not found.
     */
    public Resource getResource( String url )
    {
        String name = getResourceName( url );
        if ( name == null )
            return null;

        try
        {
            return ResourceTreeManager.getInstance().findResource( name );
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    /**
     * Gets a Resource via resource_id.
     * @param resource_id for resource.
     * @return Resource or Null if not found.
     */
    public Resource getResource( PrimaryKey resource_id )
    {
        if ( resource_id == null )
            return null;
        try
        {
            PersistentObject obj = PersistentObject.findPersistentObject(
                resource_id, "org.bodington.server.resources.Resource" );
            if ( obj == null || !(obj instanceof Resource) )
                return null;
            return (Resource)obj;
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    private PrimaryKey getResourceId( String url )
    {
        Resource res = null;
        boolean deleted = false;

        res = getResource( url );
        if ( res == null )
            return null;

        try
        {
            deleted = isResourceDeleted( res );
        }
        catch ( BuildingServerException ex1 )
        {
        }
        if ( deleted )
        {
            parseWarning.add( url
                + " - this Bodington resource has been deleted" );
            return null;
        }
        return res.getResourceId();
    }

    /**
     * Gets UploadedFile name from a Bodington URL.
     * @param url pointing to Bodington UploadedFile.
     * @return String the UploadedFile name.
     */
    public String getUploadedFileName( String url )
    {
        UploadedFile ulf;

        try
        {
            ulf = getUploadedFile( url );
            if ( ulf == null )
                return null;
            return ulf.getNameInResource();
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    /**
     * Gets an UploadedFile via file_id.
     * @param file_id for UploadedFile.
     * @return UploadedFile found by file_id or Null if not found.
     */
    public UploadedFile getUploadedFile( PrimaryKey file_id )
    {
        if ( file_id == null )
            return null;
        try
        {
            PersistentObject obj = PersistentObject.findPersistentObject(
                file_id, "org.bodington.server.resources.UploadedFile" );
            if ( obj == null || !(obj instanceof UploadedFile) )
                return null;
            return (UploadedFile)obj;
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    /**
     * Gets an UploadedFile from a Bodington URL.
     * @param url pointing to an UploadedFile.
     * @return UploadedFile or Null if not found.
     */
    public UploadedFile getUploadedFile( String url )
    {
        Resource res;
        PrimaryKey resId;
        UploadedFile ulf;
        String name;
        int i;

        res = getUploadedFileResource( url );
        if ( res == null )
            return null;

        try
        {
            resId = res.getResourceId();
            name = res.getFullName();
            i = url.indexOf( name );
            if ( i < 0 )
                return null;

            i += name.length();
            name = url.substring( i );
            return UploadedFile.findUploadedFileByPath( resId, name );
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    private PrimaryKey getUploadedFileId( String url )
    {
        UploadedFile ulf = null;

        ulf = getUploadedFile( url );
        if ( ulf == null )
            return null;
        if ( ulf.isDeleted() )
            parseWarning.add( url + " - this uploaded file has been deleted" );
        return ulf.getPrimaryKey();
    }

    /**
     * Gets the Resource for an UploadedFile from a URL pointing to an
     * UploadedFile.
     * @param url pointing to Bodington resource.
     * @return Resource found by Bodington URL or Null if not found.
     */
    public Resource getUploadedFileResource( String url )
    {
        Resource res;
        String name;
        int i;

        if ( url == null || (url = url.trim()).length() == 0 )
            return null;

        if ( url.endsWith( "/" ) )
            url = url.substring( 0, url.length() - 1 );

        i = url.lastIndexOf( "/" );
        if ( i < 0 )
            return null;

        url = url.substring( 0, i + 1 );
        name = getResourceName( url );
        if ( name == null )
            return null;

        try
        {
            res = ResourceTreeManager.getInstance().findResource( name );
            if ( res == null )
                return getUploadedFileResource( url );
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
        return res;
    }

    /**
     * Gets the Bodington URL for a Resource.
     * @param resource_id PrimaryKey for Bodington Resource.
     * @param checkStatus when true checks if Resource has been put in the
     *        recycle building i.e. deleted.
     * @return String the Bodington URL or null if the resource does not exist
     *         or has been deleted.
     */
    public String getResourceUrl( PrimaryKey resource_id, boolean checkStatus )
    {
        Resource res;

        if ( resource_id == null )
            return null;

        res = getResource( resource_id );
        if ( res == null )
            return null;

        if ( checkStatus )
        {
            try
            {
                if ( isResourceDeleted( res ) )
                    return null;
            }
            catch ( BuildingServerException ex )
            {
                return null;
            }
        }
        return getResourceUrl( res );
    }

    /**
     * Gets the Bodington URL for an UploadedFile.
     * @param file_id PrimaryKey for UploadedFile.
     * @param checkStatus if true checks if UploadedFile has been put in the
     *        recycle building i.e. deleted.
     * @return String the Bodington URL or null if the UploadedFile does not
     *         exist or has been deleted.
     */
    public String getUploadedFileUrl( PrimaryKey file_id, boolean checkStatus )
    {
        PrimaryKey resource_id;
        Resource resource;
        UploadedFile ulf;

        if ( file_id == null )
            return null;

        ulf = getUploadedFile( file_id );
        if ( ulf == null )
            return null;

        if ( checkStatus && ulf.isDeleted() )
            return null;

        resource_id = ulf.getResourceId();
        resource = getResource( resource_id );
        if ( resource == null )
            return null;

        return getUploadedFileUrl( resource, ulf );
    }

    /**
     * Gets the Bodington URL from an UploadedFile.
     * @param ulf the UploadedFile.
     * @return String the Bodington URL for this UploadedFile.
     */
    public String getUploadedFileUrl( UploadedFile ulf )
    {
        PrimaryKey resource_id;
        Resource resource;
        String path, resName, ulfName;
        StringBuffer url = new StringBuffer();

        if ( ulf == null )
            return null;

        path = getPath( false );
        try
        {
            resource_id = ulf.getResourceId();
            resource = getResource( resource_id );
            if ( resource == null )
                return null;

            resName = resource.getFullName();
            ulfName = ulf.getNameInResource();
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
        if ( path.endsWith( "/" ) && resName.startsWith( "/" ) )
            path = path.substring( 0, path.length() - 1 );
        url.append( path );
        url.append( resName );
        url.append( ulfName );
        return url.toString();
    }

    /**
     * Gets the Bodington URL from an UploadedFile and it's parent Resource.
     * @param resource the UploadedFile parent Resource.
     * @param ulf the UploadedFile.
     * @return String the Bodington URL for this UploadedFile.
     */
    public String getUploadedFileUrl( Resource resource, UploadedFile ulf )
    {
        PrimaryKey resource_id;
        String path, resName, ulfName;
        StringBuffer url = new StringBuffer();

        if ( resource == null || ulf == null )
            return null;

        path = getPath( false );
        try
        {
            resName = resource.getFullName();
            ulfName = ulf.getNameInResource();
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
        if ( path.endsWith( "/" ) && resName.startsWith( "/" ) )
            path = path.substring( 0, path.length() - 1 );
        url.append( path );
        url.append( resName );
        url.append( ulfName );
        return url.toString();

    }

    /**
     * Gets the URL from a Bodington Resource.
     * @param resource the Bodington Resource.
     * @return String the Bodington URL for this Resource.
     */
    public String getResourceUrl( Resource resource )
    {
        String path, name;

        if ( resource == null )
            return getPath( false );

        StringBuffer url = new StringBuffer();
        path = getPath( false );
        try
        {
            name = resource.getFullName();
            if ( path.endsWith( "/" ) && name.startsWith( "/" ) )
                path = path.substring( 0, path.length() - 1 );
            url.append( path );
            url.append( name );
            return url.toString();
        }
        catch ( BuildingServerException ex )
        {
            return null;
        }
    }

    /**
     * Get the path to the start of the Bodington installation.
     * @param secure If the URL should be a https one.
     * @return A string for the URL.
     */
    private String getPath( boolean secure )
    {
        StringBuffer string = new StringBuffer();
        if ( "http".equalsIgnoreCase( protocol ) )
        {
            string.append( "http://" );
            string.append( hostname );
            string.append( (80 == port) ? "" : (":" + port) );
            string.append( servlet );
        }
        else if ( "https".equalsIgnoreCase( protocol ) )
        {
            string.append( "https://" );
            string.append( hostname );
            string.append( (443 == port) ? "" : (":" + port) );
            string.append( servlet );
        }
        else
        {
            string.append( protocol + "://" );
            string.append( hostname );
            string.append( ":" + port );
            string.append( servlet );
        }

        return string.toString();
    }

    /**
     * Checks if a URL points to the Bodington site. Use
     * {@link #isValidBodingtonUrl(String)} to test if a Resource or
     * UploadedFile within the Bodington site exists.
     * @param url the Bodington URL.
     * @return true if url points to the Bodington site.
     */
    public boolean isBodingtonUrl( String url )
    {
        return getResourceName( url ) != null;
    }

    /**
     * Checks if a URL points to a Resource or UploadedFile within the Bodington
     * site.
     * @param url the Bodington URL.
     * @return true if url points to a Resource or UploadedFile within the
     *         Bodington site.
     */
    public boolean isValidBodingtonUrl( String url )
    {
        String name = getResourceName( url );
        if ( name == null )
            return false;
        if ( getResource( url ) != null )
            return true;
        if ( getUploadedFile( url ) != null )
            return true;
        return false;
    }

    /**
     * Gets Resource name from a Bodington URL.
     * @param url pointing to Bodington Resource.
     * @return String the Resource name.
     */
    public String getResourceName( String url )
    {

        String port;
        int i;
        if ( url == null || (url = url.trim()).length() == 0 )
            return null;

        if ( url.startsWith( protocol + "://" + hostname ) )
        {
            i = (protocol + "://" + hostname).length();
            port = new Integer( this.port ).toString();
        }
        else
            return null;

        if ( url.substring( i ).startsWith( ":" ) )
        {
            if ( url.substring( i ).startsWith( ":" + port ) )
                i += port.length() + 1;
            else
                return null;
        }

        if ( url.substring( i ).startsWith( servlet ) )
            i += servlet.length();
        else
            return null;

        return url.substring( i - 1 );
    }

    public void setHttp( int port )
    {
        this.port = port;
        this.protocol = "http";
    }

    public void setHttps( int port )
    {
        this.port = port;
        this.protocol = "https";
    }
}