/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
 *    http://www.geo-solutions.it/
 *    Copyright 2014 - 2015 GeoSolutions


 * 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 it.geosolutions.jaiext;

import it.geosolutions.jaiext.interpolators.InterpolationBicubic;
import it.geosolutions.jaiext.interpolators.InterpolationBilinear;
import it.geosolutions.jaiext.interpolators.InterpolationNearest;

import java.awt.image.renderable.ParameterBlock;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.OperationNode;
import javax.media.jai.OperationRegistry;
import javax.media.jai.PropertyGenerator;
import javax.media.jai.PropertySource;
import javax.media.jai.RegistryElementDescriptor;
import javax.media.jai.registry.RenderedRegistryMode;
import javax.media.jai.util.ImagingException;
import javax.media.jai.util.ImagingListener;

import com.sun.media.jai.util.PropertyUtil;

/**
 * A thread safe implementation of OperationRegistry using Java 5 Concurrent {@link ReadWriteLock}
 * Also it is able to substitute JAI operations with JAI-EXT ones and vice versa.
 * 
 * @author Andrea Aime - GeoSolutions
 * @author Nicola Lagomarsini - GeoSolutions
 */
public final class ConcurrentOperationRegistry extends OperationRegistry {
    /** Path to the JAI default registryfile.jai */
    static String JAI_REGISTRY_FILE = "META-INF/javax.media.jai.registryFile.jai";

    /** Name of the other registryfile.jai */
    static String USR_REGISTRY_FILE = "META-INF/registryFile.jaiext";

    /** String associated to the vendor key */
    static final String VENDOR_NAME = "Vendor";

    /** String associated to the JAIEXT product */
    static final String JAIEXT_PRODUCT = "it.geosolutions.jaiext";

    /** String associated to the JAI product */
    static final String JAI_PRODUCT = "com.sun.media.jai";

    /** String associated to the JAI product when the operation is "Null" */
    static final String JAI_PRODUCT_NULL = "javax.media.jai";

    /** Logger associated to the class*/
    private static final Logger LOGGER = Logger.getLogger(ConcurrentOperationRegistry.class.toString());

    public static OperationRegistry initializeRegistry() {
        return initializeRegistry(true);
    }

    public static OperationRegistry initializeRegistry(boolean useJaiExtOps) {
        try {
            // URL associated to the default JAI registryfile.jai
            InputStream url = PropertyUtil.getFileFromClasspath(JAI_REGISTRY_FILE);

            if (url == null) {
                throw new RuntimeException("Could not find the main registry file");
            }
            // Creation of a new registry
            ConcurrentOperationRegistry registry = new ConcurrentOperationRegistry();
            // Registration of the JAI operations
            if (url != null) {
                registry.updateFromStream(url);
            }
            // Registration of the operation defined in any registryFile.jai file
            registry.registerServices(null);
            // Listing of all the registered operations
            List<OperationDescriptor> descriptors = registry
                    .getDescriptors(RenderedRegistryMode.MODE_NAME);
            // Creation of a new OperationCollection object
            OperationCollection input = new OperationCollection(registry);
            input.createMapFromDescriptors(descriptors);
            // Saving of the all initial operations
            Map<String, OperationItem> map = input.copy().filter(JAI_PRODUCT).map;
            map.put("Null", input.get("Null"));
            registry.jaiMap = map;

            // First load all the REGISTRY_FILEs that are found in
            // the specified class loader.
            ClassLoader loader = registry.getClass().getClassLoader();
            Enumeration<URL> en = loader.getResources(USR_REGISTRY_FILE);
            // Creation of another OperationCollection instance to use for storing all the 
            OperationCollection changed = new OperationCollection(registry);
            // Loop on all the registryFile.jai files
            while (en.hasMoreElements()) {
                URL url1 = en.nextElement();
                changed.add(RegistryFileParser.parseFile(null, url1));
            }
            // Filter only the JAIEXT operations
            changed = changed.filter(JAIEXT_PRODUCT);
            // Copy the available JAIEXT operations
            registry.jaiExtMap = changed.copy().map;
            // Substitute the JAIEXT operations only if required
            if (useJaiExtOps) {
                input.substituteOperations(changed);
            }
            // Set the Collection inside the registry file
            registry.setOperationCollection(input);
            // Return the registry
            return registry;

        } catch (IOException ioe) {
            ImagingListener listener = JAI.getDefaultInstance().getImagingListener();
            String message = "Error occurred while initializing JAI";
            listener.errorOccurred(message, new ImagingException(message, ioe),
                    OperationRegistry.class, false);

            return null;
        }
    }

    /** The reader/writer lock for this class. */
    private ReadWriteLock lock;

    /** Collection of the registered Operations */
    OperationCollection collection;

    /** Map of the JAI operations */
    private Map<String, OperationItem> jaiMap;

    /** Map of the JAI-EXT operations */
    private Map<String, OperationItem> jaiExtMap;

    public ConcurrentOperationRegistry() {
        super();

        // Create a concurrent RW lock.
        lock = new ReentrantReadWriteLock();
    }

    public String toString() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.toString();
        } finally {
            readLock.unlock();
        }
    }

    public void writeToStream(OutputStream out) throws IOException {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            super.writeToStream(out);
        } finally {
            readLock.unlock();
        }
    }

    public void initializeFromStream(InputStream in) throws IOException {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.initializeFromStream(in);
        } finally {
            writeLock.unlock();
        }
    }

    public void updateFromStream(InputStream in) throws IOException {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.updateFromStream(in);
        } finally {
            writeLock.unlock();
        }
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.readExternal(in);
        } finally {
            writeLock.unlock();
        }
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            super.writeExternal(out);
        } finally {
            readLock.unlock();
        }
    }

    public void removeRegistryMode(String modeName) {

        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.removeRegistryMode(modeName);
        } finally {
            writeLock.unlock();
        }
    }

    public String[] getRegistryModes() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getRegistryModes();
        } finally {
            readLock.unlock();
        }
    }

    public void registerDescriptor(RegistryElementDescriptor descriptor) {
        Lock writeLock = lock.writeLock();
        boolean changed = false;
        try {
            writeLock.lock();
            super.registerDescriptor(descriptor);
            changed = true;
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Registered descriptor for the operation: "
                        + descriptor.getName());
            }
            // If the collection is present, then it is updated
            if(collection != null && changed && descriptor instanceof OperationDescriptor){
                OperationItem item = new OperationItem((OperationDescriptor) descriptor);
                collection.substituteSingleOp(item);
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Added descriptor for the operation: "
                            + descriptor.getName()  + " to the registry operation list");
                }
            }
        }catch(Exception e){
            // Remove logging for OperationDescriptor registration
            if(e.getMessage().contains("A descriptor is already registered against the name")){
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, e.getMessage());
                }
            } else {
                throw new RuntimeException(e);
            }
        }finally {
            writeLock.unlock();
        }
    }

    public void unregisterDescriptor(RegistryElementDescriptor descriptor) {

        Lock writeLock = lock.writeLock();
        boolean changed = false;
        try {
            writeLock.lock();
            super.unregisterDescriptor(descriptor);
            changed = true;
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Unregistered descriptor for the operation: "
                        + descriptor.getName());
            }
            // If the collection is present, then it is updated
            if(collection != null && changed && descriptor instanceof OperationDescriptor){
                OperationItem item = new OperationItem((OperationDescriptor) descriptor);
                collection.removeSingleOp(item);
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Removed descriptor for the operation: "
                            + descriptor.getName()  + " from the registry operation list");
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public RegistryElementDescriptor getDescriptor(Class descriptorClass, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptor(descriptorClass, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public List getDescriptors(Class descriptorClass) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptors(descriptorClass);
        } finally {
            readLock.unlock();
        }
    }

    public String[] getDescriptorNames(Class descriptorClass) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptorNames(descriptorClass);
        } finally {
            readLock.unlock();
        }
    }

    public RegistryElementDescriptor getDescriptor(String modeName, String descriptorName) {

        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptor(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public List getDescriptors(String modeName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptors(modeName);
        } finally {
            readLock.unlock();
        }
    }

    public String[] getDescriptorNames(String modeName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getDescriptorNames(modeName);
        } finally {
            readLock.unlock();
        }
    }

    public void setProductPreference(String modeName, String descriptorName,
            String preferredProductName, String otherProductName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.setProductPreference(modeName, descriptorName, preferredProductName,
                    otherProductName);
        } finally {
            writeLock.unlock();
        }
    }

    public void unsetProductPreference(String modeName, String descriptorName,
            String preferredProductName, String otherProductName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.unsetProductPreference(modeName, descriptorName, preferredProductName,
                    otherProductName);
        } finally {
            writeLock.unlock();
        }
    }

    public void clearProductPreferences(String modeName, String descriptorName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.clearProductPreferences(modeName, descriptorName);
        } finally {
            writeLock.unlock();
        }
    }

    public String[][] getProductPreferences(String modeName, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getProductPreferences(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public Vector getOrderedProductList(String modeName, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getOrderedProductList(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public void registerFactory(String modeName, String descriptorName, String productName,
            Object factory) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.registerFactory(modeName, descriptorName, productName, factory);
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Registered factory for the operation: "
                        + descriptorName);
            }
            // If the collection is present, then it is updated
            if(collection != null && modeName.equalsIgnoreCase(RenderedRegistryMode.MODE_NAME)){
                collection.substituteFactory(factory, descriptorName, productName); 
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Added factory for the operation: "
                            + descriptorName  + " to the registry operation list");
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public void unregisterFactory(String modeName, String descriptorName, String productName,
            Object factory) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.unregisterFactory(modeName, descriptorName, productName, factory);
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Unregistered factory for the operation: "
                        + descriptorName);
            }
            // If the collection is present, then it is updated
            if(collection != null && modeName.equalsIgnoreCase(RenderedRegistryMode.MODE_NAME)){
                collection.removeFactory(factory, descriptorName, productName); 
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Removed factory for the operation: "
                            + descriptorName  + " from the registry operation list");
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    public void setFactoryPreference(String modeName, String descriptorName, String productName,
            Object preferredOp, Object otherOp) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.setFactoryPreference(modeName, descriptorName, productName, preferredOp, otherOp);
        } finally {
            writeLock.unlock();
        }
    }

    public void unsetFactoryPreference(String modeName, String descriptorName, String productName,
            Object preferredOp, Object otherOp) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.unsetFactoryPreference(modeName, descriptorName, productName, preferredOp,
                    otherOp);
        } finally {
            writeLock.unlock();
        }
    }

    public void clearFactoryPreferences(String modeName, String descriptorName, String productName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.clearFactoryPreferences(modeName, descriptorName, productName);
        } finally {
            writeLock.unlock();
        }
    }

    public Object[][] getFactoryPreferences(String modeName, String descriptorName,
            String productName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getFactoryPreferences(modeName, descriptorName, productName);
        } finally {
            readLock.unlock();
        }
    }

    public List getOrderedFactoryList(String modeName, String descriptorName, String productName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getOrderedFactoryList(modeName, descriptorName, productName);
        } finally {
            readLock.unlock();
        }
    }

    public Iterator getFactoryIterator(String modeName, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getFactoryIterator(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public Object getFactory(String modeName, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getFactory(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public Object invokeFactory(String modeName, String descriptorName, Object[] args) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            // For Rendered Mode, a check on the interpolations objects is made
            // in order to convert each eventual JAI-EXT interpolation class
            // if the Factory belongs to the JAI API
            if(modeName.equalsIgnoreCase(RenderedRegistryMode.MODE_NAME)){
                checkInterpolation(descriptorName, args);
            }

            return super.invokeFactory(modeName, descriptorName, args);
        } finally {
            readLock.unlock();
        }
    }

    public void addPropertyGenerator(String modeName, String descriptorName,
            PropertyGenerator generator) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.addPropertyGenerator(modeName, descriptorName, generator);
        } finally {
            writeLock.unlock();
        }
    }

    public void removePropertyGenerator(String modeName, String descriptorName,
            PropertyGenerator generator) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.removePropertyGenerator(modeName, descriptorName, generator);
        } finally {
            writeLock.unlock();
        }
    }

    public void copyPropertyFromSource(String modeName, String descriptorName, String propertyName,
            int sourceIndex) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.copyPropertyFromSource(modeName, descriptorName, propertyName, sourceIndex);
        } finally {
            writeLock.unlock();
        }
    }

    public void suppressProperty(String modeName, String descriptorName, String propertyName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.suppressProperty(modeName, descriptorName, propertyName);
        } finally {
            writeLock.unlock();
        }
    }

    public void suppressAllProperties(String modeName, String descriptorName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.suppressAllProperties(modeName, descriptorName);
        } finally {
            writeLock.unlock();
        }
    }

    public void clearPropertyState(String modeName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.clearPropertyState(modeName);
        } finally {
            writeLock.unlock();
        }
    }

    public String[] getGeneratedPropertyNames(String modeName, String descriptorName) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getGeneratedPropertyNames(modeName, descriptorName);
        } finally {
            readLock.unlock();
        }
    }

    public PropertySource getPropertySource(String modeName, String descriptorName, Object op,
            Vector sources) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getPropertySource(modeName, descriptorName, op, sources);
        } finally {
            readLock.unlock();
        }
    }

    public PropertySource getPropertySource(OperationNode op) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();

            return super.getPropertySource(op);
        } finally {
            readLock.unlock();
        }
    }

    public void registerServices(ClassLoader cl) throws IOException {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.registerServices(cl);
        } finally {
            writeLock.unlock();
        }
    }

    public void unregisterOperationDescriptor(String operationName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.unregisterOperationDescriptor(operationName);
        } finally {
            writeLock.unlock();
        }
    }

    public void clearOperationPreferences(String operationName, String productName) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            super.clearOperationPreferences(operationName, productName);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Sets the {@link OperationCollection} containing the list of all the operations contained by the registry.
     * 
     * @param coll
     */
    public void setOperationCollection(OperationCollection coll) {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock();
            this.collection = coll;
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * This method returns an {@link OperationCollection} object containing all the registered operations.
     * 
     * @return
     */
    OperationCollection getOperationCollection() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            return collection;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Returns a {@link Collection} object containing a view of the {@link OperationCollection} inside the registry.
     * 
     * @return
     */
    public Collection<OperationItem> getOperations() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            return collection.getOperations();
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Returns a Map containing the {@link OperationItem} objects for each operation. The jai parameter indicates whether must be returned the map of
     * the jai operations or of the Jai-ext ones.
     * 
     * @param jai
     * @return
     */
    public Map<String, OperationItem> getOperationMap(boolean jai) {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            if (jai) {
                return jaiMap;
            } else {
                return jaiExtMap;
            }
        } finally {
            readLock.unlock();
        }
    }

    /**
     * This method internally check if the descriptor used is a
     * 
     * @param descriptorName
     * @param args
     */
    void checkInterpolation(String descriptorName, Object[] args) {
        // First check if the collection is present and then get the OperationItem associated
        OperationItem item = null;
        if (collection != null) {
            item = collection.get(descriptorName);
        }
        // By default we do not change the Interpolation
        boolean jaiext = true;
        // If the item is present we check if it belongs to jaiext
        if (item != null) {
            jaiext = item.isJAIEXTProduct();
        } else {
            // Else we check the Descriptor vendor parameter
            OperationDescriptor op = (OperationDescriptor) getDescriptor(
                    RenderedRegistryMode.MODE_NAME, descriptorName);
            String vendor = op.getResourceBundle(null).getString(VENDOR_NAME);
            jaiext = vendor.equalsIgnoreCase(JAIEXT_PRODUCT);
        }
        // If the operation is not a JAI-EXT one then we start to check if there is any Interpolation
        // instance
        if (!jaiext) {
            // Cycle on the parameterBlock parameters
            ParameterBlock block = (ParameterBlock) args[0];
            Vector<Object> params = block.getParameters();
            int index = 0;
            for (Object param : params) {
                if (param instanceof Interpolation) {
                    Interpolation interp = null;
                    // If the parameter is an instance of one of the JAI-EXT Interpolation classes
                    // then it is transformed into the related JAI Interpolation class.
                    if (param instanceof InterpolationNearest) {
                        interp = new javax.media.jai.InterpolationNearest();
                    } else if (param instanceof InterpolationBilinear) {
                        InterpolationBilinear bil = (InterpolationBilinear) param;
                        interp = new javax.media.jai.InterpolationBilinear(bil.getSubsampleBitsH());
                    } else if (param instanceof InterpolationBicubic) {
                        InterpolationBicubic bic = (InterpolationBicubic) param;
                        if (bic.isBicubic2()) {
                            interp = new javax.media.jai.InterpolationBicubic2(
                                    bic.getSubsampleBitsH());
                        } else {
                            interp = new javax.media.jai.InterpolationBicubic(
                                    bic.getSubsampleBitsH());
                        }
                    }
                    if (interp != null) {
                        block.set(interp, index);
                        if (LOGGER.isLoggable(Level.FINEST)) {
                            LOGGER.log(Level.FINEST, "Converted JAI-EXT Interpolation object to JAI one");
                        }
                    }
                    break;
                }
                index++;
            }
        }
    }

    /**
     * The {@link OperationItem} class is a wrapper for the {@link OperationDescriptor} class which can store informations about the operations and
     * the associated factory.
     * 
     * @author Nicola Lagomarsini GeoSolutions S.A.S.
     * 
     */
    public static class OperationItem {
        /** {@link OperationDescriptor} instance associated to the operation */
        private OperationDescriptor op;

        /** Descriptor vendor name */
        private String vendor;

        /** Operation Name */
        private String opName;

        /** Factory object (May be null) */
        private Object factory;

        /** MediaLib Factory object, used by JAI. (May be null) */
        private Object mlibFactory;

        /** Boolean indicating if the MediaLib acceleration must be used. By default is set to false */
        private boolean isMediaLibPreferred;

        public OperationItem(OperationDescriptor op) {
            this.op = op;
            this.opName = op.getName();
            this.vendor = op.getResourceBundle(null).getString(VENDOR_NAME);
            this.isMediaLibPreferred = false;
        }

        public OperationItem(OperationItem item) {
            this.op = item.getDescriptor();
            this.opName = item.getName();
            this.vendor = item.getVendor();
            this.factory = item.getFactory();
            this.mlibFactory = item.getMlibFactory();
            this.isMediaLibPreferred = item.isMediaLibPreferred;
        }

        public String getVendor() {
            return vendor;
        }

        public String getName() {
            return opName;
        }

        public OperationDescriptor getDescriptor() {
            return op;
        }

        /***
         * Returns the factory defined by the user. If medialib is preferred, then the MediaLib factory is returned, otherwise the JAI default factory
         * is returned.
         * 
         * @return
         */
        public Object getCurrentFactory() {
            if (mlibFactory != null && isMediaLibPreferred) {
                return mlibFactory;
            }
            return factory;
        }

        public Object getFactory() {
            return factory;
        }

        public Object getMlibFactory() {
            return mlibFactory;
        }

        public OperationDescriptor getOp() {
            return op;
        }

        public boolean isMediaLibPreferred() {
            return isMediaLibPreferred;
        }

        public void setFactory(Object factory) {
            this.factory = factory;
        }

        public void setMlibFactory(Object factory) {
            this.mlibFactory = factory;
        }

        public void setMlibPreference(boolean preferred) {
            this.isMediaLibPreferred = preferred;
        }

        /**
         * Indicates if the {@link OperationItem} operation is a JAI-EXT one
         * 
         * @return
         */
        public boolean isJAIEXTProduct() {
            return vendor.equalsIgnoreCase(JAIEXT_PRODUCT);
        }
    }

    /**
     * This class is a container class which stores internally all the {@link OperationItem}s, each one for an {@link OperationDescriptor}. This class
     * contains all the operations inside an inner map and provides some utility methods for using it.
     * 
     * @author Nicola Lagomarsini GeoSolutions S.A.S.
     * 
     */
    static class OperationCollection {
        /** Inner Map containing all the {@link OperationItem}s for each operation */
        private Map<String, OperationItem> map = new ConcurrentHashMap<String, OperationItem>();

        /** {@link OperationRegistry} used by the collection for registering and unregistering operations */
        private OperationRegistry registry;

        public OperationCollection(OperationRegistry registry, Map<String, OperationItem> map) {
            this.map = map;
            this.registry = registry;
        }

        public OperationCollection(OperationRegistry registry) {
            this.registry = registry;
        }

        /**
         * This method copies all the values inside a new {@link OperationCollection} instance
         */
        public OperationCollection copy() {
            Collection<OperationItem> values = map.values();
            OperationCollection newColl = new OperationCollection(registry);
            for (OperationItem item : values) {
                newColl.add(new OperationItem(item));
            }
            return newColl;
        }

        /**
         * This method populates the inner map with a List of {@link OperationDescriptor}s.
         * 
         * @param listDesc
         */
        public void createMapFromDescriptors(List<OperationDescriptor> listDesc) {
            for (OperationDescriptor desc : listDesc) {
                OperationItem value = createItem(desc);
                map.put(desc.getName(), value);
            }
        }

        /**
         * This method returns a new {@link OperationCollection} instance filtered by the vendor name.
         * 
         * @param vendor
         * @return
         */
        public OperationCollection filter(String vendor) {
            Collection<OperationItem> values = map.values();
            OperationCollection filtered = new OperationCollection(registry);
            for (OperationItem item : values) {
                if (item.getVendor().equalsIgnoreCase(vendor)) {
                    filtered.map.put(item.getName(), item);
                }
            }
            return filtered;
        }

        /**
         * Add a new Item to the map.
         * 
         * @param item
         */
        public void add(OperationItem item) {
            map.put(item.getName(), item);
        }

        /**
         * Add the contents of an external map inside the inner map.
         * 
         * @param items
         */
        public void add(Map<String, OperationItem> items) {
            map.putAll(items);
        }

        /**
         * Returns the {@link OperationItem} associated to the input operation name.
         * 
         * @param decriptorname
         * @return
         */
        public OperationItem get(String decriptorname) {
            return map.get(decriptorname);
        }

        /**
         * Returns a view of the all the {@link OperationItem}s contained by the map.
         * 
         * @return
         */
        public Collection<OperationItem> getOperations() {
            return map.values();
        }

        /**
         * Creates a new {@link OperationItem} from an {@link OperationDescriptor}.
         * 
         * @param desc
         * @return
         */
        public OperationItem createItem(OperationDescriptor desc) {

            OperationItem value = new OperationItem(desc);
            // Selection of a List of the Factories associated to the operation and the vendor
            List<Object> list = registry.getOrderedFactoryList(RenderedRegistryMode.MODE_NAME,
                    desc.getName(), value.getVendor());
            // If the List is not null then we start iterating on it.
            if (list != null) {
                // If there is the MediaLib factory, it is saved inside the OperationItem
                // but it is not used by default
                for (Object factory : list) {
                    if (factory.getClass().getName().contains("Mlib")) {
                        // Ensure Medialib is present
                        if(JAIExt.isMedialibavailable()){
                            value.setMlibFactory(factory);
                        }
                    } else {
                        value.setFactory(factory);
                        break;
                    }
                }
            }
            return value;
        }

        /**
         * This method substitutes all the operations contained by an external {@link OperationCollection} inside the current
         * {@link OperationCollection}.
         * 
         * @param changedOps
         */
        void substituteOperations(OperationCollection changedOps) {
            // Selection of all the operations of the external OperationCollection
            Map<String, OperationItem> mapChanged = changedOps.map;
            Collection<OperationItem> items = mapChanged.values();
            // Iteration on all the new operations and registration of them
            for (OperationItem item : items) {
                substituteDescriptors(item);
                // Insert the new Item inside the map
                map.put(item.getName(), item);
            }
        }

        /**
         * This method substitute an old {@link OperationItem} object with a new one, if not already present.
         * 
         * @param changedOp
         */
        private void substituteDescriptors(OperationItem changedOp) {
            // Selection of the old OperationItem
            OperationItem operationItem = map.get(changedOp.getName());
            // Check if the item is present
            boolean present = operationItem != null
                    && !operationItem.getVendor().equalsIgnoreCase(changedOp.getVendor());
            // Selection of the new factory
            Object factory = changedOp.getCurrentFactory();
            // If the OperationItem is already present, then it is unregistered and the new item is registered.
            if (present) {
                // Unregistering Descriptor and Factory
                Object currentFactory = operationItem.getCurrentFactory();
                boolean registerFactory = factory == null
                        || (currentFactory != null && !currentFactory.getClass().isAssignableFrom(factory.getClass()));
                if (currentFactory != null && registerFactory) {
                    //registry.unregisterFactory(RenderedRegistryMode.MODE_NAME, operationItem
                            //.getDescriptor().getName(), operationItem.getVendor(), currentFactory);
                }
                registry.unregisterDescriptor(operationItem.getDescriptor());
                // registering descriptor
                registry.registerDescriptor(changedOp.getDescriptor());
                // registering factory
                if (factory != null) {
                    registry.registerFactory(RenderedRegistryMode.MODE_NAME, changedOp
                            .getDescriptor().getName(), changedOp.getVendor(), changedOp
                            .getCurrentFactory());
                	//registry.setFactoryPreference(RenderedRegistryMode.MODE_NAME,
                			//changedOp.getDescriptor().getName(),
                			//changedOp.getVendor(),
                			//factory,
                			//currentFactory);
                }
            } else {
                // If the operationItem is null then it is registered
                if (operationItem == null) {
                    // registering descriptor
                    registry.registerDescriptor(changedOp.getDescriptor());
                    // registering factory
                    if (changedOp.getCurrentFactory() != null) {
                        registry.registerFactory(RenderedRegistryMode.MODE_NAME, changedOp
                                .getDescriptor().getName(), changedOp.getVendor(), changedOp
                                .getCurrentFactory());
                    }
                }
            }
        }

        /**
         * This method add a new {@link OperationItem} inside the map, replacing an old one if present.
         * 
         * @param changedOp
         */
        public void substituteSingleOp(OperationItem changedOp) {
            map.put(changedOp.getName(), changedOp);
        }

        /**
         * This method substitute the factory associated to the operation
         * 
         * @param factory
         * @param descriptorName
         * @param vendor
         */
        public void substituteFactory(Object factory, String descriptorName, String vendor) {
            OperationItem item = map.get(descriptorName);

            // Check if the Operation descriptor is present
            boolean present = item != null && item.getVendor().equalsIgnoreCase(vendor);
            // If present, the factory is set.
            if (present) {
                if (factory.getClass().getName().contains("Mlib")) {
                    item.setMlibFactory(factory);
                    item.setMlibPreference(true);
                } else {
                    item.setFactory(factory);
                    item.setMlibPreference(false);
                }
            } else {
                // Create a new Item and set it in the map
                RegistryElementDescriptor desc = registry.getDescriptor(
                        RenderedRegistryMode.MODE_NAME, descriptorName);
                // The new item is added
                if (desc != null && desc instanceof OperationDescriptor) {
                    try {
                        OperationItem itemNew = new OperationItem((OperationDescriptor) desc);
                        if (factory.getClass().getName().contains("Mlib")) {
                            item.setMlibFactory(factory);
                            item.setMlibPreference(true);
                        } else {
                            item.setFactory(factory);
                            item.setMlibPreference(false);
                        }
                        map.put(descriptorName, itemNew);
                    } catch (IllegalArgumentException e) {
                        throw new IllegalArgumentException(
                                "Unable to register the Factory for the following descriptor: "
                                        + descriptorName, e);
                    }
                } else {
                    throw new IllegalArgumentException(
                            "Unable to register the Factory for the following descriptor: "
                                    + descriptorName);
                }
            }
        }

        /**
         * Removes an {@link OperationItem} from the inner map
         * 
         * @param operationItem
         */
        public void removeSingleOp(OperationItem operationItem) {
            map.remove(operationItem.getName());
        }

        /**
         * Removes the factory from the map.
         * 
         * @param factory
         * @param descriptorName
         * @param productName
         */
        public void removeFactory(Object factory, String descriptorName, String productName) {
            OperationItem item = map.get(descriptorName);

            // Check if the Operation descriptor is present
            boolean present = item != null && item.getVendor().equalsIgnoreCase(productName);

            if (present) {
                item.setFactory(null);
                item.setMlibFactory(null);
                item.setMlibPreference(false);
            } else {
                // Create a new Item and set it in the map without defining the factory
                RegistryElementDescriptor desc = registry.getDescriptor(
                        RenderedRegistryMode.MODE_NAME, descriptorName);

                if (desc != null && desc instanceof OperationDescriptor) {
                    OperationItem itemNew = new OperationItem((OperationDescriptor) desc);
                    map.put(descriptorName, itemNew);
                } else {
                    throw new IllegalArgumentException(
                            "Unable to unregister the Factory for the following descriptor: "
                                    + descriptorName);
                }
            }
        }
    }
}
