package com.atlassian.bitbucket.scm.cache.internal;

import com.atlassian.bitbucket.scm.cache.ScmCacheConfig;
import com.atlassian.bitbucket.scm.cache.ScmRequestType;
import com.atlassian.util.contentcache.CacheEntry;
import com.atlassian.util.contentcache.ContentCache;
import com.atlassian.util.contentcache.TtlCacheExpiryStrategy;
import com.google.common.collect.Sets;

import javax.annotation.Nonnull;
import java.util.*;

public class ScmCacheExpiryStrategy extends TtlCacheExpiryStrategy {

    private static final Comparator<CacheEntry> ENTRY_COMPARATOR = (first, second) -> {
        // least recently accessed first
        int lastAccessedCompare = first.getStatistics().getLastAccessedDate().compareTo(second.getStatistics().getLastAccessedDate());

        return lastAccessedCompare == 0 ? first.getStatistics().getKey().compareTo(second.getStatistics().getKey()) :
                lastAccessedCompare;
    };

    private final ScmCacheConfig config;

    public ScmCacheExpiryStrategy(ScmCacheConfig config) {
        this.config = config;
    }

    @Nonnull
    @Override
    public Iterable<CacheEntry> getExpiredEntries(@Nonnull ContentCache cache) {
        Map<ScmRequestType, TreeSet<CacheEntry>> entriesByType = new HashMap<>();
        Set<CacheEntry> expired = Sets.newHashSet();

        for (Map.Entry<String, CacheEntry> entry : cache.getEntries().entrySet()) {

            if (isExpired(entry.getValue())) {
                expired.add(entry.getValue());
                continue;
            }

            ScmRequestType requestType = getType(entry.getKey());
            if (requestType != null) {
                TreeSet<CacheEntry> entries = entriesByType.get(requestType);
                if (entries == null) {
                    entries = new TreeSet<>(ENTRY_COMPARATOR);
                    entriesByType.put(requestType, entries);
                }
                entries.add(entry.getValue());
            }
        }

        for (Map.Entry<ScmRequestType, TreeSet<CacheEntry>> entry : entriesByType.entrySet()) {
            int toBeRemoved = entry.getValue().size() - config.getMaxCount(entry.getKey());
            Iterator<CacheEntry> it = entry.getValue().iterator();
            while (toBeRemoved-- > 0) {
                expired.add(it.next());
            }
        }

        return expired;
    }

    private ScmRequestType getType(String key) {
        if (CacheUtils.REFS_KEY.equals(key)) {
            return ScmRequestType.REFS;
        }
        if (key.contains("clone|")) { // clone and shallow clone
            return ScmRequestType.UPLOAD_PACK;
        }
        // disregard the pack negotiation caches - they're small anyway
        return null;
    }
}
