/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.navercorp.redis.cluster.triples;

import static com.navercorp.redis.cluster.connection.RedisProtocol.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.navercorp.redis.cluster.connection.RedisConnection;
import com.navercorp.redis.cluster.connection.RedisProtocol;
import com.navercorp.redis.cluster.connection.RedisProtocol.Command;

import redis.clients.jedis.GeoCoordinate;
import redis.clients.util.SafeEncoder;

/**
 * The Class BinaryTriplesRedisClusterClient.
 *
 * @author jaehong.kim
 */
public class BinaryTriplesRedisClusterClient extends RedisConnection {

    /**
     * Instantiates a new binary triples redis cluster client.
     *
     * @param host the host
     */
    public BinaryTriplesRedisClusterClient(final String host) {
        super(host);
    }

    /**
     * Instantiates a new binary triples redis cluster client.
     *
     * @param host the host
     * @param port the port
     */
    public BinaryTriplesRedisClusterClient(String host, final int port) {
        super(host, port);
    }

    // Hashes of Lists ////////////////////

    public BinaryTriplesRedisClusterClient(String host, int port, boolean async) {
        super(host, port, async);
    }

    /**
     * Slget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slget(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LGET, keyspace, uid, serviceCode, key);
    }

    /**
     * Slmget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slmget(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[]... key) {
        byte[][] params = toByteArrays(keyspace, uid, serviceCode, key);
        sendCommand(Command.S3LMGET, params);
    }

    /**
     * Slkeys.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void slkeys(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3KEYS, keyspace, uid);
    }

    /**
     * Slkeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slkeys(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3LKEYS, keyspace, uid, serviceCode);
    }

    /**
     * Slkeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slkeys(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LKEYS, keyspace, uid, serviceCode, key);
    }

    /**
     * Sladd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param value        the value
     */
    public void sladd(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[] value) {
        sendCommand(Command.S3LADD, keyspace, uid, serviceCode, key, value, RedisProtocol.toByteArray(expireMillis));
    }

    /**
     * Sladd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void sladd(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[]... values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, expireMillis, values);
        sendCommand(Command.S3LADD, params);
    }

    public void sladdAt(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                        final long unixTime, final byte[]... values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, unixTime, values);
        sendCommand(Command.S3LADDAT, params);
    }

    /**
     * Sldel.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sldel(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3REM, keyspace, uid);
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3LREM, keyspace, uid, serviceCode);
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LREM, keyspace, uid, serviceCode, key);
    }

    /**
     * Slrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param values      the values
     */
    public void slrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final byte[]... values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, values);
        sendCommand(Command.S3LREM, params);
    }

    /**
     * Slmrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param keys        the keys
     */
    public void slmrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[]... keys) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, keys);
        sendCommand(Command.S3LMREM, params);
    }

    /**
     * Slcount.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void slcount(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3COUNT, keyspace, uid);
    }

    /**
     * Slcount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slcount(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3LCOUNT, keyspace, uid, serviceCode);
    }

    /**
     * Slcount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slcount(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LCOUNT, keyspace, uid, serviceCode, key);
    }

    /**
     * Slexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slexists(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LEXISTS, keyspace, uid, serviceCode, key);
    }

    /**
     * Slexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void slexists(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         final byte[] value) {
        sendCommand(Command.S3LEXISTS, keyspace, uid, serviceCode, key, value);
    }

    /**
     * Slmexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire millis
     * @param serviceCode  the service code
     * @param keys         the keys
     */
    public void slmexpire(final byte[] keyspace, final byte[] uid, long expireMillis, final byte[] serviceCode,
                          final byte[]... keys) {
        byte[][] params = toByteArrays(keyspace, uid, serviceCode, expireMillis, keys);
        sendCommand(Command.S3LMEXPIRE, params);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire millis
     */
    public void slexpire(final byte[] keyspace, final byte[] uid, long expireMillis) {
        sendCommand(Command.S3EXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis));
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param expireMillis the expire millis
     */
    public void slexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, long expireMillis) {
        sendCommand(Command.S3LEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     */
    public void slexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         long expireMillis) {
        sendCommand(Command.S3LEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode, key);
    }

    /**
     * Slexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param value        the value
     * @param expireMillis the expire millis
     */
    public void slexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         final byte[] value, final long expireMillis) {
        sendCommand(Command.S3LEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode, key, value);
    }

    /**
     * Slttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void slttl(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3LTTL, keyspace, uid, serviceCode, key);
    }

    /**
     * Slttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void slttl(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final byte[] value) {
        sendCommand(Command.S3LTTL, keyspace, uid, serviceCode, key, value);
    }

    /**
     * Slset.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void slset(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[]... values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, expireMillis, values);
        sendCommand(Command.S3LSET, params);
    }

    /**
     * Slreplace.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param oldValue     the old value
     * @param newValue     the new value
     */
    public void slreplace(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                          final long expireMillis, final byte[] oldValue, final byte[] newValue) {
        sendCommand(Command.S3LREPLACE, keyspace, uid, serviceCode, key, oldValue, newValue,
                RedisProtocol.toByteArray(expireMillis));
    }

    /**
     * Slvals
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void slvals(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3LVALS, keyspace, uid, serviceCode);
    }

    // Hashes of Sets ///////////////

    /**
     * Ssget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssget(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3SGET, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssmget.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssmget(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[]... key) {
        byte[][] params = toByteArrays(keyspace, uid, serviceCode, key);
        sendCommand(Command.S3SMGET, params);
    }

    /**
     * Sskeys.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sskeys(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3KEYS, keyspace, uid);
    }

    /**
     * Sskeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void sskeys(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3SKEYS, keyspace, uid, serviceCode);
    }

    /**
     * Sskeys.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void sskeys(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3SKEYS, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssadd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param value        the value
     */
    public void ssadd(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[] value) {
        sendCommand(Command.S3SADD, keyspace, uid, serviceCode, key, value, RedisProtocol.toByteArray(expireMillis));
    }

    /**
     * Ssadd.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void ssadd(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[][] values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, expireMillis, values);
        sendCommand(Command.S3SADD, params);
    }

    public void ssaddAt(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                        final long unixTime, final byte[][] values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, unixTime, values);
        sendCommand(Command.S3SADDAT, params);
    }

    /**
     * Ssset.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     */
    public void ssset(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final long expireMillis, final byte[][] values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, expireMillis, values);
        sendCommand(Command.S3SSET, params);
    }

    /**
     * Ssdel.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void ssdel(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3REM, keyspace, uid);
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void ssrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3SREM, keyspace, uid, serviceCode);
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3SREM, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final byte[] value) {
        sendCommand(Command.S3SREM, keyspace, uid, serviceCode, key, value);
    }

    /**
     * Ssrem.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param values      the values
     */
    public void ssrem(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final byte[][] values) {
        final byte[][] params = toByteArrays(keyspace, uid, serviceCode, key, values);
        sendCommand(Command.S3SREM, params);
    }

    /**
     * Sscount.
     *
     * @param keyspace the keyspace
     * @param uid      the uid
     */
    public void sscount(final byte[] keyspace, final byte[] uid) {
        sendCommand(Command.S3COUNT, keyspace, uid);
    }

    /**
     * Sscount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void sscount(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3SCOUNT, keyspace, uid, serviceCode);
    }

    /**
     * Sscount.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void sscount(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3SCOUNT, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssexists(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3SEXISTS, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssexists.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssexists(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         final byte[] value) {
        sendCommand(Command.S3SEXISTS, keyspace, uid, serviceCode, key, value);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param expireMillis the expire millis
     */
    public void ssexpire(final byte[] keyspace, final byte[] uid, long expireMillis) {
        sendCommand(Command.S3EXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis));
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param expireMillis the expire millis
     */
    public void ssexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, long expireMillis) {
        sendCommand(Command.S3SEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     */
    public void ssexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         long expireMillis) {
        sendCommand(Command.S3SEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode, key);
    }

    /**
     * Ssexpire.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param value        the value
     * @param expireMillis the expire millis
     */
    public void ssexpire(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                         final byte[] value, final long expireMillis) {
        sendCommand(Command.S3SEXPIRE, keyspace, uid, RedisProtocol.toByteArray(expireMillis), serviceCode, key, value);
    }

    /**
     * Ssttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     */
    public void ssttl(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key) {
        sendCommand(Command.S3STTL, keyspace, uid, serviceCode, key);
    }

    /**
     * Ssttl.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param value       the value
     */
    public void ssttl(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                      final byte[] value) {
        sendCommand(Command.S3STTL, keyspace, uid, serviceCode, key, value);
    }

    /**
     * Ssvals.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     */
    public void ssvals(final byte[] keyspace, final byte[] uid, final byte[] serviceCode) {
        sendCommand(Command.S3SVALS, keyspace, uid, serviceCode);
    }

    /**
     * To byte arrays.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param expireMillis the expire millis
     * @param keys         the keys
     * @return the byte[][]
     */
    private byte[][] toByteArrays(final byte[] keyspace, final byte[] uid, final byte[] serviceCode,
                                  final long expireMillis, final byte[][] keys) {
        final byte[][] allArgs = new byte[keys.length + 4][];

        int index = 0;
        allArgs[index++] = keyspace;
        allArgs[index++] = uid;
        allArgs[index++] = serviceCode;
        allArgs[index++] = RedisProtocol.toByteArray(expireMillis);

        for (int i = 0; i < keys.length; i++) {
            allArgs[index++] = keys[i];
        }

        return allArgs;
    }

    /**
     * To byte arrays.
     *
     * @param keyspace     the keyspace
     * @param uid          the uid
     * @param serviceCode  the service code
     * @param key          the key
     * @param expireMillis the expire millis
     * @param values       the values
     * @return the byte[][]
     */
    private byte[][] toByteArrays(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                                  final long expireMillis, final byte[][] values) {
        final byte[][] allArgs = new byte[values.length * 2 + 4][];

        int index = 0;
        allArgs[index++] = keyspace;
        allArgs[index++] = uid;
        allArgs[index++] = serviceCode;
        allArgs[index++] = key;
        byte[] expireTime = RedisProtocol.toByteArray(expireMillis);

        for (int i = 0; i < values.length; i++) {
            allArgs[index++] = values[i];
            allArgs[index++] = expireTime;
        }

        return allArgs;
    }

    /**
     * To byte arrays.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @param values      the values
     * @return the byte[][]
     */
    private byte[][] toByteArrays(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[] key,
                                  final byte[][] values) {
        final byte[][] allArgs = new byte[values.length + 4][];
        int index = 0;
        allArgs[index++] = keyspace;
        allArgs[index++] = uid;
        allArgs[index++] = serviceCode;
        allArgs[index++] = key;

        for (int i = 0; i < values.length; i++) {
            allArgs[index++] = values[i];
        }
        return allArgs;
    }

    /**
     * To byte arrays.
     *
     * @param keyspace    the keyspace
     * @param uid         the uid
     * @param serviceCode the service code
     * @param key         the key
     * @return the byte[][]
     */
    private byte[][] toByteArrays(final byte[] keyspace, final byte[] uid, final byte[] serviceCode, final byte[][] key) {
        final byte[][] allArgs = new byte[key.length + 3][];
        int index = 0;
        allArgs[index++] = keyspace;
        allArgs[index++] = uid;
        allArgs[index++] = serviceCode;

        for (int i = 0; i < key.length; i++) {
            allArgs[index++] = key[i];
        }
        return allArgs;
    }

    protected HashMap<byte[], GeoCoordinate> convertMemberCoordinateMapToBinary(
            Map<String, GeoCoordinate> memberCoordinateMap) {
        HashMap<byte[], GeoCoordinate> binaryMemberCoordinateMap = new HashMap<byte[], GeoCoordinate>();

        for (Entry<String, GeoCoordinate> entry : memberCoordinateMap.entrySet()) {
            binaryMemberCoordinateMap.put(SafeEncoder.encode(entry.getKey()), entry.getValue());
        }
        return binaryMemberCoordinateMap;
    }

    protected ArrayList<byte[]> convertScoreMembersToByteArrays(final Map<byte[], Double> scoreMembers) {
      ArrayList<byte[]> args = new ArrayList<byte[]>(scoreMembers.size() * 2);

      for (Map.Entry<byte[], Double> entry : scoreMembers.entrySet()) {
        args.add(toByteArray(entry.getValue()));
        args.add(entry.getKey());
      }

      return args;
    }

    protected List<byte[]> convertGeoCoordinateMapToByteArrays(Map<byte[], GeoCoordinate> memberCoordinateMap) {
        List<byte[]> args = new ArrayList<byte[]>(memberCoordinateMap.size() * 3);

        for (Entry<byte[], GeoCoordinate> entry : memberCoordinateMap.entrySet()) {
            GeoCoordinate coordinate = entry.getValue();
            args.add(toByteArray(coordinate.getLongitude()));
            args.add(toByteArray(coordinate.getLatitude()));
            args.add(entry.getKey());
        }

        return args;
    }
}


/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.navercorp.nbasearc.gcp;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.buffer.ByteBuf;
import redis.clients.jedis.exceptions.JedisConnectionException;

/**
 * @author seongjoon.ahn@navercorp.com
 * @Author seunghoo.han@navercorp.com (maintenance)
 *
 */
class RedisDecoder {
    private final static Logger log = LoggerFactory.getLogger(RedisDecoder.class);

    private final byte[] lookasideBuffer;
    private int lookasideBufferReaderIndex;
    private int lookasideBufferLength;

    RedisDecoder() {
        // 32 : 20(bytes of a maximum long) + 2(\r\n) + 1($ or *)
        //      and a power of 2
        this.lookasideBuffer = new byte[32];
    }

    void getFrames(ByteBuf in, List<byte[]> out) {
        while (true) {
            in.markReaderIndex();
            final int stx = in.readerIndex();

            if (hasFrame(in) == false) {
                in.resetReaderIndex();
                break;
            }

            int length = in.readerIndex() - stx;
            byte[] readBytes = new byte[length];

            in.resetReaderIndex();
            in.readBytes(readBytes);
            out.add(readBytes);
        }
    }

    private int readableLookasideBufferBytes() {
        return lookasideBufferLength - lookasideBufferReaderIndex;
    }

    private boolean hasFrame(ByteBuf in) {
        if (in.isReadable() == false) {
            return false;
        }

        lookasideBufferLength = Math.min(in.readableBytes(), lookasideBuffer.length);
        in.readBytes(lookasideBuffer, 0, lookasideBufferLength);
        lookasideBufferReaderIndex = 0;
        in.readerIndex(in.readerIndex() - lookasideBufferLength);

        byte b = lookasideBuffer[lookasideBufferReaderIndex++];
        in.skipBytes(1);
        if (b == MINUS_BYTE) {
            return processError(in);
        } else if (b == ASTERISK_BYTE) {
            return processMultiBulkReply(in);
        } else if (b == COLON_BYTE) {
            return processInteger(in);
        } else if (b == DOLLAR_BYTE) {
            return processBulkReply(in);
        } else if (b == PLUS_BYTE) {
            return processStatusCodeReply(in);
        } else {
            throw new JedisConnectionException("Unknown reply: " + (char) b);
        }
    }

    /**
     * Process error.
     *
     * @param is the is
     */
    private boolean processError(ByteBuf in) {
        int length = lineLength(in);
        if (length == -1) {
            return false;
        }

        in.skipBytes(length);
        return true;
    }

    /**
     * Process multi bulk reply.
     *
     * @param is the is
     * @return the list
     */
    private boolean processMultiBulkReply(ByteBuf in) {
        final int length = lineLength();
        if (length == 0) {
            return false;
        }
        in.skipBytes(length);

        int num = readInt(length - 2);
        if (num == -1) {
            return true;
        }

        for (int i = 0; i < num; i++) {
            if (hasFrame(in) == false) {
                return false;
            }
        }
        return true;
    }

    /**
     * Process integer.
     *
     * @param is the is
     * @return the long
     */
    private boolean processInteger(ByteBuf in) {
        int length = lineLength();
        if (length == 0) {
            return false;
        }

        in.skipBytes(length);
        return true;
    }

    /**
     * Process bulk reply.
     *
     * @param is the is
     * @return the byte[]
     */
    private boolean processBulkReply(ByteBuf in) {
        int length = lineLength();
        if (length == 0) {
            return false;
        }
        in.skipBytes(length);

        int numBytes = readInt(length - 2); // 2 == \r\n
        if (numBytes == -1) {
            return true;
        }

        if (in.readableBytes() < numBytes + 2) {
            return false;
        }

        in.skipBytes(numBytes + 2);

        return true;
    }

    /**
     * Process status code reply.
     *
     * @param is the is
     * @return the byte[]
     */
    private boolean processStatusCodeReply(ByteBuf in) {
        int length = lineLength();
        if (length == 0) {
            return false;
        }

        in.skipBytes(length);
        return true;
    }

    private int readInt(int lineLength) {
        /* minus */
        boolean minus = false;

        if (lookasideBuffer[lookasideBufferReaderIndex] == MINUS_BYTE) {
            minus = true;

            lookasideBufferReaderIndex++;
            lineLength--;
        }

        if (lineLength <= 0) {
            throw new RuntimeException("Redis protocol exception; malformed number format");
        }

        /* get value */
        int value = 0;

        for (int i = 0; i < lineLength; ++i) {
            byte b = lookasideBuffer[lookasideBufferReaderIndex++];
            if (b < '0' || '9' < b) {
                throw new RuntimeException("Reids protocol exception; invalid byte in number format; byte=" + b);
            }

            value = value * 10 + (b - '0');
        }

        if (minus) {
            value = -value;
        }

        return value;
    }

    private int lineLength(ByteBuf in) {
        int readableBytes = in.readableBytes();
        if (readableBytes < 2) {
            return -1;
        }

        /* CR */
        int length = in.bytesBefore(CR_BYTE);
        if (length < 0) {
            return -1;
        }

        if (readableBytes < length + 2) {
            return -1;
        }

        /* LF */
        byte eolLF = in.getByte(in.readerIndex() + length + 1);
        if (eolLF != LF_BYTE) {
            throw new RuntimeException("Redis protocol exception; malformed end of line; byte=" + eolLF);
        }

        return length + 2;
    }

    private int lineLength() {
        final int stx = lookasideBufferReaderIndex;
        try {
            while (readableLookasideBufferBytes() > 0) {
                if (lookasideBuffer[lookasideBufferReaderIndex++] == CR_BYTE) {
                    if (readableLookasideBufferBytes() < 1) {
                        return 0;
                    }

                    if (lookasideBuffer[lookasideBufferReaderIndex++] == LF_BYTE) {
                        return lookasideBufferReaderIndex - stx;
                    }
                }
            }

            if (lookasideBufferReaderIndex >= lookasideBuffer.length) {
                throw new RuntimeException("Redis protocol exception; malformed end of line");
            }

            return 0;
        } finally {
            lookasideBufferReaderIndex = stx;
        }
    }

    /**
     * The Constant DEFAULT_DATABASE.
     */
    static final int DEFAULT_DATABASE = 0;

    /**
     * The Constant CHARSET.
     */
    static final String CHARSET = "UTF-8";

    /**
     * The Constant DOLLAR_BYTE.
     */
    static final byte DOLLAR_BYTE = '$';

    /**
     * The Constant ASTERISK_BYTE.
     */
    static final byte ASTERISK_BYTE = '*';

    /**
     * The Constant PLUS_BYTE.
     */
    static final byte PLUS_BYTE = '+';

    /**
     * The Constant MINUS_BYTE.
     */
    static final byte MINUS_BYTE = '-';

    /**
     * The Constant COLON_BYTE.
     */
    static final byte COLON_BYTE = ':';

    static final byte CR_BYTE = '\r';
    static final byte LF_BYTE = '\n';
}
/* end of class RedisDecoder */


/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.navercorp.redis.cluster.spring;

import org.springframework.data.redis.core.RedisTemplate;

/**
 * The Class RedisClusterTemplate using JdkSerializationRedisSerializer.
 * You must annotate @Qualifier to use this class with <String, String> type of generic,
 * otherwise StringRedisClusterTemplate using StringRedisSerializer will be selected.
 *
 * @param <K> the key type
 * @param <V> the value type
 * @author jaehong.kim
 */
public class RedisClusterTemplate<K, V> extends RedisTemplate<K, V> implements RedisClusterOperations<K, V> {

    /*
     * @see RedisClusterOperations#opsForSessionOfHashList()
     */
    public <HK, HV> SessionOfHashListOperations<K, HK, HV> opsForSessionOfHashList() {
        return new DefaultSessionOfHashListOperations<K, HK, HV>(this);
    }

    /*
     * @see RedisClusterOperations#opsForSessionOfHashSet()
     */
    public <HK, HV> SessionOfHashSetOperations<K, HK, HV> opsForSessionOfHashSet() {
        return new DefaultSessionOfHashSetOperations<K, HK, HV>(this);
    }

    /*
     * @see RedisClusterOperations#boundSessionOfHashListOps(java.lang.Object, java.lang.Object)
     */
    public <HK, HV> BoundSessionOfHashListOperations<K, HK, HV> boundSessionOfHashListOps(K key, HK field) {
        return new DefaultBoundSessionOfHashListOperations<K, HK, HV>(key, field, this);
    }

    /*
     * @see RedisClusterOperations#boundSessionOfHashSetOps(java.lang.Object, java.lang.Object)
     */
    public <HK, HV> BoundSessionOfHashSetOperations<K, HK, HV> boundSessionOfHashSetOps(K key, HK field) {
        return new DefaultBoundSessionOfHashSetOperations<K, HK, HV>(key, field, this);
    }
}


/*
 * Copyright 2015 Naver Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.nbasearc.confmaster.server.workflow;

import org.springframework.stereotype.Component;

import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtSetquorumException;
import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtSmrCommandException;
import com.navercorp.nbasearc.confmaster.server.cluster.PartitionGroupServer;

@Component("MGSetquorum")
public class MGSetquorum {

    // It is not allowed to decalre any member variable in this class.
    // Since it is a singleton instance and represents a part of workflow logic running in multiple threads.

    public void setquorum(PartitionGroupServer master, int q,
            String quorumMembers) throws MgmtSetquorumException,
            MgmtSmrCommandException {
        master.setQuorum(q, quorumMembers);
    }

}


/*
 * Copyright 2015 Naver Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.nbasearc.confmaster;

import static com.navercorp.nbasearc.confmaster.server.lock.LockType.*;

import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtZooKeeperException;
import com.navercorp.nbasearc.confmaster.context.ReservedCallHolder;
import com.navercorp.nbasearc.confmaster.logger.LogHistoryHolder;
import com.navercorp.nbasearc.confmaster.server.cluster.ZNodePermission;
import com.navercorp.nbasearc.confmaster.server.lock.LockType;
import com.navercorp.nbasearc.confmaster.server.workflow.WorkflowLogger;

public class ThreadLocalVariableHolder {

    /* LogHistory */
    private static final ThreadLocal<LogHistoryHolder> logHistoryHolder =
            new ThreadLocal<LogHistoryHolder>() {
        @Override
        protected synchronized LogHistoryHolder initialValue() {
            return new LogHistoryHolder();
        }
    };

    public static LogHistoryHolder getLogHistoryHolder() {
        return logHistoryHolder.get();
    }

    /* Workflow */
    public static final ThreadLocal<ReservedCallHolder> reservedCall =
            new ThreadLocal<ReservedCallHolder>() {
        @Override
        protected synchronized ReservedCallHolder initialValue() {
            return new ReservedCallHolder();
        }
    };

    public static ReservedCallHolder getReservedCallHolder() {
        return reservedCall.get();
    }

    /* ZNodePermission */
    private static final ThreadLocal<ZNodePermission> znodePermission = new ThreadLocal<ZNodePermission>();

    public static void addPermission(String path, LockType type) {
        if (znodePermission.get() == null) {
            znodePermission.set(new ZNodePermission());
            addDefaultPermission();
        }
        znodePermission.get().addPermission(path, type);
    }

    public static void clearPermission(String path, LockType type) {
        if (znodePermission.get() != null) {
            znodePermission.get().clearPermission(path, type);
        }
    }

    public static void clearAllPermission() {
        if (znodePermission.get() != null) {
            znodePermission.set(null);
        }
    }

    /**
     * Clear ZNodePermission of current-thread to null.
     *
     * If znodePermission.get() returns null, this method skip permission-check. Otherwise, do permission-check.
     *
There are two different scenarios.
     * 1. workflows, commands, watch-event for clusters do permission-check.
     * 2. leader-election, confmaster-init/release skip permission-check.

     * It is not deterministic that what thread will allocated to the scenarios.
     * Call this method not to affect a next thread, after calling addPermission() method.
     */
    public static void checkPermission(String path, LockType type) throws MgmtZooKeeperException {
        if (znodePermission.get() != null) {
            znodePermission.get().checkPermission(path, type);
        }
    }

    private static void addDefaultPermission() {
        znodePermission.get().addPermission(WorkflowLogger.rootPathOfLog(), WRITE);
    }

}


/*
 * Copyright 2015 NAVER Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.navercorp.redis.cluster;

import static com.navercorp.redis.cluster.connection.RedisProtocol.*;
import static redis.clients.jedis.Protocol.toByteArray;
import static redis.clients.jedis.Protocol.Command.ZADD;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import redis.clients.jedis.BinaryClient;
import redis.clients.jedis.DebugParams;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.params.geo.GeoRadiusParam;
import redis.clients.jedis.params.sortedset.ZAddParams;
import redis.clients.util.SafeEncoder;

import com.navercorp.redis.cluster.connection.RedisProtocol;
import com.navercorp.redis.cluster.connection.RedisProtocol.Command;
import com.navercorp.redis.cluster.connection.RedisProtocol.Keyword;
import com.navercorp.redis.cluster.triples.TriplesRedisClusterClient;

/**
 * The Class BinaryRedisClusterClient.
 *
 * @author jaehong.kim
 */
public class BinaryRedisClusterClient extends TriplesRedisClusterClient {

    /**
     * Instantiates a new binary redis cluster client.
     *
     * @param host the host
     */
    public BinaryRedisClusterClient(final String host) {
        super(host);
    }

    /**
     * Instantiates a new binary redis cluster client.
     *
     * @param host the host
     * @param port the port
     */
    public BinaryRedisClusterClient(final String host, final int port) {
        super(host, port);
    }

    public BinaryRedisClusterClient(String host, int port, boolean async) {
        super(host, port, async);
    }

    /**
     * Join parameters.
     *
     * @param first the first
     * @param rest  the rest
     * @return the byte[][]
     */
    private byte[][] joinParameters(byte[] first, byte[][] rest) {
        byte[][] result = new byte[rest.length + 1][];
        result[0] = first;
        for (int i = 0; i < rest.length; i++) {
            result[i + 1] = rest[i];
        }
        return result;
    }

    /*
     * @see RedisConnection#connect()
     */
    @Override
    public void connect() {
        if (!isConnected()) {
            super.connect();
        }
    }

	public void execute(Command cmd, byte[]... args) {
		sendCommand(cmd, args);
	}

    /**
     * Ping.
     */
    public void ping() {
        sendCommand(Command.PING);
    }

    /**
     * Sets the.
     *
     * @param key   the key
     * @param value the value
     */
    public void set(final byte[] key, final byte[] value) {
        sendCommand(Command.SET, key, value);
    }

    public void set(final byte[] key, final byte[] value, final byte[] nxxx, final byte[] expx, final long time) {
        sendCommand(Command.SET, key, value, nxxx, expx, toByteArray(time));
    }

    /**
     * Gets the.
     *
     * @param key the key
     */
    public void get(final byte[] key) {
        sendCommand(Command.GET, key);
    }

    /**
     * Quit.
     */
    public void quit() {
        sendCommand(Command.QUIT);
    }

    /**
     * Exists.
     *
     * @param key the key
     */
    public void exists(final byte[] key) {
        sendCommand(Command.EXISTS, key);
    }

    /**
     * Exists.
     *
     * @param keys the keys
     */
    public void exists(final byte[]... keys) {
        sendCommand(Command.EXISTS, keys);
    }

    /**
     * Del.
     *
     * @param keys the keys
     */
    public void del(final byte[]... keys) {
        sendCommand(Command.DEL, keys);
    }

    /**
     * Type.
     *
     * @param key the key
     */
    public void type(final byte[] key) {
        sendCommand(Command.TYPE, key);
    }

    public void persist(final byte[] key) {
        sendCommand(Command.PERSIST, key);
    }

    /**
     * Expire.
     *
     * @param key     the key
     * @param seconds the seconds
     */
    public void expire(final byte[] key, final int seconds) {
        sendCommand(Command.EXPIRE, key, RedisProtocol.toByteArray(seconds));
    }

    /**
     * Expire at.
     *
     * @param key                   the key
     * @param millisecondsTimestamp the unix time
     */
    public void expireAt(final byte[] key, final long millisecondsTimestamp) {
        sendCommand(Command.EXPIREAT, key, RedisProtocol.toByteArray(millisecondsTimestamp));
    }

    public void pexpire(final byte[] key, final long milliseconds) {
        sendCommand(Command.PEXPIRE, key, RedisProtocol.toByteArray(milliseconds));
    }

    public void pexpireAt(final byte[] key, final long millisecondsTimestamp) {
        sendCommand(Command.PEXPIREAT, key, RedisProtocol.toByteArray(millisecondsTimestamp));
    }

    public void ttl(final byte[] key) {
        sendCommand(Command.TTL, key);
    }

    public void pttl(final byte[] key) {
        sendCommand(Command.PTTL, key);
    }

    public void flushAll() {
        sendCommand(Command.FLUSHALL);
    }

    public void getSet(final byte[] key, final byte[] value) {
        sendCommand(Command.GETSET, key, value);
    }

    /**
     * Setnx.
     *
     * @param key   the key
     * @param value the value
     */
    public void setnx(final byte[] key, final byte[] value) {
        sendCommand(Command.SETNX, key, value);
    }

    /**
     * Setex.
     *
     * @param key     the key
     * @param seconds the seconds
     * @param value   the value
     */
    public void setex(final byte[] key, final int seconds, final byte[] value) {
        sendCommand(Command.SETEX, key, RedisProtocol.toByteArray(seconds), value);
    }

    /**
     * Msetnx.
     *
     * @param keysvalues the keysvalues
     */
    public void msetnx(final byte[]... keysvalues) {
        sendCommand(Command.MSETNX, keysvalues);
    }

    /**
     * Decr by.
     *
     * @param key     the key
     * @param integer the integer
     */
    public void decrBy(final byte[] key, final long integer) {
        sendCommand(Command.DECRBY, key, RedisProtocol.toByteArray(integer));
    }

    /**
     * Decr.
     *
     * @param key the key
     */
    public void decr(final byte[] key) {
        sendCommand(Command.DECR, key);
    }

    /**
     * Incr by.
     *
     * @param key     the key
     * @param integer the integer
     */
    public void incrBy(final byte[] key, final long integer) {
        sendCommand(Command.INCRBY, key, RedisProtocol.toByteArray(integer));
    }

    public void incrByFloat(final byte[] key, final double increment) {
        sendCommand(Command.INCRBYFLOAT, key, RedisProtocol.toByteArray(increment));
    }

    /**
     * Incr.
     *
     * @param key the key
     */
    public void incr(final byte[] key) {
        sendCommand(Command.INCR, key);
    }

    /**
     * Append.
     *
     * @param key   the key
     * @param value the value
     */
    public void append(final byte[] key, final byte[] value) {
        sendCommand(Command.APPEND, key, value);
    }

    /**
     * Substr.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void substr(final byte[] key, final int start, final int end) {
        sendCommand(Command.SUBSTR, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    public void psetex(final byte[] key, final long milliseconds, final byte[] value) {
        sendCommand(Command.PSETEX, key, RedisProtocol.toByteArray(milliseconds), value);
    }

    public void bitcount(final byte[] key) {
        sendCommand(Command.BITCOUNT, key);
    }

    public void bitcount(final byte[] key, long start, long end) {
        sendCommand(Command.BITCOUNT, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    public void bitpos(byte[] key, boolean bit) {
        sendCommand(Command.BITPOS, key, SafeEncoder.encode(bit ? "1" : "0"));
    }

    public void bitpos(byte[] key, boolean bit, int start) {
        sendCommand(Command.BITPOS, key, SafeEncoder.encode(bit ? "1" : "0"), toByteArray(start));
    }

    public void bitpos(byte[] key, boolean bit, int start, int end) {
        sendCommand(Command.BITPOS, key, SafeEncoder.encode(bit ? "1" : "0"), toByteArray(start), toByteArray(end));
    }

    public void bitfield(final byte[] key, final byte[]... arguments) {
        final byte[][] params = new byte[arguments.length + 1][];
        params[0] = key;
        System.arraycopy(arguments, 0, params, 1, arguments.length);
        sendCommand(Command.BITFIELD, params);
    }

    /**
     * Hset.
     *
     * @param key   the key
     * @param field the field
     * @param value the value
     */
    public void hset(final byte[] key, final byte[] field, final byte[] value) {
        sendCommand(Command.HSET, key, field, value);
    }

    /**
     * Hget.
     *
     * @param key   the key
     * @param field the field
     */
    public void hget(final byte[] key, final byte[] field) {
        sendCommand(Command.HGET, key, field);
    }

    /**
     * Hsetnx.
     *
     * @param key   the key
     * @param field the field
     * @param value the value
     */
    public void hsetnx(final byte[] key, final byte[] field, final byte[] value) {
        sendCommand(Command.HSETNX, key, field, value);
    }

    /**
     * Hmset.
     *
     * @param key  the key
     * @param hash the hash
     */
    public void hmset(final byte[] key, final Map<byte[], byte[]> hash) {
        final List<byte[]> params = new ArrayList<byte[]>();
        params.add(key);

        for (final Entry<byte[], byte[]> entry : hash.entrySet()) {
            params.add(entry.getKey());
            params.add(entry.getValue());
        }
        sendCommand(Command.HMSET, params.toArray(new byte[params.size()][]));
    }

    /**
     * Hmget.
     *
     * @param key    the key
     * @param fields the fields
     */
    public void hmget(final byte[] key, final byte[]... fields) {
        final byte[][] params = new byte[fields.length + 1][];
        params[0] = key;
        System.arraycopy(fields, 0, params, 1, fields.length);
        sendCommand(Command.HMGET, params);
    }

    /**
     * Hincr by.
     *
     * @param key   the key
     * @param field the field
     * @param value the value
     */
    public void hincrBy(final byte[] key, final byte[] field, final long value) {
        sendCommand(Command.HINCRBY, key, field, RedisProtocol.toByteArray(value));
    }

    /**
     * Hexists.
     *
     * @param key   the key
     * @param field the field
     */
    public void hexists(final byte[] key, final byte[] field) {
        sendCommand(Command.HEXISTS, key, field);
    }

    /**
     * Hdel.
     *
     * @param key    the key
     * @param fields the fields
     */
    public void hdel(final byte[] key, final byte[]... fields) {
        sendCommand(Command.HDEL, joinParameters(key, fields));
    }

    /**
     * Hlen.
     *
     * @param key the key
     */
    public void hlen(final byte[] key) {
        sendCommand(Command.HLEN, key);
    }

    /**
     * Hkeys.
     *
     * @param key the key
     */
    public void hkeys(final byte[] key) {
        sendCommand(Command.HKEYS, key);
    }

    /**
     * Hvals.
     *
     * @param key the key
     */
    public void hvals(final byte[] key) {
        sendCommand(Command.HVALS, key);
    }

    /**
     * Hget all.
     *
     * @param key the key
     */
    public void hgetAll(final byte[] key) {
        sendCommand(Command.HGETALL, key);
    }

    public void hscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        final List<byte[]> args = new ArrayList<byte[]>();
        args.add(key);
        args.add(cursor);
        args.addAll(params.getParams());
        sendCommand(Command.HSCAN, args.toArray(new byte[args.size()][]));
    }

    public void hstrlen(byte[] key, byte[] field) {
        sendCommand(Command.HSTRLEN, key, field);
    }

    public void hincrByFloat(final byte[] key, final byte[] field, double increment) {
        sendCommand(Command.HINCRBYFLOAT, key, field, RedisProtocol.toByteArray(increment));
    }

    /**
     * Rpush.
     *
     * @param key     the key
     * @param strings the strings
     */
    public void rpush(final byte[] key, final byte[]... strings) {
        sendCommand(Command.RPUSH, joinParameters(key, strings));
    }

    /**
     * Lpush.
     *
     * @param key     the key
     * @param strings the strings
     */
    public void lpush(final byte[] key, final byte[]... strings) {
        sendCommand(Command.LPUSH, joinParameters(key, strings));
    }

    /**
     * Llen.
     *
     * @param key the key
     */
    public void llen(final byte[] key) {
        sendCommand(Command.LLEN, key);
    }

    /**
     * Lrange.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void lrange(final byte[] key, final long start, final long end) {
        sendCommand(Command.LRANGE, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    /**
     * Ltrim.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void ltrim(final byte[] key, final long start, final long end) {
        sendCommand(Command.LTRIM, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    /**
     * Lindex.
     *
     * @param key   the key
     * @param index the index
     */
    public void lindex(final byte[] key, final long index) {
        sendCommand(Command.LINDEX, key, RedisProtocol.toByteArray(index));
    }

    /**
     * Lset.
     *
     * @param key   the key
     * @param index the index
     * @param value the value
     */
    public void lset(final byte[] key, final long index, final byte[] value) {
        sendCommand(Command.LSET, key, RedisProtocol.toByteArray(index), value);
    }

    /**
     * Lrem.
     *
     * @param key   the key
     * @param count the count
     * @param value the value
     */
    public void lrem(final byte[] key, long count, final byte[] value) {
        sendCommand(Command.LREM, key, RedisProtocol.toByteArray(count), value);
    }

    /**
     * Lpop.
     *
     * @param key the key
     */
    public void lpop(final byte[] key) {
        sendCommand(Command.LPOP, key);
    }

    /**
     * Rpop.
     *
     * @param key the key
     */
    public void rpop(final byte[] key) {
        sendCommand(Command.RPOP, key);
    }

    /**
     * Sadd.
     *
     * @param key     the key
     * @param members the members
     */
    public void sadd(final byte[] key, final byte[]... members) {
        sendCommand(Command.SADD, joinParameters(key, members));
    }

    /**
     * Smembers.
     *
     * @param key the key
     */
    public void smembers(final byte[] key) {
        sendCommand(Command.SMEMBERS, key);
    }

    /**
     * Srem.
     *
     * @param key     the key
     * @param members the members
     */
    public void srem(final byte[] key, final byte[]... members) {
        sendCommand(Command.SREM, joinParameters(key, members));
    }

    /**
     * Scard.
     *
     * @param key the key
     */
    public void scard(final byte[] key) {
        sendCommand(Command.SCARD, key);
    }

    /**
     * Sismember.
     *
     * @param key    the key
     * @param member the member
     */
    public void sismember(final byte[] key, final byte[] member) {
        sendCommand(Command.SISMEMBER, key, member);
    }

    /**
     * Srandmember.
     *
     * @param key the key
     */
    public void srandmember(final byte[] key) {
        sendCommand(Command.SRANDMEMBER, key);
    }

    public void srandmember(final byte[] key, final int count) {
        sendCommand(Command.SRANDMEMBER, key, RedisProtocol.toByteArray(count));
    }

    public void sscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        final List<byte[]> args = new ArrayList<byte[]>();
        args.add(key);
        args.add(cursor);
        args.addAll(params.getParams());
        sendCommand(Command.SSCAN, args.toArray(new byte[args.size()][]));
    }

    /**
     * Zadd.
     *
     * @param key    the key
     * @param score  the score
     * @param member the member
     */
    public void zadd(final byte[] key, final double score, final byte[] member) {
        sendCommand(Command.ZADD, key, RedisProtocol.toByteArray(score), member);
    }

    public void zadd(byte[] key, final double score, byte[] member, ZAddParams params) {
        sendCommand(Command.ZADD, params.getByteParams(key, toByteArray(score), member));
    }

    public void zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params) {
        ArrayList<byte[]> args = convertScoreMembersToByteArrays(scoreMembers);
        byte[][] argsArray = new byte[args.size()][];
        args.toArray(argsArray);

        sendCommand(Command.ZADD, params.getByteParams(key, argsArray));
    }

    /**
     * Zadd binary.
     *
     * @param key          the key
     * @param scoreMembers the score members
     */
    public void zaddBinary(final byte[] key, Map<Double, byte[]> scoreMembers) {
        ArrayList<byte[]> args = new ArrayList<byte[]>(scoreMembers.size() * 2 + 1);

        args.add(key);

        for (Map.Entry<Double, byte[]> entry : scoreMembers.entrySet()) {
            args.add(RedisProtocol.toByteArray(entry.getKey()));
            args.add(entry.getValue());
        }

        byte[][] argsArray = new byte[args.size()][];
        args.toArray(argsArray);

        sendCommand(Command.ZADD, argsArray);
    }

    public void zaddBinary2(final byte[] key, final Map<byte[], Double> scoreMembers) {
        ArrayList<byte[]> args = new ArrayList<byte[]>(scoreMembers.size() * 2 + 1);
        args.add(key);

        for (Map.Entry<byte[], Double> entry : scoreMembers.entrySet()) {
            args.add(RedisProtocol.toByteArray(entry.getValue()));
            args.add(entry.getKey());
        }

        byte[][] argsArray = new byte[args.size()][];
        args.toArray(argsArray);

        sendCommand(Command.ZADD, argsArray);
    }

    /**
     * Zrange.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zrange(final byte[] key, final long start, final long end) {
        sendCommand(Command.ZRANGE, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    /**
     * Zrem.
     *
     * @param key     the key
     * @param members the members
     */
    public void zrem(final byte[] key, final byte[]... members) {
        sendCommand(Command.ZREM, joinParameters(key, members));
    }

    /**
     * Zincrby.
     *
     * @param key    the key
     * @param score  the score
     * @param member the member
     */
    public void zincrby(final byte[] key, final double score, final byte[] member) {
        sendCommand(Command.ZINCRBY, key, RedisProtocol.toByteArray(score), member);
    }

    /**
     * Zrank.
     *
     * @param key    the key
     * @param member the member
     */
    public void zrank(final byte[] key, final byte[] member) {
        sendCommand(Command.ZRANK, key, member);
    }

    public void zlexcount(final byte[] key, final byte[] min, final byte[] max) {
        sendCommand(Command.ZLEXCOUNT, key, min, max);
    }

    public void zrangeByLex(final byte[] key, final byte[] min, final byte[] max) {
        sendCommand(Command.ZRANGEBYLEX, key, min, max);
    }

    public void zrangeByLex(final byte[] key, final byte[] min, final byte[] max, final int offset, final int count) {
        sendCommand(Command.ZRANGEBYLEX, key, min, max, Keyword.LIMIT.raw, toByteArray(offset), toByteArray(count));
    }

    public void zrevrangeByLex(final byte[] key, final byte[] max, final byte[] min) {
        sendCommand(Command.ZREVRANGEBYLEX, key, max, min);
    }

    public void zrevrangeByLex(final byte[] key, final byte[] max, final byte[] min, final int offset,
            final int count) {
        sendCommand(Command.ZREVRANGEBYLEX, key, max, min, Keyword.LIMIT.raw, toByteArray(offset), toByteArray(count));
    }

    public void zremrangeByLex(byte[] key, byte[] min, byte[] max) {
        sendCommand(Command.ZREMRANGEBYLEX, key, min, max);
    }

    /**
     * Zrevrank.
     *
     * @param key    the key
     * @param member the member
     */
    public void zrevrank(final byte[] key, final byte[] member) {
        sendCommand(Command.ZREVRANK, key, member);
    }

    /**
     * Zrevrange.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zrevrange(final byte[] key, final long start, final long end) {
        sendCommand(Command.ZREVRANGE, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    /**
     * Zrange with scores.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zrangeWithScores(final byte[] key, final long start, final long end) {
        sendCommand(Command.ZRANGE, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end), Keyword.WITHSCORES.raw);
    }

    /**
     * Zrevrange with scores.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zrevrangeWithScores(final byte[] key, final long start, final long end) {
        sendCommand(Command.ZREVRANGE, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end), Keyword.WITHSCORES.raw);
    }

    /**
     * Zcard.
     *
     * @param key the key
     */
    public void zcard(final byte[] key) {
        sendCommand(Command.ZCARD, key);
    }

    /**
     * Zscore.
     *
     * @param key    the key
     * @param member the member
     */
    public void zscore(final byte[] key, final byte[] member) {
        sendCommand(Command.ZSCORE, key, member);
    }

    /**
     * Zcount.
     *
     * @param key the key
     * @param min the min
     * @param max the max
     */
    public void zcount(final byte[] key, final byte[] min, final byte[] max) {
        sendCommand(Command.ZCOUNT, key, min, max);
    }

    /**
     * Zrange by score.
     *
     * @param key the key
     * @param min the min
     * @param max the max
     */
    public void zrangeByScore(final byte[] key, final byte[] min, final byte[] max) {
        sendCommand(Command.ZRANGEBYSCORE, key, min, max);
    }

    /**
     * Zrevrange by score.
     *
     * @param key the key
     * @param max the max
     * @param min the min
     */
    public void zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min) {
        sendCommand(Command.ZREVRANGEBYSCORE, key, max, min);
    }

    /**
     * Zrange by score.
     *
     * @param key    the key
     * @param min    the min
     * @param max    the max
     * @param offset the offset
     * @param count  the count
     */
    public void zrangeByScore(final byte[] key, final byte[] min, final byte[] max, final int offset, int count) {
        sendCommand(Command.ZRANGEBYSCORE, key, min, max, Keyword.LIMIT.raw, RedisProtocol.toByteArray(offset), RedisProtocol.toByteArray(count));
    }

    /**
     * Zrevrange by score.
     *
     * @param key    the key
     * @param max    the max
     * @param min    the min
     * @param offset the offset
     * @param count  the count
     */
    public void zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min, final int offset, int count) {
        sendCommand(Command.ZREVRANGEBYSCORE, key, max, min, Keyword.LIMIT.raw, RedisProtocol.toByteArray(offset), RedisProtocol.toByteArray(count));
    }

    /**
     * Zrange by score with scores.
     *
     * @param key the key
     * @param min the min
     * @param max the max
     */
    public void zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max) {
        sendCommand(Command.ZRANGEBYSCORE, key, min, max, Keyword.WITHSCORES.raw);
    }

    /**
     * Zrevrange by score with scores.
     *
     * @param key the key
     * @param max the max
     * @param min the min
     */
    public void zrevrangeByScoreWithScores(final byte[] key, final byte[] max, final byte[] min) {
        sendCommand(Command.ZREVRANGEBYSCORE, key, max, min, Keyword.WITHSCORES.raw);
    }

    /**
     * Zrange by score with scores.
     *
     * @param key    the key
     * @param min    the min
     * @param max    the max
     * @param offset the offset
     * @param count  the count
     */
    public void zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max, final int offset,
                                        final int count) {
        sendCommand(Command.ZRANGEBYSCORE, key, min, max, Keyword.LIMIT.raw, RedisProtocol.toByteArray(offset), RedisProtocol.toByteArray(count), Keyword.WITHSCORES.raw);
    }

    /**
     * Zrevrange by score with scores.
     *
     * @param key    the key
     * @param max    the max
     * @param min    the min
     * @param offset the offset
     * @param count  the count
     */
    public void zrevrangeByScoreWithScores(final byte[] key, final byte[] max, final byte[] min, final int offset,
                                           final int count) {
        sendCommand(Command.ZREVRANGEBYSCORE, key, max, min, Keyword.LIMIT.raw, RedisProtocol.toByteArray(offset), RedisProtocol.toByteArray(count), Keyword.WITHSCORES.raw);
    }

    /**
     * Zremrange by rank.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zremrangeByRank(final byte[] key, final long start, final long end) {
        sendCommand(Command.ZREMRANGEBYRANK, key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    /**
     * Zremrange by score.
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public void zremrangeByScore(final byte[] key, final byte[] start, final byte[] end) {
        sendCommand(Command.ZREMRANGEBYSCORE, key, start, end);
    }

    public void zscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        final List<byte[]> args = new ArrayList<byte[]>();
        args.add(key);
        args.add(cursor);
        args.addAll(params.getParams());
        sendCommand(Command.ZSCAN, args.toArray(new byte[args.size()][]));
    }

    /**
     * Slaveof no one.
     */
    public void slaveofNoOne() {
        sendCommand(Command.SLAVEOF, Keyword.NO.raw, Keyword.ONE.raw);
    }

    /**
     * Config get.
     *
     * @param pattern the pattern
     */
    public void configGet(final byte[] pattern) {
        sendCommand(Command.CONFIG, Keyword.GET.raw, pattern);
    }

    /**
     * Config set.
     *
     * @param parameter the parameter
     * @param value     the value
     */
    public void configSet(final byte[] parameter, final byte[] value) {
        sendCommand(Command.CONFIG, Keyword.SET.raw, parameter, value);
    }

    /**
     * Strlen.
     *
     * @param key the key
     */
    public void strlen(final byte[] key) {
        sendCommand(Command.STRLEN, key);
    }

    /**
     * Lpushx.
     *
     * @param key    the key
     * @param string the string
     */
    public void lpushx(final byte[] key, final byte[] string) {
        sendCommand(Command.LPUSHX, key, string);
    }

    /**
     * Rpushx.
     *
     * @param key    the key
     * @param string the string
     */
    public void rpushx(final byte[] key, final byte[] string) {
        sendCommand(Command.RPUSHX, key, string);
    }

    /**
     * Linsert.
     *
     * @param key   the key
     * @param where the where
     * @param pivot the pivot
     * @param value the value
     */
    public void linsert(final byte[] key, final BinaryClient.LIST_POSITION where, final byte[] pivot, final byte[] value) {
        sendCommand(Command.LINSERT, key, where.raw, pivot, value);
    }

    /**
     * Debug.
     *
     * @param params the params
     */
    public void debug(final DebugParams params) {
        sendCommand(Command.DEBUG, params.getCommand());
    }

    /**
     * Setbit.
     *
     * @param key    the key
     * @param offset the offset
     * @param value  the value
     */
    public void setbit(byte[] key, long offset, byte[] value) {
        sendCommand(Command.SETBIT, key, RedisProtocol.toByteArray(offset), value);
    }

    /**
     * Gets the bit.
     *
     * @param key    the key
     * @param offset the offset
     */
    public void getbit(byte[] key, long offset) {
        sendCommand(Command.GETBIT, key, RedisProtocol.toByteArray(offset));
    }

    /**
     * Setrange.
     *
     * @param key    the key
     * @param offset the offset
     * @param value  the value
     */
    public void setrange(byte[] key, long offset, byte[] value) {
        sendCommand(Command.SETRANGE, key, RedisProtocol.toByteArray(offset), value);
    }

    /**
     * Gets the range.
     *
     * @param key         the key
     * @param startOffset the start offset
     * @param endOffset   the end offset
     */
    public void getrange(byte[] key, long startOffset, long endOffset) {
        sendCommand(Command.GETRANGE, key, RedisProtocol.toByteArray(startOffset), RedisProtocol.toByteArray(endOffset));
    }

    /*
     * @see RedisConnection#disconnect()
     */
    public void disconnect() {
        super.disconnect();
    }

    /**
     * Slowlog len.
     */
    public void slowlogLen() {
        sendCommand(Command.SLOWLOG, Keyword.LEN.raw);
    }

    /**
     * Object refcount.
     *
     * @param key the key
     */
    public void objectRefcount(byte[] key) {
        sendCommand(Command.OBJECT, Keyword.REFCOUNT.raw, key);
    }

    /**
     * Object idletime.
     *
     * @param key the key
     */
    public void objectIdletime(byte[] key) {
        sendCommand(Command.OBJECT, Keyword.IDLETIME.raw, key);
    }

    /**
     * Object encoding.
     *
     * @param key the key
     */
    public void objectEncoding(byte[] key) {
        sendCommand(Command.OBJECT, Keyword.ENCODING.raw, key);
    }

    public void info() {
        sendCommand(Command.INFO);
    }

    public void info(final String section) {
        sendCommand(Command.INFO, section);
    }

    public void mget(final byte[]... keys) {
        sendCommand(Command.MGET, keys);
    }

    public void mset(final byte[]... keysvalues) {
        sendCommand(Command.MSET, keysvalues);
    }

    public void dbSize() {
        sendCommand(Command.DBSIZE);
    }

    public void dump(final byte[] key) {
        sendCommand(Command.DUMP, key);
    }

    public void restore(final byte[] key, final long ttl, final byte[] serializedValue) {
        sendCommand(Command.RESTORE, key, RedisProtocol.toByteArray(ttl), serializedValue);
    }

    public void geoadd(byte[] key, double longitude, double latitude, byte[] member) {
        sendCommand(Command.GEOADD, key, toByteArray(longitude), toByteArray(latitude), member);
    }

    public void geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap) {
        final List<byte[]> params = new ArrayList<byte[]>();
        params.add(key);
        params.addAll(convertGeoCoordinateMapToByteArrays(memberCoordinateMap));
        sendCommand(Command.GEOADD, params.toArray(new byte[params.size()][]));
    }

    public void geodist(byte[] key, byte[] member1, byte[] member2) {
        sendCommand(Command.GEODIST, key, member1, member2);
    }

    public void geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) {
        sendCommand(Command.GEODIST, key, member1, member2, unit.raw);
    }

    public void geohash(byte[] key, byte[]... members) {
        final byte[][] params = new byte[members.length + 1][];
        params[0] = key;
        System.arraycopy(members, 0, params, 1, members.length);
        sendCommand(Command.GEOHASH, params);
    }

    public void geopos(byte[] key, byte[]... members) {
        final byte[][] params = new byte[members.length + 1][];
        params[0] = key;
        System.arraycopy(members, 0, params, 1, members.length);
        sendCommand(Command.GEOPOS, params);
    }

    public void georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit) {
        sendCommand(Command.GEORADIUS, key, toByteArray(longitude), toByteArray(latitude), toByteArray(radius),
                unit.raw);
    }

    public void georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit,
            GeoRadiusParam param) {
        sendCommand(Command.GEORADIUS,
                param.getByteParams(key, toByteArray(longitude), toByteArray(latitude), toByteArray(radius), unit.raw));
    }

    public void georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit) {
        sendCommand(Command.GEORADIUSBYMEMBER, key, member, toByteArray(radius), unit.raw);
    }

    public void georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit, GeoRadiusParam param) {
        sendCommand(Command.GEORADIUSBYMEMBER, param.getByteParams(key, member, toByteArray(radius), unit.raw));
    }

    public void pfadd(byte[] key, final byte[]... elements) {
        final byte[][] params = new byte[elements.length + 1][];
        params[0] = key;
        System.arraycopy(elements, 0, params, 1, elements.length);
        sendCommand(Command.PFADD, params);
    }

    public void pfcount(final byte[] key) {
        sendCommand(Command.PFCOUNT, key);
    }

    public void scan(final byte[] cursor, final ScanParams params) {
        final List<byte[]> args = new ArrayList<byte[]>();
        args.add(cursor);
        args.addAll(params.getParams());
        sendCommand(Command.SCAN, args.toArray(new byte[args.size()][]));
    }

    public void cscanlen() {
        sendCommand(Command.CSCANLEN);
    }

    public void cscan(int partitionID, byte[] cursor) {
        cscan(partitionID, cursor, new ScanParams());
    }

    public void cscan(int partitionID, byte[] cursor, ScanParams params) {
        final List<byte[]> args = new ArrayList<byte[]>();
        args.add(RedisProtocol.toByteArray(partitionID));
        args.add(cursor);
        args.addAll(params.getParams());
        sendCommand(Command.CSCAN, args.toArray(new byte[args.size()][]));
    }

    public void cscandigest() {
        sendCommand(Command.CSCANDIGEST);
    }

    public void touch(byte[]... keys) {
        sendCommand(Command.TOUCH, keys);
    }

}


/*
 * Copyright 2015 Naver Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.navercorp.nbasearc.confmaster.io;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.navercorp.nbasearc.confmaster.logger.Logger;
import com.navercorp.nbasearc.confmaster.statistics.Statistics;

public class EventSelector {

    private Selector selector;
    private Map<Integer, Session> sessions = new ConcurrentHashMap<Integer, Session>();

    private long selectTimeout;

    public EventSelector(long selectTimeout) throws IOException {
        this.selectTimeout = selectTimeout;

        try {
            selector = Selector.open();
        } catch (IOException e) {
            Logger.error("Initialize selector fail.", e);
            throw e;
        }
    }

    /**
     * Shutdown a selector
     * @throws IOException
     */
    public void shutdown() throws IOException {
        try {
            selector.close();
        } catch (IOException e) {
            Logger.error("Close nio event selector fail.", e);
            throw e;
        }
    }

    public class ElapsedTime {
        final long start;
        final long ioDone;
        final long end;

        public ElapsedTime(long start, long ioDone, long end) {
            this.start = start;
            this.ioDone = ioDone;
            this.end = end;
        }

        public long getIoTime() {
            return ioDone - start;
        }

        public long getTotalTime() {
            return ioDone - end;
        }

        public long getStart() {
            return start;
        }

        public long getIoDone() {
            return ioDone;
        }

        public long getEnd() {
            return end;
        }
    }

    /**
     * Main loop of nio
     *
     * @return elapsed time
     */
    public ElapsedTime process() {
        try {
            long start = System.currentTimeMillis();
            ioProcess();
            long ioDone = System.currentTimeMillis();
            loopProcess();
            long end = System.currentTimeMillis();

            return new ElapsedTime(start, ioDone, end);
        } catch (Exception e) {
            Logger.error("-ERR error occurs in hbMain", e);
        }

        return null;
    }

    /**
     * Demultiplex IO events
     */
    public void ioProcess()
    {
        int numberOfKeys;
        try {
            numberOfKeys = selector.select(selectTimeout);
        } catch (IOException e) {
            Logger.error("Selector.select error.", e);
            return;
        }
        if (0 == numberOfKeys) {
            return;
        }

        Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
        while (keyIter.hasNext())
        {
            SelectionKey key = keyIter.next();
            Session session = (Session) key.attachment();

            long timeMillis = System.currentTimeMillis();

            try {
                if (key.isValid() && key.isReadable()) {
                    long start = System.currentTimeMillis();
                    session.callbackRead(key, timeMillis);
                    long end = System.currentTimeMillis();
                    Statistics.updateNioRead(end - start);
                }

                if (key.isValid() && key.isWritable()) {
                    long start = System.currentTimeMillis();
                    session.callbackWrite(key, timeMillis);
                    long end = System.currentTimeMillis();
                    Statistics.updateNioWrite(end - start);
                }

                if (key.isValid() && key.isAcceptable()) {
                    session.callbackAccept(key, timeMillis);
                }

                if (key.isValid() && key.isConnectable()) {
                    session.callbackConnect(key, timeMillis);
                }
            } catch (CancelledKeyException e) {
                Logger.warn("Closed connection {}", session, e);
            } catch (ClosedChannelException e) {
                Logger.warn("Closed connection {}", session, e);
            } catch (IOException e) {
                Logger.warn("Closed connection {}", session, e);
            } catch (Exception e) {
                Logger.error("Exception occurs on {}", session, e);
            }

            keyIter.remove();
        }
    }

    /**
     * to be called on each loop to process idle routines.
     */
    public void loopProcess()
    {
        for (Session session : sessions.values())
        {
            long timeMillis = System.currentTimeMillis();
            try {
                session.callbackOnLoop(timeMillis);
            } catch (Exception e) {
                Logger.error("Exception occurs while callbackOnLoop on {}", session, e);
            }
        }
    }

    /**
     * register session to Selector in order to detect IO events.
     * @param session
     * @param ops interest IO operations, such as SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ, SelectionKey.OP_WRITE
     * @throws ClosedChannelException
     */
    public void register(Session session, int ops) throws ClosedChannelException {
        if (sessions.containsKey(session.getID())) {
            throw new IllegalStateException("Altready registered session");
        }

        SelectionKey selKey;
        try {
            selKey = session.getChannel().register(selector, ops, session);
        } catch (ClosedChannelException e) {
            Logger.error("Error while register channel to selector. {}", session, e);
            throw e;
        }
        session.setSelectionKey(selKey);
    }

    public Selector getSelector() {
        return selector;
    }

    public void addSession(Session session) {
        sessions.put(session.getID(), session);
    }

    public Session getSession(int sessionID) {
        return sessions.get(sessionID);
    }

    public void removeSession(int sessionID) {
        sessions.remove(sessionID);
    }

    public Integer getSessionCount() {
        return sessions.size();
    }

    public Collection<Session> getSessions() {
        return sessions.values();
    }

}