package cn.uncode.schedule.zk;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

import org.apache.commons.lang3.StringUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.uncode.schedule.core.Version;


/**
 * 
 * @author juny.ye
 *
 */
public class ZKManager{
    
    private static transient Logger log = LoggerFactory.getLogger(ZKManager.class);
    private ZooKeeper zk;
    private List<ACL> acl = new ArrayList<ACL>();
    private Properties properties;

    private enum keys {
        zkConnectString, rootPath, userName, password, zkSessionTimeout, autoRegisterTask, ipBlacklist
    }

    public ZKManager(Properties aProperties) throws Exception{
        this.properties = aProperties;
        this.connect();
    }
    
    /**
     * 重连zookeeper
     * @throws Exception
     */
    private synchronized void  reConnection() throws Exception{
        if (this.zk != null) {
            this.zk.close();
            this.zk = null;
            this.connect() ;
        }
    }
    
    private void connect() throws Exception {
        CountDownLatch connectionLatch = new CountDownLatch(1);
        createZookeeper(connectionLatch);
        connectionLatch.await();
    }
    
    private void createZookeeper(final CountDownLatch connectionLatch) throws Exception {
        zk = new ZooKeeper(this.properties.getProperty(keys.zkConnectString
                .toString()), Integer.parseInt(this.properties
                .getProperty(keys.zkSessionTimeout.toString())),
                new Watcher() {
                    public void process(WatchedEvent event) {
                        sessionEvent(connectionLatch, event);
                    }
                });
        String authString = this.properties.getProperty(keys.userName.toString())
                + ":"+ this.properties.getProperty(keys.password.toString());
        zk.addAuthInfo("digest", authString.getBytes());
        acl.clear();
        acl.add(new ACL(ZooDefs.Perms.ALL, new Id("digest",
                DigestAuthenticationProvider.generateDigest(authString))));
        acl.add(new ACL(ZooDefs.Perms.READ, Ids.ANYONE_ID_UNSAFE));
    }
    
    private void sessionEvent(CountDownLatch connectionLatch, WatchedEvent event) {
        if (event.getState() == KeeperState.SyncConnected) {
            log.info("收到ZK连接成功事件!");
            connectionLatch.countDown();
        } else if (event.getState() == KeeperState.Expired) {
            log.error("会话超时,等待重新建立ZK连接...");
            try {
                reConnection();
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
        } // Disconnected:Zookeeper会自动处理Disconnected状态重连
    }
    
    public void close() throws InterruptedException {
        log.info("关闭zookeeper连接");
        this.zk.close();
    }
    
    String getRootPath(){
        return this.properties.getProperty(keys.rootPath.toString());
    }

    public List<String> getIpBlacklist(){
    	List<String> ips = new ArrayList<String>();
    	String list = this.properties.getProperty(keys.ipBlacklist.toString());
    	if(StringUtils.isNotEmpty(list)){
    		ips = Arrays.asList(list.split(","));
    	}
    	return ips;
    }
    public String getConnectStr(){
        return this.properties.getProperty(keys.zkConnectString.toString());
    }

    boolean isAutoRegisterTask(){
    	String autoRegisterTask = this.properties.getProperty(keys.autoRegisterTask.toString());
    	if(StringUtils.isNotEmpty(autoRegisterTask)){
    		return Boolean.valueOf(autoRegisterTask);
    	}
        return true;
    }

    public boolean checkZookeeperState() throws Exception{
        return zk != null && zk.getState() == States.CONNECTED;
    }

    public void initial() throws Exception {
        //当zk状态正常后才能调用
        checkParent(zk,this.getRootPath());
        if(zk.exists(this.getRootPath(), false) == null){
            ZKTools.createPath(zk, this.getRootPath(), CreateMode.PERSISTENT, acl);
            //设置版本信息
            zk.setData(this.getRootPath(),Version.getVersion().getBytes(),-1);
        }else{
            //先校验父亲节点,本身是否已经是schedule的目录
            byte[] value = zk.getData(this.getRootPath(), false, null);
            if(value == null){
                zk.setData(this.getRootPath(),Version.getVersion().getBytes(),-1);
            }else{
                String dataVersion = new String(value);
                if(!Version.isCompatible(dataVersion)){
                    throw new Exception("TBSchedule程序版本 "+ Version.getVersion() +" 不兼容Zookeeper中的数据版本 " + dataVersion );
                }
                log.info("当前的程序版本:" + Version.getVersion() + " 数据版本: " + dataVersion);
            }
        }
    }
    private static void checkParent(ZooKeeper zk, String path) throws Exception {
        String[] list = path.split("/");
        String zkPath = "";
        for (int i =0;i< list.length -1;i++){
            String str = list[i];
            if (StringUtils.isNotEmpty(str)) {
                zkPath = zkPath + "/" + str;
                if (zk.exists(zkPath, false) != null) {
                    byte[] value = zk.getData(zkPath, false, null);
                    if(value != null && new String(value).contains("uncode-schedule-")){
                        throw new Exception("\"" + zkPath +"\"  is already a schedule instance's root directory, its any subdirectory cannot as the root directory of others");
                    }
                }
            }
        }
    }   
    
    List<ACL> getAcl() {
        return acl;
    }

    ZooKeeper getZooKeeper() throws Exception {
        if(!this.checkZookeeperState()){
            reConnection();
        }
        return this.zk;
    }
    
}