/*
 * 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.camel.blueprint;

import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.aries.blueprint.ext.PropertyPlaceholderExt;
import org.apache.camel.spi.PropertiesSource;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.ReflectionHelper;
import org.osgi.service.blueprint.container.BlueprintContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Blueprint {@link PropertiesSource} which supports looking up
 * property placeholders from the Blueprint Property Placeholder Service.
 */
public class BlueprintPropertiesSource extends ServiceSupport implements PropertiesSource {

    private static final Logger LOG = LoggerFactory.getLogger(BlueprintPropertiesSource.class);
    private final BlueprintContainer container;
    private final List<String> ids;
    private final Set<PropertyPlaceholderWrapper> placeholders = new LinkedHashSet<>();

    public BlueprintPropertiesSource(BlueprintContainer container, List<String> ids) {
        this.container = container;
        this.ids = ids;
    }

    @Override
    public String getName() {
        return "BlueprintPropertiesSource" + ids;
    }

    @Override
    public String getProperty(String name) {
        String answer = null;

        for (PropertyPlaceholderWrapper placeholder : placeholders) {
            boolean isDefault = false;
            if (placeholders.size() > 1) {
                // okay we have multiple placeholders and we want to return the answer that
                // is not the default placeholder if there is multiple keys
                Map map = placeholder.getDefaultProperties();
                isDefault = map != null && map.containsKey(name);
                LOG.trace("Blueprint property key: {} is part of default properties: {}", name, isDefault);
            }

            try {
                String candidate = placeholder.retrieveValue(name);
                if (candidate != null) {
                    if (answer == null || !isDefault) {
                        LOG.trace("Blueprint candidate property key: {} as value: {}", name, answer);
                        answer = candidate;
                    }
                }
            } catch (Exception ex) {
                // Here we just catch the exception and try to use other candidate
            }
        }
        LOG.debug("Blueprint getProperty: {}={}", name, answer);

        return answer;
    }

    /**
     * Adds the given Blueprint property placeholder service with the given id
     *
     * @param id id of the Blueprint property placeholder service to add.
     */
    private void addPropertyPlaceholder(String id) {
        Object component = container.getComponentInstance(id);

        if (component instanceof PropertyPlaceholderExt) {
            Class<?> clazz = component.getClass();
            if (clazz != null) {
                LOG.debug("Adding Blueprint PropertyPlaceholder: {}", id);
                Method method = ReflectionHelper.findMethod(clazz, "retrieveValue", String.class);
                Method defaultMethod = ReflectionHelper.findMethod(clazz, "getDefaultProperties");
                if (method != null) {
                    method.setAccessible(true);
                    if (defaultMethod != null) {
                        defaultMethod.setAccessible(true);
                    }
                    placeholders.add(new PropertyPlaceholderWrapper(component, method, defaultMethod));
                } else {
                    throw new IllegalStateException("Cannot add blueprint property placeholder: " + id
                            + " as the method retrieveValue is not found");
                }
            }
        }
    }

    @Override
    protected void doInit() throws Exception {
        for (String id : ids) {
            addPropertyPlaceholder(id);
        }
    }

    @Override
    protected void doStart() throws Exception {
        // noop
    }

    @Override
    protected void doStop() throws Exception {
        // noop
    }

    private class PropertyPlaceholderWrapper {

        private final Object delegate;
        private final Method method;
        private final Method defaultMethod;

        PropertyPlaceholderWrapper(Object delegate, Method method, Method defaultMethod) {
            this.delegate = delegate;
            this.method = method;
            this.defaultMethod = defaultMethod;
        }

        String retrieveValue(String key) {
            Object v = ObjectHelper.invokeMethod(method, delegate, key);
            return v == null ? null : v.toString();
        }

        Map getDefaultProperties() {
            if (defaultMethod != null) {
                return (Map) ObjectHelper.invokeMethod(defaultMethod, delegate);
            }
            return null;
        }
    }

}