from itertools import chain

from aioredis.util import wait_convert, wait_ok, _NOTSET


class StringCommandsMixin:
    """String commands mixin.

    For commands details see: http://redis.io/commands/#string
    """

    SET_IF_NOT_EXIST = 'SET_IF_NOT_EXIST'   # NX
    SET_IF_EXIST = 'SET_IF_EXIST'           # XX

    def append(self, key, value):
        """Append a value to key."""
        return self.execute(b'APPEND', key, value)

    def bitcount(self, key, start=None, end=None):
        """Count set bits in a string.

        :raises TypeError: if only start or end specified.
        """
        if start is None and end is not None:
            raise TypeError("both start and stop must be specified")
        elif start is not None and end is None:
            raise TypeError("both start and stop must be specified")
        elif start is not None and end is not None:
            args = (start, end)
        else:
            args = ()
        return self.execute(b'BITCOUNT', key, *args)

    def bitfield(self):
        raise NotImplementedError()

    def bitop_and(self, dest, key, *keys):
        """Perform bitwise AND operations between strings."""
        return self.execute(b'BITOP', b'AND', dest, key, *keys)

    def bitop_or(self, dest, key, *keys):
        """Perform bitwise OR operations between strings."""
        return self.execute(b'BITOP', b'OR', dest, key, *keys)

    def bitop_xor(self, dest, key, *keys):
        """Perform bitwise XOR operations between strings."""
        return self.execute(b'BITOP', b'XOR', dest, key, *keys)

    def bitop_not(self, dest, key):
        """Perform bitwise NOT operations between strings."""
        return self.execute(b'BITOP', b'NOT', dest, key)

    def bitpos(self, key, bit, start=None, end=None):
        """Find first bit set or clear in a string.

        :raises ValueError: if bit is not 0 or 1
        """
        if bit not in (1, 0):
            raise ValueError("bit argument must be either 1 or 0")
        bytes_range = []
        if start is not None:
            bytes_range.append(start)
        if end is not None:
            if start is None:
                bytes_range = [0, end]
            else:
                bytes_range.append(end)
        return self.execute(b'BITPOS', key, bit, *bytes_range)

    def decr(self, key):
        """Decrement the integer value of a key by one."""
        return self.execute(b'DECR', key)

    def decrby(self, key, decrement):
        """Decrement the integer value of a key by the given number.

        :raises TypeError: if decrement is not int
        """
        if not isinstance(decrement, int):
            raise TypeError("decrement must be of type int")
        return self.execute(b'DECRBY', key, decrement)

    def get(self, key, *, encoding=_NOTSET):
        """Get the value of a key."""
        return self.execute(b'GET', key, encoding=encoding)

    def getbit(self, key, offset):
        """Returns the bit value at offset in the string value stored at key.

        :raises TypeError: if offset is not int
        :raises ValueError: if offset is less than 0
        """
        if not isinstance(offset, int):
            raise TypeError("offset argument must be int")
        if offset < 0:
            raise ValueError("offset must be greater equal 0")
        return self.execute(b'GETBIT', key, offset)

    def getrange(self, key, start, end, *, encoding=_NOTSET):
        """Get a substring of the string stored at a key.

        :raises TypeError: if start or end is not int
        """
        if not isinstance(start, int):
            raise TypeError("start argument must be int")
        if not isinstance(end, int):
            raise TypeError("end argument must be int")
        return self.execute(b'GETRANGE', key, start, end, encoding=encoding)

    def getset(self, key, value, *, encoding=_NOTSET):
        """Set the string value of a key and return its old value."""
        return self.execute(b'GETSET', key, value, encoding=encoding)

    def incr(self, key):
        """Increment the integer value of a key by one."""
        return self.execute(b'INCR', key)

    def incrby(self, key, increment):
        """Increment the integer value of a key by the given amount.

        :raises TypeError: if increment is not int
        """
        if not isinstance(increment, int):
            raise TypeError("increment must be of type int")
        return self.execute(b'INCRBY', key, increment)

    def incrbyfloat(self, key, increment):
        """Increment the float value of a key by the given amount.

        :raises TypeError: if increment is not int
        """
        if not isinstance(increment, float):
            raise TypeError("increment must be of type int")
        fut = self.execute(b'INCRBYFLOAT', key, increment)
        return wait_convert(fut, float)

    def mget(self, key, *keys, encoding=_NOTSET):
        """Get the values of all the given keys."""
        return self.execute(b'MGET', key, *keys, encoding=encoding)

    def mset(self, *args):
        """Set multiple keys to multiple values or unpack dict to keys & values.

        :raises TypeError: if len of args is not event number
        :raises TypeError: if len of args equals 1 and it is not a dict
        """
        data = args
        if len(args) == 1:
            if not isinstance(args[0], dict):
                raise TypeError("if one arg it should be a dict")
            data = chain.from_iterable(args[0].items())
        elif len(args) % 2 != 0:
            raise TypeError("length of pairs must be even number")
        fut = self.execute(b'MSET', *data)
        return wait_ok(fut)

    def msetnx(self, key, value, *pairs):
        """Set multiple keys to multiple values,
        only if none of the keys exist.

        :raises TypeError: if len of pairs is not event number
        """
        if len(pairs) % 2 != 0:
            raise TypeError("length of pairs must be even number")
        return self.execute(b'MSETNX', key, value, *pairs)

    def psetex(self, key, milliseconds, value):
        """Set the value and expiration in milliseconds of a key.

        :raises TypeError: if milliseconds is not int
        """
        if not isinstance(milliseconds, int):
            raise TypeError("milliseconds argument must be int")
        fut = self.execute(b'PSETEX', key, milliseconds, value)
        return wait_ok(fut)

    def set(self, key, value, *, expire=0, pexpire=0, exist=None):
        """Set the string value of a key.

        :raises TypeError: if expire or pexpire is not int
        """
        if expire and not isinstance(expire, int):
            raise TypeError("expire argument must be int")
        if pexpire and not isinstance(pexpire, int):
            raise TypeError("pexpire argument must be int")

        args = []
        if expire:
            args[:] = [b'EX', expire]
        if pexpire:
            args[:] = [b'PX', pexpire]

        if exist is self.SET_IF_EXIST:
            args.append(b'XX')
        elif exist is self.SET_IF_NOT_EXIST:
            args.append(b'NX')
        fut = self.execute(b'SET', key, value, *args)
        return wait_ok(fut)

    def setbit(self, key, offset, value):
        """Sets or clears the bit at offset in the string value stored at key.

        :raises TypeError: if offset is not int
        :raises ValueError: if offset is less than 0 or value is not 0 or 1
        """
        if not isinstance(offset, int):
            raise TypeError("offset argument must be int")
        if offset < 0:
            raise ValueError("offset must be greater equal 0")
        if value not in (0, 1):
            raise ValueError("value argument must be either 1 or 0")
        return self.execute(b'SETBIT', key, offset, value)

    def setex(self, key, seconds, value):
        """Set the value and expiration of a key.

        If seconds is float it will be multiplied by 1000
        coerced to int and passed to `psetex` method.

        :raises TypeError: if seconds is neither int nor float
        """
        if isinstance(seconds, float):
            return self.psetex(key, int(seconds * 1000), value)
        if not isinstance(seconds, int):
            raise TypeError("milliseconds argument must be int")
        fut = self.execute(b'SETEX', key, seconds, value)
        return wait_ok(fut)

    def setnx(self, key, value):
        """Set the value of a key, only if the key does not exist."""
        fut = self.execute(b'SETNX', key, value)
        return wait_convert(fut, bool)

    def setrange(self, key, offset, value):
        """Overwrite part of a string at key starting at the specified offset.

        :raises TypeError: if offset is not int
        :raises ValueError: if offset less than 0
        """
        if not isinstance(offset, int):
            raise TypeError("offset argument must be int")
        if offset < 0:
            raise ValueError("offset must be greater equal 0")
        return self.execute(b'SETRANGE', key, offset, value)

    def strlen(self, key):
        """Get the length of the value stored in a key."""
        return self.execute(b'STRLEN', key)
