/*
 * 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 java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.navercorp.redis.cluster.connection.RedisProtocol;
import com.navercorp.redis.cluster.connection.RedisProtocol.Command;
import com.navercorp.redis.cluster.pipeline.BuilderFactory;
import com.navercorp.redis.cluster.triples.TriplesRedisCluster;

import redis.clients.jedis.BinaryClient.LIST_POSITION;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.Tuple;
import redis.clients.jedis.params.geo.GeoRadiusParam;
import redis.clients.jedis.params.sortedset.ZAddParams;
import redis.clients.util.JedisByteHashMap;
import redis.clients.util.SafeEncoder;

/**
 * The Class BinaryRedisCluster.
 *
 * @author jaehong.kim
 */
public class BinaryRedisCluster extends TriplesRedisCluster implements BinaryRedisClusterCommands {

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

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

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

    public BinaryRedisCluster(final String host, final int port, final int timeout, boolean async) {
        super(host, port, timeout);
        client = new RedisClusterClient(host, port, async);
        client.setTimeout(timeout);
    }

    public RedisClusterClient getClient() {
        return this.client;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Keys

    public Long del(final byte[]... key) {
        client.del(key);
        return client.getIntegerReply();
    }

    public Boolean exists(final byte[] key) {
        client.exists(key);
        return client.getIntegerReply() == 1;
    }

    public Long exists(final byte[]... keys) {
        client.exists(keys);
        return client.getIntegerReply();
    }

    public Long expire(final byte[] key, final int seconds) {
        client.expire(key, seconds);
        return client.getIntegerReply();
    }

    public Long expireAt(final byte[] key, final long secondsTimestamp) {
        client.expireAt(key, secondsTimestamp);
        return client.getIntegerReply();
    }

    public Long pexpire(final byte[] key, final long milliseconds) {
        client.pexpire(key, milliseconds);
        return client.getIntegerReply();
    }

    public Long pexpireAt(final byte[] key, final long millisecondsTimestamp) {
        client.pexpireAt(key, millisecondsTimestamp);
        return client.getIntegerReply();
    }

    public byte[] objectEncoding(byte[] key) {
        client.objectEncoding(key);
        return client.getBinaryBulkReply();
    }

    public Long objectIdletime(byte[] key) {
        client.objectIdletime(key);
        return client.getIntegerReply();
    }

    public Long objectRefcount(byte[] key) {
        client.objectRefcount(key);
        return client.getIntegerReply();
    }

    public Long ttl(final byte[] key) {
        client.ttl(key);
        return client.getIntegerReply();
    }

    public Long pttl(final byte[] key) {
        client.pttl(key);
        return client.getIntegerReply();
    }

    public String type(final byte[] key) {
        client.type(key);
        return client.getStatusCodeReply();
    }

    public Long persist(final byte[] key) {
        client.persist(key);
        return client.getIntegerReply();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Strings

    public Long append(final byte[] key, final byte[] value) {
        client.append(key, value);
        return client.getIntegerReply();
    }

    public Long decr(final byte[] key) {
        client.decr(key);
        return client.getIntegerReply();
    }

    public Long decrBy(final byte[] key, final long integer) {
        client.decrBy(key, integer);
        return client.getIntegerReply();
    }

    public byte[] get(final byte[] key) {
        client.get(key);
        return client.getBinaryBulkReply();
    }

    public Boolean getbit(byte[] key, long offset) {
        client.getbit(key, offset);
        return client.getIntegerReply() == 1;
    }

    public byte[] getrange(byte[] key, long startOffset, long endOffset) {
        client.getrange(key, startOffset, endOffset);
        return client.getBinaryBulkReply();
    }

    public byte[] substr(final byte[] key, final int start, final int end) {
        client.substr(key, start, end);
        return client.getBinaryBulkReply();
    }

    public byte[] getSet(final byte[] key, final byte[] value) {
        client.getSet(key, value);
        return client.getBinaryBulkReply();
    }

    public Long incr(final byte[] key) {
        client.incr(key);
        return client.getIntegerReply();
    }

    public Long incrBy(final byte[] key, final long integer) {
        client.incrBy(key, integer);
        return client.getIntegerReply();
    }

    public Double incrByFloat(final byte[] key, final double increment) {
        client.incrByFloat(key, increment);
        final String reply = client.getBulkReply();
        return (reply != null ? new Double(reply) : null);
    }

    public String set(final byte[] key, final byte[] value) {
        client.set(key, value);
        return client.getStatusCodeReply();
    }

    public String set(final byte[] key, final byte[] value, final byte[] nxxx, final byte[] expx, final long time) {
        client.set(key, value, nxxx, expx, time);
        return client.getStatusCodeReply();
    }

	public String set(final byte[]... args) {
		client.execute(Command.SET, args);
		return client.getStatusCodeReply();
	}

    public Boolean setbit(byte[] key, long offset, byte[] value) {
        client.setbit(key, offset, value);
        return client.getIntegerReply() == 1;
    }

    public String setex(final byte[] key, final int seconds, final byte[] value) {
        client.setex(key, seconds, value);
        return client.getStatusCodeReply();
    }

    public Long setnx(final byte[] key, final byte[] value) {
        client.setnx(key, value);
        return client.getIntegerReply();
    }

    public Long setrange(byte[] key, long offset, byte[] value) {
        client.setrange(key, offset, value);
        return client.getIntegerReply();
    }

    public Long strlen(final byte[] key) {
        client.strlen(key);
        return client.getIntegerReply();
    }

    public List<byte[]> mget(final byte[]... keys) {
        client.mget(keys);
        return client.getBinaryMultiBulkReply();
    }

    public String mset(final byte[]... keysvalues) {
        client.mset(keysvalues);
        return client.getStatusCodeReply();
    }

    public String psetex(final byte[] key, final long milliseconds, final byte[] value) {
        client.psetex(key, milliseconds, value);
        return client.getStatusCodeReply();
    }

    public Long bitcount(final byte[] key) {
        client.bitcount(key);
        return client.getIntegerReply();
    }

    public List<Long> bitfield(final byte[] key, final byte[]... arguments) {
        client.bitfield(key, arguments);
        return client.getIntegerMultiBulkReply();
    }

    public Long bitcount(final byte[] key, long start, long end) {
        client.bitcount(key, start, end);
        return client.getIntegerReply();
    }

    public Long bitpos(byte[] key, boolean bit) {
        client.bitpos(key, bit);
        return client.getIntegerReply();
    }

    public Long bitpos(byte[] key, boolean bit, int start) {
        client.bitpos(key, bit, start);
        return client.getIntegerReply();
    }

    public Long bitpos(byte[] key, boolean bit, int start, int end) {
        client.bitpos(key, bit, start, end);
        return client.getIntegerReply();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Hashes

    public Long hdel(final byte[] key, final byte[]... fields) {
        client.hdel(key, fields);
        return client.getIntegerReply();
    }

    public Boolean hexists(final byte[] key, final byte[] field) {
        client.hexists(key, field);
        return client.getIntegerReply() == 1;
    }

    public byte[] hget(final byte[] key, final byte[] field) {
        client.hget(key, field);
        return client.getBinaryBulkReply();
    }

    public Map<byte[], byte[]> hgetAll(final byte[] key) {
        client.hgetAll(key);
        final List<byte[]> flatHash = client.getBinaryMultiBulkReply();
        final Map<byte[], byte[]> hash = new JedisByteHashMap();
        if (flatHash == null) {
            return hash;
        }

        final Iterator<byte[]> iterator = flatHash.iterator();
        if (iterator == null) {
            return hash;
        }

        while (iterator.hasNext()) {
            hash.put(iterator.next(), iterator.next());
        }

        return hash;
    }

    public Long hincrBy(final byte[] key, final byte[] field, final long value) {
        client.hincrBy(key, field, value);
        return client.getIntegerReply();
    }

    public Double hincrByFloat(final byte[] key, final byte[] field, double increment) {
        client.hincrByFloat(key, field, increment);
        String reply = client.getBulkReply();
        return (reply != null ? new Double(reply) : null);
    }

    public Set<byte[]> hkeys(final byte[] key) {
        client.hkeys(key);
        final List<byte[]> lresult = client.getBinaryMultiBulkReply();
        return new HashSet<byte[]>(lresult);
    }

    public Long hlen(final byte[] key) {
        client.hlen(key);
        return client.getIntegerReply();
    }

    public List<byte[]> hmget(final byte[] key, final byte[]... fields) {
        client.hmget(key, fields);
        return client.getBinaryMultiBulkReply();
    }

    public String hmset(final byte[] key, final Map<byte[], byte[]> hash) {
        client.hmset(key, hash);
        return client.getStatusCodeReply();
    }

    public ScanResult<byte[]> scan(final byte[] cursor) {
        return scan(cursor, new ScanParams());
    }

    public ScanResult<byte[]> scan(final byte[] cursor, final ScanParams params) {
        client.scan(cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        byte[] newcursor = (byte[]) result.get(0);
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        return new ScanResult<byte[]>(newcursor, rawResults);
    }

    public ScanResult<byte[]> cscan(int partitionID, byte[] cursor) {
        return cscan(partitionID, cursor, new ScanParams());
    }

    public ScanResult<byte[]> cscan(int partitionID, byte[] cursor, ScanParams params) {
        client.cscan(partitionID, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        byte[] newcursor = (byte[]) result.get(0);
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        return new ScanResult<byte[]>(newcursor, rawResults);
    }

    public String cscandigest() {
        client.cscandigest();
        return client.getBulkReply();
    }

    public Long cscanlen() {
        client.cscanlen();
        return client.getIntegerReply();
    }

    public ScanResult<byte[]> sscan(final byte[] key, final byte[] cursor) {
        return sscan(key, cursor, new ScanParams());
    }

    public ScanResult<byte[]> sscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        client.sscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        byte[] newcursor = (byte[]) result.get(0);
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        return new ScanResult<byte[]>(newcursor, rawResults);
    }

    public ScanResult<Map.Entry<byte[], byte[]>> hscan(final byte[] key, final byte[] cursor) {
        client.hscan(key, cursor, new ScanParams());
        List<Object> result = client.getObjectMultiBulkReply();
        String newcursor = new String((byte[]) result.get(0));
        List<Map.Entry<byte[], byte[]>> results = new ArrayList<Map.Entry<byte[], byte[]>>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
          results.add(new AbstractMap.SimpleEntry<byte[], byte[]>(iterator.next(), iterator.next()));
        }
        return new ScanResult<Map.Entry<byte[], byte[]>>(newcursor, results);
    }

    public ScanResult<Map.Entry<byte[], byte[]>> hscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        client.hscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        byte[] newcursor = (byte[]) result.get(0);
        List<Map.Entry<byte[], byte[]>> results = new ArrayList<Map.Entry<byte[], byte[]>>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
            results.add(new AbstractMap.SimpleEntry<byte[], byte[]>(iterator.next(), iterator.next()));
        }
        return new ScanResult<Map.Entry<byte[], byte[]>>(newcursor, results);
    }

    public ScanResult<Tuple> zscan(final byte[] key, final byte[] cursor) {
        return zscan(key, cursor, new ScanParams());
    }

    public ScanResult<Tuple> zscan(final byte[] key, final byte[] cursor, final ScanParams params) {
        client.zscan(key, cursor, params);
        List<Object> result = client.getObjectMultiBulkReply();
        byte[] newcursor = (byte[]) result.get(0);
        List<Tuple> results = new ArrayList<Tuple>();
        @SuppressWarnings("unchecked")
        List<byte[]> rawResults = (List<byte[]>) result.get(1);
        Iterator<byte[]> iterator = rawResults.iterator();
        while (iterator.hasNext()) {
            results.add(new Tuple(iterator.next(), Double.valueOf(SafeEncoder.encode(iterator.next()))));
        }
        return new ScanResult<Tuple>(newcursor, results);
    }

    public Long hset(final byte[] key, final byte[] field, final byte[] value) {
        client.hset(key, field, value);
        return client.getIntegerReply();
    }

    public Long hsetnx(final byte[] key, final byte[] field, final byte[] value) {
        client.hsetnx(key, field, value);
        return client.getIntegerReply();
    }

    public Long hstrlen(byte[] key, byte[] field) {
        client.hstrlen(key, field);
        return client.getIntegerReply();
    }

    public List<byte[]> hvals(final byte[] key) {
        client.hvals(key);
        final List<byte[]> lresult = client.getBinaryMultiBulkReply();
        return lresult;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Lists

    public byte[] lindex(final byte[] key, final long index) {
        client.lindex(key, index);
        return client.getBinaryBulkReply();
    }

    public Long linsert(final byte[] key, final LIST_POSITION where, final byte[] pivot, final byte[] value) {
        client.linsert(key, where, pivot, value);
        return client.getIntegerReply();
    }

    public Long llen(final byte[] key) {
        client.llen(key);
        return client.getIntegerReply();
    }

    public byte[] lpop(final byte[] key) {
        client.lpop(key);
        return client.getBinaryBulkReply();
    }

    public Long lpush(final byte[] key, final byte[]... strings) {
        client.lpush(key, strings);
        return client.getIntegerReply();
    }

    public Long lpushx(final byte[] key, final byte[] string) {
        client.lpushx(key, string);
        return client.getIntegerReply();
    }

    public List<byte[]> lrange(final byte[] key, final long start, final long end) {
        client.lrange(key, start, end);
        return client.getBinaryMultiBulkReply();
    }

    public Long lrem(final byte[] key, final long count, final byte[] value) {
        client.lrem(key, count, value);
        return client.getIntegerReply();
    }

    public String lset(final byte[] key, final long index, final byte[] value) {
        client.lset(key, index, value);
        return client.getStatusCodeReply();
    }

    public String ltrim(final byte[] key, final long start, final long end) {
        client.ltrim(key, start, end);
        return client.getStatusCodeReply();
    }

    public byte[] rpop(final byte[] key) {
        client.rpop(key);
        return client.getBinaryBulkReply();
    }

    public Long rpush(final byte[] key, final byte[]... strings) {
        client.rpush(key, strings);
        return client.getIntegerReply();
    }

    public Long rpushx(final byte[] key, final byte[] string) {
        client.rpushx(key, string);
        return client.getIntegerReply();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Sets

    public Long sadd(final byte[] key, final byte[]... members) {
        client.sadd(key, members);
        return client.getIntegerReply();
    }

    public Long scard(final byte[] key) {
        client.scard(key);
        return client.getIntegerReply();
    }

    public Boolean sismember(final byte[] key, final byte[] member) {
        client.sismember(key, member);
        return client.getIntegerReply() == 1;
    }

    public Set<byte[]> smembers(final byte[] key) {
        client.smembers(key);
        final List<byte[]> members = client.getBinaryMultiBulkReply();
        return new HashSet<byte[]>(members);
    }

    public byte[] srandmember(final byte[] key) {
        client.srandmember(key);
        return client.getBinaryBulkReply();
    }

    public List<byte[]> srandmember(final byte[] key, final int count) {
        client.srandmember(key, count);
        return client.getBinaryMultiBulkReply();
    }

    public Long srem(final byte[] key, final byte[]... member) {
        client.srem(key, member);
        return client.getIntegerReply();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Sorted Sets

    public Long zadd(final byte[] key, final double score, final byte[] member) {
        client.zadd(key, score, member);
        return client.getIntegerReply();
    }

    public Long zadd(final byte[] key, final Map<Double, byte[]> scoreMembers) {
        client.zaddBinary(key, scoreMembers);
        return client.getIntegerReply();
    }

    public Long zadd2(final byte[] key, final Map<byte[], Double> scoreMembers) {
        client.zaddBinary2(key, scoreMembers);
        return client.getIntegerReply();
    }

    public Long zadd(byte[] key, double score, byte[] member, ZAddParams params) {
        client.zadd(key, score, member, params);
        return client.getIntegerReply();
    }

    public Long zadd(byte[] key, Map<byte[], Double> scoreMembers, ZAddParams params) {
        client.zadd(key, scoreMembers, params);
        return client.getIntegerReply();
    }

    public Long zcard(final byte[] key) {
        client.zcard(key);
        return client.getIntegerReply();
    }

    public Long zcount(final byte[] key, final byte[] min, final byte[] max) {
        client.zcount(key, min, max);
        return client.getIntegerReply();
    }

    public Long zcount(final byte[] key, final double min, final double max) {
        return zcount(key, RedisProtocol.toByteArray(min), RedisProtocol.toByteArray(max));
    }

    public Double zincrby(final byte[] key, final double score, final byte[] member) {
        client.zincrby(key, score, member);
        String newscore = client.getBulkReply();
        return Double.valueOf(newscore);
    }

    public Set<byte[]> zrange(final byte[] key, final long start, final long end) {
        client.zrange(key, start, end);
        final List<byte[]> members = client.getBinaryMultiBulkReply();
        return new LinkedHashSet<byte[]>(members);
    }

    public Set<byte[]> zrangeByScore(final byte[] key, final byte[] min, final byte[] max) {
        client.zrangeByScore(key, min, max);
        return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrangeByScore(final byte[] key, final byte[] min, final byte[] max, final int offset,
                                     final int count) {
        client.zrangeByScore(key, min, max, offset, count);
        return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrangeByScore(final byte[] key, final double min, final double max) {
        return zrangeByScore(key, RedisProtocol.toByteArray(min), RedisProtocol.toByteArray(max));
    }

    public Set<byte[]> zrangeByScore(final byte[] key, final double min, final double max, final int offset,
                                     final int count) {
        return zrangeByScore(key, RedisProtocol.toByteArray(min), RedisProtocol.toByteArray(max), offset, count);
    }

    public Set<Tuple> zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max) {
        client.zrangeByScoreWithScores(key, min, max);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final byte[] key, final byte[] min, final byte[] max, final int offset,
                                              final int count) {
        client.zrangeByScoreWithScores(key, min, max, offset, count);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Set<Tuple> zrangeByScoreWithScores(final byte[] key, final double min, final double max) {
        return zrangeByScoreWithScores(key, RedisProtocol.toByteArray(min), RedisProtocol.toByteArray(max));
    }

    public Set<Tuple> zrangeByScoreWithScores(final byte[] key, final double min, final double max, final int offset,
                                              final int count) {
        return zrangeByScoreWithScores(key, RedisProtocol.toByteArray(min), RedisProtocol.toByteArray(max), offset, count);
    }

    public Set<Tuple> zrangeWithScores(final byte[] key, final long start, final long end) {
        client.zrangeWithScores(key, start, end);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Long zrank(final byte[] key, final byte[] member) {
        client.zrank(key, member);
        return client.getIntegerReply();
    }

    public Long zlexcount(final byte[] key, final byte[] min, final byte[] max) {
      client.zlexcount(key, min, max);
      return client.getIntegerReply();
    }

    public Set<byte[]> zrangeByLex(final byte[] key, final byte[] min, final byte[] max) {
      client.zrangeByLex(key, min, max);
      return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrangeByLex(final byte[] key, final byte[] min, final byte[] max,
        final int offset, final int count) {
      client.zrangeByLex(key, min, max, offset, count);
      return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min) {
      client.zrevrangeByLex(key, max, min);
      return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrevrangeByLex(byte[] key, byte[] max, byte[] min, int offset, int count) {
      client.zrevrangeByLex(key, max, min, offset, count);
      return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Long zremrangeByLex(final byte[] key, final byte[] min, final byte[] max) {
      client.zremrangeByLex(key, min, max);
      return client.getIntegerReply();
    }

    public Long zrem(final byte[] key, final byte[]... members) {
        client.zrem(key, members);
        return client.getIntegerReply();
    }

    public Long zremrangeByRank(final byte[] key, final long start, final long end) {
        client.zremrangeByRank(key, start, end);
        return client.getIntegerReply();
    }

    public Long zremrangeByScore(final byte[] key, final byte[] start, final byte[] end) {
        client.zremrangeByScore(key, start, end);
        return client.getIntegerReply();
    }

    public Long zremrangeByScore(final byte[] key, final double start, final double end) {
        return zremrangeByScore(key, RedisProtocol.toByteArray(start), RedisProtocol.toByteArray(end));
    }

    public Set<byte[]> zrevrange(final byte[] key, final long start, final long end) {
        client.zrevrange(key, start, end);
        final List<byte[]> members = client.getBinaryMultiBulkReply();
        return new LinkedHashSet<byte[]>(members);
    }

    public Set<byte[]> zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min) {
        client.zrevrangeByScore(key, max, min);
        return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrevrangeByScore(final byte[] key, final byte[] max, final byte[] min, final int offset,
                                        final int count) {
        client.zrevrangeByScore(key, max, min, offset, count);
        return new LinkedHashSet<byte[]>(client.getBinaryMultiBulkReply());
    }

    public Set<byte[]> zrevrangeByScore(final byte[] key, final double max, final double min) {
        return zrevrangeByScore(key, RedisProtocol.toByteArray(max), RedisProtocol.toByteArray(min));
    }

    public Set<byte[]> zrevrangeByScore(final byte[] key, final double max, final double min, final int offset,
                                        final int count) {
        return zrevrangeByScore(key, RedisProtocol.toByteArray(max), RedisProtocol.toByteArray(min), offset, count);
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final byte[] key, final byte[] max, final byte[] min) {
        client.zrevrangeByScoreWithScores(key, max, min);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final byte[] key, final byte[] max, final byte[] min,
                                                 final int offset, final int count) {
        client.zrevrangeByScoreWithScores(key, max, min, offset, count);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final byte[] key, final double max, final double min) {
        return zrevrangeByScoreWithScores(key, RedisProtocol.toByteArray(max), RedisProtocol.toByteArray(min));
    }

    public Set<Tuple> zrevrangeByScoreWithScores(final byte[] key, final double max, final double min,
                                                 final int offset, final int count) {
        return zrevrangeByScoreWithScores(key, RedisProtocol.toByteArray(max), RedisProtocol.toByteArray(min), offset, count);
    }

    public Set<Tuple> zrevrangeWithScores(final byte[] key, final long start, final long end) {
        client.zrevrangeWithScores(key, start, end);
        Set<Tuple> set = getBinaryTupledSet();
        return set;
    }

    public Long zrevrank(final byte[] key, final byte[] member) {
        client.zrevrank(key, member);
        return client.getIntegerReply();
    }

    public Double zscore(final byte[] key, final byte[] member) {
        client.zscore(key, member);
        final String score = client.getBulkReply();
        return (score != null ? new Double(score) : null);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Connection

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Server

    public byte[] dump(final byte[] key) {
        client.dump(key);
        return client.getBinaryBulkReply();
    }

    public String restore(final byte[] key, final long ttl, final byte[] serializedValue) {
        client.restore(key, ttl, serializedValue);
        return client.getStatusCodeReply();
    }

    public Long geoadd(byte[] key, double longitude, double latitude, byte[] member) {
        client.geoadd(key, longitude, latitude, member);
        return client.getIntegerReply();
    }

    public Long geoadd(byte[] key, Map<byte[], GeoCoordinate> memberCoordinateMap) {
        client.geoadd(key, memberCoordinateMap);
        return client.getIntegerReply();
    }

    public Double geodist(byte[] key, byte[] member1, byte[] member2) {
        client.geodist(key, member1, member2);
        String dval = client.getBulkReply();
        return (dval != null ? new Double(dval) : null);
    }

    public Double geodist(byte[] key, byte[] member1, byte[] member2, GeoUnit unit) {
        client.geodist(key, member1, member2, unit);
        String dval = client.getBulkReply();
        return (dval != null ? new Double(dval) : null);
    }

    public List<byte[]> geohash(byte[] key, byte[]... members) {
        client.geohash(key, members);
        return client.getBinaryMultiBulkReply();
    }

    public List<GeoCoordinate> geopos(byte[] key, byte[]... members) {
        client.geopos(key, members);
        return BuilderFactory.GEO_COORDINATE_LIST.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius,
            GeoUnit unit) {
        client.georadius(key, longitude, latitude, radius, unit);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude, double radius, GeoUnit unit,
            GeoRadiusParam param) {
        client.georadius(key, longitude, latitude, radius, unit, param);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit) {
        client.georadiusByMember(key, member, radius, unit);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit,
            GeoRadiusParam param) {
        client.georadiusByMember(key, member, radius, unit, param);
        return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(client.getObjectMultiBulkReply());
    }

    public Long pfadd(final byte[] key, final byte[]... elements) {
      client.pfadd(key, elements);
      return client.getIntegerReply();
    }

    public Long pfcount(final byte[] key) {
      client.pfcount(key);
      return client.getIntegerReply();
    }

    public Long touch(byte[]... keys) {
        client.touch(keys);
        return client.getIntegerReply();
    }

    /**
     * Gets the binary tupled set.
     *
     * @return the binary tupled set
     */
    private Set<Tuple> getBinaryTupledSet() {
        List<byte[]> membersWithScores = client.getBinaryMultiBulkReply();
        Set<Tuple> set = new LinkedHashSet<Tuple>();
        if (membersWithScores == null) {
            return set;
        }

        Iterator<byte[]> iterator = membersWithScores.iterator();
        if (iterator == null) {
            return set;
        }

        while (iterator.hasNext()) {
            set.add(new Tuple(iterator.next(), Double.valueOf(SafeEncoder.encode(iterator.next()))));
        }
        return set;
    }
}


/**
 * Copyright (c) 2011 Jonathan Leibiusky
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.navercorp.redis.cluster.connection;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

import com.navercorp.nbasearc.gcp.VirtualConnection;
import com.navercorp.redis.cluster.connection.RedisProtocol.Command;
import com.navercorp.redis.cluster.gateway.AffinityState;
import com.navercorp.redis.cluster.pipeline.BuilderFactory;
import com.navercorp.redis.cluster.util.RedisInputStream;
import com.navercorp.redis.cluster.util.RedisOutputStream;

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

import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.util.SafeEncoder;

/**
 * The Class RedisConnection.
 *
 * @author jaehong.kim
 */
class RedisConnectionSync implements RedisConnectionImpl {
    private final Logger log = LoggerFactory.getLogger(RedisConnectionSync.class);

    /**
     * The host.
     */
    private String host;

    /**
     * The port.
     */
    private int port = RedisProtocol.DEFAULT_PORT;

    /**
     * The socket.
     */
    private Socket socket;

    /**
     * The output stream.
     */
    private RedisOutputStream outputStream;

    /**
     * The input stream.
     */
    private RedisInputStream inputStream;

    /**
     * The timeout.
     */
    private int timeout = RedisProtocol.DEFAULT_TIMEOUT;

    private int pipelinedCommands = 0;
    private long connectedTime = 0;
    private long disconnectedTime = 0;

    RedisConnectionSync() {
    }

    RedisConnectionSync(String host) {
        this.host = host;
    }

    RedisConnectionSync(String host, int port) {
        this.host = host;
        this.port = port;
    }

    /**
     * Gets the timeout.
     *
     * @return the timeout
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * Sets the timeout.
     *
     * @param timeoutMillisec the new timeout
     */
    public void setTimeout(final int timeoutMillisec) {
        this.timeout = timeoutMillisec;
    }

    public void commitActiveTimeout(final int timeoutMillisec) {
        try {
            socket.setSoTimeout(timeoutMillisec);
        } catch (SocketException ex) {
            throw new JedisException(ex);
        }
    }

    public void rollbackActiveTimeout() {
        try {
            socket.setSoTimeout(this.timeout);
        } catch (SocketException ex) {
            throw new JedisException(ex);
        }
    }

    /**
     * Flush.
     */
    public void flush() {
        try {
            outputStream.flush();
        } catch (IOException e) {
            throw new JedisConnectionException(e);
        }
    }

    /**
     * Send command.
     *
     * @param cmd  the cmd
     * @param args the args
     * @return the redis connection
     */
    public void sendCommand(final Command cmd, final String... args) {
        final byte[][] bargs = new byte[args.length][];
        for (int i = 0; i < args.length; i++) {
            bargs[i] = SafeEncoder.encode(args[i]);
        }
        sendCommand(cmd, bargs);
    }

    /**
     * Send command.
     *
     * @param cmd  the cmd
     * @param args the args
     * @return the redis connection
     */
    public void sendCommand(final Command cmd, final byte[]... args) {
        log.trace("[RedisConnectionSync] Command={} {}", cmd.name(), toStringArgs(args));
        connect();
        RedisProtocol.sendCommand(outputStream, cmd, args);
        this.pipelinedCommands++;
    }

    public String toStringArgs(final byte[]... args) {
        final StringBuilder sb = new StringBuilder();
        for (byte[] arg : args) {
            if (sb.length() != 0) {
                sb.append(" ");
            }
            sb.append(SafeEncoder.encode(arg));
        }

        return sb.toString();
    }

    /**
     * Send command.
     *
     * @param cmd the cmd
     * @return the redis connection
     */
    public void sendCommand(final Command cmd) {
        log.trace("[RedisConnectionSync] Command={}", cmd.name());
        connect();
        RedisProtocol.sendCommand(outputStream, cmd, new byte[0][]);
        this.pipelinedCommands++;
    }

    /**
     * Gets the host.
     *
     * @return the host
     */
    public String getHost() {
        return host;
    }

    /**
     * Sets the host.
     *
     * @param host the new host
     */
    public void setHost(final String host) {
        this.host = host;
    }

    /**
     * Gets the port.
     *
     * @return the port
     */
    public int getPort() {
        return port;
    }

    /**
     * Sets the port.
     *
     * @param port the new port
     */
    public void setPort(final int port) {
        this.port = port;
    }

    /**
     * Connect.
     */
    public void connect() {
        if (!isConnected()) {
            try {
                socket = new Socket();
                // ->@wjw_add
                socket.setReuseAddress(true);
                socket.setKeepAlive(true); // Will monitor the TCP connection is
                // valid
                socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
                // ensure timely delivery of data
                socket.setSoLinger(true, 0); // Control calls close () method,
                // the underlying socket is
                // closed immediately
                // <-@wjw_add

                socket.connect(new InetSocketAddress(host, port), timeout);
                socket.setSoTimeout(timeout);
                outputStream = new RedisOutputStream(socket.getOutputStream());
                inputStream = new RedisInputStream(socket.getInputStream());
                connectedTime = System.currentTimeMillis();
                log.trace("[RedisConnectionSync] Connected {}", toString());
            } catch (IOException ex) {
                throw new JedisConnectionException(ex);
            }
        }
    }

    /**
     * Disconnect.
     */
    public void disconnect() {
        if (isConnected()) {
            try {
                inputStream.close();
                outputStream.close();
                if (!socket.isClosed()) {
                    socket.close();
                }
                disconnectedTime = System.currentTimeMillis();
                log.trace("[RedisConnectionSync] Disconnected {}", toString());
            } catch (IOException ex) {
                throw new JedisConnectionException(ex);
            }
        }
    }

    @Override
    public void passivate() {
        // do nothing
    }

    /**
     * Checks if is connected.
     *
     * @return true, if is connected
     */
    public boolean isConnected() {
        return socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected()
                && !socket.isInputShutdown() && !socket.isOutputShutdown();
    }

    /**
     * Gets the status code reply.
     *
     * @return the status code reply
     */
    public String getStatusCodeReply() {
        flush();
        this.pipelinedCommands--;
        final byte[] resp = (byte[]) RedisProtocol.read(inputStream);
        if (null == resp) {
            return null;
        } else {
            return SafeEncoder.encode(resp);
        }
    }

    /**
     * Gets the bulk reply.
     *
     * @return the bulk reply
     */
    public String getBulkReply() {
        final byte[] result = getBinaryBulkReply();
        if (null != result) {
            return SafeEncoder.encode(result);
        } else {
            return null;
        }
    }

    /**
     * Gets the binary bulk reply.
     *
     * @return the binary bulk reply
     */
    public byte[] getBinaryBulkReply() {
        flush();
        this.pipelinedCommands--;
        return (byte[]) RedisProtocol.read(inputStream);
    }

    /**
     * Gets the integer reply.
     *
     * @return the integer reply
     */
    public Long getIntegerReply() {
        flush();
        this.pipelinedCommands--;
        return (Long) RedisProtocol.read(inputStream);
    }

    /**
     * Gets the multi bulk reply.
     *
     * @return the multi bulk reply
     */
    public List<String> getMultiBulkReply() {
        return BuilderFactory.STRING_LIST.build(getBinaryMultiBulkReply());
    }

    /**
     * Gets the binary multi bulk reply.
     *
     * @return the binary multi bulk reply
     */
    @SuppressWarnings("unchecked")
    public List<byte[]> getBinaryMultiBulkReply() {
        flush();
        this.pipelinedCommands--;
        return (List<byte[]>) RedisProtocol.read(inputStream);
    }

    /**
     * Gets the object multi bulk reply.
     *
     * @return the object multi bulk reply
     */
    @SuppressWarnings("unchecked")
    public List<Object> getObjectMultiBulkReply() {
        flush();
        this.pipelinedCommands--;
        return (List<Object>) RedisProtocol.read(inputStream);
    }

    /**
     * Gets the integer multi bulk reply.
     *
     * @return the integer multi bulk reply
     */
    @SuppressWarnings("unchecked")
    public List<Long> getIntegerMultiBulkReply() {
        flush();
        this.pipelinedCommands--;
        return (List<Long>) RedisProtocol.read(inputStream);
    }

    public List<Object> getAll() {
        return getAll(0);
    }

    public List<Object> getAll(int except) {
        List<Object> all = new ArrayList<Object>();
        flush();
        while (pipelinedCommands > except) {
            try {
                all.add(RedisProtocol.read(inputStream));
            } catch (JedisDataException e) {
                all.add(e);
            }
            pipelinedCommands--;
        }
        return all;
    }

    /*
     * @see java.lang.Object#toString()
     */
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("host=").append(host).append(", ");
        sb.append("port=").append(port).append(", ");
        sb.append("timeout=").append(timeout).append(", ");
        sb.append("connected=").append(connectedTime).append(", ");
        sb.append("disconnected=").append(disconnectedTime).append(", ");
        long elapseTime = 0;
        if (connectedTime > 0 && disconnectedTime > 0) {
            elapseTime = disconnectedTime - connectedTime;
        } else if (connectedTime > 0) {
            elapseTime = System.currentTimeMillis() - connectedTime;
        }
        sb.append("elapse=").append(elapseTime);
        sb.append("}");

        return sb.toString();
    }

    @Override
    public void setVc(VirtualConnection vc) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void allocPc(int hash, AffinityState affinity, boolean pipelineMode) {
        // Do nothing
    }
}


/*
 * 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.command;

import static com.navercorp.nbasearc.confmaster.server.mapping.ArityType.LESS;
import static com.navercorp.nbasearc.confmaster.server.mapping.Param.ArgType.NULLABLE;
import static com.navercorp.nbasearc.confmaster.server.workflow.WorkflowExecutor.TOTAL_INSPECTION;
import static org.apache.log4j.Level.INFO;
import static com.navercorp.nbasearc.confmaster.Constant.*;

import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.KeeperException.NodeExistsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import com.navercorp.nbasearc.confmaster.ConfMaster;
import com.navercorp.nbasearc.confmaster.Constant;
import com.navercorp.nbasearc.confmaster.ConfMasterException.MgmtZooKeeperException;
import com.navercorp.nbasearc.confmaster.config.Config;
import com.navercorp.nbasearc.confmaster.logger.Logger;
import com.navercorp.nbasearc.confmaster.server.ZooKeeperHolder;
import com.navercorp.nbasearc.confmaster.server.cluster.Cluster;
import com.navercorp.nbasearc.confmaster.server.cluster.GatewayLookup;
import com.navercorp.nbasearc.confmaster.server.cluster.ClusterComponentContainer;
import com.navercorp.nbasearc.confmaster.server.cluster.PathUtil;
import com.navercorp.nbasearc.confmaster.server.cluster.PhysicalMachine;
import com.navercorp.nbasearc.confmaster.server.cluster.PhysicalMachineCluster;
import com.navercorp.nbasearc.confmaster.server.leaderelection.LeaderState;
import com.navercorp.nbasearc.confmaster.server.lock.HierarchicalLockHelper;
import com.navercorp.nbasearc.confmaster.server.mapping.CommandMapping;
import com.navercorp.nbasearc.confmaster.server.mapping.LockMapping;
import com.navercorp.nbasearc.confmaster.server.mapping.Param;
import com.navercorp.nbasearc.confmaster.server.workflow.WorkflowExecutor;
import com.navercorp.nbasearc.confmaster.server.workflow.WorkflowLogger;

@Service
public class ConfmasterService {

    @Autowired
    private ApplicationContext context;

    @Autowired
    private ZooKeeperHolder zk;

    @Autowired
    private CommandExecutor commandTemplate;
    @Autowired
    private WorkflowExecutor wfExecutor;

    @Autowired
    private ClusterComponentContainer container;

    @Autowired
    private GatewayLookup gwLookup;
    @Autowired
    private WorkflowLogger workflowLogger;

    @Autowired
    private Config config;

    @Autowired
    private ConfMaster confMaster;

    private boolean clusterRootWatcherRegistered = false;

    @CommandMapping(
            name="help",
            arityType=LESS,
            usage="help <command>",
            requiredState=ConfMaster.LOADING)
    public String help(@Param(type=NULLABLE) String commandName) {
        if (commandName == null) {
            return commandTemplate.getHelp();
        } else {
            return commandTemplate.getUsage(commandName);
        }
    }

    @LockMapping(name="help")
    public void helpLock(HierarchicalLockHelper lockHelper) {
        // Do nothing...
    }

    @CommandMapping(
            name="ping",
            usage="ping",
            requiredState=ConfMaster.LOADING)
    public String ping() {
        return "+PONG";
    }

    @LockMapping(name="ping")
    public void pingLock(HierarchicalLockHelper lockHelper) {
        // Do nothing...
    }

    @CommandMapping(
            name="cm_start",
            requiredState=ConfMaster.READY)
    public String start() {
        if (confMaster.getState() == ConfMaster.RUNNING) {
            return EXCEPTIONMSG_CM_ALREADY_RUNNING;
        }

        confMaster.setState(ConfMaster.RUNNING);

        wfExecutor.perform(TOTAL_INSPECTION);

        return S2C_OK;
    }

    @LockMapping(name="cm_start")
    public void startLock(HierarchicalLockHelper lockHelper) {
        // Do nothing...
    }

    @CommandMapping(
            name="cm_info",
            requiredState=ConfMaster.LOADING)
    public String info() {
        return "{\"state\":\"" + confMaster.getState() + "\"}";
    }

    @LockMapping(name="cm_info")
    public void infoLock(HierarchicalLockHelper lockHelper) {
        // Do nothing...
    }

    public void initialize() throws MgmtZooKeeperException,
            NodeExistsException, NoNodeException {
        // Create default znode structure for ConfMaster.
        try {
            zk.createPersistentZNode(PathUtil.rcRootPath());
        } catch (NodeExistsException e) {
            // Ignore.
        }
        try {
            zk.createPersistentZNode(PathUtil.pmRootPath());
        } catch (NodeExistsException e) {
            // Ignore.
        }
        try {
            zk.createPersistentZNode(PathUtil.clusterRootPath());
        } catch (NodeExistsException e) {
            // Ignore.
        }
        try {
            zk.createPersistentZNode(PathUtil.ccRootPath());
        } catch (NodeExistsException e) {
            // Ignore.
        }
        try {
            zk.createPersistentZNode(PathUtil.rootPathOfLE());
        } catch (NodeExistsException e) {
            // Ignore.
        }

        // Create failure detector znode in order to decide majority used on failover.
        try {
            zk.createPersistentZNode(PathUtil.fdRootPath(), Constant.ZERO_BYTE);
        } catch (NodeExistsException e) {
            // Ignore.
        }

        String heartbeaterPath =
            PathUtil.fdRootPath() + "/" + config.getIp() + ":" + config.getPort();
        zk.createEphemeralZNode(heartbeaterPath);

        gwLookup.initialize();
        workflowLogger.initialize();
    }

    public void release() throws MgmtZooKeeperException {
        final String path = PathUtil.fdRootPath() + "/" + config.getIp() + ":" + config.getPort();
        zk.deleteZNode(path, -1);
        container.relase();
    }

    public void loadAll() throws MgmtZooKeeperException, NoNodeException {
        // Load physical machines.
        List<String> pmList = zk.getChildren(PathUtil.pmRootPath());
        for (String pmName : pmList) {
            final String path = PathUtil.pmPath(pmName);
            byte []d = zk.getData(path, null);
            PhysicalMachine pm = (PhysicalMachine) container.get(path);
            if (pm != null) {
                pm.setPersistentData(d);
            } else {
                pm = new PhysicalMachine(d, pmName);
                container.put(pm.getPath(), pm);
            }

            if (LeaderState.isLeader()) {
                List<String> pmClusterList = zk.getChildren(path, null);
                for (String pmClusterName : pmClusterList) {
                    PhysicalMachineCluster pmCluster = new PhysicalMachineCluster(
                    		zk.getData(PathUtil.pmClusterPath(pmClusterName, pmName), null),
                    		pmClusterName, pmName);
                    container.put(pmCluster.getPath(), pmCluster);
                }
            }
        }

        // Register failure detector watch
        String path = PathUtil.fdRootPath();
        zk.registerChildEventWatcher(path);

        // Register watcher to the root of cluster.
        if (clusterRootWatcherRegistered == false) {
            zk.registerChangedEventWatcher(PathUtil.clusterRootPath());
            zk.registerChildEventWatcher(PathUtil.clusterRootPath());
            clusterRootWatcherRegistered = true;
        }

        // Load clusters
        List<String> children = zk.getChildren(PathUtil.clusterRootPath());
        for (String clusterName : children) {
        	Cluster.loadClusterFromZooKeeper(context, clusterName);
            Logger.info("Load cluster success. {}", clusterName);
            Logger.flush(INFO);
        }
    }

    /*
     * @date 20140812
     * @version 1.2 ~ 1.3
     * @desc Gateway Affinity 적용을 위해 주키퍼의 znode 구조에 변경이 생겼다. 기존 버전에는 이 노드가 없으니까 만들어주자.
     */
    public void updateZNodeDirectoryStructure()
            throws NodeExistsException, MgmtZooKeeperException {
        List<Cluster> clusterList = container.getAllCluster();
        for (Cluster cluster : clusterList) {
            String affinityPath = PathUtil.pathOfGwAffinity(cluster.getName());
            if (zk.isExists(affinityPath)) {
                return;
            }

            String gatewayAffinity = cluster.getGatewayAffinity(context);

            try {
                zk.createPersistentZNode(affinityPath,
                        gatewayAffinity.getBytes(config.getCharset()));
            } catch (UnsupportedEncodingException e) {
                throw new AssertionError(config.getCharset() + " is unkown.");
            }
        }
    }
}


/*
 * Copyright 2011-2014 the original author or authors.
 *
 * 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 java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationUtils;
import org.springframework.util.Assert;

/**
 * Internal base class used by various RedisTemplate XXXOperations implementations.
 *
 * @author Costin Leau
 * @author Jennifer Hickey
 * @author Christoph Strobl
 * @author David Liu
 */
public abstract class AbstractOperations<K, V> {
    // utility methods for the template internal methods

    /**
     * The Class ValueDeserializingRedisCallback.
     */
    abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {

        /**
         * The key.
         */
        private Object key;

        /**
         * Instantiates a new value deserializing redis callback.
         *
         * @param key the key
         */
        public ValueDeserializingRedisCallback(Object key) {
            this.key = key;
        }

        /**
         * Do in redis.
         *
         * @param connection the connection
         * @return the v
         */
        public final V doInRedis(RedisClusterConnection connection) {
            byte[] result = inRedis(rawKey(key), connection);
            return deserializeValue(result);
        }

        /**
         * In redis.
         *
         * @param rawKey     the raw key
         * @param connection the connection
         * @return the byte[]
         */
        protected abstract byte[] inRedis(byte[] rawKey, RedisClusterConnection connection);
    }

    /**
     * The template.
     */
    RedisClusterTemplate<K, V> template;

    /**
     * Instantiates a new abstract operations.
     *
     * @param template the template
     */
    AbstractOperations(RedisClusterTemplate<K, V> template) {
        this.template = template;
    }

    /**
     * Key serializer.
     *
     * @return the redis serializer
     */
    RedisSerializer keySerializer() {
        return template.getKeySerializer();
    }

    /**
     * Value serializer.
     *
     * @return the redis serializer
     */
    RedisSerializer valueSerializer() {
        return template.getValueSerializer();
    }

    /**
     * Hash key serializer.
     *
     * @return the redis serializer
     */
    RedisSerializer hashKeySerializer() {
        return template.getHashKeySerializer();
    }

    /**
     * Hash value serializer.
     *
     * @return the redis serializer
     */
    RedisSerializer hashValueSerializer() {
        return template.getHashValueSerializer();
    }

    /**
     * String serializer.
     *
     * @return the redis serializer
     */
    RedisSerializer stringSerializer() {
        return template.getStringSerializer();
    }

    /**
     * Execute.
     *
     * @param <T>      the generic type
     * @param callback the callback
     * @param b        the b
     * @return the t
     */
    <T> T execute(RedisCallback<T> callback, boolean b) {
        return template.execute(callback, b);
    }

    /**
     * Gets the operations.
     *
     * @return the operations
     */
    public RedisOperations<K, V> getOperations() {
        return template;
    }

    /**
     * Raw key.
     *
     * @param key the key
     * @return the byte[]
     */
    @SuppressWarnings("unchecked")
    byte[] rawKey(Object key) {
        Assert.notNull(key, "non null key required");
        return keySerializer().serialize(key);
    }

    /**
     * Raw string.
     *
     * @param key the key
     * @return the byte[]
     */
    byte[] rawString(String key) {
        return stringSerializer().serialize(key);
    }

    /**
     * Raw value.
     *
     * @param value the value
     * @return the byte[]
     */
    @SuppressWarnings("unchecked")
    byte[] rawValue(Object value) {
        return valueSerializer().serialize(value);
    }

    /**
     * Raw hash key.
     *
     * @param <HK>    the generic type
     * @param hashKey the hash key
     * @return the byte[]
     */
    @SuppressWarnings("unchecked")
    <HK> byte[] rawHashKey(HK hashKey) {
        Assert.notNull(hashKey, "non null hash key required");
        return hashKeySerializer().serialize(hashKey);
    }

    /**
     * Raw hash value.
     *
     * @param <HV>  the generic type
     * @param value the value
     * @return the byte[]
     */
    @SuppressWarnings("unchecked")
    <HV> byte[] rawHashValue(HV value) {
        return hashValueSerializer().serialize(value);
    }

    /**
     * Raw keys.
     *
     * @param key      the key
     * @param otherKey the other key
     * @return the byte[][]
     */
    byte[][] rawKeys(K key, K otherKey) {
        final byte[][] rawKeys = new byte[2][];

        rawKeys[0] = rawKey(key);
        rawKeys[1] = rawKey(key);
        return rawKeys;
    }

    /**
     * Raw keys.
     *
     * @param keys the keys
     * @return the byte[][]
     */
    byte[][] rawKeys(Collection<K> keys) {
        return rawKeys(null, keys);
    }

    /**
     * Raw keys.
     *
     * @param key  the key
     * @param keys the keys
     * @return the byte[][]
     */
    byte[][] rawKeys(K key, Collection<K> keys) {
        final byte[][] rawKeys = new byte[keys.size() + (key != null ? 1 : 0)][];

        int i = 0;

        if (key != null) {
            rawKeys[i++] = rawKey(key);
        }

        for (K k : keys) {
            rawKeys[i++] = rawKey(k);
        }

        return rawKeys;
    }

    /**
     * Deserialize values.
     *
     * @param rawValues the raw values
     * @return the sets the
     */
    @SuppressWarnings("unchecked")
    Set<V> deserializeValues(Set<byte[]> rawValues) {
        return SerializationUtils.deserialize(rawValues, valueSerializer());
    }

    /**
     * Deserialize tuple values.
     *
     * @param rawValues the raw values
     * @return the sets the
     */
    @SuppressWarnings("unchecked")
    Set<TypedTuple<V>> deserializeTupleValues(Set<Tuple> rawValues) {
        Set<TypedTuple<V>> set = new LinkedHashSet<TypedTuple<V>>(rawValues.size());
        for (Tuple rawValue : rawValues) {
            set.add(new DefaultTypedTuple(valueSerializer().deserialize(rawValue.getValue()), rawValue.getScore()));
        }
        return set;
    }

    /**
     * Deserialize values.
     *
     * @param rawValues the raw values
     * @return the list
     */
    @SuppressWarnings("unchecked")
    List<V> deserializeValues(List<byte[]> rawValues) {
        return SerializationUtils.deserialize(rawValues, valueSerializer());
    }

    /**
     * Deserialize hash keys.
     *
     * @param <T>     the generic type
     * @param rawKeys the raw keys
     * @return the sets the
     */
    @SuppressWarnings("unchecked")
    <T> Set<T> deserializeHashKeys(Set<byte[]> rawKeys) {
        return SerializationUtils.deserialize(rawKeys, hashKeySerializer());
    }

    /**
     * Deserialize hash values.
     *
     * @param <T>       the generic type
     * @param rawValues the raw values
     * @return the list
     */
    @SuppressWarnings("unchecked")
    <T> List<T> deserializeHashValues(List<byte[]> rawValues) {
        return SerializationUtils.deserialize(rawValues, hashValueSerializer());
    }

    /**
     * Deserialize hash map.
     *
     * @param <HK>    the generic type
     * @param <HV>    the generic type
     * @param entries the entries
     * @return the map
     */
    @SuppressWarnings("unchecked")
    <HK, HV> Map<HK, HV> deserializeHashMap(Map<byte[], byte[]> entries) {
        // connection in pipeline/multi mode
        if (entries == null) {
            return null;
        }

        Map<HK, HV> map = new LinkedHashMap<HK, HV>(entries.size());

        for (Map.Entry<byte[], byte[]> entry : entries.entrySet()) {
            map.put((HK) deserializeHashKey(entry.getKey()), (HV) deserializeHashValue(entry.getValue()));
        }

        return map;
    }

    /**
     * Deserialize hash list.
     *
     * @param <HK>    the generic type
     * @param <HV>    the generic type
     * @param entries the entries
     * @return the map
     */
    <HK, HV> Map<HK, List<HV>> deserializeHashList(Map<byte[], List<byte[]>> entries) {
        // connection in pipeline/multi mode
        if (entries == null) {
            return null;
        }

        Map<HK, List<HV>> map = new LinkedHashMap<HK, List<HV>>(entries.size());

        for (Map.Entry<byte[], List<byte[]>> entry : entries.entrySet()) {
            map.put((HK) deserializeHashKey(entry.getKey()), (List<HV>) deserializeHashValues(entry.getValue()));
        }

        return map;
    }

    /**
     * Deserialize hash set.
     *
     * @param <HK>    the generic type
     * @param <HV>    the generic type
     * @param entries the entries
     * @return the map
     */
    <HK, HV> Map<HK, Set<HV>> deserializeHashSet(Map<byte[], Set<byte[]>> entries) {
        // connection in pipeline/multi mode
        if (entries == null) {
            return null;
        }

        Map<HK, Set<HV>> map = new LinkedHashMap<HK, Set<HV>>(entries.size());

        for (Map.Entry<byte[], Set<byte[]>> entry : entries.entrySet()) {
            map.put((HK) deserializeHashKey(entry.getKey()), (Set<HV>) deserializeValues(entry.getValue()));
        }

        return map;
    }

    /**
     * Deserialize key.
     *
     * @param value the value
     * @return the k
     */
    @SuppressWarnings("unchecked")
    K deserializeKey(byte[] value) {
        return (K) keySerializer().deserialize(value);
    }

    /**
     * Deserialize value.
     *
     * @param value the value
     * @return the v
     */
    @SuppressWarnings("unchecked")
    V deserializeValue(byte[] value) {
        return (V) valueSerializer().deserialize(value);
    }

    /**
     * Deserialize string.
     *
     * @param value the value
     * @return the string
     */
    String deserializeString(byte[] value) {
        return (String) stringSerializer().deserialize(value);
    }

    /**
     * Deserialize hash key.
     *
     * @param <HK>  the generic type
     * @param value the value
     * @return the hK
     */
    @SuppressWarnings({"unchecked"})
    <HK> HK deserializeHashKey(byte[] value) {
        return (HK) hashKeySerializer().deserialize(value);
    }

    /**
     * Deserialize hash value.
     *
     * @param <HV>  the generic type
     * @param value the value
     * @return the hV
     */
    @SuppressWarnings("unchecked")
    <HV> HV deserializeHashValue(byte[] value) {
        return (HV) hashValueSerializer().deserialize(value);
    }
}


/*
 * 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.gateway;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

/**
 * The Class GatewayAddress.
 *
 * @author jaehong.kim
 */
public class GatewayAddress {

    /**
     * The name.
     */
    private final String name;

    /**
     * The host.
     */
    private final String host;

    /**
     * The port.
     */
    private final int port;

    private int id;

    /**
     * As list.
     *
     * @param addresses the addresses
     * @return the list
     */
    public static List<GatewayAddress> asList(final String addresses) {
        if (addresses == null) {
            throw new IllegalArgumentException("addresses must not be null");
        }

        List<GatewayAddress> list = new ArrayList<GatewayAddress>();
        final String[] tokens = addresses.split(",");
        if (tokens == null) {
            return list;
        }

        int id = 1;
        for (String token : tokens) {
            list.add(new GatewayAddress(id++, token));
        }

        return list;
    }

    /**
     * As list.
     *
     * @param addressList the addresses
     * @return the list
     */
    public static List<GatewayAddress> asList(final List<String> addressList) {
        if (addressList == null || addressList.size() == 0) {
            throw new IllegalArgumentException("addresses must not be null");
        }

        List<GatewayAddress> list = new ArrayList<GatewayAddress>();

        int id = 1;
        for (String address : addressList) {
            list.add(new GatewayAddress(id++, address));
        }

        return list;
    }

    /**
     * As list from domain.
     *
     * @param domainAddress the domain address
     * @return the list
     */
    public static List<GatewayAddress> asListFromDomain(final String domainAddress) {
        if (domainAddress == null) {
            throw new IllegalArgumentException("domain address must not be null");
        }

        GatewayAddress domain = new GatewayAddress(0, domainAddress);
        InetAddress[] addresses;
        try {
            addresses = InetAddress.getAllByName(domain.getHost());
        } catch (Exception e) {
            throw new IllegalArgumentException("invalid domain '" + domain + "' " + e.getMessage());
        }

        List<GatewayAddress> list = new ArrayList<GatewayAddress>();
        int id = 1;
        for (InetAddress address : addresses) {
            list.add(new GatewayAddress(id++, address.getHostAddress(), domain.getPort()));
        }

        return list;
    }

    /**
     * Instantiates a new gateway address.
     *
     * @param id the id of gateway
     * @param address the address of gateway
     */
    public GatewayAddress(final int id, final String address) {
        this.id = id;
        this.name = address;
        this.host = parseHost(address);
        this.port = parsePort(address);
    }

    /**
     * Instantiates a new gateway address.
     *
     * @param id the id of gateway
     * @param host the host
     * @param port the port
     */
    public GatewayAddress(final int id, final String host, final int port) {
        this.id = id;
        this.name = host + ":" + port;
        this.host = host;
        this.port = port;
    }

    /**
     * Parses the host.
     *
     * @param address the address
     * @return the string
     */
    public static String parseHost(final String address) {
        int ep = address.indexOf(":");
        if (ep == -1 || ep == 0) {
            throw new IllegalArgumentException("invalid address '" + address + "'");
        }
        return address.substring(0, ep).trim();
    }

    /**
     * Parses the port.
     *
     * @param address the address
     * @return the int
     */
    public static int parsePort(final String address) {
        int sp = address.indexOf(":");
        if (sp == -1 && sp + 1 >= address.length()) {
            throw new IllegalArgumentException("not found port '" + address + "'");
        }

        try {
            return Integer.parseInt(address.substring(sp + 1, address.length()).trim());
        } catch (NumberFormatException nfe) {
            throw new IllegalArgumentException("bad port number '" + address + "'");
        }
    }

    /**
     * Gets the host.
     *
     * @return the host
     */
    public String getHost() {
        return host;
    }

    /**
     * Gets the port.
     *
     * @return the port
     */
    public int getPort() {
        return port;
    }

    /**
     * Gets the name.
     *
     * @return the name
     */
    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    /*
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return this.name;
    }
}