package com.xj.zk;

import com.xj.zk.listener.Listener;
import com.xj.zk.listener.StateListener;
import com.xj.zk.lock.HALock;
import com.xj.zk.lock.Lock;
import com.xj.zk.lock.SimpleLock;
import com.xj.zk.watcher.WatcherProcess;
import com.xj.zk.watcher.ZkWatcher;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Author: xiajun
 * Date: 14/5/20
 * zookeeper 客户端
 */
public class ZkClient {
    private final static Logger LOGGER = LoggerFactory.getLogger(ZkClient.class);
    private final Semaphore connLock = new Semaphore(0);//连接同步锁
    private String hosts;//zookeeper  地址列表
    private int sessionTimeout = 3000;//会话超时时间
    private int connTimeout;//连接超时
    private volatile boolean isConnection = false;//是否连接成功
    private ZooKeeper zk;
    private ZkWatcher watcher;
    private WatcherProcess process;

    /**
     * 创建zookeeper客户端
     *
     * @param hosts zookeeper服务地址 10.0.1.121:2181,10.0.1.131:2181
     */
    public ZkClient(String hosts) throws ZkClientException {
        this(hosts, 3000, 3000);
    }

    /**
     * 创建zookeeper客户端
     *
     * @param hosts          zookeeper服务地址 10.0.1.121:2181,10.0.1.131:2181
     * @param sessionTimeout 会话超时时间
     * @param connTimeout    连接超时时间
     */
    public ZkClient(String hosts, int sessionTimeout, int connTimeout) throws ZkClientException {
        this(hosts, sessionTimeout, connTimeout, 1);
    }

    /**
     * @param hosts             zookeeper服务地址 10.0.1.121:2181,10.0.1.131:2181
     * @param sessionTimeout    会话超时时间
     * @param connTimeout       连接超时时间
     * @param watcherThreadSize 处理watcher的线程数
     * @throws ZkClientException
     */
    public ZkClient(String hosts, int sessionTimeout, int connTimeout, int watcherThreadSize) throws ZkClientException {
        this.hosts = hosts;
        this.sessionTimeout = sessionTimeout;
        this.connTimeout = connTimeout;
        watcher = new ZkWatcher(connLock, this);
        this.process = new WatcherProcess(this, watcherThreadSize);
        this.connection();
    }

    /**
     * 获取节点下的数据
     *
     * @param path 节点路径
     * @return
     */
    public byte[] getData(String path) throws ZkClientException {
        return getData(path, false);
    }

    /**
     * 获取节点下的数据
     *
     * @param path    节点路径
     * @param watcher 是否对该节点进行数据变动监听(只能收到一次变动消息)
     * @return
     * @throws ZkClientException
     */
    public byte[] getData(String path, boolean watcher) throws ZkClientException {
        this.checkStatus();
        try {
            return this.zk.getData(path, watcher, null);
        } catch (Exception e) {
            throw new ZkClientException("getData node " + path, e);
        }
    }

    /**
     * 插入数据
     *
     * @param path
     * @param data
     * @throws ZkClientException
     */
    public void setData(String path, byte[] data) throws ZkClientException {
        this.checkStatus();
        try {
            this.zk.setData(path, data, -1);
        } catch (Exception e) {
            throw new ZkClientException("setData node " + path, e);
        }
    }

    /**
     * 获取child节点信息
     *
     * @param path
     * @throws ZkClientException
     */
    protected List<String> getChild(String path) throws ZkClientException {
        return this.getChild(path, false);
    }

    /**
     * 获取child节点信息
     *
     * @param path
     * @throws ZkClientException
     */
    public List<String> getChild(String path, boolean watcher) throws ZkClientException {
        this.checkStatus();
        try {
            return this.zk.getChildren(path, watcher);
        } catch (Exception e) {
            throw new ZkClientException("getChildren node " + path, e);
        }
    }

    /**
     * 创建节点
     * 不支持多层节点创建
     *
     * @param path 节点路径
     * @param data 节点数据
     * @param mode 节点类型 CreateMode.PERSISTENT 永久节点,CreateMode.EPHEMERAL临时节点
     */
    public String create(String path, byte[] data, CreateMode mode) throws ZkClientException {
        this.checkStatus();
        String createNode;
        try {
            createNode = this.zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, mode);
        } catch (Exception e) {
            throw new ZkClientException("create node " + path + ", mode=" + mode.name(), e);
        }
        return createNode;
    }

    /**
     * 创建一个临时节点
     * 不支持多层节点创建
     *
     * @param path     节点路径
     * @param data     节点数据
     * @param reCreate true:session超时重连后自动创建该节点,false:创建普通的临时节点
     */
    public void create(String path, byte[] data, boolean reCreate) throws ZkClientException {
        this.create(path, data, CreateMode.EPHEMERAL);
        if (reCreate) {
            this.process.stubborn(path, data);
        }
    }

    /**
     * 创建一个永久节点
     * 不支持多层节点创建
     *
     * @param path 路径
     * @param data 数据
     * @throws ZkClientException
     */
    public void create(String path, byte[] data) throws ZkClientException {
        this.create(path, data, CreateMode.PERSISTENT);
    }

    /**
     * 支持多层节点创建
     *
     * @param path
     * @param mode
     * @throws ZkClientException
     */
    public String create(String path, CreateMode mode) throws ZkClientException {
        if (path != null && !path.trim().equals("")) {
            if (exists(path)) {
                throw new ZkClientException("Node path: " + path + " already exists.");
            }
            String[] paths = path.trim().split("/");
            String p = "";
            for (int i = 0; i < paths.length; i++) {
                String s = paths[i];
                if (s != null && !s.equals("")) {
                    p += "/" + s;
                    if (!exists(p)) {
                        if (i < paths.length - 1) {
                            this.create(p, new byte[1], CreateMode.PERSISTENT);
                        } else {
                            this.create(p, new byte[1], mode);
                        }
                    }
                }
            }
        }
        return path;
    }

    /**
     * 删除节点
     *
     * @param path 路径
     * @throws ZkClientException
     */
    public void delete(String path) throws ZkClientException {
        this.checkStatus();
        try {
            this.zk.delete(path, -1);
        } catch (Exception e) {
            throw new ZkClientException("delete node " + path, e);
        }
    }

    /**
     * 监听节点的数据变化
     *
     * @param listener
     * @param path
     */
    public void listenData(String path, Listener listener) throws ZkClientException {
        this.listen(path, listener, false, false);
    }

    /**
     * 取消对节点的数据变化监听
     *
     * @param path
     * @throws ZkClientException
     */
    public void unlistenData(String path) throws ZkClientException {
        this.unlisten(path, false, false);
    }

    /**
     * 监听节点的子节点变化
     *
     * @param path
     * @param listener
     * @throws ZkClientException
     */
    public void listenChild(String path, Listener listener) throws ZkClientException {
        this.listen(path, listener, true, false);
    }

    /**
     * 取消对节点的子节点变化监听
     *
     * @param path
     * @throws ZkClientException
     */
    public void unlintenChild(String path) throws ZkClientException {
        this.unlisten(path, true, false);
    }

    /**
     * 监听孩子节点数据变化
     *
     * @param path     父节点地址
     * @param listener 监听器
     */
    public void listenChildData(String path, Listener listener) {
        this.listen(path, listener, false, true);
    }

    /**
     * 监听孩子节点数据变化
     *
     * @param path 父节点地址
     */
    public void unlistenChildData(String path) {
        this.unlisten(path, false, true);
    }


    /**
     * 监听zookeeper信息变化
     *
     * @param path      节点地址
     * @param listener  监听器
     * @param child     true为监听子节点变化,false 为监听节点数据变化
     * @param childData true为监听孩子节点数据变化
     * @throws ZkClientException
     */
    private void listen(String path, Listener listener, boolean child, boolean childData) throws ZkClientException {
        this.checkStatus();
        if (!this.exists(path)) {
            throw new NullPointerException("listen path " + path + "  not found.");
        }
        if (this.process != null) {
            this.process.listen(path, listener, child, childData);
        } else {
            LOGGER.warn("not found WatcherProcess instance,Listening can't be triggered.");
        }
    }

    /**
     * 解除节点监听
     *
     * @param path  节点地址
     * @param child true表示监听节点的子节点变化
     * @throws ZkClientException
     */
    private void unlisten(String path, boolean child, boolean childData) throws ZkClientException {
        this.checkStatus();
        if (!this.exists(path)) {
            throw new NullPointerException("listen path " + path + "  not found.");
        }
        if (this.process != null) {
            this.process.unlisten(path, child, childData);
        }
    }

    /**
     * 目前只支持2种zookeeper状态
     * 1. KeeperState.Expired session 超时
     * 2. KeeperState.Disconnected 连接断开时
     *
     * @param state 监听的状态
     */
    public void listenState(KeeperState state, StateListener listener) {
        if (state.getIntValue() == KeeperState.Expired.getIntValue()) {
            process.listenState(state, listener);
        } else if (state.getIntValue() == KeeperState.Disconnected.getIntValue()) {
            process.listenState(state, listener);
        } else {
            throw new ZkClientException("Listener state not is Expired or Disconnected.");
        }
    }

    /**
     * 取消状态监听
     *
     * @param state
     */
    public void unlistenState(KeeperState state) {
        process.nulistenState(state);
    }

    /**
     * 同步阻塞连接zookeeper
     *
     * @throws ZkClientException
     */
    private synchronized void connection() throws ZkClientException {
        if (this.checkConnection()) {
            throw new ZkClientException("Has been connected to the server, please do not repeat connection. host:" + hosts);
        }
        try {
            connLock.drainPermits();
            zk = new ZooKeeper(hosts, sessionTimeout, watcher);
        } catch (IOException e) {
            throw new ZkClientException("Connect zookeeper fail, hosts=" + hosts, e);
        }
        try {
            watcher.setWatcherProcess(process);
            boolean isConn = connLock.tryAcquire(connTimeout, TimeUnit.MILLISECONDS);
            if (!isConn) {
                zk.close();
                LOGGER.warn("zookeeper connection timeout. host:{}", hosts);
                throw new ZkClientException("zookeeper connection timeout. host: " + hosts);
            }
        } catch (InterruptedException e) {
            throw new ZkClientException("connection lock exceprion.", e);
        }
    }

    /**
     * 重连zookeeper
     *
     * @throws ZkClientException
     */
    public void reconnection() throws ZkClientException {
        this.connection();
        this.process.relisten();
    }

    /**
     * 关闭客户端
     *
     * @throws ZkClientException
     */
    public void close() throws ZkClientException {
        try {
            if (zk != null && zk.getState().isConnected()) {
                zk.close();
            }
        } catch (InterruptedException e) {
            throw new ZkClientException("close zookeeper client error.", e);
        }
    }

    /**
     * 判断节点是否存在
     *
     * @param path
     * @return
     * @throws ZkClientException
     */
    public boolean exists(String path) throws ZkClientException {
        this.checkStatus();
        try {
            return this.zk.exists(path, false) != null;
        } catch (Exception e) {
            throw new ZkClientException("exists node " + path, e);
        }
    }

    /**
     * 检查zookeeper是否处于连接状态
     *
     * @return
     */
    public boolean checkConnection() {
        boolean conn = false;
        if (zk != null) {
            conn = zk.getState().isConnected();
        }
        return conn && this.isConnection();
    }

    /**
     * 检查zookeeper连接状态
     *
     * @return
     * @throws ZkClientException
     */
    public boolean checkStatus() throws ZkClientException {
        if (zk == null) {
            throw new ZkClientException("Not connected to the zookeeper server,host=" + hosts + ",invoking this.connect().");
        }
        if (zk.getState().isAlive() && this.isConnection()) {
            return true;
        }
        throw new ZkClientException("Not connected to the zookeeper server,host=" + hosts + ",state: " + zk.getState());
    }

    /**
     * 设置zookeeper连接状态
     *
     * @param isConnection
     */
    public void setIsConnection(boolean isConnection) {
        this.isConnection = isConnection;
    }

    public boolean isConnection() {
        return isConnection;
    }

    /**
     * 获取锁对象
     *
     * @param lockPath
     * @return
     */
    public Lock getLock(String lockPath) {
        SimpleLock lock = SimpleLock.getInstance();
        lock.init(this, lockPath);
        return lock;
    }

    /**
     * 获取主从锁,获取到锁的进程将一直持有该锁
     * 直到该进程死掉,其它进程才能重新争夺该锁
     *
     * @param lockPath 锁路径 建议使用相对路径
     * @return 锁对象
     */
    public Lock getHaLock(String lockPath) {
        HALock lock = new HALock(this, lockPath);
        return lock;
    }

    /**
     * 获取原生的zookeeper客户端对象
     *
     * @return
     */
    public ZooKeeper getZookeeper() {
        return zk;
    }
}