import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * Created by Sequarius on 2016/9/7.
 */
@Slf4j
public abstract class BaseRetry<E, T> {
    @Resource
    private ExpiresMessageController expiresMessageController;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    public Class<E> clazz;
    // 重试尝试间隔为1s,10s,1min,10min,1h,3h,6h一次递增
    private final List<Long> RETRY_TABLE = new ArrayList<Long>() {
        {
            add(0L);
            add(1L);
            add(10L);
            add(60L);
            add(10 * 60L);
            add(60 * 60L);
            add(3 * 60 * 60L);
            add(6 * 60 * 60L);
        }
    };
    public BaseRetry(Class<E> clazz) {
        this.clazz = clazz;
    }
    public void retry(E entity, T tag) {
        RetryEntity retryEntity = new RetryEntity(entity, tag).invoke();
        String timeKey = retryEntity.getTimeKey();
        String entryKey = retryEntity.getEntryKey();
        StringBuffer entityTag = retryEntity.getEntityTag();
        String memoryValue = stringRedisTemplate.opsForValue().get(timeKey);
        int lastRetryTime = (memoryValue == null) ? 0 : Integer.valueOf(memoryValue);
        //first Retry save entity
        if (lastRetryTime == 0) {
            redisTemplate.setDefaultSerializer(new FastJsonSerializer<>(clazz));
            redisTemplate.opsForValue().set(entryKey, entity);
        }
        //max Retry delete entity
        if (lastRetryTime >= RETRY_TABLE.size() - 1) {
            log.warn("retry in max times entityTag=={}", entityTag.toString());
            log.warn("retry entity=={}",stringRedisTemplate.opsForValue().get(entryKey));
            redisTemplate.delete(entryKey);
            stringRedisTemplate.delete(timeKey);
            throw new RetryTimeOutOfRangeException();
        }
        log.debug("time key={};entrykey={};lastRetryTime={}", timeKey, entryKey, lastRetryTime);
        //add 1 times
        stringRedisTemplate.opsForValue().increment(timeKey, 1);
        stringRedisTemplate.opsForValue().set(entityTag.insert(0, Constant.PREFIX_PAYMENT_KEY).toString(),
                "notify_key", RETRY_TABLE.get(lastRetryTime + 1), TimeUnit.SECONDS);
    }
//
    protected void addListenner(Class<?> clazz, SubMessageListener listener) {
        expiresMessageController.addSubMessageListener(clazz, listener);
    }
    public abstract void setMessageListener();
    public void retrySuccess(E entity, T tag){
        RetryEntity retryEntity = new RetryEntity(entity, tag).invoke();
        redisTemplate.delete(retryEntity.getTimeKey());
        redisTemplate.delete(retryEntity.getEntryKey());
    }
    private class RetryEntity {
        private E entity;
        private T tag;
        private StringBuffer entityTag;
        private String timeKey;
        private String entryKey;
        public RetryEntity(E entity, T tag) {
            this.entity = entity;
            this.tag = tag;
        }
        public StringBuffer getEntityTag() {
            return entityTag;
        }
        public String getTimeKey() {
            return timeKey;
        }
        public String getEntryKey() {
            return entryKey;
        }
        public RetryEntity invoke() {
            entityTag = new StringBuffer(entity.getClass().getName())
                    .append(":").append(tag.toString());
            //retry times key
            timeKey = new StringBuffer(Constant.PREFIX_PAYMENT_TIME_KEY).append(entityTag).toString();
            //retry entity key
            entryKey = new StringBuffer(Constant.PREFIX_PAYMENT_ENTITY_KEY).append(entityTag).toString();
            return this;
        }
    }
}