/* ======================================================================
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.server.resources;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.*;
import java.net.URLDecoder;
import java.net.URLEncoder;

import org.bodington.server.BuildingServerException;
import org.bodington.sqldatabase.*;
import org.bodington.database.*;
import org.bodington.server.realm.User;
import org.bodington.server.BuildingContext;
import org.bodington.text.BigString;
import org.bodington.util.VisitationObject;

public class UploadedFile
    extends org.bodington.sqldatabase.SqlPersistentObject 
    implements VisitationObject, Serializable, Comparable
    {
    
    private static Logger log = Logger.getLogger(UploadedFile.class);
    
    public static final byte FLAG_FOLDER=1;
    public static final byte FLAG_DELETED=2;


    UploadedFileSummary summary;
    String real_name;
    String[] name_in_resource;
    
    File file;

    private UploadedFileIndexKey[] ikeys;

    private static String encoding = null;

    public static String getURLCharEncoding()
	{
	if ( encoding != null )
	    return encoding;
	encoding = BuildingContext.getProperty( "webpublish.url.charencoding", "bodington_underscore" );
	return encoding;
	}

    
    public static String nameToURL( String name )
	{
	try
	{
	return org.bodington.util.URLEncoder.encode( name, getURLCharEncoding() );
	}
	catch ( UnsupportedEncodingException e )
	{
	    log.error( e.getMessage(), e );
	    return null;
	}
	}
	
    public static String URLToName( String url )
	{
	 try
	 {
	return org.bodington.util.URLDecoder.decode( url, getURLCharEncoding() );
	}
	catch ( UnsupportedEncodingException e )
	{
	    log.error(e.getMessage(), e );
	    return null;
	}
    }
		
	public static void incrementIndices( PrimaryKey resource_id, int left, int right )
		throws BuildingServerException
		{
		if ( right<=left )
			throw new IndexOutOfBoundsException( "Right index must be greater than left." );
		
		Enumeration enumeration;
		UploadedFile uf;
		int increment = right-left+1;
		
		enumeration = findUploadedFiles( "resource_id = " + resource_id.toString() + " AND left_index >= " + left );
		while ( enumeration.hasMoreElements() )
			{
			uf = (UploadedFile)enumeration.nextElement();
			uf.setLeftIndex( uf.getLeftIndex()+increment );
			uf.save();
			}
		enumeration = findUploadedFiles( "resource_id = " + resource_id.toString() + " AND right_index >= " + left );
		while ( enumeration.hasMoreElements() )
			{
			uf = (UploadedFile)enumeration.nextElement();
			uf.setRightIndex( uf.getRightIndex()+increment );
			uf.save();
			}
		}
	
	public static String[] parsePath( String path )
		{
		if ( path==null )
			path="/";
		StringTokenizer tokenizer = new StringTokenizer( path, "/" );
		int n = tokenizer.countTokens();
		String[] list = new String[n];
		for ( int i=0; i<n; i++ )
		    list[i] = tokenizer.nextToken();
		return list;
		}
		
		
		
	public static UploadedFile findUploadedFileByPath( PrimaryKey resource_id, String path )
		throws BuildingServerException
		{
		return findUploadedFileByPath( resource_id, parsePath( path ) );
		}
	
	public static Enumeration findUploadedFileAncestors( PrimaryKey resource_id, String path )
		throws BuildingServerException
		{
		return findUploadedFileAncestors( resource_id, parsePath( path ) );
		}
	
	public static Enumeration findUploadedFileDescendents( PrimaryKey resource_id, String path, boolean omit_deleted )
		throws BuildingServerException
		{
		return findUploadedFileDescendents( resource_id, parsePath( path ), omit_deleted );
		}
	
	public static UploadedFile findUploadedFileByParentAndName( PrimaryKey resource_id, PrimaryKey parent_uf_id, String name )
		throws BuildingServerException
		{
		UploadedFileIndexKey ikey = new UploadedFileIndexKey();
		ikey.setResourceId( resource_id );
		ikey.setParentUploadedFileId( parent_uf_id );
		ikey.setName( name );
		return (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		}
		
	private static Vector findPath( PrimaryKey resource_id, String[] path, boolean only_leaf )
		throws BuildingServerException
		{
		Vector list = new Vector();
		
		UploadedFileIndexKey ikey = new UploadedFileIndexKey();
		ikey.setResourceId( resource_id );
		ikey.setParentUploadedFileId( null );
		ikey.setName( "" );
		
		UploadedFile current = (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		if ( current == null )
		    return null;
		
		if ( !only_leaf )
		    list.addElement( current );
		
		for ( int i=0; i< path.length; i++ )
		    {
		    ikey = new UploadedFileIndexKey();
		    ikey.setResourceId( resource_id );
		    ikey.setParentUploadedFileId( current.getUploadedFileId() );
		    ikey.setName( path[i] );
		    current = (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		    if ( current == null )
			return null;
		    if ( !only_leaf )
			list.addElement( current );
		    }
		
		if ( only_leaf )
		    list.addElement( current );
		
		return list;
		}
		
	public static UploadedFile findUploadedFileByPath( PrimaryKey resource_id, String[] path )
		throws BuildingServerException
		{
		Vector list = findPath( resource_id, path, true );
		if ( list == null || list.size() == 0 )
		    return null;
		return (UploadedFile)list.elementAt( 0 );
		}
		
	public static Enumeration findUploadedFileAncestors( PrimaryKey uf_id )
		throws BuildingServerException
		{
		Vector found = new Vector();
		PrimaryKey parent_id = uf_id;

		while ( parent_id != null )
		    {
		    UploadedFile uf = findUploadedFile( parent_id );
		    if ( uf == null )
		        return null;
		    found.insertElementAt( uf, 0 );
		    parent_id = uf.getParentUploadedFileId();
		    }
		    
		return found.elements();
		}
		
	public static Enumeration findUploadedFileAncestors( PrimaryKey resource_id, String[] path )
		throws BuildingServerException
		{
		//break down path first
		Vector found = findPath( resource_id, path, false );
		if ( found == null )
		    return new Vector().elements();
		
		return found.elements();
		}

	public static Enumeration findUploadedFileDescendents( PrimaryKey resource_id, String[] path, boolean omit_deleted )
		throws BuildingServerException
		{
		Vector found = new Vector();

		UploadedFile uf = findUploadedFileByPath( resource_id, path );
		if ( uf == null )
			return (new Vector()).elements();
		
		StringBuffer where = new StringBuffer( 100 );
		
		where.append( "resource_id = " );
		where.append( resource_id );
		// This clause is modified to avoid arithmetic operators not
		// supported by some databases - i.e. Oracle lacks the modulo
		// operator.
		if ( omit_deleted )
			{
			where.append( " AND FLOOR((flags + " );
			where.append( FLAG_DELETED );
			where.append( ") / " );
			where.append( FLAG_DELETED*2 );
			where.append( ") = FLOOR(flags / " );
			where.append( FLAG_DELETED*2 );
			where.append( ")" );
			}
		where.append( " AND left_index BETWEEN " );
		where.append( uf.getLeftIndex() );
		where.append( " AND " );
		where.append( uf.getRightIndex() );
		return findUploadedFiles( where.toString(), "left_index" );
		}
		
	public static UploadedFile findUploadedFile( PrimaryKey key )
		throws BuildingServerException
		{
		return (UploadedFile)findPersistentObject( key, "org.bodington.server.resources.UploadedFile" );
		}
	public static UploadedFile findUploadedFile( String where )
		throws BuildingServerException
		{
		return (UploadedFile)findPersistentObject( where, "org.bodington.server.resources.UploadedFile" );
		}
	public static Enumeration findUploadedFiles( String where )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, "org.bodington.server.resources.UploadedFile" );
	    }
	public static Enumeration findUploadedFiles( String where, String order )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, order, "org.bodington.server.resources.UploadedFile" );
	    }
	
	
	public UploadedFile()
		{
		ikeys = new UploadedFileIndexKey[1];
		ikeys[0] = new UploadedFileIndexKey();
		
		summary = new UploadedFileSummary();
		file = null;
		real_name = null;
		name_in_resource = new String[4];
		}
	
	// dummy save method put in for development work
	//static int xxx = 0;
	//public void save()
	//	{
	//	summary.setUploadedFileId( new PrimaryKey( xxx++ ) );
	//	}
	
	
    public PrimaryKey getPrimaryKey()
		{
        return getUploadedFileId();
        }

    public void setPrimaryKey(PrimaryKey key)
    	{
    	setUploadedFileId( key );
    	}
    	
    public PrimaryKey getUploadedFileId()
		{
        return summary.getUploadedFileId();
        }

    public void setUploadedFileId(PrimaryKey key)
    	{
    	summary.setUploadedFileId( key );
    	setUnsaved();
    	}
    	

    public PrimaryKey getResourceId()
		{
        return summary.getResourceId();
        }

    public void setResourceId(PrimaryKey key)
    	{
	ikeys[0].setResourceId( key );
    	summary.setResourceId( key );
    	setUnsaved();
    	}
    	

    public PrimaryKey getParentUploadedFileId()
		{
        return summary.getParentUploadedFileId();
        }

    public void setParentUploadedFileId(PrimaryKey key)
    	{
	ikeys[0].setParentUploadedFileId( key );
    	summary.setParentUploadedFileId( key );
    	setUnsaved();
    	}
    	

    public void setCreateUserId( PrimaryKey key )
	    {
	    summary.setCreateUserId( key );
    	setUnsaved();
	    }
    public PrimaryKey getCreateUserId()
	    {
        return summary.getCreateUserId();
	    }


    	
    public void setUpdateUserId( PrimaryKey key )
	    {
	    summary.setUpdateUserId( key );
    	setUnsaved();
	    }
    public PrimaryKey getUpdateUserId()
	    {
        return summary.getUpdateUserId();
	    }


    	
	public java.sql.Timestamp getCreatedTime()
		{
		return summary.getCreatedTime();
		}
	public void setCreatedTime( java.sql.Timestamp t )
		{
		summary.setCreatedTime( t );
    	setUnsaved();
		}


	public java.sql.Timestamp getUpdatedTime()
		{
		return summary.getUpdatedTime();
		}
	public void setUpdatedTime( java.sql.Timestamp t )
		{
		summary.setUpdatedTime( t );
    	setUnsaved();
		}


   	public int getFlags()
		{
		return summary.getFlags();
		}
	public void setFlags( int f )
		{
		if ( f==summary.getFlags() )
			return;
		summary.setFlags( f );
    	setUnsaved();
		}
	public boolean isDeleted()
		{
		return summary.isDeleted();
		}
	public void setDeleted( boolean b )
		{
		if ( b == summary.isDeleted() )
			return;
		summary.setFlags( summary.getFlags() ^ UploadedFileSummary.FLAG_DELETED );
    	setUnsaved();
		}
	public boolean isFolder()
		{
		return summary.isFolder();
		}
	public void setFolder( boolean b )
		{
		if ( b == summary.isFolder() )
			return;
		summary.setFlags( summary.getFlags() ^ UploadedFileSummary.FLAG_FOLDER );
    	setUnsaved();
		}
	
	public int getLowSize()
		{
		return (int)(getSize() & 0x00000000ffffffffL);
		}
	public void setLowSize( int s )
		{
		setSize( (getSize() & 0xffffffff00000000L) | ((long)s & 0x00000000ffffffffL) );
		}
	public int getHighSize()
		{
		return (int)(getSize() / 0x100000000L);
		}
	public void setHighSize( int s )
		{
		setSize( (getSize() & 0x00000000ffffffffL) | ((long)s << 32) );
		}

	public long getSize()
		{
		return summary.getSize();
		}

	public void setSize( long l )
		{
		if ( summary.getSize() == l )
			return;
		summary.setSize( l );
    	setUnsaved();
		}

	public int getLowBytesUploaded()
		{
		return (int)(getBytesUploaded() & 0x00000000ffffffffL);
		}
	public void setLowBytesUploaded( int s )
		{
		setBytesUploaded( (getBytesUploaded() & 0xffffffff00000000L) | ((long)s & 0x00000000ffffffffL) );
		}
	public int getHighBytesUploaded()
		{
		return (int)(getBytesUploaded() / 0x100000000L);
		}
	public void setHighBytesUploaded( int s )
		{
		setBytesUploaded( (getBytesUploaded() & 0x00000000ffffffffL) | ((long)s << 32) );
		}

	public long getBytesUploaded()
		{
		return summary.getBytesUploaded();
		}

	public void setBytesUploaded( long l )
		{
		if ( summary.getBytesUploaded() == l )
			return;
		summary.setBytesUploaded( l );
    	setUnsaved();
		}

	public boolean isComplete()
		{
		return summary.isComplete();
		}


    public void setName( String n )
	    throws BuildingServerException
	    {
	    // Accomadates Oracle, which insists on changing "" to null in
	    // the database.
	    if ( n==null )
	   	n="";

	    ikeys[0].setName( n );

	    summary.setName( n );
	    real_name = null;
	    name_in_resource = new String[4];
	    file=null;
	    try
	        {
	        summary.setUrl( nameToURL( n ) );
	        }
	    catch ( Exception ex )
	        {
	        throw new BuildingServerException( "Problem setting name of uploaded file. " + ex.toString() );
	        }
	    setUnsaved();
	    }

    public String getName()
	    {
        return summary.getName();
	    }


    public void setUrl( String n )
        throws BuildingServerException
	    {
	    // do nothing !!
	    // no longer interested in the url in the database
	    }

    public String getUrl()
	    {
	    return summary.getUrl();
	    }

	    
    public void setMimeType( String n )
	    {
	    summary.setMimeType( n );
    	setUnsaved();
	    }

    public String getMimeType()
	    {
        return summary.getMimeType();
	    }



   	public int getLeftIndex()
		{
		return summary.getLeftIndex();
		}
	public void setLeftIndex( int l )
		{
		if ( l==summary.getLeftIndex() ) return;
		summary.setLeftIndex( l );
    	setUnsaved();
		}

	public int getRightIndex()
		{
		return summary.getRightIndex();
		}
	public void setRightIndex( int r )
		{
		if ( r==summary.getRightIndex() ) return;
		summary.setRightIndex( r );
    	setUnsaved();
		}


    public String getRealName()
	{
	if ( real_name != null )
	    return real_name;
	    
	char c;
	StringBuffer fs_name = new StringBuffer();
	fs_name.append( Integer.toHexString( getUploadedFileId().intValue() ) );
	fs_name.append( "_" );
	
	boolean in_valid=true;
	String name = getName();
	for ( int i=0; i < name.length(); i++ )
	    {
	    c = name.charAt( i );
	    if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' )
		{
		fs_name.append( c );
		in_valid = true;
		continue;
		}
	    if ( in_valid )
		{
		in_valid = false;
		fs_name.append( "_" );
		}
	    }
	
	real_name = fs_name.toString();
	return real_name;
	}
		

    private String getNameInResource( String separator, boolean real, boolean url )
	throws BuildingServerException
	{
	StringBuffer fs_name;
	Enumeration enumeration;
	UploadedFile uf;

	if ( getParentUploadedFileId() == null )
	    return "";
	
	int item;
	if ( url )
	    {
	    if ( real )
		item = 0;
	    else
		item = 1;
	    }
	else
	    {
	    if ( real )
		item = 2;
	    else
		item = 3;
	    }
	
	if ( name_in_resource[item] != null )
	    return name_in_resource[item];
	
	enumeration=findUploadedFileAncestors( getUploadedFileId() );

	fs_name = new StringBuffer();
	//skip root
	if ( enumeration.hasMoreElements() )
	    enumeration.nextElement();
	while ( enumeration.hasMoreElements() )
	    {
	    uf = (UploadedFile)enumeration.nextElement();
	    if ( url )
		{
		if ( real )
		    fs_name.append( nameToURL( uf.getRealName() ) );
		else
		    fs_name.append( nameToURL( uf.getName() ) );
		}
	    else
		{
		if ( real )
		    fs_name.append( uf.getRealName() );
		else
		    fs_name.append( uf.getName() );
		}
		
	    if ( enumeration.hasMoreElements() )
		fs_name.append( separator );
	    }
	
	name_in_resource[item] = fs_name.toString();
	
	return name_in_resource[item];
	}

    public String getNameInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", false, false );
	}
	
    public String getRealNameInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", true, false );
	}
	
    public String getURLInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", false, true );
	}
	
    public String getRealURLInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", true, true );
	}
	
    public File getFile()
        throws BuildingServerException
	{
	if ( file!=null ) return file;

	Resource resource;
	PrimaryKey rid;
	File root;

	rid = getResourceId();
	if ( rid == null )
	    return null;
	resource = Resource.findResource( rid );
	if ( resource==null )
	    return null;

	root = resource.getWebPublishFolder();

	file = new File( root, getNameInResource( File.separator, true, false ) );

	return file;
	}
		

    public File getFileAsPerVersion2_0( File root )
    throws BuildingServerException
    {
	return getFileAsPerOldVersion( root, "bodington_escape" );
    }
    
    public File getFileAsPerVersion1_X( File root )
    throws BuildingServerException
    {
	return getFileAsPerOldVersion( root, "ISO-8859-1" );
    }
    
    private File getFileAsPerOldVersion( File root, String encoding )
    throws BuildingServerException
    {
        try
        {
        // this method is only used to upgrade an old installation
        // it depends on url field which is no longer used
        // root will refer to a folder inside the old installation
        File old_file;

        Resource resource;
        PrimaryKey rid;
        StringTokenizer tokenizer;
        String token;
        StringBuffer fs_name;
        Enumeration enumeration;
        UploadedFile uf;

        rid = getResourceId();
        if ( rid == null )
            return null;
        resource = Resource.findResource( rid );
        if ( resource==null )
            return null;

	File res_folder = new File( root, resource.getResourceId().toString() );

        enumeration=findUploadedFileAncestors( getUploadedFileId() );

        fs_name = new StringBuffer();
        fs_name.append( File.separator );
        //skip root
        if ( enumeration.hasMoreElements() )
        enumeration.nextElement();
        while ( enumeration.hasMoreElements() )
        {
            uf = (UploadedFile)enumeration.nextElement();
            fs_name.append( org.bodington.util.URLEncoder.encode( uf.getName(), encoding ) );
            if ( enumeration.hasMoreElements() )
            fs_name.append( File.separator );
        }
        //if ( fs_name.length()==0 )
        //	return null;

        old_file = new File( res_folder, fs_name.toString() );

        return old_file;
        }
        catch ( UnsupportedEncodingException e )
        {
            throw new BuildingServerException( "Unsupported encoding." );
        }
    }
		
		
	public UploadedFileSummary getSummary()	
		{
		return summary;
		}

	public void save()
		throws BuildingServerException
		{
		if ( this.isUnsaved() )
			super.save();
		}


	public int compareTo( Object o )
		{
		if ( o==null )
			return 1;
		if ( !(o instanceof UploadedFile) )
			return 1;
			
		UploadedFile other = (UploadedFile)o;
		
		if ( isDeleted() )
			{
			if ( !other.isDeleted() )
				return 1;
			}

		if ( other.isDeleted() )
			return -1;
		
		if ( getName() == null )
			{
			if ( other.getName() == null )
				return 0;
			else
				return 1;
			}
			
		if ( other.getName() == null )
			return -1;
			
		return getName().compareTo( other.getName() );
		}

	public IndexKey[] getIndexKeys()
	    {
	    if ( ikeys[0].getName() == null || 
		  ikeys[0].getResourceId() == null )
	        return null;

	    return ikeys;
	    }
	    
	public boolean matchesKey( IndexKey testikey )
	    {
	    return testikey.equals( ikeys[0] );
	    }
	
	
	}
	
