/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * 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 org.switchyard.internal;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.namespace.QName;

import org.jboss.logging.Logger;
import org.switchyard.ExchangeHandler;
import org.switchyard.Service;
import org.switchyard.ServiceDomain;
import org.switchyard.ServiceMetadata;
import org.switchyard.ServiceReference;
import org.switchyard.ServiceSecurity;
import org.switchyard.event.DomainShutdownEvent;
import org.switchyard.event.DomainStartupEvent;
import org.switchyard.event.EventObserver;
import org.switchyard.event.EventPublisher;
import org.switchyard.event.ReferenceRegistrationEvent;
import org.switchyard.event.ServiceRegistrationEvent;
import org.switchyard.internal.transform.BaseTransformerRegistry;
import org.switchyard.internal.validate.BaseValidatorRegistry;
import org.switchyard.metadata.InOutService;
import org.switchyard.metadata.ServiceInterface;
import org.switchyard.security.service.DefaultServiceDomainSecurity;
import org.switchyard.security.service.SecureServiceDomain;
import org.switchyard.security.service.ServiceDomainSecurity;
import org.switchyard.spi.Dispatcher;
import org.switchyard.spi.ExchangeBus;
import org.switchyard.spi.ServiceRegistry;
import org.switchyard.transform.TransformerRegistry;
import org.switchyard.validate.ValidatorRegistry;

/**
 * Implementation of ServiceDomain.
 */
public class DomainImpl implements SecureServiceDomain {

    private static Logger _logger = Logger.getLogger(DomainImpl.class);

    private final QName _name;
    private EventManager _eventManager;
    private ServiceRegistry _serviceRegistry;
    private ExchangeBus _exchangeBus;
    private TransformerRegistry _transformerRegistry;
    private ValidatorRegistry _validatorRegistry;
    private Map<String, Object> _properties = new ConcurrentHashMap<String, Object>();
    private ServiceDomainSecurity _serviceDomainSecurity;
    
    /**
     * Create a new ServiceDomain.  This is a convenience constructor which uses default
     * resources and initializes via init() in the constructor.  Do not use this in runtime
     * code!  This is for test/embedded use only.
     * @param name name
     */
    protected DomainImpl(QName name) {
        this(name,
            new DefaultServiceRegistry(),
            new LocalExchangeBus(),
            new BaseTransformerRegistry(),
            new BaseValidatorRegistry(),
            new EventManager(),
            new DefaultServiceDomainSecurity());
        init();
    }
    
    /**
     * Create a new ServiceDomain.
     * @param name name
     * @param serviceRegistry serviceRegistry
     * @param exchangeBus exchangeBus
     * @param transformerRegistry transformerRegistry
     * @param validatorRegistry validatorRegistry
     * @param eventManager eventManager
     * @param serviceDomainSecurity serviceDomainSecurity
     */
    public DomainImpl(QName name,
            ServiceRegistry serviceRegistry,
            ExchangeBus exchangeBus,
            TransformerRegistry transformerRegistry,
            ValidatorRegistry validatorRegistry,
            EventManager eventManager,
            ServiceDomainSecurity serviceDomainSecurity) {

        _name = name;
        _serviceRegistry = serviceRegistry;
        _exchangeBus  = exchangeBus;
        _transformerRegistry = transformerRegistry;
        _validatorRegistry = validatorRegistry;
        _eventManager = eventManager;
        _serviceDomainSecurity = serviceDomainSecurity;

        setEventPublisher(_transformerRegistry);
        setEventPublisher(_validatorRegistry);

        if (_logger.isDebugEnabled()) {
            _logger.debug("Created SwitchYard ServiceDomain instance '" + name + "'.");
        }
    }

    @Override
    public Service registerService(QName serviceName, ServiceInterface contract, 
            ExchangeHandler handler) {
        return registerService(serviceName, contract, handler, null);
    }

    @Override
    public Service registerService(QName serviceName,
            ServiceInterface contract, ExchangeHandler handler, ServiceMetadata metadata) {
        
        // If no service interface is provided, we default to InOutService
        if (contract == null) {
            contract = new InOutService();
        }
        // Create the service 
        Service service = new ServiceImpl(serviceName, contract, this, handler, metadata);
        // register the service
        _serviceRegistry.registerService(service);
        _eventManager.publish(new ServiceRegistrationEvent(service));
        return service;
    }

    @Override
    public ServiceReference registerServiceReference(QName serviceName,
            ServiceInterface contract) {
        return registerServiceReference(serviceName, contract, null);
    }

    @Override
    public ServiceReference registerServiceReference(QName serviceName,
            ServiceInterface contract, ExchangeHandler handler) {
        return registerServiceReference(serviceName, contract, handler, null);
    }
    
    @Override
    public ServiceReference registerServiceReference(QName serviceName,
            ServiceInterface contract, ExchangeHandler handler,  ServiceMetadata metadata) {
        ServiceReferenceImpl reference = new ServiceReferenceImpl(serviceName, contract, this, handler, metadata);
        Dispatcher dispatch = _exchangeBus.createDispatcher(reference);
        reference.setDispatcher(dispatch);
        _serviceRegistry.registerServiceReference(reference);
        _eventManager.publish(new ReferenceRegistrationEvent(reference));
        
        return reference;
    }
    
    @Override
    public ServiceReference getServiceReference(QName serviceName) {
        return _serviceRegistry.getServiceReference(serviceName);
    }
    
    @Override
    public QName getName() {
        return _name;
    }
    
    @Override
    public TransformerRegistry getTransformerRegistry() {
        return _transformerRegistry;
    }
    
    @Override
    public ValidatorRegistry getValidatorRegistry() {
        return _validatorRegistry;
    }
    
    @Override
    public List<Service> getServices() {
        return _serviceRegistry.getServices();
    }
       

    @Override
    public List<Service> getServices(QName serviceName) {
        return _serviceRegistry.getServices(serviceName);
    }
    
    /**
     * Convenient access to the domain's service registry.
     * @return service registry
     */
    public ServiceRegistry getServiceRegistry() {
        return _serviceRegistry;
    }
    
    /**
     * Convenient access to the domain's exchange bus.
     * @return exchange bus
     */
    public ExchangeBus getBus() {
        return _exchangeBus;
    }
    
    @Override
    public void init() {
        _eventManager.publish(new DomainStartupEvent(this));
        _exchangeBus.init(this);
        _exchangeBus.start();
    }
    
    @Override
    public void destroy() {
        _exchangeBus.stop();
        _eventManager.publish(new DomainShutdownEvent(this));
    }

    @Override
    public ServiceDomain addEventObserver(EventObserver observer, Class<? extends EventObject> eventType) {
        _eventManager.addObserver(observer, eventType);
        return this;
    }

    @Override
    public EventPublisher getEventPublisher() {
        return _eventManager;
    }

    @Override
    public Map<String, Object> getProperties() {
        return Collections.unmodifiableMap(_properties);
    }

    @Override
    public Object getProperty(String name) {
        return _properties.get(name);
    }

    @Override
    public ServiceDomain setProperty(String name, Object value) {
        if (value != null) {
            _properties.put(name, value);
        } else {
            _properties.remove(name);
        }
        return this;
    }

    private void setEventPublisher(Object target) {
        if (target == null) {
            return;
        }
        
        try {
            Method setter = target.getClass().getMethod("setEventPublisher", EventPublisher.class);
            setter.invoke(target, _eventManager);
        } catch (Exception ex) {
            _logger.debug("Attempt to set EventPublisher failed on " 
                    + target.getClass().getCanonicalName(), ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ServiceSecurity getServiceSecurity(String name) {
        return getServiceDomainSecurity().getServiceSecurity(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ServiceDomainSecurity getServiceDomainSecurity() {
        return _serviceDomainSecurity;
    }

    @Override
    public String toString() {
        return "ServiceDomain [name=" + _name + "]";
    }

    @Override
    public void removeObserver(EventObserver observer) {
        _eventManager.removeObserver(observer);
    }

    @Override
    public void removeObserverForEvent(
            EventObserver observer, Class<? extends EventObject> eventType) {
        _eventManager.removeObserverForEvent(observer, eventType);
    }
}
