// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.autoscale;

import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.applications.Cluster;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * A list of metric snapshots from a cluster, sorted by increasing time (newest last).
 *
 * @author bratseth
 */
public class ClusterTimeseries {

    private final ClusterSpec.Id cluster;
    private final List<ClusterMetricSnapshot> snapshots;

    ClusterTimeseries(ClusterSpec.Id cluster, List<ClusterMetricSnapshot> snapshots) {
        this.cluster = cluster;
        List<ClusterMetricSnapshot> sortedSnapshots = new ArrayList<>(snapshots);
        Collections.sort(sortedSnapshots);
        this.snapshots = Collections.unmodifiableList(sortedSnapshots);
    }

    public boolean isEmpty() { return snapshots.isEmpty(); }

    public int size() { return snapshots.size(); }

    public ClusterMetricSnapshot get(int index) { return snapshots.get(index); }

    public List<ClusterMetricSnapshot> asList() { return snapshots; }

    public ClusterSpec.Id cluster() { return cluster; }

    public ClusterTimeseries add(ClusterMetricSnapshot snapshot) {
        List<ClusterMetricSnapshot> list = new ArrayList<>(snapshots);
        list.add(snapshot);
        return new ClusterTimeseries(cluster, list);
    }

    /** The max query growth rate we can predict from this time-series as a fraction of the current traffic per minute */
    public double maxQueryGrowthRate() {
        if (snapshots.isEmpty()) return 0.1;

        // Find the period having the highest growth rate, where total growth exceeds 30% increase
        double maxGrowthRate = 0; // In query rate per minute
        for (int start = 0; start < snapshots.size(); start++) {
            if (start > 0) { // Optimization: Skip this point when starting from the previous is better relative to the best rate so far
                Duration duration = durationBetween(start - 1, start);
                if ( duration.toMinutes() != 0) {
                    double growthRate = (queryRateAt(start - 1) - queryRateAt(start)) / duration.toMinutes();
                    if (growthRate >= maxGrowthRate)
                        continue;
                }
            }
            for (int end = start + 1; end < snapshots.size(); end++) {
                if (queryRateAt(end) >= queryRateAt(start) * 1.3) {
                    Duration duration = durationBetween(start, end);
                    if (duration.toMinutes() == 0) continue;
                    double growthRate = (queryRateAt(end) - queryRateAt(start)) / duration.toMinutes();
                    if (growthRate > maxGrowthRate)
                        maxGrowthRate = growthRate;
                }
            }
        }
        if (maxGrowthRate == 0) { // No periods of significant growth
            if (durationBetween(0, snapshots.size() - 1).toHours() < 24)
                return 0.1; //       ... because not much data
            else
                return 0.0; //       ... because load is stable
        }
        if (queryRateNow() == 0) return 0.1; // Growth not expressible as a fraction of the current rate
        return maxGrowthRate / queryRateNow();
    }

    /** The current query rate as a fraction of the peak rate in this timeseries */
    public double currentQueryFractionOfMax() {
        if (snapshots.isEmpty()) return 0.5;
        var max = snapshots.stream().mapToDouble(ClusterMetricSnapshot::queryRate).max().getAsDouble();
        return snapshots.get(snapshots.size() - 1).queryRate() / max;
    }

    private double queryRateAt(int index) {
        return snapshots.get(index).queryRate();
    }

    private double queryRateNow() {
        return queryRateAt(snapshots.size() - 1);
    }

    private Duration durationBetween(int startIndex, int endIndex) {
        return Duration.between(snapshots.get(startIndex).at(), snapshots.get(endIndex).at());
    }

}

@@ -2315,16 +2315,18 @@
     if (
+ !
  duratio
@@ -2331,24 +2331,16 @@
 ion.
-toMinutes() != 0
+isZero()
 ) %7B%0A
@@ -2813,24 +2813,16 @@
 ion.
-toMinutes() == 0
+isZero()
 ) co

Revert "Check for zero minutes"