/**
 * 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.net.URI;
import java.util.Collection;

import org.chris.portmapper.model.PortMapping;
import org.chris.portmapper.model.Protocol;
import org.chris.portmapper.router.AbstractRouter;
import org.chris.portmapper.router.RouterException;
import org.chris.portmapper.router.cling.action.ActionService;
import org.chris.portmapper.router.cling.action.AddPortMappingAction;
import org.chris.portmapper.router.cling.action.DeletePortMappingAction;
import org.chris.portmapper.router.cling.action.GetExternalIpAction;
import org.fourthline.cling.controlpoint.ControlPoint;
import org.fourthline.cling.model.meta.RemoteService;
import org.fourthline.cling.model.meta.Service;
import org.fourthline.cling.model.meta.UDAVersion;
import org.fourthline.cling.registry.Registry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClingRouter extends AbstractRouter {

    /**
     * The maximum number of port mappings that we will try to retrieve from the router.
     */
    private final static int MAX_NUM_PORTMAPPINGS = 500;

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

    private final RemoteService service;

    private final Registry registry;

    private final ActionService actionService;

    public ClingRouter(final RemoteService service, final Registry registry, final ControlPoint controlPoint) {
        super(getName(service));
        this.service = service;
        this.registry = registry;
        actionService = new ActionService(service, controlPoint);
    }

    private static String getName(final Service<?, ?> service) {
        return service.getDevice().getDisplayString();
    }

    @Override
    public String getExternalIPAddress() throws RouterException {
        return actionService.run(new GetExternalIpAction(service));
    }

    @Override
    public String getInternalHostName() {
        final URI uri = getUri();
        return uri != null ? uri.getHost() : null;
    }

    @Override
    public int getInternalPort() throws RouterException {
        final URI uri = getUri();
        return uri != null ? uri.getPort() : null;
    }

    private URI getUri() {
        if (service.getDevice().getDetails().getPresentationURI() != null) {
            return service.getDevice().getDetails().getPresentationURI();
        }
        if (service.getControlURI() != null) {
            return service.getControlURI();
        }
        if (service.getDescriptorURI() != null) {
            return service.getDescriptorURI();
        }
        if (service.getEventSubscriptionURI() != null) {
            return service.getEventSubscriptionURI();
        }
        return null;
    }

    @Override
    public Collection<PortMapping> getPortMappings() throws RouterException {
        return new ClingPortMappingExtractor(actionService, MAX_NUM_PORTMAPPINGS).getPortMappings();
    }

    @Override
    public void logRouterInfo() throws RouterException {
        logger.info("Service id: " + service.getServiceId());
        logger.info("Reference: " + service.getReference());
        logger.info("Display name: " + service.getDevice().getDisplayString());
        final UDAVersion version = service.getDevice().getVersion();
        logger.info("Version: " + version.getMajor() + "." + version.getMinor());
        logger.info("Control uri: {}", service.getControlURI());
        logger.info("Descriptor uri: {}", service.getDescriptorURI());
        logger.info("Event subscription uri: {}", service.getEventSubscriptionURI());
        logger.info("Device base url: {}", service.getDevice().getDetails().getBaseURL());
        logger.info("Device presentation uri: {}", service.getDevice().getDetails().getPresentationURI());
    }

    @Override
    public void addPortMappings(final Collection<PortMapping> mappings) throws RouterException {
        for (final PortMapping portMapping : mappings) {
            addPortMapping(portMapping);
        }
    }

    @Override
    public void addPortMapping(final PortMapping mapping) throws RouterException {
        actionService.run(new AddPortMappingAction(service, mapping));
    }

    @Override
    public void removeMapping(final PortMapping mapping) throws RouterException {
        actionService.run(new DeletePortMappingAction(service, mapping));
    }

    @Override
    public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort)
            throws RouterException {
        removeMapping(new PortMapping(protocol, remoteHost, externalPort, null, 0, null));
    }

    @Override
    public void disconnect() {
        logger.debug("Shutdown registry");
        registry.shutdown();
    }
}
