package cn.com.qmth.stmms.common.cache;

import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;

public class RedisCache implements Cache {

    protected static final Logger log = LoggerFactory.getLogger(RedisCache.class);

    private String name;

    private byte[] cacheKey;

    @SuppressWarnings("rawtypes")
    private RedisTemplate template;

    public RedisCache(String name, RedisTemplate<? extends Object, ? extends Object> template, final long expiration) {
        Assert.hasText(name, "non-empty cache name is required");
        this.name = name;
        try {
            this.cacheKey = name.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            log.error("cache key build error", e);
        }
        this.template = template;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Object getNativeCache() {
        return template;
    }

    @SuppressWarnings("unchecked")
    @Override
    public ValueWrapper get(final Object key) {
        return (ValueWrapper) template.execute(new RedisCallback<ValueWrapper>() {

            public ValueWrapper doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] bs = connection.hGet(cacheKey, computeKey(key));
                Object value = template.getValueSerializer() != null ? template.getValueSerializer().deserialize(bs)
                        : bs;
                return (bs == null ? null : new SimpleValueWrapper(value));
            }
        }, true);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void put(final Object key, final Object value) {
        final byte[] k = computeKey(key);

        template.execute(new RedisCallback<Object>() {

            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] v;
                if (template.getValueSerializer() == null && value instanceof byte[]) {
                    v = (byte[]) value;
                } else {
                    v = template.getValueSerializer().serialize(value);
                }
                connection.hSet(cacheKey, k, v);
                return null;
            }
        }, true);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void evict(Object key) {
        final byte[] k = computeKey(key);

        template.execute(new RedisCallback<Object>() {

            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.hDel(cacheKey, k);
                return null;
            }
        }, true);

    }

    @SuppressWarnings("unchecked")
    @Override
    public void clear() {
        template.execute(new RedisCallback<Object>() {

            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.del(cacheKey);
                return null;
            }
        }, true);
    }

    @SuppressWarnings("unchecked")
    public Set<Object> getKeys() {
        final Set<Object> keys = new HashSet<Object>();
        template.execute(new RedisCallback<Object>() {

            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                Set<byte[]> result = connection.hKeys(cacheKey);
                if (result != null && result.size() > 0) {
                    for (byte[] item : result) {
                        if (template.getKeySerializer() != null) {
                            keys.add(template.getKeySerializer().deserialize(item));
                        } else {
                            keys.add(item);
                        }
                    }
                }
                return null;
            }
        }, true);
        return keys;
    }

    @SuppressWarnings("unchecked")
    private byte[] computeKey(Object key) {
        if (template.getKeySerializer() == null && key instanceof byte[]) {
            return (byte[]) key;
        }
        return template.getKeySerializer().serialize(key);
    }

}
