/* ======================================================================
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 java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.bodington.database.PrimaryKey;
import org.bodington.server.BuildingServerException;

/**
 * Class that should be used when adding uploaded files.
 * Locking is done on a per resource basis. Really we should be using
 * WeakReferences rather than crappy reference counting in and out.
 * @author buckett
 */
public class UploadedFileManager
{
    private static Logger log = Logger.getLogger(UploadedFileManager.class);
    private static Map instances = new HashMap();

    private Resource resource;
    private int copies;
    
    private UploadedFileManager(Resource resource)
    {
        this.resource = resource;
        copies = 0;
    }
    
    /**
     * Get an UploadedFileManager object. {@link #release(Resource)} must be called
     * when the object has finished being used.
     * @param resource The resource to which the uploads are happening.
     * @return An UploadedFileManager object.
     */
    public static synchronized UploadedFileManager getInstance(Resource resource)
    {
        UploadedFileManager instance = (UploadedFileManager) instances
            .get(resource);
        if (instance == null)
        {
            instance = new UploadedFileManager(resource);
            instances.put(resource, instance);
        }
        instance.copies++;
        return instance;
    }
    
    /**
     * Indicate that the UploadedFileManager previously obtained is no longer needed.
     * @param resource The resource which the uploadeds are happening.
     */
    public static synchronized void release(Resource resource)
    {
        UploadedFileManager instance = (UploadedFileManager) instances
        .get(resource);
        if (instance == null)
        {
            log.warn("Attempted to release unmanaged resource: "+ resource);
            return;
        }
        instance.copies--;
        if (instance.copies == 0)
        {
            instances.remove(resource);
        }
    }
    

    /**
     * Add a new file to the tree. If the addition succeeds then the child is saved.
     * The child is always added as the last child of the parent.
     * @param parent The containing UploadedFile, if null then it becomes the root.
     * @param child The UploadedFile that is being added.
     * @throws BuildingServerException If we can't update the table or save the child.
     */
    public synchronized void  addNode(UploadedFile parent, UploadedFile child)
        throws BuildingServerException
    {
        if (parent == null)
        {
            child.setLeftIndex(0);
            child.setRightIndex(1);
        }
        else
        {
            child.setLeftIndex(parent.getRightIndex());
            child.setRightIndex(parent.getRightIndex()+1);
            child.setParentUploadedFileId(parent.getPrimaryKey());
        }
        child.setResourceId(resource.getPrimaryKey());
        incrementIndices(child.getLeftIndex(), child.getRightIndex());
        child.save();
    }
    
    /**
     * Delete this UploadedFile and all the children.
     * This is in the manager to prevent problem with multiple add/deletes being
     * done on the same section of the table at the same time. 
     * @param node The UploadedFile to be deleted.
     */
    public synchronized void delete(UploadedFile node)
        throws BuildingServerException
    {
        Enumeration enumeration = UploadedFile.findUploadedFileDescendents(
            resource.getPrimaryKey(), node, false);
        UploadedFile uploadedFile;
        while (enumeration.hasMoreElements())
        {
            uploadedFile = (UploadedFile) enumeration.nextElement();
            if (!uploadedFile.isDeleted())
            {
                uploadedFile.setDeleted(true);
                uploadedFile.save();
            }
        }
    }
    
    /**
     * Undelete the selected file and all the parents. If recurse is set to true then
     * undelete all the children too.
     * @param node The UploadedFile to be undeleted.
     * @param recurse If set to true the all the children are undeleted.
     * @throws BuildingServerException If we can't find the other files related to this
     * one or we can't set them as undeleted.
     */
    public synchronized void restore(UploadedFile node, boolean recurse)
        throws BuildingServerException
    {
        Enumeration enumeration;
        UploadedFile uploaded;

        enumeration = UploadedFile.findUploadedFileAncestors(resource
            .getResourceId(), node);
        while (enumeration.hasMoreElements())
        {
            uploaded = (UploadedFile) enumeration.nextElement();
            if (uploaded.isDeleted())
            {
                uploaded.setDeleted(false);
                uploaded.save();
            }
        }
        if (recurse)
        {
            enumeration = UploadedFile.findUploadedFileDescendents(resource
                .getResourceId(), node, false);
            while (enumeration.hasMoreElements())
            {
                uploaded = (UploadedFile) enumeration.nextElement();
                if (uploaded.isDeleted())
                {
                    uploaded.setDeleted(false);
                    uploaded.save();
                }
            }
        }
    }
    
    
    
    private void incrementIndices( 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 = UploadedFile.findUploadedFiles("resource_id = "
            + resource.getPrimaryKey().toString() + " AND left_index >= "
            + left);
    while ( enumeration.hasMoreElements() )
    	{
    	uf = (UploadedFile)enumeration.nextElement();
    	uf.setLeftIndex( uf.getLeftIndex()+increment );
    	uf.save();
    	}
    enumeration = UploadedFile.findUploadedFiles("resource_id = "
            + resource.getPrimaryKey().toString() + " AND right_index >= "
            + left);
    while ( enumeration.hasMoreElements() )
    	{
    	uf = (UploadedFile)enumeration.nextElement();
    	uf.setRightIndex( uf.getRightIndex()+increment );
    	uf.save();
    	}
    }
    
    /**
     * Attempt to correct the left and right indexes of the uploaded files for this
     * resource. Should probably also check that we only have files as the leaves.
     */
    public void reindex() throws BuildingServerException
    {
        
        Enumeration files = UploadedFile.findUploadedFileDescendents(resource
            .getPrimaryKey(), (String) null, false);
        
        if (!files.hasMoreElements())
        {
            if (log.isDebugEnabled())
                log.debug("No uploaded files for Resource ID: "
                    + resource.getPrimaryKey());
            return;
        }
        
        UploadedFileNode lastNode = new UploadedFileNode((UploadedFile)files.nextElement());
        UploadedFileNode currentNode;
        PrimaryKey currentPK, lastPK;
        
        
        while( files.hasMoreElements() )
        {
            currentNode = new UploadedFileNode((UploadedFile)files.nextElement());
            if (currentNode.getUploadedFile().getParentUploadedFileId() == null)
            {
                log.error("Found another root resource: "
                    + currentNode.getUploadedFile().getPrimaryKey());
            }
            else
            {
                currentPK = currentNode.getUploadedFile().getParentUploadedFileId();
                do
                {
                    
                    lastPK = lastNode.getUploadedFile().getPrimaryKey();
                    if (currentPK.equals(lastPK))
                    {
                        lastNode.add(currentNode);
                        lastNode = currentNode;
                        break;
                    }
                }
                while ( (lastNode = (UploadedFileNode)lastNode.getParent()) != null );
                
                if (currentNode.getParent() == null)
                    log.error("Couldn't find anywhere to put this in the tree :-(");
            }
            
        }
        
        
    }

}
