/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.openide.filesystems;

import java.awt.Image;
import java.awt.Toolkit;
import java.beans.BeanInfo;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Set;
import static org.openide.filesystems.FileSystem.LOG;
import org.openide.modules.PatchFor;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.actions.SystemAction;

/**
 * The class provides an API compatibility bridge to support implementations
 * compiled against <code>org.openide.filesystems&lt;8.0</code> and earlier
 *
 * @author sdedic
 */
@PatchFor(FileSystem.class)
public abstract class FileSystemCompat {
    /** system actions for this FS */
    private static final SystemAction[] NO_SYSTEM_ACTIONS = new SystemAction[0];

    /** Property name giving capabilities state. @deprecated No more capabilities. */
    static final String PROP_CAPABILITIES = "capabilities"; // NOI18N    

    /** hidden flag */
    private boolean hidden = false;

    /** Describes capabilities of the filesystem.
    */
    @Deprecated // have to store it for compat
    private /* XXX JDK #6460147: javac still reports it even though @Deprecated,
               and @SuppressWarnings("deprecation") does not help either: FileSystemCapability*/Object capability;

    /** property listener on FileSystemCapability. */
    private transient PropertyChangeListener capabilityListener;

    /** Returns an array of actions that can be invoked on any file in
    * this filesystem.
    * These actions should preferably
    * support the {@link org.openide.util.actions.Presenter.Menu Menu},
    * {@link org.openide.util.actions.Presenter.Popup Popup},
    * and {@link org.openide.util.actions.Presenter.Toolbar Toolbar} presenters.
    *
    * @return array of available actions
    */
    @SuppressWarnings("ReturnOfCollectionOrArrayField")
    public SystemAction[] getActions() {
        // If implementations don't override getActions, an empty array is
        // returned to satisfy the FileSystem#getActions contract. As the
        // empty array is immutable, it is save to return a singleton in this
        // case
        return NO_SYSTEM_ACTIONS;
    }

    /**
     * Get actions appropriate to a certain file selection.
     * By default, returns the same list as {@link #getActions()}.
     * @param foSet one or more files which may be selected
     * @return zero or more actions appropriate to those files
     */
    public SystemAction[] getActions(Set<FileObject> foSet) {
        return this.getActions();
    }

    /** Allows filesystems to set up the environment for external execution
    * and compilation.
    * Each filesystem can add its own values that
    * influence the environment. The set of operations that can modify
    * environment is described by the {@link Environment} interface.
    * <P>
    * The default implementation throws an exception to signal that it does not
    * support external compilation or execution.
    *
    * @param env the environment to setup
    * @exception EnvironmentNotSupportedException if external execution
    *    and compilation cannot be supported
    * @deprecated Please use the <a href="@org-netbeans-api-java-classpath@/org/netbeans/api/java/classpath/ClassPath.html">ClassPath API</a> instead.
    */
    @Deprecated
    public void prepareEnvironment(FileSystem$Environment env) throws EnvironmentNotSupportedException {
        Object o = this;
        throw new EnvironmentNotSupportedException((FileSystem)o);
    }
    
    /**
     * Provides access to deprecated methods at runtime.
     * 
     * @param fs The FileSystem
     * @return the FileSystemCompat instanceo for the FileSystem.
     */
    public static FileSystemCompat compat(FileSystem fs) {
        Object o = fs;
        return (FileSystemCompat)o;
    }
    
    private FileSystem fs() {
        Object o = this;
        return (FileSystem)o;
    }

    /** The object describing capabilities of this filesystem.
     * Subclasses cannot override it.
     * @return object describing capabilities of this filesystem.
     * @deprecated Capabilities are no longer used.
     */
    @Deprecated
    public final FileSystemCapability getCapability() {
        if (capability == null) {
            capability = new FileSystemCapability.Bean();
            ((FileSystemCapability) capability).addPropertyChangeListener(getCapabilityChangeListener());
        }

        return (FileSystemCapability) capability;
    }

    /** Allows subclasses to change a set of capabilities of the
    * filesystem.
    * @param capability the capability to use
     * @deprecated Capabilities are no longer used.
    */
    @Deprecated
    protected final void setCapability(FileSystemCapability capability) {
        if (this.capability != null) {
            ((FileSystemCapability) this.capability).removePropertyChangeListener(getCapabilityChangeListener());
        }

        this.capability = capability;

        if (this.capability != null) {
            ((FileSystemCapability) this.capability).addPropertyChangeListener(getCapabilityChangeListener());
        }
    }

    /** returns property listener on FileSystemCapability. */
    private synchronized PropertyChangeListener getCapabilityChangeListener() {
        if (capabilityListener == null) {
            capabilityListener = new PropertyChangeListener() {
                        public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) {
                            fs().firePropertyChange(
                                PROP_CAPABILITIES, propertyChangeEvent.getOldValue(), propertyChangeEvent.getNewValue()
                            );
                        }
                    };
        }

        return capabilityListener;
    }
    
    /** Reads object from stream and creates listeners.
    * @param in the input stream to read from
    * @exception IOException error during read
    * @exception ClassNotFoundException when class not found
    */
    @SuppressWarnings("deprecation")
    private void readObject(java.io.ObjectInputStream in)
    throws java.io.IOException, java.lang.ClassNotFoundException {
        in.defaultReadObject();

        if (capability != null) {
            ((FileSystemCapability) capability).addPropertyChangeListener(getCapabilityChangeListener());
        }
    }

    /** Set hidden state of the object.
     * A hidden filesystem is not presented to the user in the Repository list (though it may be present in the Repository Settings list).
    *
    * @param hide <code>true</code> if the filesystem should be hidden
     * @deprecated This property is now useless.
    */
    @Deprecated
    public final void setHidden(boolean hide) {
        if (hide != hidden) {
            hidden = hide;
            fs().firePropertyChange(FileSystem.PROP_HIDDEN, (!hide) ? Boolean.TRUE : Boolean.FALSE, hide ? Boolean.TRUE : Boolean.FALSE);
        }
    }

    /** Getter for the hidden property.
     * @return the hidden property.
     * @deprecated This property is now useless.
    */
    @Deprecated
    public final boolean isHidden() {
        return hidden;
    }

    /** Tests whether filesystem will survive reloading of system pool.
    * If true then when
    * {@link Repository} is reloading its content, it preserves this
    * filesystem in the pool.
    * <P>
    * This can be used when the pool contains system level and user level
    * filesystems. The system ones should be preserved when the user changes
    * the content (for example when he is loading a new project).
    * <p>The default implementation returns <code>false</code>.
    *
    * @return true if the filesystem should be persistent
     * @deprecated This property is long since useless.
    */
    @Deprecated
    protected boolean isPersistent() {
        return false;
    }
    
    static {
        try {
            Field f = FileSystem.class.getDeclaredField("SFS_STATUS"); // NOI18N
            f.setAccessible(true);
            StatusDecorator del = (StatusDecorator)f.get(null);
            f.set(null, new SystemStatus(del));
        } catch (NoSuchFieldException ex) {
            Exceptions.printStackTrace(ex);
        } catch (SecurityException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalArgumentException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalAccessException ex) {
            Exceptions.printStackTrace(ex);
        }
    }

    /**
     * Enhancement of the default SFS_Status, loads image through ImageUtilities.
     */
    private static class SystemStatus implements FileSystem$Status, StatusDecorator {
        private final StatusDecorator delegate;

        public SystemStatus(StatusDecorator delegate) {
            this.delegate = delegate;
        }

        @Override
        public String annotateName(String name, Set<? extends FileObject> files) {
            return delegate.annotateName(name, files);
        }

        @Override
        public Image annotateIcon(Image im, int type, Set<? extends FileObject> files) {
            for (FileObject fo : files) {
                Image img = annotateIcon(fo, type);
                if (img != null) {
                    return img;
                }
            }
            return im;
        }

        private Image annotateIcon(FileObject fo, int type) {
            String attr = null;
            if (type == BeanInfo.ICON_COLOR_16x16) {
                attr = "SystemFileSystem.icon"; // NOI18N
            } else if (type == BeanInfo.ICON_COLOR_32x32) {
                attr = "SystemFileSystem.icon32"; // NOI18N
            }
            if (attr != null) {
                Object value = fo.getAttribute(attr);
                if (value != null) {
                    if (value instanceof URL) {
                        return Toolkit.getDefaultToolkit().getImage((URL) value);
                    } else if (value instanceof Image) {
                        // #18832
                        return (Image) value;
                    } else {
                        LOG.warning("Attribute " + attr + " on " + fo + " expected to be a URL or Image; was: " + value);
                    }
                }
            }
            String base = (String) fo.getAttribute("iconBase"); // NOI18N
            if (base != null) {
                if (type == BeanInfo.ICON_COLOR_16x16) {
                    return ImageUtilities.loadImage(base, true);
                } else if (type == BeanInfo.ICON_COLOR_32x32) {
                    return ImageUtilities.loadImage(insertBeforeSuffix(base, "_32"), true); // NOI18N
                }
            }
            return null;
        }


        private static String insertBeforeSuffix(String path, String toInsert) {
            String withoutSuffix = path;
            String suffix = ""; // NOI18N
            if (path.lastIndexOf('.') >= 0) {
                withoutSuffix = path.substring(0, path.lastIndexOf('.'));
                suffix = path.substring(path.lastIndexOf('.'), path.length());
            }
            return withoutSuffix + toInsert + suffix;
        }

        @Override
        public String annotateNameHtml(String name, Set<? extends FileObject> files) {
            return name;
        }
    }
    
    private FileSystem$Status status;
    
    /**
     * Compatible implementation of FileSystem.getStatus()
     * 
     * @return 
     */
    public FileSystem$Status getStatus() {
        if (status == null) {
            status = new StatusImpl(fs().getDecorator());
        }
        return status;
    }
    
    /**
     * This is a compatible implementation of FileSystem.Status, which bridges
     * a new FS implementation (Decorator, IconDecorator) to the old Status interface
     */
    private static class StatusImpl implements FileSystem$Status, FileSystem$HtmlStatus {
        private final StatusDecorator decorator;
        private final Method annotateIconDel;
        
        public StatusImpl(StatusDecorator decorator) {
            this.decorator = decorator;
            Method m = null;
            try {
                m = decorator.getClass().getMethod("annotateIcon", Image.class, Integer.TYPE, Set.class);
                m.setAccessible(true);
            } catch (NoSuchMethodException ex) {
            } catch (SecurityException ex) {
                Exceptions.printStackTrace(ex);
            }
            this.annotateIconDel = m;
        }
        
        @Override
        public String annotateName(String name, Set<? extends FileObject> files) {
            return decorator.annotateName(name, files);
        }

        @Override
        public Image annotateIcon(Image icon, int iconType, Set<? extends FileObject> files) {
            if (annotateIconDel == null) {
                return icon;
            }
            try {
                return (Image)annotateIconDel.invoke(decorator, icon, iconType, files);
            } catch (IllegalAccessException ex) {
                Exceptions.printStackTrace(ex);
            } catch (IllegalArgumentException ex) {
                Exceptions.printStackTrace(ex);
            } catch (InvocationTargetException ex) {
                Exceptions.printStackTrace(ex);
            }
            return icon;
        }

        @Override
        public String annotateNameHtml(String name, Set<? extends FileObject> files) {
            return decorator.annotateNameHtml(name, files);
        }
    }
}
