package com.sonic.common.client;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 间隔一定时间刷新的缓存。
 * <pre>
 * 1、初次调用时，直接获取需要缓存的内容，并缓存到cache中
 * 2、每间隔intervalMillis尝试刷新缓存，
 *      如果刷新缓存成功，更新cache缓存的值
 *      如果刷新缓存失败，则不更新
 * </pre>
 *
 * @param <T>
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class TimerRefreshCache<T> {
    private ScheduledThreadPoolExecutor executor;
    private String name;
    private Long initialDelayMillis;
    private Long intervalMillis;
    private Supplier<T> refresher;
    private Cache<String, T> cache;
    /** 缓存是否以初始化，如果为false，会进行主动加载进行初始化 */
    private AtomicBoolean initialized = new AtomicBoolean(false);

    public TimerRefreshCache(String name, Long initialDelayMillis, Long intervalMillis,
                             ScheduledThreadPoolExecutor executor, Supplier<T> refresher) {
        Preconditions.checkArgument(executor != null);
        Preconditions.checkArgument(refresher != null);
        Preconditions.checkArgument(intervalMillis != null);

        this.name = name;
        this.initialDelayMillis = initialDelayMillis;
        this.intervalMillis = intervalMillis;
        this.refresher = refresher;
        // 该方法获取的，往往是完整的缓存内容。所以maxSize暂定为2即可
        long maximumSize = 2L;
        cache = CacheBuilder.newBuilder()
                .maximumSize(maximumSize)
                .build();
        this.executor = executor;
        startTimer();
    }

    private void startTimer() {
        executor.scheduleAtFixedRate(this::doRefresh, initialDelayMillis, intervalMillis, TimeUnit.MILLISECONDS);
    }

    private void doRefresh() {
        try {
            T value = refresher.get();
            // 当返回为null，表示Value没有变化，这时不用刷新Cache
            if (value == null) {
                log.debug("{} already has latest value.", name);
                return;
            }

            cache.put(name, value);
            log.info("{} refreshed, new value={}", name, value);
        } catch (Throwable e) {
            log.error("{} refresh failed", name, e);
        }
    }

    public T get() {
        // 如果没有加载过，手动加载一次
        if (!initialized.getAndSet(true)) {
            // 如果get的值不为空，放到cache中
            doRefresh();
        }
        return cache.getIfPresent(name);
    }

    // XXX: for unittest
    public void put(T configs) {
        log.info("{},set->config={}", TimerRefreshCache.this, configs);
        cache.put(name, configs);
    }
}
