/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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 net.java.sip.communicator.service.contactsource;

import java.util.*;

/**
 * Provides an abstract implementation of the basic functionality of
 * <tt>ContactQuery</tt> and allows extenders to focus on the specifics of their
 * implementation.
 *
 * @author Lyubomir Marinov
 * @param <T> the very type of <tt>ContactSourceService</tt> which performs the
 * <tt>ContactQuery</tt>
 */
public abstract class AbstractContactQuery<T extends ContactSourceService>
    implements ContactQuery
{
    /**
     * The <tt>ContactSourceService</tt> which is performing this
     * <tt>ContactQuery</tt>.
     */
    private final T contactSource;

    /**
     * The <tt>List</tt> of <tt>ContactQueryListener</tt>s which are to be
     * notified by this <tt>ContactQuery</tt> about changes in its status, the
     * receipt of new <tt>ContactSource</tt>s via this <tt>ContactQuery</tt>,
     * etc.
     */
    private final List<ContactQueryListener> listeners
        = new LinkedList<ContactQueryListener>();

    /**
     * The status of this <tt>ContactQuery</tt> which is one of the
     * <tt>QUERY_XXX</tt> constants defined by the <tt>ContactQuery</tt> class.
     */
    private int status = QUERY_IN_PROGRESS;

    /**
     * Initializes a new <tt>AbstractContactQuery</tt> which is to be performed
     * by a specific <tt>ContactSourceService</tt>. The status of the new
     * instance is {@link ContactQuery#QUERY_IN_PROGRESS}.
     *
     * @param contactSource the <tt>ContactSourceService</tt> which is to
     * perform the new <tt>AbstractContactQuery</tt>
     */
    protected AbstractContactQuery(T contactSource)
    {
        this.contactSource = contactSource;
    }

    /**
     * Adds a <tt>ContactQueryListener</tt> to the list of listeners interested
     * in notifications about this <tt>ContactQuery</tt> changing its status,
     * the receipt of new <tt>SourceContact</tt>s via this
     * <tt>ContactQuery</tt>, etc.
     *
     * @param l the <tt>ContactQueryListener</tt> to be added to the list of
     * listeners interested in the notifications raised by this
     * <tt>ContactQuery</tt>
     * @see ContactQuery#addContactQueryListener(ContactQueryListener)
     */
    public void addContactQueryListener(ContactQueryListener l)
    {
        if (l == null)
            throw new NullPointerException("l");
        else
        {
            synchronized (listeners)
            {
                if (!listeners.contains(l))
                    listeners.add(l);
            }
        }
    }

    /**
     * Cancels this <tt>ContactQuery</tt>.
     *
     * @see ContactQuery#cancel()
     */
    public void cancel()
    {
        if (getStatus() == QUERY_IN_PROGRESS)
            setStatus(QUERY_CANCELED);
    }

    /**
     * Notifies the <tt>ContactQueryListener</tt>s registered with this
     * <tt>ContactQuery</tt> that a new <tt>SourceContact</tt> has been
     * received.
     *
     * @param contact the <tt>SourceContact</tt> which has been received and
     * which the registered <tt>ContactQueryListener</tt>s are to be notified
     * about
     * @param showMoreEnabled indicates whether show more label should be shown 
     * or not.
     */
    protected void fireContactReceived(SourceContact contact, 
        boolean showMoreEnabled)
    {
        ContactQueryListener[] ls;

        synchronized (listeners)
        {
            ls = listeners.toArray(new ContactQueryListener[listeners.size()]);
        }

        ContactReceivedEvent ev 
            = new ContactReceivedEvent(this, contact, showMoreEnabled);

        for (ContactQueryListener l : ls)
        {
            l.contactReceived(ev);
        }
    }
    
    /**
     * Notifies the <tt>ContactQueryListener</tt>s registered with this
     * <tt>ContactQuery</tt> that a new <tt>SourceContact</tt> has been
     * received.
     *
     * @param contact the <tt>SourceContact</tt> which has been received and
     * which the registered <tt>ContactQueryListener</tt>s are to be notified
     * about
     */
    protected void fireContactReceived(SourceContact contact)
    {
        fireContactReceived(contact, true);
    }

    /**
     * Notifies the <tt>ContactQueryListener</tt>s registered with this
     * <tt>ContactQuery</tt> that a <tt>SourceContact</tt> has been
     * removed.
     *
     * @param contact the <tt>SourceContact</tt> which has been removed and
     * which the registered <tt>ContactQueryListener</tt>s are to be notified
     * about
     */
    protected void fireContactRemoved(SourceContact contact)
    {
        ContactQueryListener[] ls;

        synchronized (listeners)
        {
            ls = listeners.toArray(new ContactQueryListener[listeners.size()]);
        }

        ContactRemovedEvent ev = new ContactRemovedEvent(this, contact);

        for (ContactQueryListener l : ls)
            l.contactRemoved(ev);
    }

    /**
     * Notifies the <tt>ContactQueryListener</tt>s registered with this
     * <tt>ContactQuery</tt> that a <tt>SourceContact</tt> has been
     * changed.
     *
     * @param contact the <tt>SourceContact</tt> which has been changed and
     * which the registered <tt>ContactQueryListener</tt>s are to be notified
     * about
     */
    protected void fireContactChanged(SourceContact contact)
    {
        ContactQueryListener[] ls;

        synchronized (listeners)
        {
            ls = listeners.toArray(new ContactQueryListener[listeners.size()]);
        }

        ContactChangedEvent ev = new ContactChangedEvent(this, contact);

        for (ContactQueryListener l : ls)
            l.contactChanged(ev);
    }

    /**
     * Notifies the <tt>ContactQueryListener</tt>s registered with this
     * <tt>ContactQuery</tt> that its state has changed.
     *
     * @param eventType the type of the <tt>ContactQueryStatusEvent</tt> to be
     * fired which can be one of the <tt>QUERY_XXX</tt> constants defined by
     * <tt>ContactQueryStatusEvent</tt>
     */
    protected void fireQueryStatusChanged(int eventType)
    {
        ContactQueryListener[] ls;

        synchronized (listeners)
        {
            ls = listeners.toArray(new ContactQueryListener[listeners.size()]);
        }

        ContactQueryStatusEvent ev
            = new ContactQueryStatusEvent(this, eventType);

        for (ContactQueryListener l : ls)
            l.queryStatusChanged(ev);
    }

    /**
     * Gets the <tt>ContactSourceService</tt> which is performing this
     * <tt>ContactQuery</tt>.
     *
     * @return the <tt>ContactSourceService</tt> which is performing this
     * <tt>ContactQuery</tt>
     * @see ContactQuery#getContactSource()
     */
    public T getContactSource()
    {
        return contactSource;
    }

    /**
     * Gets the status of this <tt>ContactQuery</tt> which can be one of the
     * <tt>QUERY_XXX</tt> constants defined by <tt>ContactQuery</tt>.
     *
     * @return the status of this <tt>ContactQuery</tt> which can be one of the
     * <tt>QUERY_XXX</tt> constants defined by <tt>ContactQuery</tt>
     * @see ContactQuery#getStatus()
     */
    public int getStatus()
    {
        return status;
    }

    /**
     * Removes a <tt>ContactQueryListener</tt> from the list of listeners
     * interested in notifications about this <tt>ContactQuery</tt> changing its
     * status, the receipt of new <tt>SourceContact</tt>s via this
     * <tt>ContactQuery</tt>, etc.
     *
     * @param l the <tt>ContactQueryListener</tt> to be removed from the list of
     * listeners interested in notifications raised by this <tt>ContactQuery</tt>
     * @see ContactQuery#removeContactQueryListener(ContactQueryListener)
     */
    public void removeContactQueryListener(ContactQueryListener l)
    {
        if (l != null)
        {
            synchronized (listeners)
            {
                listeners.remove(l);
            }
        }
    }

    /**
     * Sets the status of this <tt>ContactQuery</tt>.
     *
     * @param status {@link ContactQuery#QUERY_CANCELED},
     * {@link ContactQuery#QUERY_COMPLETED}, or
     * {@link ContactQuery#QUERY_ERROR}
     */
    public void setStatus(int status)
    {
        if (this.status != status)
        {
            int eventType;

            switch (status)
            {
            case QUERY_CANCELED:
                eventType = ContactQueryStatusEvent.QUERY_CANCELED;
                break;
            case QUERY_COMPLETED:
                eventType = ContactQueryStatusEvent.QUERY_COMPLETED;
                break;
            case QUERY_ERROR:
                eventType = ContactQueryStatusEvent.QUERY_ERROR;
                break;
            case QUERY_IN_PROGRESS:
            default:
                throw new IllegalArgumentException("status");
            }

            this.status = status;
            fireQueryStatusChanged(eventType);
        }
    }
}
