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

import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import org.bodington.assessment.McqPaperSession;
import org.bodington.assessment.QuestionnaireSession;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSession;
import org.bodington.server.realm.Acl;
import org.bodington.server.realm.AclEntry;
import org.bodington.server.realm.Permission;
import org.bodington.servlet.FacilityList;
import org.bodington.servlet.Request;
import org.bodington.servlet.Response;
import org.bodington.servlet.facilities.Facility;

/**
 * Helper functions that don't need to be in resource because they clutter the API
 * or make resource depend on packages it shouldn't.
 * @author buckett
 */
public class ResourceUtils
{
	private static Logger log = Logger.getLogger(ResourceUtils.class);
	 
    /**
     * Attempt to work out the URL for uploaded files.
     * Looks at the property <code>webpublish.webaddress</code>.
     * If it starts with a "/" then use it, otherwise prefix it with the context.
     * It doesn't at the moment support using a completly different URL.
     * @param resource The resource to get the path for.
     * @param request The request through which the call is being made.
     * @return A the path.
     * @throws BuildingServerException
     */
    public static String getWebPublishAddress(Resource resource, Request request)
    {
        String path = BuildingContext.getProperty( "webpublish.webaddress", "files/");
        if (!path.startsWith("/"))
        {
            path = request.getContextPath()+ "/" + path;
        }
        
        return path + resource.getShardedFolder("/") + "/";
    }
    /**
     * Attempt to work out the URL for generated files.
     * Looks at the property <code>filegeneration.webaddress</code>.
     * If it starts with a "/" then use it, otherwise prefix it with the context.
     * It doesn't at the moment support using a completly different URL.
     * @param resource The resource to get the path for.
     * @param request The request through which the call is being made.
     * @return A the path.
     * @throws BuildingServerException
     */
    public static String getGeneratedFileAddress(Resource resource, Request request)
    {
        String path = BuildingContext.getProperty( "filegeneration.webaddress", "generated/");
        if (!path.startsWith("/"))
        {
            path = request.getContextPath()+ "/" + path;
        }
        return path + resource.getShardedFolder("/")+ "/";
    }
    
    /**
     * Attempt to grant some permissions to a Resource. This is often 
     * used by facilities to grant additional permissions on a resource straight after
     * it has been created. This method doesn't save the ACL after is has been
     * updated and it is up to the calling code.
     * @param resource The resource to add the permissions to.
     * @param grant An array of permissions to be added
     * @return True if the permissions were sucessfully added.
     */
    public static boolean grantPermissions(Resource resource, Permission[] grant)
    {
        try
        {
            Acl acl = resource.getAcl();
            if (acl != null)
            {
                AclEntry aclEntry = acl.getOwnerGroupAclEntry();
                if (aclEntry != null)
                {
                    for(int count = 0; count < grant.length; count++)
                    {
                        aclEntry.addPermission(grant[count]);
                    }
                    return true;
                }
            }
            log.warn("Resource doesn't have default ACLs: "+ resource);
        }
        catch (BuildingServerException bse)
        {
            log.warn("Could not get ACL details for: " + resource);
        }
        return false;
    }
    
    /**
     * Check to see if the current user has <em>any</em> of the supplied
     * permissions on supplied resource.
     * @param resource The Resource to perform the permission checks against.
     * @param check An array of permissions to check. If the array is empty 
     * false will be returned.
     * @return <code>true</code> if the current user has any of the permissions
     * on the supplied resource.
     */
    public static boolean anyPermission(Resource resource, Permission[] check)
    {
        for (int i = 0; i < check.length; i++)
            if (resource.checkPermission(check[i]))
                return true;
        return false;
    }
    
    /**
     * Check to see if any of the principals have the requested permission on the
     * supplied resource.
     * @param resource The resource to perform the check against.
     * @param principal An array of principals to check. Typically this will be groups.
     * @param permission The permission to test for.
     * @return <code>true</code> if the permission is had by any of the principals
     * on the resource, otherwise <code>false</code>.
     */
    public static boolean anyPrincipal(Resource resource, Principal principal[], Permission permission)
    {
        for (int i = 0; i < principal.length; i++)
            if (resource.checkPermission( principal[i], permission ))
                return true;
        return false;
    }
    
    public static Facility getFacility(Resource resource)
    {
        return FacilityList.getFacilities().get(new Integer(resource.getHttpFacilityNo()));
    }
    
    /**
     * Walk the Resource tree starting from current node and add content to a Zip archive.
     * An XML manifest file is included if requested. Recursion is also optional.
     * Response contains error if method is called from a building or floor, unless 'force' param is set to true.
     * Exceptions are handled by adding a text file with error message to the zip archive.
     * @param request The request.
     * @param response The response that zipOutputStream writes to.
     * @throws IOException
     */
    public static void sendWebDocumentContentPackages( Request request, HttpServletResponse response) throws IOException
    {
    	Resource resource = request.getResource();
    	boolean force = (null != request.getParameter("force"));
    	
    	if (!force &&
    			(resource.getResourceType() == Resource.RESOURCE_BUILDING ||
    			resource.getResourceType() == Resource.RESOURCE_FLOOR )) {
    		response.sendError( Response.SC_INTERNAL_SERVER_ERROR, "Incorrect resource type - the command can't be run from within a Floor or Building." );
    	}
    	else
    	{
    		response.setContentType( "application/octet-stream" );
    		response.setHeader("Content-Disposition","attachment; filename=\"" + resource.getName() +".zip\"");

    		ContentZipper zipper;
    		if (request.getParameter("includeManifest") != null)
    			zipper = new ContentPackager();
    		else zipper = new ContentZipper();

    		try {
    			zipper.packageResource( request, response.getOutputStream() );

    		} catch (Exception e) {
    			response.setHeader("Content-Disposition", null);
    			String message = "An error occurred while generating the material to export: " + e.getMessage();
    			response.sendError( Response.SC_INTERNAL_SERVER_ERROR, message );
    		}
    	}
    }
    
    /**
     * Export questions as XML QTI file.
     * See {@link sendWebDocumentContentPackages}  which will export QTI and supporting Uploaded Files.
     * @param req
     * @param res
     * @throws BuildingServerException
     * @throws IOException
     */
    public static void sendQtiPackage(Request req, HttpServletResponse res) throws BuildingServerException, IOException {

    	if (req.getResource().checkPermission(Permission.MANAGE))
    	{
    		Resource resource = req.getResource();
    		BuildingSession session = req.getBuildingSession();
    		
    		// Assessments might include image files as Uploaded Files:
    		if (Boolean.parseBoolean(req.getParameter("includeFiles")))
    		{
    			sendWebDocumentContentPackages(req, res);
    			return;
    		}
    		
    		String filename = resource.getName() +".xml";
    		OutputStream stream = res.getOutputStream();
    		res.setContentType( "application/octet-stream" );
    		res.setHeader("Content-Disposition","attachment; filename=\"" + filename +  "\"");
    		
    		if (session instanceof McqPaperSession)
    			((McqPaperSession)session).exportXML( stream );
    		else if (session instanceof QuestionnaireSession)
    			((QuestionnaireSession)session).exportXML( stream );
    		
    		stream.close();

    	}
    	else
    		res.sendError(HttpServletResponse.SC_FORBIDDEN, "You need manage access");
    }
    
    /**
     * Set the resource properties to display a b'ground image, and set a flag to indicate 
     * which resources have been marked as migrated/archived.
     * The display properties are inherited, so don't need to be set recursively, but for 
     * migration monitoring purposes, the migrate flag is set in each resource.
     * @see unsetMigratePropertiesIncludingChildren
     * @param resource
     * @param imageUrl
     * @throws BuildingServerException
     */
    public static void setMigratePropertiesIncludingChildren(Resource resource, String imageUrl) throws BuildingServerException
    {
    	// when setting, set the *display* properties at this level only.
    	resource.specifyProperty("style_specified", "true");
    	resource.specifyProperty("style_navigation_background_image", imageUrl); //"/static/archived.gif"
    	resource.specifyProperty("style_navigation_menu_highlight_colour_1", "transparent");

    	// set the migrated flag in all children:
    	setMigrateFlagRecursively(resource, true);
    }
    
    /**
     * Unset the resource properties used to display a b'ground image, and unset the flag/s 
     * in each resource that indicates that it's marked as migrated/archived.
     * The display properties may previously have been set lower down, so children are also checked
     *  and properties removed.
     * @see setMigratePropertiesIncludingChildren
     * @param resource
     * @throws BuildingServerException
     */
    
    public static void unsetMigratePropertiesIncludingChildren(Resource resource) throws BuildingServerException
	{
    	// when unsetting, recursively unset display properties in any children that have them set too.
    	// (when setting, the display properties were set at the top resource only.)    	
		
			resource.unspecifyProperty("style_specified");
			resource.unspecifyProperty("style_navigation_background_image");
			resource.unspecifyProperty("style_navigation_menu_highlight_colour_1");
			
			for (Enumeration children = resource.findChildren(); children.hasMoreElements();)
			{
				Resource child = (Resource)children.nextElement();
				unsetMigratePropertiesIncludingChildren(child);
			}
		
		// unset the migrated flag in all children:
		setMigrateFlagRecursively(resource, false);
	}
    
    
    
    private static void setMigrateFlagRecursively(Resource resource, boolean set) throws BuildingServerException
    {
    	if (set)
    		resource.specifyProperty("flagged_as_migrated", "true");
    	else
    		resource.unspecifyProperty("flagged_as_migrated");

    	for (Enumeration children = resource.findChildren(); children.hasMoreElements();)
    	{
    		Resource child = (Resource)children.nextElement();
    		setMigrateFlagRecursively(child, set);
    	}
    }
}