/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.cloudstack.spring.lifecycle.registry;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.StringUtils;

import com.cloud.utils.component.Registry;

public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(RegistryLifecycle.class);

    public static final String EXTENSION_EXCLUDE = "extensions.exclude";
    public static final String EXTENSION_INCLUDE_PREFIX = "extensions.include.";

    Registry<Object> registry;

    /* The bean name works around circular dependency issues in Spring.  This shouldn't be
     * needed if your beans are already nicely organized.  If they look like spaghetti, then you
     * can use this.
     */
    String registryBeanName;
    Set<Object> beans = new HashSet<Object>();
    Class<?> typeClass;
    ApplicationContext applicationContext;
    Set<String> excludes = null;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (typeClass.isAssignableFrom(bean.getClass()) && !isExcluded(bean)) {
            beans.add(bean);
        }

        return bean;
    }

    protected synchronized boolean isExcluded(Object bean) {
        String name = RegistryUtils.getName(bean);

        if (excludes == null) {
            loadExcluded();
        }

        boolean result = excludes.contains(name);
        if (result) {
            log.info("Excluding extension [{}] based on configuration", name);
        }

        return result;
    }

    protected synchronized void loadExcluded() {
        Properties props = applicationContext.getBean("DefaultConfigProperties", Properties.class);
        excludes = new HashSet<String>();
        for (String exclude : props.getProperty(EXTENSION_EXCLUDE, "").trim().split("\\s*,\\s*")) {
            if (StringUtils.hasText(exclude)) {
                excludes.add(exclude);
            }
        }

        for (String key : props.stringPropertyNames()) {
            if (key.startsWith(EXTENSION_INCLUDE_PREFIX)) {
                String module = key.substring(EXTENSION_INCLUDE_PREFIX.length());
                boolean include = props.getProperty(key).equalsIgnoreCase("true");
                if (!include) {
                    excludes.add(module);
                }
            }
        }
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public void start() {
        Iterator<Object> iter = beans.iterator();
        Registry<Object> registry = lookupRegistry();

        while (iter.hasNext()) {
            Object next = iter.next();
            if (registry.register(next)) {
                log.debug("Registered {}", next);
            } else {
                iter.remove();
            }
        }
    }

    @Override
    public void stop() {
        Registry<Object> registry = lookupRegistry();

        for (Object bean : beans) {
            registry.unregister(bean);
        }

        beans.clear();
    }

    @Override
    public boolean isRunning() {
        return false;
    }

    @Override
    public int getPhase() {
        return 2000;
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable callback) {
        stop();
        callback.run();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @SuppressWarnings("unchecked")
    protected Registry<Object> lookupRegistry() {
        return registry == null ? applicationContext.getBean(registryBeanName, Registry.class) : registry;
    }

    public Registry<Object> getRegistry() {
        return registry;
    }

    public void setRegistry(Registry<Object> registry) {
        this.registry = registry;
    }

    public Class<?> getTypeClass() {
        return typeClass;
    }

    public void setTypeClass(Class<?> typeClass) {
        this.typeClass = typeClass;
    }

    public String getRegistryBeanName() {
        return registryBeanName;
    }

    public void setRegistryBeanName(String registryBeanName) {
        this.registryBeanName = registryBeanName;
    }

}
