/*
 * Copyright (c) 2017, 2018, Salesforce.com, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *   disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.salesforce.storm.spout.dynamic.metrics;

import com.salesforce.storm.spout.dynamic.config.SpoutConfig;
import org.apache.storm.metric.api.MeanReducer;
import org.apache.storm.metric.api.MultiCountMetric;
import org.apache.storm.metric.api.MultiReducedMetric;
import org.apache.storm.task.TopologyContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
 * A wrapper for recording metrics in Storm
 *
 * ONLY USE WITH STORM 1.1 or lower.
 *
 * If you are running Storm 1.2 or higher you should use {@link DropwizardRecorder}
 *
 * Learn more about Storm metrics here: http://storm.apache.org/releases/1.0.1/Metrics.html
 *
 * Use this as an instance variable on your bolt, make sure to create it inside of prepareBolt()
 * and pass it down stream to any classes that need to track metrics in your application.
 *
 * This will report metrics in the following format:
 *
 * Gauge Values: GAUGES.[className].[metricPrefix].[metricName]
 * Timed Values: TIMERS.[className].[metricPrefix].[metricName]
 * Counter Values: COUNTERS.[className].[metricPrefix].[metricName]
 */
@Deprecated
public class StormRecorder implements MetricsRecorder {
    private static final Logger logger = LoggerFactory.getLogger(StormRecorder.class);

    /**
     * Constructs metric keys from a {@link MetricDefinition}.
     */
    private KeyBuilder keyBuilder;

    /**
     * Contains a map of Assigned Metrics, which are used to set a metric to a specific value.
     */
    private MultiAssignableMetric assignedValues;

    /**
     * Contains a map of Reduced Metrics, which are used to calculate timings of something over time.
     */
    private MultiReducedMetric timers;

    /**
     * Contains a map of Counter metrics, which are used to count how often something happens,
     * always increasing.
     */
    private MultiCountMetric counters;

    /**
     * Tracks timers start points and provides elapsed time when stopping.
     */
    private final TimerManager timerManager = new TimerManager();

    /**
     * Allow configuring a prefix for metric keys.
     */
    private String metricPrefix = "";

    @Override
    public void open(final Map<String, Object> spoutConfig, final TopologyContext topologyContext) {
        // Load configuration items.

        // Determine our time bucket window, in seconds, defaulted to 60.
        int timeBucketSeconds = 60;
        if (spoutConfig.containsKey(SpoutConfig.METRICS_RECORDER_TIME_BUCKET)) {
            final Object timeBucketCfgValue = spoutConfig.get(SpoutConfig.METRICS_RECORDER_TIME_BUCKET);
            if (timeBucketCfgValue instanceof Number) {
                timeBucketSeconds = ((Number) timeBucketCfgValue).intValue();
            }
        }

        // Conditionally enable prefixing with taskId
        if (spoutConfig.containsKey(SpoutConfig.METRICS_RECORDER_ENABLE_TASK_ID_PREFIX)) {
            final Object taskIdCfgValue = spoutConfig.get(SpoutConfig.METRICS_RECORDER_ENABLE_TASK_ID_PREFIX);
            if (taskIdCfgValue instanceof Boolean && (Boolean) taskIdCfgValue) {
                this.metricPrefix = "task-" + topologyContext.getThisTaskIndex();
            }
        }

        this.keyBuilder = new KeyBuilder(this.metricPrefix);

        // Log how we got configured.
        logger.info("Configured with time window of {} seconds and using taskId prefixes?: {}",
            timeBucketSeconds, Boolean.toString(metricPrefix.isEmpty()));

        // Register the top level metrics.
        assignedValues = topologyContext.registerMetric("GAUGES", new MultiAssignableMetric(), timeBucketSeconds);
        timers = topologyContext.registerMetric("TIMERS", new MultiReducedMetric(new MeanReducer()), timeBucketSeconds);
        counters = topologyContext.registerMetric("COUNTERS", new MultiCountMetric(), timeBucketSeconds);
    }

    @Override
    public void countBy(final MetricDefinition metric, final long incrementBy, final Object... metricParameters) {
        final String key = generateKey(metric, metricParameters);
        counters.scope(key).incrBy(incrementBy);
    }

    @Override
    public void assignValue(final MetricDefinition metric, final Object value, final Object... metricParameters) {
        final String key = generateKey(metric, metricParameters);
        assignedValues.scope(key).setValue(value);
    }

    @Override
    public void startTimer(final MetricDefinition metric, final Object... metricParameters) {
        final String key = generateKey(metric, metricParameters);
        timerManager.start(key);
    }

    @Override
    public long stopTimer(final MetricDefinition metric, final Object... metricParameters) {
        // Build key from the metric
        final String key = generateKey(metric, metricParameters);

        final long elapsedMs = timerManager.stop(key);

        recordTimer(key, elapsedMs);

        return elapsedMs;
    }

    @Override
    public void recordTimer(final MetricDefinition metric, final long elapsedTimeMs, final Object... metricParameters) {
        final String key = generateKey(metric, metricParameters);
        recordTimer(key, elapsedTimeMs);
    }

    /**
     * Internal helper to record the value of a timer.
     * @param key String representation of the key to record the timer under
     * @param elapsedTimeMs How long the timer ran for, in milliseconds.
     */
    private void recordTimer(final String key, final long elapsedTimeMs) {
        // Update averaged timer key
        timers.scope(key).update(elapsedTimeMs);

        // Increment total time counter, this keeps a running count of total time spent in this timer
        counters.scope(key + "_totalTimeMs").incrBy(elapsedTimeMs);
    }

    /**
     * Internal utility class to help generate metric keys.
     *
     * @return in format of: "className.metricPrefix.metricName"
     */
    private String generateKey(final MetricDefinition metric, final Object[] parameters) {
        return keyBuilder.build(metric, parameters);
    }

    /**
     * Package protected getter.
     * @return Configured metric prefix.
     */
    String getMetricPrefix() {
        return metricPrefix;
    }
}