/* ======================================================================
   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.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import org.bodington.database.PrimaryKey;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSession;
import org.bodington.server.BuildingSessionManagerImpl;
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;
import org.bodington.util.TextUtils;

/**
 * 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.getResourceId().toString() + "/";
    }
    /**
     * 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.getEncodedGeneratedFolder("/")+ "/";
    }
    
    /**
     * 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()));
    }
    
        //uhi:awc
        /**
         * Walk the Resource tree starting from current node and add content packages to a ZipFile
         * when a MediaDocument (WebDocument) is encountered. Response contains error if method is
         * called from a containing resource that is not a room or suite of rooms. Exceptions encountered
         * when attempting to add a file to the ZipFile will be handled by adding an 'error' ZipEntry .
         * @param request The request.
         * @param response The response that zipOutputStream writes to.
         * @throws ServletException
         * @throws IOException
         * @see  org.bodington.servlet.facilities.MediaDocumentFacility#sendVirtualFile( Request, javax.servlet.http.HttpServletResponse)
         */
        public static void sendWebDocumentContentPackages( Request request, HttpServletResponse response) throws ServletException, IOException
        {
            Resource resource = request.getResource();
            if (resource.getResourceType() == Resource.RESOURCE_SUITE ||
                resource.getResourceType() == Resource.RESOURCE_ROOM) {
                ZipOutputStream zipOutputStream = null;
                try {
                    response.setContentType( "application/octet-stream" );
                    zipOutputStream = new ZipOutputStream( response.getOutputStream() );
                    walkResourceTree(resource, "imsCP_" + resource.getName() + "/", zipOutputStream);
                } catch (BuildingServerException e) {
                    throw new ServletException("An error occurred whilst attempting to zip WebDocument content packages", e);
                } finally {
                    zipOutputStream.close();
                }
            } else {
                response.sendError( Response.SC_INTERNAL_SERVER_ERROR, "Incorrect resource - the command can only be run from within a room or suite of rooms." );
                BuildingContext.trace( "ending, incorrect resource" );
                BuildingContext.dumpTrace();
            }
        }

        private static void walkResourceTree( Resource resource, String path, ZipOutputStream zipOutputStream ) throws BuildingServerException
        {
                if (resource.getResourceType() == Resource.RESOURCE_SUITE ||
                    resource.getResourceType() == Resource.RESOURCE_ROOM ) {

                    // handle any uploaded files in these container resources
                    processUploadedFiles( resource, path, zipOutputStream );

                    //recursively get child resources for above container resources
                    Vector ids = resource.getChildIds();
                    Enumeration childIds = ids.elements();
                    while (childIds.hasMoreElements()){
                        resource = Resource.findResource((PrimaryKey)childIds.nextElement());
                        walkResourceTree(resource, path + resource.getName() + "/", zipOutputStream);
                    }
                } else if (resource.getResourceType() == Resource.RESOURCE_MEDIADOCUMENT) {
                    // handle uploaded files in Media (Web) document
                    processUploadedFiles( resource, path, zipOutputStream );
                }
        }

        private static void processUploadedFiles( Resource resource, String path, ZipOutputStream zipOutputStream )
        {
            String root = "/";

            try {
                BuildingSession session = BuildingSessionManagerImpl.getSession( resource );
                session.getUploadedFileSession().getFileAndDescendentSummaryTree( null, false  );
                session.generateImsManifest();

                // Obtain a list of entries in the root directory and its descendants
                UploadedFileSummary[] summaries = session.getUploadedFileSession().getFileAndDescendentSummaries( root, true );

                // Obtain the location of the filestore for the entries
                File source_filestore = resource.getWebPublishFolder();


                // summary for current resource and manifest, if more then file has been uploaded here
                for ( int summaryIndex = 0; summaryIndex < summaries.length; summaryIndex++ )
                    {
                    UploadedFile f = UploadedFile.findUploadedFile( summaries[summaryIndex].getUploadedFileId() );
                    // don't emit deleted files and folders; don't emit folders
                    if ( f==null || f.isDeleted() || f.isFolder() )
                        continue;

                    File source = new File( source_filestore, f.getRealNameInResource() );
                    FileInputStream inputStream = new FileInputStream( source );
                    writeToZip(zipOutputStream, inputStream, path + f.getNameInResource());
                    }
            } catch (Exception e) {
                try {
                    //read exception and write to zip
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(e.toString().getBytes());
                    writeToZip(zipOutputStream, inputStream, "errors/" + path.substring(path.indexOf('_') + 1) + "missing_entry.txt");
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }

        private static void writeToZip( ZipOutputStream zipOutputStream, InputStream inputStream, String zipEntryName ) throws IOException
        {

            try {
                //create and add zip entry
                ZipEntry zipEntry = new ZipEntry( zipEntryName );
                zipOutputStream.putNextEntry( zipEntry );
                //write  bytes from the inputStream
                byte[] readBuffer = new byte[8192];
                int bytesIn = 0;
                while( ( bytesIn = inputStream.read( readBuffer ) ) != -1 ) {
                    zipOutputStream.write( readBuffer, 0, bytesIn );
                }
            } finally {
                //close the stream and zipEntry
                inputStream.close();
                zipOutputStream.closeEntry();
            }
        }
}
