/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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.bodington.util;

import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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 static Pattern HREF_PATTERN = Pattern.compile("href\\w*=\\w*(['\"])(.*)\\1");
    private Vector parseWarning;

    /**
     * Create a new BodingtonURL using the properties from the BuildingServer.
     */
    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 put in the recycling building.
     * Calls method of the same name in ResourceTree
     * (see {@link org.bodington.server.resources.ResourceTree#isDeleted(Resource)}).
     * @see org.bodington.server.resources.ResourceTree#isDeleted(Resource)
     * @param resource the resource to be checked.
     * @return true if recycle building can be found, and contains the resource.
     * @throws BuildingServerException if error in checking ancestors.
     */
    public boolean isResourceDeleted( Resource resource )
        throws BuildingServerException
    {
        return ResourceTreeManager.getInstance().isDeleted( resource );
    }

    /**
     * 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();

        Matcher matcher = HREF_PATTERN.matcher(in);
        int endPrevMatch = 0;
        while ( matcher.find())
        {
            out.append( in.substring( endPrevMatch, matcher.start(2) ) );
            s = matcher.group(2);
            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();
                    }
                }
            }
            out.append(s);
            out.append(in.substring(matcher.end(2), matcher.end()));
            endPrevMatch = matcher.end();

        }
        if ( endPrevMatch < in.length() )
            out.append( in.substring( endPrevMatch ) );
        return out.toString();
    }

    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";
    }
}
