/*-
 * ~~~~~~licensing~~~~~~
 * core-support-runtime
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.support.runtime.camel;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;

import org.apache.aries.blueprint.ext.PropertyPlaceholderExt;
import org.apache.camel.spi.PropertiesFunction;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.util.ReflectionHelper;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CmPropertiesAccessorFunction implements PropertiesFunction {
	
	protected static final Logger log = LoggerFactory.getLogger(CmPropertiesAccessorFunction.class);
	
	// private CmPropertyPlaceholder _wrappedObject;
	protected String prefix;
	protected String persistentId;
	protected BundleContext bundleContext;
	protected ConfigurationAdmin configurationAdmin;
	protected Object wrappedPlaceholder;
	protected PropertyPlaceholderWrapper wrapper;
	
	/*
	 * public CmPropertyPlaceholder getWrappedObject() { return _wrappedObject; }
	 */
	/*
	 * public void setWrappedObject(CmPropertyPlaceholder wrappedObject) {
	 * log.info("-->> WRAPPED :: " + wrappedObject.getClass().getName());
	 * this._wrappedObject = wrappedObject; ServiceReference<ConfigurationAdmin> ref
	 * = this._wrappedObject.getBundle().getBundleContext().getServiceReference(
	 * ConfigurationAdmin.class); this.configurationAdmin =
	 * this._wrappedObject.getBundle().getBundleContext().getService(ref); }
	 */
	
	@Override
	public String getName() {
		return this.prefix;
	}

	@Override
	public String apply(String remainder) {
		Object val = null;
		try {
			if (this.wrapper != null)
				val = getPropertyFromPlaceholder(remainder);
			else
				val = this.configurationAdmin.getConfiguration(this.persistentId).getProperties().get(remainder);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		return val==null?"":val.toString();
	}

	public String getPrefix() {
		return prefix;
	}

	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	public void setBundleContext(BundleContext bundleContext) {
		this.bundleContext = bundleContext;
		log.info("-->> BUNDLE :: " + bundleContext.getBundle().getBundleId());
		ServiceReference<ConfigurationAdmin> ref = this.bundleContext.getServiceReference(ConfigurationAdmin.class); 
		this.configurationAdmin = this.bundleContext.getService(ref); 
	}

	public String getPersistentId() {
		return persistentId;
	}

	public void setPersistentId(String persistentId) {
		this.persistentId = persistentId;
	}

	public Object getWrappedPlaceholder() {
		return wrappedPlaceholder;
	}

	public void setWrappedPlaceholder(Object wrappedPlaceholder) {
		this.wrappedPlaceholder = wrappedPlaceholder;
		if (this.wrappedPlaceholder != null)
			this.initPlaceholder();
	}

	/*
	 * From here and further: fragmentary copied from 
	 * org.apache.camel.blueprint.BlueprintPropertiesSource
	 * 
	 * get sources at 
	 * https://search.maven.org/artifact/org.apache.camel.karaf/apache-camel/3.4.4/pom
	 * 
	 */

    protected String getPropertyFromPlaceholder(String name) {
        String answer = null;

        boolean isDefault = false;
        // 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 = this.wrapper.getDefaultProperties();
        isDefault = map != null && map.containsKey(name);
        log.trace("Blueprint property key: {} is part of default properties: {}", name, isDefault);

        try {
            String candidate = this.wrapper.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;
    }
	
	
	protected void initPlaceholder() {
		
		if (this.wrappedPlaceholder instanceof PropertyPlaceholderExt) {
            Class<?> clazz = this.wrappedPlaceholder.getClass();
            if (clazz != null) {
                log.debug("Adding Blueprint PropertyPlaceholder for {}", this.persistentId);
                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);
                    }
                    this.wrapper = new PropertyPlaceholderWrapper(this.wrappedPlaceholder, method, defaultMethod);
                } else {
                    throw new IllegalStateException("Cannot add blueprint property placeholder for: " + this.persistentId
                            + " as the method retrieveValue is not found");
                }
            }
        }
		
	}
	
    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;
        }
    }
	
}
