/**
 * 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 com.alibaba.jstorm.daemon.worker;

import backtype.storm.Config;
import backtype.storm.tuple.MessageId;
import backtype.storm.tuple.Tuple;

import com.alibaba.jstorm.client.ConfigExtension;
import com.alibaba.jstorm.config.Refreshable;
import com.alibaba.jstorm.config.RefreshableComponents;
import com.alibaba.jstorm.utils.JStormUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * manage the global state of topology.debug & topology.debug.recv.tuple & topology.debug.sample.rate on the fly
 *
 * @author Jark (wuchong.wc@alibaba-inc.com)
 */
public class JStormDebugger implements Refreshable {
    private static Logger LOG = LoggerFactory.getLogger(JStormDebugger.class);

    private JStormDebugger() {
        RefreshableComponents.registerRefreshable(this);
    }

    private static final JStormDebugger INSTANCE = new JStormDebugger();

    public static JStormDebugger getInstance() {
        return INSTANCE;
    }

    public static volatile boolean isDebug = false;
    public static volatile boolean isDebugRecv = false;
    public static volatile double sampleRate = 1.0d;

    // PRECISION represent the sampling rate precision , such as: 10000 -> 0.0001
    private static final int PRECISION = 10000;


    public static boolean isDebug(Long root_id) {
        return isDebug && sample(root_id);
    }

    public static boolean isDebug(Set<Long> rootIds) {
        return isDebug && sample(rootIds);
    }

    public static boolean isDebug(Collection<Tuple> anchors) {
        return isDebug && sample(anchors);
    }

    public static boolean isDebug(Object id) {
        return isDebug && sample(id);
    }

    public static boolean isDebugRecv(MessageId msgId) {
        return msgId != null && isDebugRecv(msgId.getAnchors());
    }

    public static boolean isDebugRecv(Set<Long> rootIds) {
        return isDebugRecv && sample(rootIds);
    }

    public static boolean isDebugRecv(Collection<Tuple> anchors) {
        return isDebugRecv && sample(anchors);
    }

    public static boolean isDebugRecv(Object id) {
        return isDebugRecv && sample(id);
    }

    /**
     * the tuple debug logs only output `rate`% the tuples with same rootId, should be logged or not logged together.
     */
    private static boolean sample(Long rootId) {
        if (Double.compare(sampleRate, 1.0d) >= 0)
            return true;
        int mod = (int) (Math.abs(rootId) % PRECISION);
        int threshold = (int) (sampleRate * PRECISION);
        return mod < threshold;
    }

    /**
     * one of the rootIds has been chosen , the logs should be output
     */
    private static boolean sample(Set<Long> rootIds) {
        if (Double.compare(sampleRate, 1.0d) >= 0)
            return true;
        int threshold = (int) (sampleRate * PRECISION);
        for (Long id : rootIds) {
            int mod = (int) (Math.abs(id) % PRECISION);
            if (mod < threshold) {
                return true;
            }
        }
        return false;
    }

    /**
     * one of the tuples has been chosen, the logs should be output
     */
    private static boolean sample(Collection<Tuple> anchors) {
        if (Double.compare(sampleRate, 1.0d) >= 0)
            return true;
        for (Tuple t : anchors) {
            if (sample(t.getMessageId().getAnchors())) {
                return true;
            }
        }
        return false;
    }

    private static boolean sample(Object id) {
        if (Double.compare(sampleRate, 1.0d) >= 0)
            return true;
        return id != null && id instanceof Long && sample((Long) id);
    }

    public static void update(Map conf) {
        boolean _isDebug = JStormUtils.parseBoolean(conf.get(Config.TOPOLOGY_DEBUG), isDebug);
        if (_isDebug != isDebug) {
            isDebug = _isDebug;
            LOG.info("switch topology.debug to {}", _isDebug);
        }
        boolean _isDebugRecv = ConfigExtension.isTopologyDebugRecvTuple(conf);
        if (_isDebugRecv != isDebugRecv) {
            isDebugRecv = _isDebugRecv;
            LOG.info("switch topology.debug.recv.tuple to {}", _isDebug);
        }
        double _sampleRate = ConfigExtension.getTopologyDebugSampleRate(conf);
        if (Double.compare(_sampleRate, sampleRate) != 0) {
            sampleRate = _sampleRate;
            LOG.info("switch topology.debug.sample.rate to {}", _sampleRate);
        }
    }

    @Override
    public void refresh(Map conf) {
        update(conf);
    }
}
