// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package redis

import (
	"context"

	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/database/gredis"
	"github.com/gogf/gf/v2/util/gconv"
)

// GroupString is the function group manager for string operations.
type GroupString struct {
	Operation gredis.AdapterOperation
}

// GroupString is the redis group object for string operations.
func (r *Redis) GroupString() gredis.IGroupString {
	return GroupString{
		Operation: r.AdapterOperation,
	}
}

// Set key to hold the string value. If key already holds a value, it is overwritten,
// regardless of its type.
// Any previous time to live associated with the key is discarded on successful SET operation.
//
// https://redis.io/commands/set/
func (r GroupString) Set(ctx context.Context, key string, value interface{}, option ...gredis.SetOption) (*gvar.Var, error) {
	var usedOption interface{}
	if len(option) > 0 {
		usedOption = option[0]
	}
	return r.Operation.Do(ctx, "Set", mustMergeOptionToArgs(
		[]interface{}{key, value}, usedOption,
	)...)
}

// SetNX sets key to hold string value if key does not exist.
// In that case, it is equal to SET.
// When key already holds a value, no operation is performed.
// SetNX is short for "SET if Not exists".
//
// It returns:
// true:  if the all the keys were set.
// false: if no key was set (at least one key already existed).
//
// https://redis.io/commands/setnx/
func (r GroupString) SetNX(ctx context.Context, key string, value interface{}) (bool, error) {
	v, err := r.Operation.Do(ctx, "SetNX", key, value)
	return v.Bool(), err
}

// SetEX sets key to hold the string value and set key to timeout after a given number of seconds.
// This command is equivalent to executing the following commands:
//
//	SET myKey value
//	EXPIRE myKey seconds
//
// SetEX is atomic, and can be reproduced by using the previous two commands inside an MULTI / EXEC block.
// It is provided as a faster alternative to the given sequence of operations, because this operation is very
// common when Redis is used as a cache.
//
// An error is returned when seconds invalid.
//
// https://redis.io/commands/setex/
func (r GroupString) SetEX(ctx context.Context, key string, value interface{}, ttlInSeconds int64) error {
	_, err := r.Operation.Do(ctx, "SetEX", key, ttlInSeconds, value)
	return err
}

// Get the value of key. If the key does not exist the special value nil is returned.
// An error is returned if the value stored at key is not a string, because GET only handles string values.
//
// https://redis.io/commands/get/
func (r GroupString) Get(ctx context.Context, key string) (*gvar.Var, error) {
	return r.Operation.Do(ctx, "Get", key)
}

// GetDel gets the value of key and delete the key.
// This command is similar to GET, except for the fact that it also deletes the key on success
// (if and only if the key's value type is a string).
//
// https://redis.io/commands/getdel/
func (r GroupString) GetDel(ctx context.Context, key string) (*gvar.Var, error) {
	return r.Operation.Do(ctx, "GetDel", key)
}

// GetEX is similar to GET, but is a write command with additional options.
//
// https://redis.io/commands/getex/
func (r GroupString) GetEX(ctx context.Context, key string, option ...gredis.GetEXOption) (*gvar.Var, error) {
	var usedOption interface{}
	if len(option) > 0 {
		usedOption = option[0]
	}
	return r.Operation.Do(ctx, "GetEX", mustMergeOptionToArgs(
		[]interface{}{key}, usedOption,
	)...)
}

// GetSet atomically sets key to value and returns the old value stored at key.
// Returns an error when key exists but does not hold a string value. Any previous time to live associated with
// the key is discarded on successful SET operation.
//
// https://redis.io/commands/getset/
func (r GroupString) GetSet(ctx context.Context, key string, value interface{}) (*gvar.Var, error) {
	return r.Operation.Do(ctx, "GetSet", key, value)
}

// StrLen returns the length of the string value stored at key.
// An error is returned when key holds a non-string value.
//
// It returns the length of the string at key, or 0 when key does not exist.
//
// https://redis.io/commands/strlen/
func (r GroupString) StrLen(ctx context.Context, key string) (int64, error) {
	v, err := r.Operation.Do(ctx, "StrLen", key)
	return v.Int64(), err
}

// Append appends the value at the end of the string, if key already exists and is a string.
// If key does not exist it is created and set as an empty string,
// so APPEND will be similar to SET in this special case.
//
// https://redis.io/commands/append/
func (r GroupString) Append(ctx context.Context, key string, value string) (int64, error) {
	v, err := r.Operation.Do(ctx, "Append", key, value)
	return v.Int64(), err
}

// SetRange overwrites part of the string stored at key, starting at the specified offset, for the entire length
// of value. If the offset is larger than the current length of the string at key, the string is padded with
// zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will
// make sure it holds a string large enough to be able to set value at offset.
//
// It returns the length of the string after it was modified by the command.
//
// https://redis.io/commands/setrange/
func (r GroupString) SetRange(ctx context.Context, key string, offset int64, value string) (int64, error) {
	v, err := r.Operation.Do(ctx, "SetRange", key, offset, value)
	return v.Int64(), err
}

// GetRange returns the substring of the string value stored at key,
// determined by the offsets start and end (both are inclusive). Negative offsets can be used in order to provide
// an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth.
//
// The function handles out of range requests by limiting the resulting range to the actual length of the string.
//
// https://redis.io/commands/getrange/
func (r GroupString) GetRange(ctx context.Context, key string, start, end int64) (string, error) {
	v, err := r.Operation.Do(ctx, "GetRange", key, start, end)
	return v.String(), err
}

// Incr increments the number stored at key by one.
// If the key does not exist, it is set to 0 before performing the operation.
// An error is returned if the key contains a value of the wrong type or contains a string that can not be
// represented as integer. This operation is limited to 64 bits signed integers.
//
// https://redis.io/commands/incr/
func (r GroupString) Incr(ctx context.Context, key string) (int64, error) {
	v, err := r.Operation.Do(ctx, "Incr", key)
	return v.Int64(), err
}

// IncrBy increments the number stored at key by increment. If the key does not exist, it is set to 0 before
// performing the operation.
//
// An error is returned if the key contains a value of the wrong type or contains a
// string that can not be represented as integer. This operation is limited to 64 bits signed integers.
//
// https://redis.io/commands/incrby/
func (r GroupString) IncrBy(ctx context.Context, key string, increment int64) (int64, error) {
	v, err := r.Operation.Do(ctx, "IncrBy", key, increment)
	return v.Int64(), err
}

// IncrByFloat increments the string representing a floating point number stored at key by the specified increment.
//
// https://redis.io/commands/incrbyfloat/
func (r GroupString) IncrByFloat(ctx context.Context, key string, increment float64) (float64, error) {
	v, err := r.Operation.Do(ctx, "IncrByFloat", key, increment)
	return v.Float64(), err
}

// Decr decrements the number stored at key by one.
//
// https://redis.io/commands/decr/
func (r GroupString) Decr(ctx context.Context, key string) (int64, error) {
	v, err := r.Operation.Do(ctx, "Decr", key)
	return v.Int64(), err
}

// DecrBy decrements the number stored at key by decrement.
//
// https://redis.io/commands/decrby/
func (r GroupString) DecrBy(ctx context.Context, key string, decrement int64) (int64, error) {
	v, err := r.Operation.Do(ctx, "DecrBy", key, decrement)
	return v.Int64(), err
}

// MSet sets the given keys to their respective values.
// MSet replaces existing values with new values, just as regular SET.
// See MSetNX if you don't want to overwrite existing values.
//
// MSet is atomic, so all given keys are set at once. It is not possible for clients to see that some keys
// were updated while others are unchanged.
//
// https://redis.io/commands/mset/
func (r GroupString) MSet(ctx context.Context, keyValueMap map[string]interface{}) error {
	var args []interface{}
	for k, v := range keyValueMap {
		args = append(args, k, v)
	}
	_, err := r.Operation.Do(ctx, "MSet", args...)
	return err
}

// MSetNX sets the given keys to their respective values.
//
// It returns:
// true:  if the all the keys were set.
// false: if no key was set (at least one key already existed).
func (r GroupString) MSetNX(ctx context.Context, keyValueMap map[string]interface{}) (bool, error) {
	var args []interface{}
	for k, v := range keyValueMap {
		args = append(args, k, v)
	}
	v, err := r.Operation.Do(ctx, "MSetNX", args...)
	return v.Bool(), err
}

// MGet returns the values of all specified keys.
//
// https://redis.io/commands/mget/
func (r GroupString) MGet(ctx context.Context, keys ...string) (map[string]*gvar.Var, error) {
	var result = make(map[string]*gvar.Var)
	v, err := r.Operation.Do(ctx, "MGet", gconv.Interfaces(keys)...)
	if err == nil {
		values := v.Vars()
		for i, key := range keys {
			result[key] = values[i]
		}
	}
	return result, err
}
