/* ======================================================================
   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 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 :-(");
            }
            
        }
        
        
    }

}
