/**
 * 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.management.mbean;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import javax.management.Notification;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.Traceable;
import org.apache.camel.api.management.NotificationSender;
import org.apache.camel.api.management.NotificationSenderAware;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.processor.interceptor.TraceEventHandler;
import org.apache.camel.processor.interceptor.TraceInterceptor;
import org.apache.camel.processor.interceptor.Tracer;
import org.apache.camel.util.MessageHelper;

public final class JMXNotificationTraceEventHandler implements TraceEventHandler, NotificationSenderAware {
    private static final int MAX_MESSAGE_LENGTH = 60;
    private final AtomicLong num = new AtomicLong();
    private final Tracer tracer;
    private NotificationSender notificationSender;

    public JMXNotificationTraceEventHandler(Tracer tracer) {
        this.tracer = tracer;
    }

    public void traceExchangeOut(ProcessorDefinition<?> node, Processor target, TraceInterceptor traceInterceptor, Exchange exchange, Object traceState) throws Exception {
        // We do nothing here
    }

    public Object traceExchangeIn(ProcessorDefinition<?> node, Processor target, TraceInterceptor traceInterceptor, Exchange exchange) throws Exception {
        // Just trace the exchange as usual
        traceExchange(node, target, traceInterceptor, exchange);
        return null;
    }

    public void traceExchange(ProcessorDefinition<?> node, Processor target, TraceInterceptor traceInterceptor, Exchange exchange) throws Exception {
        if (notificationSender != null && tracer.isJmxTraceNotifications()) {
            String body = MessageHelper.extractBodyForLogging(exchange.getIn(), "", false, true, tracer.getTraceBodySize());
            
            if (body == null) {
                body = "";
            }
            String message = body.substring(0, Math.min(body.length(), MAX_MESSAGE_LENGTH));
            Map<String, Object> tm = createTraceMessage(node, exchange, body);

            Notification notification = new Notification("TraceNotification", exchange.toString(), num.getAndIncrement(), System.currentTimeMillis(), message);
            notification.setUserData(tm);

            notificationSender.sendNotification(notification);
        }

    }

    private Map<String, Object> createTraceMessage(ProcessorDefinition<?> node, Exchange exchange, String body) {
        Map<String, Object> mi = new HashMap<>();
        mi.put("ExchangeId", exchange.getExchangeId());
        mi.put("EndpointURI", getEndpointUri(node));
        mi.put("TimeStamp", new Date(System.currentTimeMillis()));
        mi.put("Body", body);

        Message message = exchange.getIn();
        Map<String, Object> sHeaders = message.getHeaders();
        Map<String, Object> sProperties = exchange.getProperties();

        Map<String, String> headers = new HashMap<>();
        for (String key : sHeaders.keySet()) {
            headers.put(key, message.getHeader(key, String.class));
        }
        mi.put("Headers", headers);

        Map<String, String> properties = new HashMap<>();
        for (String key : sProperties.keySet()) {
            properties.put(key, exchange.getProperty(key, String.class));
        }
        mi.put("Properties", properties);
        return mi;
    }

    private String getEndpointUri(ProcessorDefinition<?> node) {
        if (node instanceof Traceable) {
            Traceable tr = (Traceable)node;
            return tr.getTraceLabel();
        } else {
            return node.getLabel();
        }
    }

    @Override
    public void setNotificationSender(NotificationSender sender) {
        this.notificationSender = sender;
    }

}
