/**
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package com.mucommander.commons.file;

import com.mucommander.commons.file.filter.FileFilter;
import com.mucommander.commons.file.filter.FilenameFilter;
import com.mucommander.commons.io.FileTransferException;
import com.mucommander.commons.io.RandomAccessInputStream;
import com.mucommander.commons.io.RandomAccessOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * ProxyFile is an {@link AbstractFile} that acts as a proxy between the class that extends it
 * and the proxied <code>AbstractFile</code> instance specified to the constructor.
 * All <code>AbstractFile</code> public methods (abstract or not) are delegated to the proxied file.
 * The {@link #getProxiedFile()} method allows to retrieve the proxied file instance.
 *
 * <p>This class is useful for wrapper files, such as {@link com.mucommander.commons.file.archive.AbstractArchiveFile archive files},
 * that provide additional functionality over an existing <code>AbstractFile</code> instance (the proxied file).
 * By implementing/overriding every <code>AbstractFile</code> methods, <code>ProxyFile</code> ensures that
 * all <code>AbstractFile</code> methods can safely be used, even if they are overridden by the proxied
 * file instance's class.
 *
 * <p><b>Implementation note:</b> the <code>java.lang.reflect.Proxy</code> class can unfortunately not be
 * used as it only works with interfaces (not abstract class). There doesn't seem to be any dynamic way to
 * proxy method invocations, so any modifications made to {@link com.mucommander.commons.file.AbstractFile} must be also
 * reflected in <code>ProxyFile</code>.
 *
 * @see com.mucommander.commons.file.archive.AbstractArchiveFile
 * @author Maxence Bernard
 */
public abstract class ProxyFile extends AbstractFile {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyFile.class);

    /** The proxied file instance */
    protected AbstractFile file;


    /**
     * Creates a new ProxyFile using the given file to delegate AbstractFile method calls to.
     *
     * @param file the file to be proxied
     */
    public ProxyFile(AbstractFile file) {
        super(file.getURL());
        this.file = file;
    }

    /**
     * Returns the <code>AbstractFile</code> instance proxied by this </code>ProxyFile</code>.
     *
     * @return the <code>AbstractFile</code> instance proxied by this </code>ProxyFile</code>
     */
    public AbstractFile getProxiedFile() {
        return file;
    }


    /////////////////////////////////
    // AbstractFile implementation //
    /////////////////////////////////

    @Override
    public long getDate() {
        return file.getDate();
    }

    @Override
    public void changeDate(long lastModified) throws IOException, UnsupportedFileOperationException {
        file.changeDate(lastModified);
    }

    @Override
    public long getSize() {
        return file.getSize();
    }

    @Override
    public AbstractFile getParent() {
        return file.getParent();
    }

    @Override
    public void setParent(AbstractFile parent) {
        file.setParent(parent);
    }

    @Override
    public boolean exists() {
        return file.exists();
    }

    @Override
    public void changePermission(PermissionAccess access, PermissionType permission, boolean enabled) throws IOException, UnsupportedFileOperationException {
        file.changePermission(access, permission, enabled);
    }

    @Override
    public String getOwner() {
        return file.getOwner();
    }

    @Override
    public boolean canGetOwner() {
        return file.canGetOwner();
    }

    @Override
    public String getGroup() {
        return file.getGroup();
    }

    @Override
    public boolean canGetGroup() {
        return file.canGetGroup();
    }

    @Override
    public boolean isDirectory() {
        return file.isDirectory();
    }

    @Override
    public boolean isSymlink() {
        return file.isSymlink();
    }

    @Override
    public boolean isSystem() {
        return file.isSystem();
    }

    @Override
    public AbstractFile[] ls() throws IOException, UnsupportedFileOperationException {
        return file.ls();
    }

    @Override
    public void mkdir() throws IOException, UnsupportedFileOperationException {
        file.mkdir();
    }

    @Override
    public InputStream getInputStream() throws IOException, UnsupportedFileOperationException {
        return file.getInputStream();
    }

    @Override
    public OutputStream getOutputStream() throws IOException, UnsupportedFileOperationException {
        return file.getOutputStream();
    }

    @Override
    public OutputStream getAppendOutputStream() throws IOException, UnsupportedFileOperationException {
        return file.getAppendOutputStream();
    }

    @Override
    public RandomAccessInputStream getRandomAccessInputStream() throws IOException, UnsupportedFileOperationException {
        return file.getRandomAccessInputStream();
    }

    @Override
    public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException, UnsupportedFileOperationException {
        return file.getRandomAccessOutputStream();
    }

    @Override
    public void delete() throws IOException, UnsupportedFileOperationException {
        file.delete();
    }

    @Override
    public void copyRemotelyTo(AbstractFile destFile) throws IOException, UnsupportedFileOperationException {
        file.copyRemotelyTo(destFile);
    }

    @Override
    public void renameTo(AbstractFile destFile) throws IOException, UnsupportedFileOperationException {
        file.renameTo(destFile);
    }

    @Override
    public long getFreeSpace() throws IOException, UnsupportedFileOperationException {
        return file.getFreeSpace();
    }

    @Override
    public long getTotalSpace() throws IOException, UnsupportedFileOperationException {
        return file.getTotalSpace();
    }

    @Override
    public Object getUnderlyingFileObject() {
        return file.getUnderlyingFileObject();
    }

    
    /////////////////////////////////////
    // Overridden AbstractFile methods //
    /////////////////////////////////////

    @Override
    public final boolean isFileOperationSupported(FileOperation op) {
        Class<? extends AbstractFile> thisClass = getClass();
        Method opMethod = op.getCorrespondingMethod(thisClass);
        // If the method corresponding to the file operation has been overridden by this class (a ProxyFile subclass),
        // check the presence of the UnsupportedFileOperation annotation in this class.
        try {
            if(!thisClass.getMethod(opMethod.getName(), opMethod.getParameterTypes()).getDeclaringClass().equals(ProxyFile.class))
                return AbstractFile.isFileOperationSupported(op, thisClass);
        }
        catch(Exception e) {
            // Should never happen, unless AbstractFile method signatures have changed and FileOperation has not been updated
            LOGGER.warn("Exception caught, this should not have happened", e);
        }

        // Otherwise, check for the presence of the UnsupportedFileOperation annotation in the wrapped AbstractFile.
        return file.isFileOperationSupported(op);
    }

    @Override
    public FileURL getURL() {
        return file.getURL();
    }

    @Override
    public URL getJavaNetURL() throws MalformedURLException {
        return file.getJavaNetURL();
    }

    @Override
    public String getName() {
        return file.getName();
    }

    @Override
    public String getExtension() {
        return file.getExtension();
    }

    @Override
    public String getBaseName() {
    	return file.getBaseName();
    }
    
    @Override
    public String getAbsolutePath() {
        return file.getAbsolutePath();
    }

    @Override
    public String getCanonicalPath() {
        return file.getCanonicalPath();
    }

    @Override
    public AbstractFile getCanonicalFile() {
        return file.getCanonicalFile();
    }

    @Override
    public String getSeparator() {
        return file.getSeparator();
    }

    @Override
    public boolean isArchive() {
        return file.isArchive();
    }

    @Override
    public boolean isHidden() {
        return file.isHidden();
    }

    @Override
    public FilePermissions getPermissions() {
        return file.getPermissions();
    }

    @Override
    public void changePermissions(int permissions) throws IOException, UnsupportedFileOperationException {
        file.changePermissions(permissions);
    }

    @Override
    public PermissionBits getChangeablePermissions() {
        return file.getChangeablePermissions();
    }

    @Override
    public String getPermissionsString() {
        return file.getPermissionsString();
    }

    @Override
    public AbstractFile getRoot() {
        return file.getRoot();
    }

    @Override
    public boolean isRoot() {
        return file.isRoot();
    }

    @Override
    public AbstractFile getVolume() {
        return file.getVolume();
    }

    @Override
    public InputStream getInputStream(long offset) throws IOException, UnsupportedFileOperationException {
        return file.getInputStream(offset);
    }

    @Override
    public void copyStream(InputStream in, boolean append, long length) throws FileTransferException {
        file.copyStream(in, append, length);
    }

    @Override
    public AbstractFile[] ls(FileFilter filter) throws IOException, UnsupportedFileOperationException {
        return file.ls(filter);
    }

    @Override
    public AbstractFile[] ls(FilenameFilter filter) throws IOException, UnsupportedFileOperationException {
        return file.ls(filter);
    }

    @Override
    public void mkfile() throws IOException, UnsupportedFileOperationException {
        file.mkfile();
    }

    @Override
    public void deleteRecursively() throws IOException, UnsupportedFileOperationException {
        file.deleteRecursively();
    }

    @Override
    public MonitoredFile toMonitoredFile() {
        return file.toMonitoredFile();
    }

    public boolean equals(Object f) {
        return file.equals(f);
    }

    @Override
    public boolean equalsCanonical(Object f) {
        return file.equalsCanonical(f);
    }

    public int hashCode() {
        return file.hashCode();
    }

    public String toString() {
        return file.toString();
    }
}
