/*
 * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *     * 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.
 * 
 *     * Neither the name of Business Objects nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 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.
 */


/*
 * ResourceNullaryStore.java
 * Creation date: Nov 23, 2004.
 * By: Edward Lam
 */
package org.openquark.cal.services;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleSourceDefinition;
import org.openquark.cal.services.ResourcePath.FilePath;



/**
 * Warning- this class should only be used by the CAL services implementation. It is not part of the
 * external API of the CAL platform.
 * <p>
 * A resource store for the nullary workspace, using files and folders in the file system.
 * @author Edward Lam
 */
public abstract class ResourceNullaryStore extends AbstractResourcePathStore {
    
    /**
     * Constructor for a ResourceNullaryStore.
     * @param resourceType the resource type for the associated resource.
     * @param pathMapper the path mapper for this store.
     */
    public ResourceNullaryStore(String resourceType, ResourcePathMapper pathMapper) {
        super(resourceType, pathMapper);
    }
    
    /**
     * @return the nullary environment.
     */
    private static NullaryEnvironment getNullaryEnvironment() {
        return NullaryEnvironment.getNullaryEnvironment();
    }
    
    /**
     * Get the file which contains the resource for the given feature.
     * @param resourceName the identifier of the resource.
     * @param createDirectoryIfAbsent whether to create the directory to the file if it does not exist.
     * @return the file which corresponds to that module.
     */
    File getFeatureFile(ResourceName resourceName, boolean createDirectoryIfAbsent) {
        FilePath resourceFilePath = getResourcePath(resourceName);
        return getNullaryEnvironment().getFile(resourceFilePath, createDirectoryIfAbsent);
    }

    /**
     * {@inheritDoc}
     */
    public List<ResourceName> getFolderResourceNames(ResourcePath.Folder folder) {
        return getNullaryEnvironment().getFolderResourceNames(folder, getPathMapper());
    }
    
    /**
     * {@inheritDoc}
     */
    public List<ResourceName> getFilteredFolderResourceNames(ResourcePath.Folder folder, ResourceName.Filter filter) {
        return getNullaryEnvironment().getFilteredFolderResourceNames(folder, getPathMapper(), filter);
    }
    
    /**
     * {@inheritDoc}
     */
    public void removeResource(ResourceName resourceName, Status removeStatus) {
        
        NullaryEnvironment nullaryEnvironment = getNullaryEnvironment();
        
        // Get the path to the file to remove.
        ResourcePath.FilePath filePathToRemove = getPathMapper().getResourcePath(resourceName);
        
        if (nullaryEnvironment.exists(filePathToRemove)) {
            File fileToRemove = nullaryEnvironment.getFile(filePathToRemove, false);

            if (fileToRemove == null) {
                String message = "Could not find resource to remove: (Type: " + getResourceType() + ".  Name: " + resourceName + ")";
                removeStatus.add(new Status(Status.Severity.WARNING, message));
                
            } else {
                // Attempt to delete the file.
                if (!fileToRemove.delete()) {
                    String message = "Could not remove resource: (Type: " + getResourceType() + ".  Name: " + resourceName + ")";
                    removeStatus.add(new Status(Status.Severity.WARNING, message));
                }
            }
        }
    }
    
    /**
     * {@inheritDoc}
     */
    public void removeAllResources(Status removeStatus) {
        
        NullaryEnvironment nullaryEnvironment = getNullaryEnvironment();
        
        // Delete subdirectories of the base resource folder, but leave the folder itself.
        ResourcePath.Folder baseResourceFolder = getPathMapper().getBaseResourceFolder();
        if (nullaryEnvironment.exists(baseResourceFolder)) {
            nullaryEnvironment.delSubtrees(baseResourceFolder, removeStatus);
        }
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean renameResource(ResourceName oldResourceName, ResourceName newResourceName, ResourceStore newResourceStore, Status renameStatus) {
        
        NullaryEnvironment nullaryEnvironment = getNullaryEnvironment();

        // Get the file path to rename.
        ResourcePath.FilePath oldFilePath = getPathMapper().getResourcePath(oldResourceName);
        
        if (nullaryEnvironment.exists(oldFilePath)) {
            File oldFile = nullaryEnvironment.getFile(oldFilePath, false);
            
            if (oldFile == null) {
                String message = "Could not find resource to rename: (Type: " + getResourceType() + ". Name: " + oldResourceName + ")";
                renameStatus.add(new Status(Status.Severity.INFO, message));
                return false;
            } else {
                
                if (newResourceStore == this) {
                    // if the resource store for the renamed resource is also this store, then we can simply do a file rename
                    ResourcePath.FilePath newFilePath = getPathMapper().getResourcePath(newResourceName);
                    File newFile = nullaryEnvironment.getFile(oldFilePath, newFilePath, true);
                    
                    // Attempt to rename the file.
                    if (!oldFile.renameTo(newFile)) {
                        // If a simple rename didn't work (e.g. renaming a module A to B.C causes the source file to move from CAL/A.cal to CAL/B/C.cal - a different directory)
                        // then we do a import/delete combo
                        return transferResourceToAnotherStore(oldResourceName, newResourceName, newResourceStore, renameStatus);
                    }
                    
                    return true;
                    
                } else {
                    // the old and new resource stores are different, so we'll attempt to do a import/delete combo
                    return transferResourceToAnotherStore(oldResourceName, newResourceName, newResourceStore, renameStatus);
                }
            }
        } else {
            String message = "Could not find resource to rename: (Type: " + getResourceType() + ". Name: " + oldResourceName + ")";
            renameStatus.add(new Status(Status.Severity.INFO, message));
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasFeature(ResourceName resourceName) {
        FilePath filePath = getResourcePath(resourceName);
        return getNullaryEnvironment().exists(filePath);
    }
    
    /**
     * {@inheritDoc}
     */
    public InputStream getInputStream(ResourceName resourceName) {
        FilePath filePath = getResourcePath(resourceName);
        return getNullaryEnvironment().getInputStream(filePath, new Status("Input stream status."));
    }

    /**
     * {@inheritDoc}
     */
    public OutputStream getOutputStream(ResourceName resourceName, Status saveStatus) {
        File resourceFile = getFeatureFile(resourceName, true);
        return getOutputStream(resourceFile, saveStatus);
    }
    
    /**
     * Helper method to create an output stream for a file corresponding to a ResourceName.
     * @param resourceFile the file for the resource.
     * @param saveStatus the tracking status object
     * @return an output stream for the given file, or null if the output stream could not be created.
     */
    static final OutputStream getOutputStream(File resourceFile, Status saveStatus) {

        OutputStream os;
        try {
            // Write out the resource file
            os = new FileOutputStream(resourceFile);
            
        } catch (FileNotFoundException e) {
            saveStatus.add(new Status(Status.Severity.WARNING, "Failed to save resource.", e));
            return null;
        }

        return os;
    }
    

    /**
     * {@inheritDoc}
     */
    public String getDebugInfo(ResourceName resourceName) {
        FilePath filePath = getResourcePath(resourceName);
        return getNullaryEnvironment().getDebugInfo(filePath);
    }

    /**
     * {@inheritDoc}
     */
    public long getTimeStamp(ResourceName resourceName) {
        File resourceFile = getFeatureFile(resourceName, false);

        if (resourceFile != null && resourceFile.exists()) {
            return resourceFile.lastModified();
        }
        return 0;
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean isWriteable() {
        return true;
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean isWriteable(ResourceName resourceName) {
        File resourceFile = getFeatureFile(resourceName, false);
        return resourceFile != null && resourceFile.isFile() && resourceFile.canWrite();
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean isRemovable(ResourceName resourceName) {
        return isWriteable(resourceName);
    }
    
    
    /**
     * A ResourceNullaryStore for module resources.
     * @author Edward Lam
     */
    public static abstract class Module extends ResourceNullaryStore implements ResourceStore.Module {
        
        /**
         * Constructor for a ResourceNullaryStore.Module.
         * @param resourceType
         * @param pathMapper
         */
        public Module(String resourceType, ResourcePathMapper.Module pathMapper) {
            super(resourceType, pathMapper);
        }
        
        /**
         * {@inheritDoc}
         */
        public void removeModuleResources(ModuleName moduleName, Status removeStatus) {
            // Note: we're assuming somewhat that all the resources of a given type for the given module live in the same directory.
            deleteModuleResourceFiles(moduleName, removeStatus);
            
            NullaryEnvironment nullaryEnvironment = getNullaryEnvironment();
            
            // Now remove the base folder, if the module is represented by a folder.
            ResourcePath moduleResourcePath = ((ResourcePathMapper.Module)getPathMapper()).getModuleResourcePath(moduleName);
            if (nullaryEnvironment.exists(moduleResourcePath)) {
                nullaryEnvironment.delTree(moduleResourcePath, removeStatus);
            }
        }
        
        /**
         * Removes just the files (but not the directories) associated with the resources of the given module.
         * @param moduleName the name of a module.
         * @param removeStatus the tracking status object.
         */
        public void removeFilesOfResourcesInModule(ModuleName moduleName, Status removeStatus) {
            // Note: we're assuming somewhat that all the resources of a given type for the given module live in the same directory.
            deleteModuleResourceFiles(moduleName, removeStatus);
            
            // We do not remove the base folder, because there may be other modules living in subfolders.
        }

        /**
         * Removes just the files (but not the directories) associated with the resources of the given module.
         * @param moduleName the name of a module.
         * @param removeStatus the tracking status object.
         */
        private void deleteModuleResourceFiles(ModuleName moduleName, Status removeStatus) {
            List<ResourceName> featureNameList = getModuleResourceNameList(moduleName);

            for (final ResourceName resourceName : featureNameList) {
                File resourceFile = getFeatureFile(resourceName, false);
                if (resourceFile == null || !resourceFile.isFile()) {
                    String message = "Could not determine location for " + resourceName;
                    removeStatus.add(new Status(Status.Severity.ERROR, message, null));
                    
                } else {
                    if (!resourceFile.delete()) {
                        String message = "Could not remove resource for " + resourceName;
                        removeStatus.add(new Status(Status.Severity.ERROR, message, null));
                    }
                }
            }
        }
        
        /**
         * {@inheritDoc}
         */
        public final Iterator<WorkspaceResource> getResourceIterator() {
            return ModuleResourceStoreHelper.getResourceIterator(this);
        }
        
        /**
         * {@inheritDoc}
         */
        public final Iterator<WorkspaceResource> getResourceIterator(ModuleName moduleName) {
            return ModuleResourceStoreHelper.getResourceIterator(this, moduleName);
        }
        
        /**
         * {@inheritDoc}
         */
        public OutputStream getOutputStream(ResourceName resourceName, Status saveStatus) {

            File resourceFile = null;  // should be non-null by the time we create the output stream.
            
            if (getResourceType() != ModuleSourceDefinition.RESOURCE_TYPE) {   
                // A module store which is not a cal source store.
                // Attempt to co-locate the module resources with the CAL source file.
                FilePath resourceFilePath = getResourcePath(resourceName);

                // (But don't change the location if the file already exists).
                if (!getNullaryEnvironment().exists(resourceFilePath)) {
                    
                    // Extract the module name (if any) from the resource name.
                    FeatureName featureName = resourceName.getFeatureName();
                    if (featureName instanceof CALFeatureName) {
                        
                        CALFeatureName calFeatureName = (CALFeatureName)featureName;

                        if (calFeatureName.hasModuleName()) {
                            ModuleName moduleName = calFeatureName.toModuleName();

                            // Get the base file path from the path mapper and use this to get the file with the right base folder.
                            ResourceName moduleResourceName = new ResourceName(CALFeatureName.getModuleFeatureName(moduleName));
                            FilePath baseFilePath = CALSourcePathMapper.INSTANCE.getResourcePath(moduleResourceName);
                            resourceFile = getNullaryEnvironment().getFile(baseFilePath, resourceFilePath, true);
                        }
                    }
                }
            }
            
            if (resourceFile == null) {
                resourceFile = getFeatureFile(resourceName, true);
            }
            
            return getOutputStream(resourceFile, saveStatus);
        }
    }
}
