/*
 * Copyright 2016 the original author or authors.
 *
 * 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 org.gradle.launcher.daemon.server.health.gc;

import org.gradle.internal.util.NumberUtil;

import java.lang.management.MemoryUsage;
import java.util.Set;

public class GarbageCollectionStats {
    final private double rate;
    final private long used;
    final private long max;
    final private long eventCount;

    public GarbageCollectionStats(Set<GarbageCollectionEvent> events) {
        this.rate = calculateRate(events);
        this.used = calculateAverageUsage(events);
        this.max = calculateMaxSize(events);
        this.eventCount = events.size();
    }

    static double calculateRate(Set<GarbageCollectionEvent> events) {
        long firstGC = 0;
        long lastGC = 0;
        long firstCount = 0;
        long lastCount = 0;
        for (GarbageCollectionEvent event : events) {
            // Skip if this was a polling event and the garbage collector did not fire in between events
            if (event.getCount() == lastCount || event.getCount() == 0) {
                continue;
            }

            lastCount = event.getCount();

            if (firstGC == 0) {
                firstGC = event.getTimestamp();
                firstCount = event.getCount();
            } else {
                lastGC = event.getTimestamp();
            }
        }

        if (events.size() < 2 || lastCount == 0) {
            return 0;
        } else {
            long elapsed = lastGC - firstGC;
            long totalCount = lastCount - firstCount;
            return ((double) totalCount) / elapsed * 1000;
        }
    }

    static long calculateAverageUsage(Set<GarbageCollectionEvent> events) {
        if (events.size() < 1) {
            return -1;
        }

        long total = 0;
        long firstCount = 0;
        long lastCount = 0;
        for (GarbageCollectionEvent event : events) {
            // Skip if the garbage collector did not fire in between events
            if (event.getCount() == lastCount || event.getCount() == 0) {
                continue;
            }

            MemoryUsage usage = event.getUsage();
            if (firstCount == 0) {
                firstCount = event.getCount();
                total += usage.getUsed();
            } else {
                total += usage.getUsed() * (event.getCount() - lastCount);
            }

            lastCount = event.getCount();
        }

        if (lastCount == 0 || lastCount == firstCount) {
            return -1;
        } else {
            long totalCount = lastCount - firstCount + 1;
            return total / totalCount;
        }
    }

    static long calculateMaxSize(Set<GarbageCollectionEvent> events) {
        if (events.size() < 1) {
            return -1;
        }

        // Maximum pool size is fixed, so we should only need to get it from the first event
        MemoryUsage usage = events.iterator().next().getUsage();
        return usage.getMax();
    }

    public double getRate() {
        return rate;
    }

    public int getUsage() {
        if (used > 0) {
            return NumberUtil.percentOf(used, max);
        } else {
            return -1;
        }
    }

    public double getUsed() {
        return used;
    }

    public long getMax() {
        return max;
    }

    public long getEventCount() {
        return eventCount;
    }
}
