package ctbrec.io;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BandwidthMeter {

    private static final Duration MEASURE_TIMEFRAME = Duration.ofSeconds(10);
    private static List<Record> records = new ArrayList<>(100);
    private static Lock lock = new ReentrantLock(true);
    private static List<Listener> listeners = new ArrayList<>();
    private static Instant lastUpdate = Instant.EPOCH;

    private BandwidthMeter() {
    }

    public static void add(long bytes) {
        Record r = new Record(bytes);
        lock.lock();
        try {
            records.add(r);
        } finally {
            lock.unlock();
        }

        Instant oneSecondAgo = Instant.now().minus(Duration.ofSeconds(1));
        if (lastUpdate.isBefore(oneSecondAgo)) {
            long throughput = getThroughput();
            for (Listener listener : listeners) {
                listener.bandwidthCalculated(throughput, MEASURE_TIMEFRAME);
            }
            lastUpdate = Instant.now();
        }
    }

    /**
     * Get the throughput over the last 10 seconds
     * @return throughput in bytes
     */
    public static long getThroughput() {
        return getThroughput(MEASURE_TIMEFRAME);
    }

    /**
     * Get the throughput over the given duration
     * @return throughput in bytes
     */
    public static long getThroughput(Duration d) {
        Instant now = Instant.now();
        Instant measureStart = now.minus(d);
        long throughput = 0;
        lock.lock();
        try {
            for (Iterator<Record> iterator = records.iterator(); iterator.hasNext();) {
                Record record = iterator.next();
                if (record.timestamp.isBefore(measureStart)) {
                    iterator.remove();
                } else {
                    throughput += record.bytes;
                }
            }

        } finally {
            lock.unlock();
        }
        return throughput;
    }

    public static void addListener(Listener l) {
        listeners.add(l);
    }

    public static void removeListener(Listener l) {
        listeners.remove(l);
    }

    private static class Record {
        public Instant timestamp;
        public long bytes;

        public Record(long bytes) {
            timestamp = Instant.now();
            this.bytes = bytes;
        }
    }

    @FunctionalInterface
    public static interface Listener {
        void bandwidthCalculated(long bytes, Duration timeframe);
    }
}
