/**
 * UPnP PortMapper - A tool for managing port forwardings via UPnP
 * Copyright (C) 2015 Christoph Pirkl <christoph at users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.chris.portmapper.router.cling;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

import org.fourthline.cling.model.meta.Device;
import org.fourthline.cling.model.meta.Service;
import org.fourthline.cling.model.types.DeviceType;
import org.fourthline.cling.model.types.ServiceType;
import org.fourthline.cling.model.types.UDADeviceType;
import org.fourthline.cling.model.types.UDAServiceType;
import org.fourthline.cling.registry.DefaultRegistryListener;
import org.fourthline.cling.registry.Registry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClingRegistryListener extends DefaultRegistryListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public static final DeviceType IGD_DEVICE_TYPE = new UDADeviceType("InternetGatewayDevice", 1);
    public static final DeviceType CONNECTION_DEVICE_TYPE = new UDADeviceType("WANConnectionDevice", 1);

    public static final ServiceType IP_SERVICE_TYPE = new UDAServiceType("WANIPConnection", 1);
    public static final ServiceType PPP_SERVICE_TYPE = new UDAServiceType("WANPPPConnection", 1);

    private final SynchronousQueue<Service<?, ?>> foundServices;

    public ClingRegistryListener() {
        this.foundServices = new SynchronousQueue<>();
    }

    public Service<?, ?> waitForServiceFound(final long timeout, final TimeUnit unit) {
        try {
            return foundServices.poll(timeout, unit);
        } catch (final InterruptedException e) {
            logger.warn("Interrupted when waiting for a service");
            return null;
        }
    }

    @Override
    public void deviceAdded(final Registry registry, @SuppressWarnings("rawtypes") final Device device) {
        @SuppressWarnings("unchecked")
        final Service<?, ?> connectionService = discoverConnectionService(device);
        if (connectionService == null) {
            return;
        }

        logger.debug("Found connection service {}", connectionService);
        foundServices.offer(connectionService);
    }

    protected Service<?, ?> discoverConnectionService(@SuppressWarnings("rawtypes") final Device<?, Device, ?> device) {
        if (!device.getType().equals(IGD_DEVICE_TYPE)) {
            logger.debug("Found service of wrong type {}, expected {}.", device.getType(), IGD_DEVICE_TYPE);
            return null;
        }

        @SuppressWarnings("rawtypes")
        final Device[] connectionDevices = device.findDevices(CONNECTION_DEVICE_TYPE);
        if (connectionDevices.length == 0) {
            logger.debug("IGD doesn't support '{}': {}", CONNECTION_DEVICE_TYPE, device);
            return null;
        }

        logger.debug("Found {} devices", connectionDevices.length);

        return findConnectionService(connectionDevices);
    }

    @SuppressWarnings("rawtypes")
    private Service<?, ?> findConnectionService(final Device[] connectionDevices) {
        for (final Device connectionDevice : connectionDevices) {

            final Service ipConnectionService = connectionDevice.findService(IP_SERVICE_TYPE);
            final Service pppConnectionService = connectionDevice.findService(PPP_SERVICE_TYPE);

            if (ipConnectionService != null) {
                logger.debug("Device {} supports ip service type: {}", connectionDevice, ipConnectionService);
                return ipConnectionService;
            }
            if (pppConnectionService != null) {
                logger.debug("Device {} supports ppp service type: {}", connectionDevice, pppConnectionService);
                return pppConnectionService;
            }

            logger.debug("IGD {} doesn't support IP or PPP WAN connection service", connectionDevice);
        }
        logger.debug("None of the {} devices supports IP or PPP WAN connections", connectionDevices.length);
        return null;
    }
}
