/*
 * 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.karaf.core;

import java.util.Dictionary;
import java.util.Hashtable;

import org.apache.camel.spi.CamelEvent;
import org.apache.camel.support.EventNotifierSupport;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.util.tracker.ServiceTracker;

/**
 * This {@link org.apache.camel.spi.EventNotifier} is in charge of propagating events
 * to OSGi {@link EventAdmin} if present.
 */
public class OsgiEventAdminNotifier extends EventNotifierSupport {

    public static final String TYPE = "type";
    public static final String EVENT = "event";
    public static final String TIMESTAMP = "timestamp";
    public static final String BUNDLE = "bundle";
    public static final String BUNDLE_ID = "bundle.id";
    public static final String BUNDLE_SYMBOLICNAME = "bundle.symbolicName";
    public static final String BUNDLE_VERSION = "bundle.version";
    public static final String CAUSE = "cause";

    public static final String TOPIC_CAMEL_EVENTS = "org/apache/camel/";
    public static final String TOPIC_CAMEL_CONTEXT_EVENTS = TOPIC_CAMEL_EVENTS + "context/";
    public static final String TOPIC_CAMEL_EXCHANGE_EVENTS = TOPIC_CAMEL_EVENTS + "exchange/";
    public static final String TOPIC_CAMEL_SERVICE_EVENTS = TOPIC_CAMEL_EVENTS + "service/";
    public static final String TOPIC_CAMEL_ROUTE_EVENTS = TOPIC_CAMEL_EVENTS + "route/";

    private final BundleContext bundleContext;
    private final ServiceTracker<EventAdmin, EventAdmin> tracker;

    public OsgiEventAdminNotifier(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        this.tracker = new ServiceTracker<>(bundleContext, EventAdmin.class.getName(), null);
        setIgnoreExchangeEvents(true);
    }

    @Override
    public void notify(CamelEvent event) throws Exception {
        EventAdmin eventAdmin = tracker.getService();
        if (eventAdmin == null) {
            return;
        }

        Dictionary<String, Object> props = new Hashtable<>();
        props.put(TYPE, getType(event));
        props.put(EVENT, event);
        props.put(TIMESTAMP, System.currentTimeMillis());
        props.put(BUNDLE, bundleContext.getBundle());
        props.put(BUNDLE_SYMBOLICNAME, bundleContext.getBundle().getSymbolicName());
        props.put(BUNDLE_ID, bundleContext.getBundle().getBundleId());
        props.put(BUNDLE_VERSION, getBundleVersion(bundleContext.getBundle()));
        try {
            props.put(CAUSE, event.getClass().getMethod("getCause").invoke(event));
        } catch (Throwable t) {
            // ignore
        }
        eventAdmin.postEvent(new Event(getTopic(event), props));
    }

    @Override
    public boolean isEnabled(CamelEvent event) {
        return true;
    }

    @Override
    protected void doStart() throws Exception {
        tracker.open();
    }

    @Override
    protected void doStop() throws Exception {
        tracker.close();
    }

    public static String toUpper(String text) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (Character.isUpperCase(c) && sb.length() > 0) {
                sb.append('_');
            }
            sb.append(Character.toUpperCase(c));
        }
        return sb.toString();
    }

    public static String getType(CamelEvent event) {
        return event.getType().name();
    }

    public static String getTopic(CamelEvent event) {
        String topic;
        String type = getType(event);
        if (type.startsWith("CamelContext")) {
            topic = TOPIC_CAMEL_CONTEXT_EVENTS;
            type = type.substring("CamelContext".length());
        } else if (type.startsWith("Exchange")) {
            topic = TOPIC_CAMEL_EXCHANGE_EVENTS;
            type = type.substring("Exchange".length());
        } else if (type.startsWith("Route")) {
            topic = TOPIC_CAMEL_ROUTE_EVENTS;
            type = type.substring("Route".length());
        } else if (type.startsWith("Service")) {
            topic = TOPIC_CAMEL_SERVICE_EVENTS;
            type = type.substring("Service".length());
        } else {
            topic = TOPIC_CAMEL_EVENTS + "unknown/";
        }
        topic += toUpper(type);
        return topic;
    }

    public static Version getBundleVersion(Bundle bundle) {
        Dictionary<?, ?> headers = bundle.getHeaders();
        String version = (String)headers.get(Constants.BUNDLE_VERSION);
        return (version != null) ? Version.parseVersion(version) : Version.emptyVersion;
    }

}