package h2o.common.cluster;

import h2o.common.Mode;
import h2o.common.exception.ExceptionUtil;
import h2o.common.thirdparty.redis.JedisCallBack;
import h2o.common.thirdparty.redis.JedisProvider;
import h2o.common.util.id.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class ClusterLock {

    private static final Logger log = LoggerFactory.getLogger( ClusterLock.class.getName() );

    private static final long DEFAULT_TIME_OUT = 5000;

    private final String id;

    private final JedisProvider jedisProvider;

    private final JedisCommands _jedis;

    private final String key;

    private final int expire;

    volatile boolean run = true;

    private volatile boolean locked = false;


    public ClusterLock( JedisProvider jedisProvider, String key, int expire ) {
        this( uuid(), jedisProvider , key , expire  );
    }

    public ClusterLock( String id, JedisProvider jedisProvider, String key, int expire ) {
        this.id = id;
        this.jedisProvider = jedisProvider;
        this._jedis = null;
        this.key = "H2OClusterLock_" + key;
        this.expire = expire;
    }


    public ClusterLock( JedisCommands jedis, String key, int expire ) {
        this( uuid(), jedis , key , expire  );
    }

    public ClusterLock( String id, JedisCommands jedis, String key, int expire ) {
        this.id = id;
        this.jedisProvider = null;
        this._jedis = jedis;
        this.key = "H2OClusterLock_" + key;
        this.expire = expire;
    }





    private void tryLock( JedisCommands jedis ) {

        if ( "OK".equals(jedis.set( key, id ,  "NX" , "EX" , expire ) ) ) {

            locked = true;

        } else if ( id.equals(jedis.get(key) ) )  {

            jedis.expire(key, expire);
            locked = true;

        } else {

            locked = false;

        }

    }

    public boolean tryLock() {

        try {

            if ( jedisProvider == null ) {

                tryLock( this._jedis );

            } else jedisProvider.callback(new JedisCallBack<Void>() {

                @Override
                public Void doCallBack( JedisCommands jedis) throws Exception {

                    tryLock( jedis );

                    return null;

                }

            });

        } catch (Exception e) {

            e.printStackTrace();
            log.error( "" , e);

            locked = false;

            throw ExceptionUtil.toRuntimeException( e );

        }

        return locked;

    }


    public boolean lock() {
        return lock( DEFAULT_TIME_OUT );
    }

    public boolean lock( long timeout ) {

        long t = System.currentTimeMillis();

        do {

            if ( tryLock() ) {
                return true;
            }

            try {

                TimeUnit.MILLISECONDS.sleep(50 );

            } catch (InterruptedException e) {
            }

        } while ( System.currentTimeMillis() - t < timeout);

        return false;

    }



    private void unlockUNLUA( JedisCommands jedis ) {

        if ( id.equals( jedis.get(key) ) ) {
            jedis.del(key);
        }

    }

    private boolean UNSUPPORT_LUA = Mode.isUserMode("REDIS_UNSUPPORT_LUA");

    private static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    private void unlock( JedisCommands jedis ) {

        if ( UNSUPPORT_LUA ) {

            unlockUNLUA(jedis);

        } else  try {

                List<String> keys = Collections.singletonList( key );
                List<String> values = Collections.singletonList( id );

                Long result = 0L;
                if (jedis instanceof JedisCluster) {

                    result = (Long) (( JedisClusterScriptingCommands) jedis).eval(UNLOCK_LUA, keys, values);

                } else if ( jedis instanceof Jedis) {

                    result = (Long) (( ScriptingCommands ) jedis).eval(UNLOCK_LUA, keys, values);

                }

                if ( result == null || result.longValue() != 1L ) {
                    unlockUNLUA(jedis);
                }

        } catch (Throwable e) {

            log.error( "" , e);

            unlockUNLUA(jedis);

        }
    }





    public void unlock() {

        locked = false;

        try {

            if ( jedisProvider == null ) {

                unlock( this._jedis );

            } else jedisProvider.callback(new JedisCallBack<Void>() {

                @Override
                public Void doCallBack( JedisCommands jedis ) throws Exception {

                    unlock( jedis );

                    return null;

                }

            });

        } catch ( Exception e ) {

            e.printStackTrace();
            log.error( "" , e);

        }

    }

    public boolean isLocked() {
        return run && locked;
    }


    private static synchronized String uuid() {
        return UuidUtil.getUuid();
    }

}

@@ -185,24 +185,64 @@
 isProvider;%0A
+import h2o.common.util.id.RandomString;%0A
 import h2o.c
@@ -5369,16 +5369,62 @@
 etUuid()
+ + %22-%22 + new RandomString().makeNumberCode(10)
 ;%0A    %7D%0A

修改id生成方式，减少重复可能性