/*
 * 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.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.openide.util.GlobalLookup;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.lookup.ServiceProvider;

/**
 * A general registry permitting clients to find instances of services
 * (implementation of a given interface).
 * This class is inspired by the
 * <a href="http://www.jini.org/">Jini</a>
 * registration and lookup mechanism. The difference is that the methods do
 * not throw checked exceptions (as they usually work only locally and not over the network)
 * and that the Lookup API concentrates on the lookup, not on the registration
 * (although {@link Lookup#getDefault} is strongly encouraged to support
 * {@link Lookups#metaInfServices} for registration in addition to whatever
 * else it decides to support).
 * <p>
 * For a general talk about the idea behind the lookup pattern please see
 * <UL>
 *      <LI><a href="lookup/doc-files/index.html">The Solution to Communication Between Components</a>
 *      page
 *      <LI>the introduction to the <a href="lookup/doc-files/lookup-api.html">lookup API via
 *      use cases</a>
 *      <LI>the examples of <a href="lookup/doc-files/lookup-spi.html">how to write your own lookup</a>
 * </UL>
 *
 * @see org.openide.util.lookup.AbstractLookup
 * @see Lookups
 * @see LookupListener
 * @see LookupEvent
 * @author  Jaroslav Tulach
 */
public abstract class Lookup {
    private static final Logger LOG = Logger.getLogger("org.openide.util.lookup.init"); // NOI18N
    
    /** A dummy lookup that never returns any results.
     */
    public static final Lookup EMPTY = new Empty();

    /** default instance */
    private static Lookup defaultLookup;
    
    /**
     * Default instance's provider
     */
    private static Lookup.Provider defaultLookupProvider;

    /** Empty constructor for use by subclasses. */
    public Lookup() {
    }

    /** Static method to obtain the global lookup in the whole system.
     * The actual returned implementation can be different in different
     * systems, but the default one is based on
     * {@link org.openide.util.lookup.Lookups#metaInfServices}
     * with the context classloader of the first caller. Each system is
     * adviced to honor this and include some form of <code>metaInfServices</code>
     * implementation in the returned lookup as usage of <code>META-INF/services</code>
     * is a JDK standard.
     *
     * @return the global lookup in the system
     * @see ServiceProvider
     */
    public static synchronized Lookup getDefault() {
        Lookup gLpk = GlobalLookup.current();
        if (gLpk != null) {
            return gLpk;
        }
        
        if (defaultLookup != null || defaultLookupProvider != null) {
            if (defaultLookupProvider != null) {
                Lookup lkp = defaultLookupProvider.getLookup();
                if (lkp != null) {
                    return lkp;
                }
            }
            return defaultLookup;
        }
        LOG.log(Level.FINER, "About to initialize Lookup@{0}.getDefault() by {1}", 
            new Object[] { Lookup.class.getClassLoader(), Thread.currentThread() }
        );
        // You can specify a Lookup impl using a system property if you like.
        String className = System.getProperty("org.openide.util.Lookup"); // NOI18N

        LOG.log(Level.FINER, "Specified by property? Value: {0}", className);

        if ("-".equals(className)) { // NOI18N
            // Suppress even MetaInfServicesLookup.
            return EMPTY;
        }

        ClassLoader l = Thread.currentThread().getContextClassLoader();
        LOG.log(Level.FINER, "Searching in classloader {0}", l);
        try {
            if (className != null) {
                Object o = Class.forName(className, true, l).newInstance();
                defaultLookup = (Lookup)o;
                // set the global global Lookuo
                GlobalLookup.setSystemLookup(defaultLookup);
                LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
                // for testing purposes, tests may setup a class implementing both interfaces
                if (o instanceof Lookup.Provider) {
                    defaultLookupProvider = (Lookup.Provider)o;
                    Lookup lkp = defaultLookupProvider.getLookup();
                    if (lkp != null) {
                        return lkp;
                    }
                }
                return defaultLookup;
            }
        } catch (Exception e) {
            LOG.log(Level.WARNING, "Constuction of " + className + " via " + l + " failed", e);
        }

        // OK, none specified (successfully) in a system property.
        // Try MetaInfServicesLookup as a default, which may also
        // have a org.openide.util.Lookup line specifying the lookup.
        Lookup misl = Lookups.metaInfServices(l);
        defaultLookup = misl.lookup(Lookup.class);
        LOG.log(Level.FINER, "Searching for {0} in {1} yields {2}", new Object[]{Lookup.class, misl, defaultLookup});
        if (defaultLookup != null) {
            if (defaultLookup instanceof Lookup.Provider) {
                defaultLookupProvider = (Lookup.Provider)defaultLookup;
                Lookup lkp = defaultLookupProvider.getLookup();
                if (lkp != null) {
                    return lkp;
                }
            }
            LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
            return defaultLookup;
        }

        // You may also specify a Lookup.Provider.
        Lookup.Provider prov = misl.lookup(Lookup.Provider.class);
        LOG.log(Level.FINER, "Searching for {0} in {1} yields {2}", new Object[]{Lookup.Provider.class, misl, defaultLookup});
        if (prov != null) {
            defaultLookup = Lookups.proxy(prov);
            LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
            return defaultLookup;
        }

        DefLookup def = new DefLookup();
        def.init(l, misl, false);
        defaultLookup = def;
        def.init(l, misl, true);
        LOG.log(Level.FINE, "Default lookup initialized {0}", defaultLookup);
        return defaultLookup;
    }
    
    private static final class DefLookup extends ProxyLookup {
        public DefLookup() {
            super(new Lookup[0]);
        }
        
        public void init(ClassLoader loader, Lookup metaInfLookup, boolean addPath) {
            // Had no such line, use simple impl.
            // It does however need to have ClassLoader available or many things will break.
            // Use the thread context classloader in effect now.
            Lookup clLookup = Lookups.singleton(loader);
            List<Lookup> arr = new ArrayList<Lookup>();
            arr.add(metaInfLookup);
            arr.add(clLookup);
            String paths = System.getProperty("org.openide.util.Lookup.paths"); // NOI18N
            if (addPath && paths != null) {
                LOG.log(Level.FINE, "Adding search paths {0}", paths);
                for (String p : paths.split(":")) { // NOI18N
                    arr.add(Lookups.forPath(p));
                }
            }
            LOG.log(Level.FINER, "Setting DefLookup delegates {0}", arr);
            setLookups(arr.toArray(new Lookup[0]));
        }
    }
    
    /** Called from MockServices to reset default lookup in case services change
     */
    private static synchronized void resetDefaultLookup() {
        if (defaultLookup == null || defaultLookup instanceof DefLookup) {
            DefLookup def = (DefLookup)defaultLookup;
            ClassLoader l = Thread.currentThread().getContextClassLoader();
            Lookup misl = Lookups.metaInfServices(l);
            if (def == null) {
                def = new DefLookup();
                def.init(l, misl, false);
                defaultLookup = def;
            }
            def.init(l, misl, true);
        }
    }

    /** Look up an object matching a given interface.
     * This is the simplest method to use.
     * If more than one object matches, the first will be returned.
     * The template class may be a class or interface; the instance is
     * guaranteed to be assignable to it.
     *
     * @param clazz class of the object we are searching for
     * @return an object implementing the given class or <code>null</code> if no such
     *         implementation is found
     */
    public abstract <T> T lookup(Class<T> clazz);

    /** The general lookup method. Callers can get list of all instances and classes
     * that match the given <code>template</code>, request more info about
     * them in form of {@link Lookup.Item} and attach a listener to
     * this be notified about changes. The general interface does not
     * specify whether subsequent calls with the same template produce new
     * instance of the {@link Lookup.Result} or return shared instance. The
     * prefered behaviour however is to return shared one.
     *
     * @param template a template describing the services to look for
     * @return an object containing the results
     */
    public abstract <T> Result<T> lookup(Template<T> template);

    /** Look up the first item matching a given template.
     * Includes not only the instance but other associated information.
     * @param template the template to check
     * @return a matching item or <code>null</code>
     *
     * @since 1.8
     */
    public <T> Item<T> lookupItem(Template<T> template) {
        Result<T> res = lookup(template);
        Iterator<? extends Item<T>> it = res.allItems().iterator();
        return it.hasNext() ? it.next() : null;
    }

    /**
     * Find a result corresponding to a given class.
     * Equivalent to calling {@link #lookup(Lookup.Template)} but slightly more convenient.
     * Subclasses may override this method to produce the same semantics more efficiently.
     * @param clazz the supertype of the result
     * @return a live object representing instances of that type
     * @since org.openide.util 6.10
     */
    public <T> Lookup.Result<T> lookupResult(Class<T> clazz) {
        return lookup(new Lookup.Template<T>(clazz));
    }

    /**
     * Find all instances corresponding to a given class.
     * Equivalent to calling {@link #lookupResult} and asking for {@link Lookup.Result#allInstances} but slightly more convenient.
     * Subclasses may override this method to produce the same semantics more efficiently.
     * <p>Example usage:</p>
     * {@codesnippet org.openide.util.lookup.SampleLookupUsages#iterate}
     * @param clazz the supertype of the result
     * @return all currently available instances of that type
     * @since org.openide.util 6.10
     */
    public <T> Collection<? extends T> lookupAll(Class<T> clazz) {
        return lookupResult(clazz).allInstances();
    }

    /**
     * Objects implementing interface Lookup.Provider are capable of
     * and willing to provide a lookup (usually bound to the object).
     * @since 3.6
     */
    public interface Provider {
        /**
         * Returns lookup associated with the object.
         * @return fully initialized lookup instance provided by this object
         */
        Lookup getLookup();
    }

    /*
     * I expect this class to grow in the future, but for now, it is
     * enough to start with something simple.
     */

    /** Template defining a pattern to filter instances by.
     */
    public static final class Template<T> extends Object {
        /** cached hash code */
        private int hashCode;

        /** type of the service */
        private Class<T> type;

        /** identity to search for */
        private String id;

        /** instance to search for */
        private T instance;

        /** General template to find all possible instances.
         * @deprecated Use <code>new Template (Object.class)</code> which
         *   is going to be better typed with JDK1.5 templates and should produce
         *   the same result.
         */
        @Deprecated
        public Template() {
            this(null);
        }

        /** Create a simple template matching by class.
         * @param type the class of service we are looking for (subclasses will match)
         */
        public Template(Class<T> type) {
            this(type, null, null);
        }

        /** Constructor to create new template.
         * @param type the class of service we are looking for or <code>null</code> to leave unspecified
         * @param id the ID of the item/service we are looking for or <code>null</code> to leave unspecified
         * @param instance a specific known instance to look for or <code>null</code> to leave unspecified
         */
        public Template(Class<T> type, String id, T instance) {
            this.type = extractType(type);
            this.id = id;
            this.instance = instance;
        }

        @SuppressWarnings("unchecked")
        private Class<T> extractType(Class<T> type) {
            return (type == null) ? (Class<T>)Object.class : type;
        }

        /** Get the class (or superclass or interface) to search for.
         * If it was not specified in the constructor, <code>Object</code> is used as
         * this will match any instance.
         * @return the class to search for
         */
        public Class<T> getType() {
            return type;
        }

        /** Get the persistent identifier being searched for, if any.
         * @return the ID or <code>null</code>
         * @see Lookup.Item#getId
         *
         * @since 1.8
         */
        public String getId() {
            return id;
        }

        /** Get the specific instance being searched for, if any.
         * Most useful for finding an <code>Item</code> when the instance
         * is already known.
         *
         * @return the object to find or <code>null</code>
         *
         * @since 1.8
         */
        public T getInstance() {
            return instance;
        }

        /* Computes hashcode for this template. The hashcode is cached.
         * @return hashcode
         */
        @Override
        public int hashCode() {
            if (hashCode != 0) {
                return hashCode;
            }

            hashCode = ((type == null) ? 1 : type.hashCode()) + ((id == null) ? 2 : id.hashCode()) +
                ((instance == null) ? 3 : 0);

            return hashCode;
        }

        /* Checks whether two templates represent the same query.
         * @param obj another template to check
         * @return true if so, false otherwise
         */
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof Template)) {
                return false;
            }

            Template t = (Template) obj;

            if (hashCode() != t.hashCode()) {
                // this is an optimalization - the hashCodes should have been
                // precomputed
                return false;
            }

            if (type != t.type) {
                return false;
            }

            if (id == null) {
                if (t.id != null) {
                    return false;
                }
            } else {
                if (!id.equals(t.id)) {
                    return false;
                }
            }

            if (instance == null) {
                return (t.instance == null);
            } else {
                return instance.equals(t.instance);
            }
        }

        /* for debugging */
        @Override
        public String toString() {
            return "Lookup.Template[type=" + type + ",id=" + id + ",instance=" + instance + "]"; // NOI18N
        }
    }

    /** Result of a lookup request.
     * Allows access to all matching instances at once.
     * Also permits listening to changes in the result.
     * Result can contain duplicate items.
     */
    public static abstract class Result<T> extends Object {
        /** Registers a listener that is invoked when there is a possible
         * change in this result. 
         * <p>
         * <div class="nonnormative">
         * Sometimes people report that their listener is not receiving 
         * events (for example <a href="https://netbeans.org/bugzilla/show_bug.cgi?id=191471">IZ 191471</a>)
         * or that the listener receives few events, but then it <em>stops</em>
         * listening.
         * Such behavior is often caused by not keeping strong reference to 
         * the {@link Result} object. When it gets garbage collected
         * it can no longer deliver events. Thus remember to keep reference
         * to the object you are attaching listener to.
         * </div>
         *
         * @param l the listener to add
         */
        public abstract void addLookupListener(LookupListener l);

        /** Unregisters a listener previously added.
         * @param l the listener to remove
         */
        public abstract void removeLookupListener(LookupListener l);

        /** Get all instances in the result. The return value is an
         * unmodifiable list (hence the type
         * should be {@link List} as the order matters, but the {@link Collection}
         * is kept for compatibility reasons) of all instances present in
         * the {@link Result} right now that will never change its content.
         * <p></p>
         * <div class="nonnormative">
         * While the returned collection never changes its content, some
         * implementation like {@link ProxyLookup} may
         * <a href="@TOP@/apichanges.html#lazy.proxy.lookup">compute the content
         * lazily</a>. At least
         * <a href="https://github.com/apache/netbeans/pull/1739">once</a>
         * such behavior resulted in a deadlock.
         * </div>
         * @return unmodifiable collection of all instances that will never change its content
         */
        public abstract Collection<? extends T> allInstances();

        /** Get all classes represented in the result.
         * That is, the set of concrete classes
         * used by instances present in the result.
         * All duplicate classes will be omitted.
         * @return unmodifiable set of <code>Class</code> objects that will never change its content
         *
         * @since 1.8
         */
        public Set<Class<? extends T>> allClasses() {
            return Collections.emptySet();
        }

        /** Get all registered items.
         * This includes all pairs of instances together
         * with their classes, {@link Item#getId() IDs}, and so on.
         * The return value is an unmodifiable list (hence the type
         * should be {@link List} as the order matters, but the {@link Collection}
         * is kept for compatibility reasons) of all {@link Item items} present in
         * the {@link Result} right now that will never change its content.
         * <p></p>
         * <div class="nonnormative">
         * While the returned collection never changes its content, some
         * implementation like {@link ProxyLookup} may
         * <a href="@TOP@/apichanges.html#lazy.proxy.lookup">compute the content
         * lazily</a>.
         * </div>
         *
         * @return unmodifiable collection of {@link Lookup.Item} that will never change its content
         *
         * @since 1.8
         */
        public Collection<? extends Item<T>> allItems() {
            return Collections.emptyList();
        }
    }

    /** A single item in a lookup result.
     * This wrapper provides unified access to not just the instance,
     * but its class, a possible persistent identifier, and so on.
     *
     * @since 1.25
     */
    public static abstract class Item<T> extends Object {
        /** Get the instance itself.
         * @return the instance or null if the instance cannot be created
         */
        public abstract T getInstance();

        /** Get the implementing class of the instance.
         * @return the class of the item
         */
        public abstract Class<? extends T> getType();

        // XXX can it be null??

        /** Get a persistent identifier for the item.
         * This identifier should uniquely represent the item
         * within its containing lookup (and if possible within the
         * global lookup as a whole). For example, it might represent
         * the source of the instance as a file name. The ID may be
         * persisted and in a later session used to find the same instance
         * as was encountered earlier, by means of passing it into a
         * lookup template.
         *
         * @return a string ID of the item
         */
        public abstract String getId();

        /** Get a human presentable name for the item.
         * This might be used when summarizing all the items found in a
         * lookup result in some part of a GUI.
         * @return the string suitable for presenting the object to a user
         */
        public abstract String getDisplayName();

        /* show ID for debugging */
        @Override
        public String toString() {
            return getId();
        }
    }

    //
    // Implementation of the default lookup
    //
    private static final class Empty extends Lookup {
        private static final Result NO_RESULT = new Result() {
                public void addLookupListener(LookupListener l) {
                }

                public void removeLookupListener(LookupListener l) {
                }

                public Collection allInstances() {
                    return Collections.EMPTY_SET;
                }
            };

        Empty() {
        }

        public <T> T lookup(Class<T> clazz) {
            return null;
        }

        @SuppressWarnings("unchecked")
        public <T> Result<T> lookup(Template<T> template) {
            return NO_RESULT;
        }
    }
}
