/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.messenger.container.spi;

import org.jivesoftware.messenger.container.*;
import org.jivesoftware.messenger.container.EventListener;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * <p>Simple classs to track and manage service
 * registrations and respond to service lookups.</p>
 *
 * @author Iain Shigeoka
 */
public class ServiceLookupImpl implements ServiceLookup {

    private ServiceID id;
    private Map idTable = new HashMap();

    /**
     * Simple constructor
     */
    public ServiceLookupImpl() {
        id = new ServiceID();
    }

    public ServiceID getServiceID() {
        return id;
    }

    public Class[] getServiceTypes(ServiceTemplate tmpl, String prefix) {
        throw new UnsupportedOperationException();
    }

    public Object lookup(Class type) {
        ServiceTemplate tmpl = new ServiceTemplate();
        tmpl.types = new Class[]{type};
        return lookup(tmpl);
    }

    public Object lookup(ServiceTemplate tmpl) {
        Object found = null;
        try {
            matchLock.readLock().lock();

            Iterator itemItr;
            // Add all matching service IDs to the items list
            if (tmpl.serviceID == null) {
                itemItr = idTable.values().iterator();
            }
            else {
                LinkedList items = new LinkedList();
                items.add(idTable.get(tmpl.serviceID));
                itemItr = items.iterator();
            }

            // Now check each item for a match against attributes
            while (itemItr.hasNext()) {
                ServiceItem item = (ServiceItem)itemItr.next();
                if (isMatch(tmpl, item)) {
                    found = item.service;
                    break;
                }
            }
        }
        finally {
            matchLock.readLock().unlock();
        }
        return found;
    }

    private boolean isMatch(ServiceTemplate tmpl, ServiceItem item) {
        boolean isMatch = true;
        if (tmpl.attributes != null) {
            for (int i = 0; i < tmpl.attributes.length; i++) {
                boolean hasAttribute = false;
                for (int j = 0; j < item.attributes.length; j++) {
                    if (item.attributes[j].equals(tmpl.attributes[i])) {
                        hasAttribute = true;
                    }
                }
                if (!hasAttribute) {
                    isMatch = false;
                    item = null;
                }
            }
        }
        if (item != null && tmpl.types != null) {
            for (int i = 0; i < tmpl.types.length; i++) {
                if (!tmpl.types[i].isInstance(item.service)) {
                    isMatch = false;
                    item = null;
                    break;
                }
            }
        }
        return isMatch;
    }

    public ServiceMatches lookup(ServiceTemplate tmpl, int maxMatches) {
        throw new UnsupportedOperationException();
    }

    //private long eventID = 101;
    private static final long EVENT_ID = 101;
    private long sequenceID = 0;
    private ReadWriteLock matchLock = new ReentrantReadWriteLock();
    private HashMap matchMatchListeners = new HashMap();
    private HashMap matchNoMatchListeners = new HashMap();
    private HashMap noMatchMatchListeners = new HashMap();
    private HashMap listeners = new HashMap();


    public EventRegistration notifyRegister(ServiceTemplate tmpl,
                                            int transitions,
                                            EventListener listener) {
        try {
            matchLock.writeLock().lock();
            if ((transitions & TRANSITION_MATCH_MATCH) != 0) {
                matchMatchListeners.put(tmpl, listener);
            }
            if ((transitions & TRANSITION_MATCH_NOMATCH) != 0) {
                matchNoMatchListeners.put(tmpl, listener);
            }
            if ((transitions & TRANSITION_NOMATCH_MATCH) != 0) {
                noMatchMatchListeners.put(tmpl, listener);
            }
            List templates = (List)listeners.get(listener);
            if (templates == null) {
                templates = new LinkedList();
                listeners.put(listener, templates);
            }
            templates.add(tmpl);
            return new ServiceEventRegistrationImpl(this, listener, EVENT_ID, sequenceID);
        }
        finally {
            matchLock.writeLock().unlock();
        }
    }

    public ServiceRegistration register(ServiceItem item) {
        // Generate a service id if needed
        if (item.serviceID == null) {
            item.serviceID = new ServiceID();
        }

        // Register the service
        Object result = null;
        try {
            matchLock.writeLock().lock();
            result = idTable.put(item.serviceID, item);
        }
        finally {
            matchLock.writeLock().unlock();
        }

        // Handle transition notifications
        // This can trigger a nomatch-match or match-match event
        if (result == null) { // nomatch-match
            notifyTransitions(noMatchMatchListeners, TRANSITION_NOMATCH_MATCH, item);
        }
        else { // match-match
            notifyTransitions(matchMatchListeners, TRANSITION_MATCH_MATCH, item);
        }
        return new ServiceRegistrationImpl(this, item.serviceID);
    }

    private void notifyTransitions(Map listenerMap, int transition, ServiceItem item) {
        LinkedList notifyListenerList = new LinkedList();
        try {
            matchLock.readLock().lock();
            Iterator matches = listenerMap.keySet().iterator();
            while (matches.hasNext()) {
                ServiceTemplate tmpl = (ServiceTemplate)matches.next();
                EventListener listener = (EventListener)listenerMap.get(tmpl);
                if (isMatch(tmpl, item)) {
                    notifyListenerList.add(listener);
                }
            }
        }
        finally {
            matchLock.readLock().unlock();
        }
        Iterator notifyListeners = notifyListenerList.iterator();
        while (notifyListeners.hasNext()) {
            EventListener listener = (EventListener)notifyListeners.next();
            try {
                listener.notifyEvent(new ServiceEvent(this,
                        EVENT_ID,
                        sequenceID++,
                        null,
                        item,
                        id,
                        transition));
            }
            catch (UnknownEventException e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
        }
    }

    /**
     * Remove a service (only should be called by a ServiceRegistrationImpl.cancel()
     *
     * @param removeID The service id of the servce to remove
     */
    final void remove(ServiceID removeID) {
        ServiceItem item;
        // Remove registration
        try {
            matchLock.writeLock().lock();
            item = (ServiceItem)idTable.remove(removeID);
        }
        finally {
            matchLock.writeLock().unlock();
        }
        // Notify listeners
        if (item != null) {
            notifyTransitions(matchNoMatchListeners, TRANSITION_MATCH_NOMATCH, item);
        }
    }

    /**
     * <p>Remove a service event listener
     * (only should be called by ServiceEventRegistrationImpl.cancel()).</p>
     *
     * @param listener The event listener to remove
     */
    final void remove(EventListener listener) {
        try {
            matchLock.writeLock().lock();
            List templates = (List)listeners.remove(listener);
            if (templates != null) {
                Iterator templateItr = templates.iterator();
                while (templateItr.hasNext()) {
                    Object template = templateItr.next();
                    matchMatchListeners.remove(template);
                    matchNoMatchListeners.remove(template);
                    noMatchMatchListeners.remove(template);
                }
            }
        }
        finally {
            matchLock.writeLock().unlock();
        }
    }

    /**
     * Sets the attributes for an entry.
     * Only should be called by a ServiceRegistrationImpl.setAttributes()
     *
     * @param serviceID  The id of the service to modify
     * @param attributes The new attributes for that service
     */
    final void setAttributes(ServiceID serviceID, Entry[] attributes) {
        try {
            matchLock.writeLock().lock();
            ServiceItem item = (ServiceItem)idTable.get(serviceID);
            item.attributes = attributes;
        }
        finally {
            matchLock.writeLock().unlock();
        }
    }
}
