package com.ssyf.plugs.cache.service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 缓存服务接口
 * @author huwei
 * @date 2023/9/21 14:00
 */
public interface CacheService {

    /**
     * 默认缓存前缀
     */
    String DEFAULT_CACHE_PREFIX = "default_cache";

    /**
     * 默认缓存时间3600秒
     * @return
     */
    default long defaultTimeout(){
        return TimeUnit.HOURS.toSeconds(1);
    }

    /**
     * 缓存数据
     * @param key 缓存的key
     * @param producer 数据生产对象
     * @param prefix 缓存key的前置前缀，用于区别相同key的问题
     * @param <T> 存入数据
     * @return T 存入数据
     */
    default <T> T put(String key, DataProducer<? extends T> producer, String... prefix){
        return this.put(key ,producer.product() ,prefix);
    }

    /**
     * 缓存数据
     * @param timeoutSecond 过期时间 ，单位秒
     * @param key 缓存的key
     * @param producer 数据生产对象
     * @param prefix 缓存key的前缀，用于区别相同key的问题
     * @param <T> 存入数据
     * @return T 存入数据
     */
    default <T> T put(long timeoutSecond, String key, DataProducer<? extends T> producer, String... prefix){
        return this.put(timeoutSecond ,key ,producer.product() ,prefix);
    }

    /**
     * 缓存数据，该方法将始终缓存中的值是当前插入的值
     * 未指定缓存时间，则缓存defaultTimeout()产生的时间
     * @param key 缓存的key
     * @param obj 插入的数据对象
     * @param prefix 缓存key的前缀，用于区别相同key的问题
     * @param <T> 存入数据
     * @return T 存入数据
     */
    default <T> T put(String key, T obj, String... prefix){
        return this.put(defaultTimeout() ,key ,obj ,prefix);
    }

    /**
     * 缓存数据，该方法将始终缓存中的值是当前插入的值
     * @param timeoutSecond 缓存时间，单位秒
     * @param key 缓存的key
     * @param obj 插入的数据对象
     * @param prefix 缓存key的前缀，用于区别相同key的问题
     * @param <T> 存入数据
     * @return T 存入数据
     */
    <T> T put(long timeoutSecond, String key, T obj, String... prefix);

    /**
     * 获取指定key ，指定域名的缓存数据
     * @param key 缓存的ky
     * @param prefix 前缀
     * @param <T> 返回对象，务必于存入时的类型一致，否则将抛出类型转换异常
     * @return
     */
    <T> T get(String key, String... prefix);

    /**
     * 获取指定的缓存数据，当且仅当缓存的数据不存在时，才利用producer对象进行数据的生产，并缓存和生产指定的数据；
     * 若需要缓存对象时，则缓存defaultTimeout()产生的时间
     * @param key 缓存的key
     * @param producer 数据生产对象
     * @param prefix 前缀
     * @param <T> 返回对象，务必于存入时的类型一致，否则将抛出类型转换异常
     * @return
     */
    default <T> T getOrPutIfAbsent(String key, DataProducer<? extends T> producer, String... prefix){
        return this.getOrPutIfAbsent(defaultTimeout() ,key ,producer ,prefix);
    }

    /**
     * 获取指定的缓存数据，当且仅当缓存的数据不存在时，才利用producer对象进行数据的生产，并缓存和生产指定的数据；
     * @param timeoutSecond 若需要缓存，则缓存该值设定的时间
     * @param key 缓存的key
     * @param producer 数据生产对象
     * @param prefix 前缀
     * @param <T> 返回对象，务必于存入时的类型一致，否则将抛出类型转换异常
     * @return
     */
    default <T> T getOrPutIfAbsent(long timeoutSecond, String key, DataProducer<? extends T> producer, String... prefix){
        T t = get(key, prefix);
        if(t != null){
            return t;
        }
        t = producer.product();
        if(t != null){
            put(timeoutSecond ,key ,t ,prefix);
        }
        return t;
    }

    /**
     * 删除指定域指定key的缓存数据
     * @param key
     * @param prefix
     */
    boolean remove(String key, String... prefix);

    /**
     * 清空指定缓存域的所有缓存数据
     * @param prefix
     */
    boolean clearPrefix(String... prefix);

    /**
     * 获取指定域中的所有key列表
     * @param prefix
     * @return key列表
     */
    Set<String> keys(String... prefix);

    /**
     * 指定域的指定key是否存在
     * @param key
     * @param prefix
     * @return
     */
    boolean isExists(String key, String... prefix);

    /**
     * 封装key值
     * @param key
     * @param prefix
     * @return
     */
    default String wrapKey(String key, String... prefix){
        return new StringBuilder(parseAndGetPrefix(prefix)).append(":").append(key).toString();
    }

    /**
     * 解析并获取前缀
     * @param prefix
     * @return
     */
    default String parseAndGetPrefix(String... prefix){
        if(prefix == null || prefix.length == 0){
            return DEFAULT_CACHE_PREFIX;
        }
        StringBuilder sb = new StringBuilder();
        for (String domain : prefix) {
            sb.append(domain).append(":");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
}
