/*
 * @Author: ZXGoto
 * @Date: 2019-10-30
 */

package cache

import (
	"exhibitionCentre/logging"
	"time"

	"github.com/gomodule/redigo/redis"
)

// Redis - redis data
type Redis struct {
	pool *redis.Pool
}

// RedisConfig - the config of redis
type RedisConfig struct {
	MaxIdle            int
	MaxActive          int
	IdleTimeout        time.Duration
	Address            string
	Password           string
	DialReadTimeout    int
	DialWriteTimeout   int
	DialConnectTimeout int
	DB                 int
}

// NewRedis - creates a redis instance
// config - the redis config
func NewRedis(config *RedisConfig) *Redis {
	r := new(Redis)
	r.pool = &redis.Pool{
		MaxIdle:     config.MaxIdle,
		MaxActive:   config.MaxActive,
		IdleTimeout: time.Duration(config.IdleTimeout) * time.Millisecond,
		Wait:        true,
		Dial: func() (redis.Conn, error) {
			return redis.Dial(
				"tcp",
				config.Address,
				redis.DialPassword(config.Password),
				redis.DialReadTimeout(time.Duration(config.DialReadTimeout)*time.Millisecond),
				redis.DialWriteTimeout(time.Duration(config.DialWriteTimeout)*time.Millisecond),
				redis.DialConnectTimeout(time.Duration(config.DialConnectTimeout)*time.Millisecond),
				redis.DialDatabase(config.DB),
			)
		},
	}
	return r
}

// Close - closes the redis
func (r *Redis) Close() {
	if err := r.pool.Close(); err != nil {
		logging.Log.Errorf("关闭redis pool失败: %s\n", err.Error())
	}
}

// Stats - gets the stats of redis
func (r *Redis) Stats() redis.PoolStats {
	return r.pool.Stats()
}

// MultiCreate - creates a record by using MULTI
// f - a function for transaction processing
func (r *Redis) MultiCreate(f func(redis.Conn)) bool {
	conn := r.pool.Get()
	defer conn.Close()
	if f == nil {
		return false
	}
	f(conn)
	if _, err := conn.Do("EXEC"); err != nil {
		logging.Log.Errorf("创建数据失败: %s\n", err.Error())
		return false
	}
	return true
}

// Delete - deletes a record by key
// key - the record key
func (r *Redis) Delete(key string) bool {
	conn := r.pool.Get()
	defer conn.Close()

	if _, err := conn.Do("DEL", key); err != nil {
		logging.Log.Errorf("删除数据失败: %s\n", err.Error())
		return false
	}
	return true
}

// GetKeys - gets all keys in the rootkey
// rootKey - the root node key
func (r *Redis) GetKeys(rootKey string) []string {
	conn := r.pool.Get()
	defer conn.Close()

	result, err := conn.Do("KEYS", rootKey)
	if err != nil {
		logging.Log.Errorf("获取keys失败: %s\n", err.Error())
		return nil
	}
	ks, ok := result.([]interface{})
	if !ok {
		logging.Log.Error(err.Error())
		return nil
	}
	var keys []string
	for _, k := range ks {
		if bs, ok := k.([]uint8); ok {
			keys = append(keys, string(bs))
		}
	}
	return keys
}

// MultiGet - gets a record by using MULTI
// f - a function for transaction processing
func (r *Redis) MultiGet(f func(redis.Conn)) (interface{}, error) {
	conn := r.pool.Get()
	defer conn.Close()
	if f == nil {
		return nil, nil
	}
	f(conn)
	return conn.Do("EXEC")
}

// ApplyDataInt - get-set a record by using WATCH
// key - the record key
// f - a function for transaction processing
func (r *Redis) ApplyDataInt(key string, f func(redis.Conn, string) int) int {
	conn := r.pool.Get()
	defer conn.Close()

	for {
		if _, err := conn.Do("WATCH", key); err != nil {
			logging.Log.Errorf("redis watch key(%s)失败: %s\n", key, err.Error())
			return 0
		}
		value, err := redis.String(conn.Do("GET", key))
		if err != nil {
			logging.Log.Errorf("redis获取key(%s)失败: %s\n", key, err.Error())
			return 0
		}
		destQuantity := f(conn, value)
		if destQuantity <= 0 {
			return 0
		}
		result, err := conn.Do("EXEC")
		if err != nil {
			logging.Log.Errorf("redis设置key(%s)失败: %s\n", key, err.Error())
			return 0
		}
		if result != nil {
			return destQuantity
		}
	}
}
