package com.github.bingoohuang.westcache.manager;

import com.github.bingoohuang.westcache.base.WestCache;
import com.github.bingoohuang.westcache.base.WestCacheItem;
import com.github.bingoohuang.westcache.base.WestCacheManager;
import com.github.bingoohuang.westcache.utils.Envs;
import com.github.bingoohuang.westcache.utils.MethodProvider;
import com.github.bingoohuang.westcache.utils.WestCacheOption;
import com.google.common.base.Optional;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author bingoohuang [bingoohuang@gmail.com] Created on 2016/12/22.
 */
@Slf4j
public abstract class BaseCacheManager implements WestCacheManager {
    private volatile long startupTime = System.currentTimeMillis();

    @Getter private WestCache westCache;
    private Method longMethod = init();

    @SneakyThrows
    private Method init() {
        return MethodProvider.class.getMethod("longMethod");
    }

    public BaseCacheManager(WestCache westCache) {
        this.westCache = westCache;
    }

    @Override @SuppressWarnings("unchecked")
    public WestCacheItem get(final WestCacheOption option,
                             final String cacheKey,
                             final Callable<WestCacheItem> callable) {
        val flusher = option.getFlusher();
        if (!flusher.isKeyEnabled(option, cacheKey)) {
            log.warn("cache key {} is not enabled", cacheKey);
            return Envs.execute(callable);
        }

        flusher.register(option, cacheKey, westCache);

        val shot = new AtomicBoolean(true);
        val flushCallable = new Callable<WestCacheItem>() {
            @Override public WestCacheItem call() {
                val raw = flusher.getDirectValue(option, cacheKey);
                if (raw.isPresent()) return new WestCacheItem(raw, option);

                shot.set(false);
                val interceptor = option.getInterceptor();
                return interceptor.intercept(option, cacheKey, callable);
            }
        };

        val wrapCallable = new Callable<WestCacheItem>() {
            @Override public WestCacheItem call() {
                return option.getSnapshot() == null
                        ? Envs.execute(flushCallable)
                        : trySnapshot(option, cacheKey, flushCallable);
            }
        };

        checkStartupTime(option, cacheKey);

        val item = westCache.get(option, cacheKey, wrapCallable);
        log.debug("cache key {} shot result {} ", cacheKey,
                shot.get() ? "bingo" : "misfired");

        return item;
    }

    private void checkStartupTime(WestCacheOption option, String cacheKey) {
        if (!"true".equals(option.getSpecs().get("restartInvalidate"))) return;


        if (startupTime == 0) return;
        startupTime = 0;

        val cloneOption = WestCacheOption.builder().clone(option).method(longMethod).build();
        val startupTimeKey = "startupTime:" + cacheKey;
        val timeItem = westCache.getIfPresent(cloneOption, startupTimeKey);

        if (timeItem.isPresent() && (Long) timeItem.orNull() >= startupTime) {
            return;
        }

        westCache.invalidate(option, cacheKey, "");
        westCache.put(option, startupTimeKey, new WestCacheItem(Optional.of(startupTime), option));
    }

    private WestCacheItem trySnapshot(final WestCacheOption option,
                                      final String cacheKey,
                                      final Callable<WestCacheItem> callable) {
        val executorService = Executors.newSingleThreadScheduledExecutor();
        val future = executorService.submit(
                new Callable<WestCacheItem>() {
                    @Override public WestCacheItem call() {
                        val item = Envs.execute(callable);
                        westCache.put(option, cacheKey, item);
                        val snapshot = option.getSnapshot();
                        snapshot.saveSnapshot(option, cacheKey, item);
                        return item;
                    }
                });

        return Envs.trySnapshot(option, future, cacheKey);
    }

    @Override
    public WestCacheItem get(WestCacheOption option, String cacheKey) {
        return westCache.getIfPresent(option, cacheKey);
    }

    @Override
    public void put(WestCacheOption option, String cacheKey, WestCacheItem cacheValue) {
        westCache.put(option, cacheKey, cacheValue);
    }

    @Override
    public void invalidate(WestCacheOption option, String cacheKey, String version) {
        westCache.invalidate(option, cacheKey, version);
    }
}

@@ -2548,16 +2548,24 @@
 rtupTime
+Validate
 (option,
@@ -2817,16 +2817,24 @@
 rtupTime
+Validate
 (WestCac
@@ -2949,18 +2949,16 @@
 return;%0A
-%0A%0A
         
@@ -2990,33 +2990,8 @@
 urn;
-%0A        startupTime = 0;
 %0A%0A  
@@ -3223,20 +3223,31 @@
         
-if (
+val validate = 
 timeItem
@@ -3301,19 +3301,17 @@
 rtupTime
-) %7B
+;
 %0A       
@@ -3315,31 +3315,29 @@
     
-    return;
+if (!validate) %7B
 %0A    
-    %7D%0A%0A
     
@@ -3380,24 +3380,28 @@
 heKey, %22%22);%0A
+    
         west
@@ -3488,16 +3488,52 @@
 tion));%0A
+        %7D%0A%0A        startupTime = 0;%0A
     %7D%0A%0A 

fix startupTime validation bug