/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.cling.registry;

import org.teleal.cling.UpnpService;
import org.teleal.cling.UpnpServiceConfiguration;
import org.teleal.cling.model.resource.Resource;
import org.teleal.cling.model.ServiceReference;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.gena.LocalGENASubscription;
import org.teleal.cling.model.gena.RemoteGENASubscription;
import org.teleal.cling.model.meta.RemoteDeviceIdentity;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.DeviceType;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.protocol.ProtocolFactory;

import java.net.URI;
import java.util.Collection;

/**
 * The core of the UPnP stack, keeping track of known devices and resources.
 * <p>
 * A running UPnP stack has one <code>Registry</code>. Any discovered device is added
 * to this registry, as well as any exposed local device. The registry then maintains
 * these devices continously (see {@link RegistryMaintainer}) and when needed refreshes
 * their announcements on the network or removes them when they have expired. The registry
 * also keeps track of GENA event subscriptions.
 * </p>
 * <p>
 * UPnP client applications typically monitor activity of the registry
 * via {@link RegistryListener}, they are inherently asynchronous.
 * </p>
 * <p>
 * The registry has to be {@link #shutdown()} properly, so it can notify all participants
 * on the network that local devices will no longer be available and cancel all
 * GENA subscriptions.
 * <p>
 * An implementation has to be thread-safe.
 * </p>
 * <p>
 * TODO: Unify all "items" the registry is keeping track of, this API should be narrowed.
 * </p>
 *
 * @author Christian Bauer
 */
public interface Registry {

    public UpnpService getUpnpService();
    public UpnpServiceConfiguration getConfiguration();
    public ProtocolFactory getProtocolFactory();

    // #################################################################################################

    /**
     * Typically called internally when the UPnP stack is stopping.
     * <p>
     * Unsubscribes all local devices and GENA subscriptions.
     * </p>
     */
    public void shutdown();

    // #################################################################################################

    public void addListener(RegistryListener listener);

    public void removeListener(RegistryListener listener);

    public Collection<RegistryListener> getListeners();

    /**
     * Called internally by the UPnP stack when the discovery protocol starts.
     * <p>
     * The registry will notify all registered listeners of this event, unless the
     * given device was already been present in the registry.
     * </p>
     *
     * @param device The half-hydrated (without services) metadata of the discovered device.
     * @return <code>false</code> if the device was already registered.
     */
    public boolean notifyDiscoveryStart(RemoteDevice device);

    /**
     * Called internally by the UPnP stack when the discovery protocol stopped abnormaly.
     * <p>
     * The registry will notify all registered listeners of this event.
     * </p>
     *
     * @param device The half-hydrated (without services) metadata of the discovered device.
     * @param ex The cause for the interruption of the discovery protocol.
     */
    public void notifyDiscoveryFailure(RemoteDevice device, Exception ex);

    // #################################################################################################

    /**
     * Call this method to add your local device metadata.
     *
     * @param localDevice The device to add and maintain.
     * @throws RegistrationException If a conflict with an already registered device was detected.
     */
    public void addDevice(LocalDevice localDevice) throws RegistrationException;

    /**
     * Called internally by the UPnP discovery protocol.
     *
     * @throws RegistrationException If a conflict with an already registered device was detected.
     */
    public void addDevice(RemoteDevice remoteDevice) throws RegistrationException;

    /**
     * Called internally by the UPnP discovery protocol.
     */
    public boolean update(RemoteDeviceIdentity rdIdentity);

    /**
     * Call this to remove your local device metadata.
     *
     * @return <code>true</code> if the device was registered and has been removed.
     */
    public boolean removeDevice(LocalDevice localDevice);

    /**
     * Called internally by the UPnP discovery protocol.
     */
    public boolean removeDevice(RemoteDevice remoteDevice);

    /**
     * Call this to remove any device metadata with the given UDN.
     *
     * @return <code>true</code> if the device was registered and has been removed.
     */
    public boolean removeDevice(UDN udn);

    /**
     * Clear the registry of all locally registered device metadata.
     */
    public void removeAllLocalDevices();

    /**
     * Clear the registry of all discovered remote device metadata.
     */
    public void removeAllRemoteDevices();

    /**
     * @param udn The device name to lookup.
     * @param rootOnly If <code>true</code>, only matches of root devices are returned.
     * @return The registered root or embedded device metadata, or <code>null</code>.
     */
    public Device getDevice(UDN udn, boolean rootOnly);

    /**
     * @param udn The device name to lookup.
     * @param rootOnly If <code>true</code>, only matches of root devices are returned.
     * @return The registered root or embedded device metadata, or <code>null</code>.
     */
    public LocalDevice getLocalDevice(UDN udn, boolean rootOnly);

    /**
     * @param udn The device name to lookup.
     * @param rootOnly If <code>true</code>, only matches of root devices are returned.
     * @return The registered root or embedded device metadata, or <code>null</code>.
     */
    public RemoteDevice getRemoteDevice(UDN udn, boolean rootOnly);

    /**
     * @return All locally registered device metadata, in no particular order, or an empty collection.
     */
    public Collection<LocalDevice> getLocalDevices();

    /**
     * @return All discovered remote device metadata, in no particular order, or an empty collection.
     */
    public Collection<RemoteDevice> getRemoteDevices();

    /**
     * @return All device metadata, in no particular order, or an empty collection.
     */
    public Collection<Device> getDevices();

    /**
     * @return All device metadata of devices which implement the given type, in no particular order,
     *         or an empty collection.
     */
    public Collection<Device> getDevices(DeviceType deviceType);

    /**
     * @return All device metadata of devices which have a service that implements the given type,
     *         in no particular order, or an empty collection.
     */
    public Collection<Device> getDevices(ServiceType serviceType);

    /**
     * @return Complete service metadata.for a service reference or <code>null</code> if no service
     *         for the given reference has been registered.
     */
    public Service getService(ServiceReference serviceReference);

    // #################################################################################################

    /**
     * Stores an arbitrary resource in the registry.
     *
     * @param resource The resource to maintain indefinitely (until it is manually removed).
     */
    public void addResource(Resource resource);

    /**
     * Stores an arbitrary resource in the registry.
     * <p>
     * Call this method repeatedly to refresh and prevent expiration of the resource.
     * </p>
     *
     * @param resource The resource to maintain.
     * @param maxAgeSeconds The time after which the registry will automatically remove the resource.
     */
    public void addResource(Resource resource, int maxAgeSeconds);

    /**
     * Removes a resource from the registry.
     *
     * @param resource The resource to remove.
     * @return <code>true</code> if the resource was registered and has been removed.
     */
    public boolean removeResource(Resource resource);

    /**
     * @param pathQuery The path and optional query string of the resource's
     *                  registration URI (e.g. <code>/dev/somefile.xml?param=value</code>)
     * @return Any registered resource that matches the given URI path.
     * @throws IllegalArgumentException If the given URI was absolute, only path and query are allowed.
     */
    public Resource getResource(URI pathQuery) throws IllegalArgumentException;

    /**
     * @param <T> The required subtype of the {@link org.teleal.cling.model.resource.Resource}.
     * @param pathQuery The path and optional query string of the resource's
     *                  registration URI (e.g. <code>/dev/somefile.xml?param=value</code>)
     * @param resourceType The required subtype of the {@link org.teleal.cling.model.resource.Resource}.
     * @return Any registered resource that matches the given URI path and subtype.
     * @throws IllegalArgumentException If the given URI was absolute, only path and query are allowed.
     */
    public <T extends Resource> T getResource(Class<T> resourceType, URI pathQuery) throws IllegalArgumentException;

    /**
     * @return All registered resources, in no particular order, or an empty collection.
     */
    public Collection<Resource> getResources();

    /**
     * @param <T> The required subtype of the {@link org.teleal.cling.model.resource.Resource}.
     * @param resourceType The required subtype of the {@link org.teleal.cling.model.resource.Resource}.
     * @return Any registered resource that matches the given subtype.
     */
    public <T extends Resource> Collection<T> getResources(Class<T> resourceType);

    // #################################################################################################

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public void addLocalSubscription(LocalGENASubscription subscription);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public LocalGENASubscription getLocalSubscription(String subscriptionId);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public boolean updateLocalSubscription(LocalGENASubscription subscription);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public boolean removeLocalSubscription(LocalGENASubscription subscription);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public void addRemoteSubscription(RemoteGENASubscription subscription);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public RemoteGENASubscription getRemoteSubscription(String subscriptionId);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public void updateRemoteSubscription(RemoteGENASubscription subscription);

    /**
     * Called internally by the UPnP stack, during GENA protocol execution.
     */
    public void removeRemoteSubscription(RemoteGENASubscription subscription);

    // #################################################################################################

}
