/*
 * 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.nifi.reporting.datadog.metrics;

import org.apache.nifi.controller.status.ConnectionStatus;
import org.apache.nifi.controller.status.PortStatus;
import org.apache.nifi.controller.status.ProcessGroupStatus;
import org.apache.nifi.controller.status.ProcessorStatus;
import org.apache.nifi.metrics.jvm.JmxJvmMetrics;
import org.apache.nifi.processor.DataUnit;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * A service used to produce key/value metrics based on a given input.
 */
public class MetricsService {

    //processor - specific metrics
    public Map<String, Double> getProcessorMetrics(ProcessorStatus status) {
        final Map<String, Double> metrics = new HashMap<>();
        metrics.put(MetricNames.FLOW_FILES_RECEIVED, new Double(status.getInputCount()));
        metrics.put(MetricNames.FLOW_FILES_SENT, new Double(status.getOutputCount()));
        metrics.put(MetricNames.BYTES_READ, new Double(status.getInputBytes()));
        metrics.put(MetricNames.BYTES_WRITTEN, new Double(status.getOutputBytes()));
        metrics.put(MetricNames.ACTIVE_THREADS, new Double(status.getActiveThreadCount()));
        metrics.put(MetricNames.TOTAL_TASK_DURATION, new Double(status.getProcessingNanos()));
        return metrics;
    }

    public Map<String, String> getProcessorTags(ProcessorStatus status) {
        Map<String, String> tags = new HashMap<>();
        tags.put("processor", status.getName());
        return tags;
    }

    public Map<String, Double> getPortStatusMetrics(PortStatus status){
        final Map<String, Double> metrics = new HashMap<>();
        metrics.put(MetricNames.ACTIVE_THREADS, new Double(status.getActiveThreadCount()));
        metrics.put(MetricNames.INPUT_COUNT, new Double(status.getInputCount()));
        metrics.put(MetricNames.OUTPUT_COUNT, new Double(status.getOutputCount()));
        metrics.put(MetricNames.INPUT_BYTES, new Double(status.getInputBytes()));
        metrics.put(MetricNames.OUTPUT_BYTES, new Double(status.getOutputBytes()));
        metrics.put(MetricNames.FLOW_FILES_RECEIVED, new Double(status.getFlowFilesReceived()));
        metrics.put(MetricNames.FLOW_FILES_SENT, new Double(status.getFlowFilesSent()));
        metrics.put(MetricNames.BYTES_RECEIVED, new Double(status.getBytesReceived()));
        metrics.put(MetricNames.BYTES_SENT, new Double(status.getBytesSent()));
        return metrics;
    }

    public Map<String, Double> getConnectionStatusMetrics(ConnectionStatus status) {
        final Map<String, Double> metrics = new HashMap<>();
        metrics.put(MetricNames.INPUT_COUNT, new Double(status.getInputCount()));
        metrics.put(MetricNames.INPUT_BYTES, new Double(status.getInputBytes()));
        metrics.put(MetricNames.QUEUED_COUNT, new Double(status.getQueuedCount()));
        metrics.put(MetricNames.QUEUED_BYTES, new Double(status.getQueuedBytes()));
        metrics.put(MetricNames.OUTPUT_COUNT, new Double(status.getOutputCount()));
        metrics.put(MetricNames.OUTPUT_BYTES, new Double(status.getOutputBytes()));
        return metrics;
    }


    //general metrics for whole dataflow
    public Map<String, Double> getDataFlowMetrics(ProcessGroupStatus status) {
        final Map<String, Double> metrics = new HashMap<>();
        metrics.put(MetricNames.FLOW_FILES_RECEIVED, new Double(status.getFlowFilesReceived()));
        metrics.put(MetricNames.BYTES_RECEIVED, new Double(status.getBytesReceived()));
        metrics.put(MetricNames.FLOW_FILES_SENT, new Double(status.getFlowFilesSent()));
        metrics.put(MetricNames.BYTES_SENT, new Double(status.getBytesSent()));
        metrics.put(MetricNames.FLOW_FILES_QUEUED, new Double(status.getQueuedCount()));
        metrics.put(MetricNames.BYTES_QUEUED, new Double(status.getQueuedContentSize()));
        metrics.put(MetricNames.BYTES_READ, new Double(status.getBytesRead()));
        metrics.put(MetricNames.BYTES_WRITTEN, new Double(status.getBytesWritten()));
        metrics.put(MetricNames.ACTIVE_THREADS, new Double(status.getActiveThreadCount()));
        metrics.put(MetricNames.TOTAL_TASK_DURATION, new Double(calculateProcessingNanos(status)));
        status.getOutputPortStatus();
        return metrics;
    }

    //virtual machine metrics
    public Map<String, Double> getJVMMetrics(JmxJvmMetrics virtualMachineMetrics) {
        final Map<String, Double> metrics = new HashMap<>();
        metrics.put(MetricNames.JVM_UPTIME, new Double(virtualMachineMetrics.uptime()));
        metrics.put(MetricNames.JVM_HEAP_USED, new Double(virtualMachineMetrics.heapUsed(DataUnit.B)));
        metrics.put(MetricNames.JVM_HEAP_USAGE, new Double(virtualMachineMetrics.heapUsage()));
        metrics.put(MetricNames.JVM_NON_HEAP_USAGE, new Double(virtualMachineMetrics.nonHeapUsage()));
        metrics.put(MetricNames.JVM_THREAD_COUNT, new Double(virtualMachineMetrics.threadCount()));
        metrics.put(MetricNames.JVM_DAEMON_THREAD_COUNT, new Double(virtualMachineMetrics.daemonThreadCount()));
        metrics.put(MetricNames.JVM_FILE_DESCRIPTOR_USAGE, new Double(virtualMachineMetrics.fileDescriptorUsage()));

        for (Map.Entry<Thread.State, Double> entry : virtualMachineMetrics.threadStatePercentages().entrySet()) {
            final int normalizedValue = (int) (100 * (entry.getValue() == null ? 0 : entry.getValue()));
            switch (entry.getKey()) {
                case BLOCKED:
                    metrics.put(MetricNames.JVM_THREAD_STATES_BLOCKED, new Double(normalizedValue));
                    break;
                case RUNNABLE:
                    metrics.put(MetricNames.JVM_THREAD_STATES_RUNNABLE, new Double(normalizedValue));
                    break;
                case TERMINATED:
                    metrics.put(MetricNames.JVM_THREAD_STATES_TERMINATED, new Double(normalizedValue));
                    break;
                case TIMED_WAITING:
                    metrics.put(MetricNames.JVM_THREAD_STATES_TIMED_WAITING, new Double(normalizedValue));
                    break;
                default:
                    break;
            }
        }

        for (Map.Entry<String, JmxJvmMetrics.GarbageCollectorStats> entry : virtualMachineMetrics.garbageCollectors().entrySet()) {
            final String gcName = entry.getKey().replace(" ", "");
            final long runs = entry.getValue().getRuns();
            final long timeMS = entry.getValue().getTime(TimeUnit.MILLISECONDS);
            metrics.put(MetricNames.JVM_GC_RUNS + "." + gcName,new Double(runs));
            metrics.put(MetricNames.JVM_GC_TIME + "." + gcName, new Double(timeMS));
        }

        return metrics;
    }


    // calculates the total processing time of all processors in nanos
    protected long calculateProcessingNanos(final ProcessGroupStatus status) {
        long nanos = 0L;

        for (final ProcessorStatus procStats : status.getProcessorStatus()) {
            nanos += procStats.getProcessingNanos();
        }

        for (final ProcessGroupStatus childGroupStatus : status.getProcessGroupStatus()) {
            nanos += calculateProcessingNanos(childGroupStatus);
        }

        return nanos;
    }


}
