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

import org.apache.log4j.Logger;

import java.io.*;
import java.sql.Timestamp;
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;

/**
 * Class used for managing Uploaded Files.
 * @author Jon Maber
 * @author buckett
 */
public class UploadedFile
    extends org.bodington.sqldatabase.SqlPersistentObject 
    implements VisitationObject, Serializable, Comparable
    {
    
    private static Logger log = Logger.getLogger(UploadedFile.class);

    /**
     * Constant to represent a file.
     */
    public final static int FILE = 0;
    /**
     * Constant to represent a folder.
     */
    public final static int FOLDER = 1;
    
    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 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 resource_id, UploadedFile uploadedFile)
		throws BuildingServerException
		{
		Vector found = new Vector();
        found.insertElementAt(uploadedFile, 0);
		PrimaryKey parent_id = uploadedFile.getParentUploadedFileId();

		while ( parent_id != null )
		    {
		    UploadedFile uf = findUploadedFile( parent_id );
		    if ( uf == null || ! uf.getResourceId().equals(resource_id) )
		        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
		{

		UploadedFile uf = findUploadedFileByPath( resource_id, path );
        return findUploadedFileDescendents(resource_id, uf, omit_deleted);
        }
        
    public static Enumeration findUploadedFileDescendents(
        PrimaryKey resource_id, UploadedFile uf, boolean omit_deleted)
        throws BuildingServerException
    {
		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( UploadedFileSummary.FLAG_DELETED );
			where.append( ") / " );
			where.append( UploadedFileSummary.FLAG_DELETED*2 );
			where.append( ") = FLOOR(flags / " );
			where.append( UploadedFileSummary.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];
		}
    
    /**
     * Create an UploadedFile object that is almost ready to use.
     * @param type The type of uploaded file.
     * @see #FILE
     * @see #FOLDER
     */
    public UploadedFile(int type)
    {
        this();
        switch (type)
        {
            case FILE:
                setFlags(0);
                setFolder( false );
                break;
            case FOLDER:
                setFlags( 0 );
                setFolder( true );
                setMimeType( "folder" );
                setSize( 0L );
                setBytesUploaded( 0L );
                break;
            default:
                throw new IllegalArgumentException("Type should be a file or folder");
        }
        
        Timestamp now = new Timestamp(System.currentTimeMillis());
        setCreatedTime( now );
        setUpdatedTime( now );
        setDeleted( false );
    }
	
    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(getResourceId(), this);

	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;
        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(getResourceId(), this);

        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] );
	    }
	
	}
	
