/*
 * 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 Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.cling.registry;

import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.gena.GENASubscription;
import org.teleal.cling.model.types.DeviceType;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.model.types.UDN;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;


abstract class RegistryItems<D extends Device, S extends GENASubscription> {

    protected final RegistryImpl registry;

    protected final Set<RegistryItem<UDN, D>> deviceItems = new HashSet();
    protected final Set<RegistryItem<String, S>> subscriptionItems = new HashSet();

    RegistryItems(RegistryImpl registry) {
        this.registry = registry;
    }

    Set<RegistryItem<UDN, D>> getDeviceItems() {
        return deviceItems;
    }

    Set<RegistryItem<String, S>> getSubscriptionItems() {
        return subscriptionItems;
    }

    abstract D add(D device);
    abstract void hydrate(final D device);
    abstract Collection<D> get(boolean hydratedOnly);
    abstract boolean remove(final D device);
    abstract void removeAll();

    abstract void maintain();
    abstract void shutdown();

    /**
     * Returns root or embedded devices registered under the given UDN.
     *
     * @param udn A unique device name.
     * @return Any registered device under the given UDN, even if it's not fully described, <tt>null</tt> if
     *         no device with the given UDN has been registered.
     */
    D get(UDN udn) {
        for (RegistryItem<UDN, D> item : deviceItems) {
            D device = item.getItem();
            if (device.getIdentity().getUdn().equals(udn)) {
                return device;
            }
            if (device.hasEmbeddedDevices()) {
                for (D embeddedDevice : (D[])device.getEmbeddedDevices()) {
                    if (embeddedDevice.getIdentity().getUdn().equals(udn)) {
                        return embeddedDevice;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Returns all devices (root or embedded) with a compatible type.
     * <p>
     * This routine will check compatible versions, as described by the UDA.
     * </p>
     *
     * @param deviceType The minimum device type required.
     * @return Any registered device with a compatible type.
     */
    Collection<D> get(DeviceType deviceType) {
        Collection<D> devices = new HashSet();
        for (RegistryItem<UDN, D> item : deviceItems) {
            // The getType() call should only work on remote devices which are described!
            if (item.getItem().getType() != null && item.getItem().getType().implementsVersion(deviceType)) {
                devices.add(item.getItem());
            }
            if (item.getItem().hasEmbeddedDevices()) {
                for (D d : (D[])item.getItem().getEmbeddedDevices()){
                    if (d.getType().implementsVersion(deviceType)) {
                        devices.add(d);
                    }
                }
            }
        }
        return devices;
    }

    Collection<D> get(ServiceType serviceType) {
        Collection<D> devices = new HashSet();
        for (RegistryItem<UDN, D> item : deviceItems) {
            for (ServiceType type : item.getItem().getServiceTypes()) {
                if (type.implementsVersion(serviceType)) {
                    devices.add(item.getItem());
                }
            }
            if (item.getItem().hasEmbeddedDevices()) {
                for (D d : (D[])item.getItem().getEmbeddedDevices()){
                    for (ServiceType type : d.getServiceTypes()) {
                        if (type.implementsVersion(serviceType)) {
                            devices.add(d);
                        }
                    }
                }
            }
        }
        return devices;
    }

    Collection<D> get() {
        Collection<D> devices = new HashSet();
        for (RegistryItem<UDN, D> item : deviceItems) {
            devices.add(item.getItem());
        }
        return devices;
    }

    boolean contains(D device) {
        return contains(device.getIdentity().getUdn());
    }

    boolean contains(UDN udn) {
        return deviceItems.contains(new RegistryItem<UDN, D>(udn));
    }

    void addSubscription(S subscription) {

        RegistryItem<String, S> subscriptionItem =
                new RegistryItem<String, S>(
                        subscription.getSubscriptionId(),
                        subscription,
                        subscription.getActualDurationSeconds()
                );

        subscriptionItems.add(subscriptionItem);
    }

    boolean updateSubscription(S subscription) {
        if (removeSubscription(subscription)) {
            addSubscription(subscription);
            return true;
        }
        return false;
    }

    boolean removeSubscription(S subscription) {
        return subscriptionItems.remove(new RegistryItem<String, S>(subscription.getSubscriptionId()));
    }

    S getSubscription(String subscriptionId) {
        for (RegistryItem<String, S> registryItem : subscriptionItems) {
            if (registryItem.getKey().equals(subscriptionId)) {
                return registryItem.getItem();
            }
        }
        return null;
    }

}
