/*
 * Copyright 2015 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.api.internal.cache;

public class HeapProportionalCacheSizer {
    public static final String CACHE_RESERVED_SYSTEM_PROPERTY = "org.gradle.cache.reserved.mb";
    private static final int DEFAULT_SIZES_MAX_HEAP_MB = 910; // when -Xmx1024m, Runtime.maxMemory() returns about 910
    private static final int ASSUMED_USED_HEAP = 150; // assume that Gradle itself uses about 150MB heap

    private static final double MIN_RATIO = 0.2d;

    private final int maxHeapMB;
    private final double sizingRatio;
    private final int reservedHeap;

    public HeapProportionalCacheSizer(int maxHeapMB) {
        this.maxHeapMB = maxHeapMB;
        this.reservedHeap = ASSUMED_USED_HEAP + Integer.getInteger(CACHE_RESERVED_SYSTEM_PROPERTY, 0);
        this.sizingRatio = calculateRatioToDefaultAvailableHeap();
    }

    public HeapProportionalCacheSizer() {
        this(calculateMaxHeapMB());
    }

    private static int calculateMaxHeapMB() {
        return (int) (Runtime.getRuntime().maxMemory() / (1024 * 1024));
    }

    private double calculateRatioToDefaultAvailableHeap() {
        double defaultAvailableHeapSpace = DEFAULT_SIZES_MAX_HEAP_MB - ASSUMED_USED_HEAP;
        double availableHeapSpace = maxHeapMB - reservedHeap;
        double ratioToDefaultAvailableHeap = availableHeapSpace / defaultAvailableHeapSpace;
        return Math.max(ratioToDefaultAvailableHeap, MIN_RATIO);
    }

    public int scaleCacheSize(int referenceValue) {
        return scaleCacheSize(referenceValue, 100);
    }

    private int scaleCacheSize(int referenceValue, int granularity) {
        if (referenceValue < granularity) {
            throw new IllegalArgumentException("reference value must be larger than granularity");
        }
        int scaledValue = (int) ((double) referenceValue * sizingRatio) / granularity * granularity;
        return Math.max(scaledValue, granularity);
    }
}
