/*
 * Copyright 2012 Netflix, Inc.
 *
 *    Licensed 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.netflix.exhibitor.core.backup.s3;

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

public class Throttle
{
    private static final Logger logger = LoggerFactory.getLogger(Throttle.class);

    private final String name;
    private final ThroughputFunction fun;

    // the bytes that had been handled the last time we delayed to throttle,
    // and the time in milliseconds when we last throttled
    private long bytesAtLastDelay;
    private long timeAtLastDelay = System.currentTimeMillis();  // JLZ - need to initialize

    // current target bytes of throughput per millisecond
    private int targetBytesPerMS = -1;

    public Throttle(String name, ThroughputFunction fun)
    {
        this.name = name;
        this.fun = fun;
    }

    /** @param currentBytes Bytes of throughput since the beginning of the task. */
    public void throttle(long currentBytes)
    {
        throttleDelta(currentBytes - bytesAtLastDelay);
    }

    /** @param bytesDelta Bytes of throughput since the last call to throttle*(). */
    public void throttleDelta(long bytesDelta)
    {
        int newTargetBytesPerMS = fun.targetThroughput();
        if ( newTargetBytesPerMS < 1 )
        // throttling disabled
        {
            return;
        }

        // if the target changed, log
        if ( newTargetBytesPerMS != targetBytesPerMS )
        {
            logger.debug("{} target throughput now {} bytes/ms.", this, newTargetBytesPerMS);
        }
        targetBytesPerMS = newTargetBytesPerMS;

        // time passed since last delay
        long msSinceLast = System.currentTimeMillis() - timeAtLastDelay;
        // the excess bytes in this period
        long excessBytes = bytesDelta - msSinceLast * targetBytesPerMS;

        // the time to delay to recap the deficit
        long timeToDelay = excessBytes / Math.max(1, targetBytesPerMS);
        if (timeToDelay > 0)
        {
            if ( logger.isTraceEnabled() )
            {
                logger.trace(String.format("%s actual throughput was %d bytes in %d ms: throttling for %d ms",
                    this, bytesDelta, msSinceLast, timeToDelay));
            }
            try
            {
                Thread.sleep(timeToDelay);
            }
            catch (InterruptedException e)
            {
                throw new AssertionError(e);
            }
        }
        bytesAtLastDelay += bytesDelta;
        timeAtLastDelay = System.currentTimeMillis();
    }

    @Override
    public String toString()
    {
        return "Throttle(for=" + name + ")";
    }
    
    public interface ThroughputFunction
    {
        /**
         * @return The instantaneous target throughput in bytes per millisecond. Targets less
         * than or equal to zero will disable throttling.
         */
        public int targetThroughput();
    }
}
