package zookeeper;

/**
 * Created by weizhenyi on 2017/6/25.
 */
import Config.Config;
import callback.WatcherCallBack;
import callback.ClusterStateCallback;
import cache.LeafCache;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.PathUtils;
import utils.Utils;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

public class DistributedClusterStat implements ClusterStat{
	private static Logger LOG = LoggerFactory.getLogger(DistributedClusterStat.class);
	private Zookeeper zkobj = new Zookeeper();
	private CuratorFramework zk;
	private Map<Object,Object> conf;
	private AtomicBoolean active;
	private WatcherCallBack watcher;
	/**
	 * why run all callbacks, when receive one event
	 */
	private ConcurrentHashMap<UUID, ClusterStateCallback> callbacks = new ConcurrentHashMap<UUID, ClusterStateCallback>();
	private LeafCache zkCache;

	public DistributedClusterStat(Map<Object,Object> _conf ) throws Exception
	{
		this.conf = _conf;
		CuratorFramework _zk = mkZk();//创建Zookeeper连接及重试策略
		String path = String.valueOf(conf.get(Config.LEAF_ZOOKEEPER_ROOT));
		zkobj.mkdirs(_zk,path);// 创建一个永久目录
		_zk.close();

		active = new AtomicBoolean(true);



		watcher = new WatcherCallBack() {
			@Override
			public void execute(KeeperState state, EventType type, String path) {
				if ( active.get())
				{
					if(!(state.equals(KeeperState.SyncConnected)))
					{
						LOG.warn("Received event " + state + ": " + type + ": " + path + "with disconnected  from Zookeeper.");
						//System.out.println("Received event " + state + ":" + type + ":" + path + "with disconnected Zookeeper.");
					}
					else
					{
						LOG.info("Received event " + state + ":" + type + ":" + path);
						//System.out.println("Received event " + state + ":" + type + ":" + path);
						if(type.equals(EventType.NodeChildrenChanged)) //leaf 的临时node节点发生了变化(server上线或者下线)
						{
							LOG.info("Node childrens changed at path: " + path);
							//重新注册watcher事件
							try {
								List<String> children = get_childern(path,true);
								LOG.info("children list at path : " + path + " is " + children);
							} catch (Exception e)
							{
								LOG.warn("faild to get children in path: " + path,e);
							}
						}
					}

					if (!type.equals(EventType.None))
					{
						//System.out.println("Received event " + state + ":" + type + ":" + path);
						LOG.info("Received event " + state + ":" + type + ":" + path);
						for (Map.Entry<UUID,ClusterStateCallback> e: callbacks.entrySet())
						{
							ClusterStateCallback fn = e.getValue();
							fn.execute(type,path);
						}
					}

				}
			}
		};
		zk = null;
		try {
			zk = mkZk(watcher);
		}
		catch (Exception e)
		{
			LOG.error(e.getMessage(),e);
		}
	}
	private CuratorFramework mkZk() throws IOException
	{
		return zkobj.mkClient(conf,(List<String>)conf.get(Config.LEAF_ZOOKEEPER_SERVERS),
				conf.get(Config.LEAF_ZOOKEEPER_PORT),
				"");
	}


	private CuratorFramework mkZk(WatcherCallBack watcher)throws NumberFormatException,IOException
	{
		return zkobj.mkClient(conf,(List<String>)conf.get(Config.LEAF_ZOOKEEPER_SERVERS),
				conf.get(Config.LEAF_ZOOKEEPER_PORT),
				String.valueOf(conf.get(Config.LEAF_ZOOKEEPER_ROOT)),watcher);
	}

	@Override
	public void set_ephemeral_node(String path,byte[] data) throws Exception
	{
		zkobj.mkdirs(zk,PathUtils.parent_path(path));
		if (zkobj.exists(zk,path,false))
		{
			zkobj.setData(zk,path,data);
		}
		else
		{
			zkobj.createNode(zk,path,data,CreateMode.EPHEMERAL);
		}
		if (zkCache != null)
			zkCache.put(path,data);
	}

	@Override
	public void delete_node(String path) throws Exception
	{
		if (zkCache != null)
		{
			zkCache.remove(path);
		}
		zkobj.deletereRcursive(zk,path);

	}

	@Override
	public void set_data(String path,byte[] data) throws Exception
	{
		if (data.length > Utils.SIZE_1_K * 800)
		{
			throw new Exception("Writing 800k+ data into ZK is not allowed!, data size is " + data.length);
		}
		if (zkobj.exists(zk,path,false))
		{
			zkobj.setData(zk,path,data);
		}
		else
		{
			zkobj.mkdirs(zk, PathUtils.parent_path(path));
			zkobj.createNode(zk,path,data,CreateMode.PERSISTENT);
		}
		if (zkCache != null)
			zkCache.put(path,data);
	}

	@Override
	public byte[] get_data(String path,boolean watch ) throws Exception
	{
		byte[] ret = null;
		if (watch == false && zkCache != null)
		{
			ret = (byte[]) zkCache.get(path);
		}
		if (ret != null)
		{
			return ret;
		}
		ret = zkobj.getData(zk,path,watch);
		if (zkCache != null)
		{
			zkCache.put(path,ret);
		}
		return ret;
	}

	@Override
	public byte[] get_data_sync(String path, boolean watch) throws Exception
	{
		byte[] ret = null;
		ret = zkobj.getData(zk,path,watch);
		if (zkCache != null && ret != null)
			zkCache.put(path,ret);
		return ret;
	}

	@Override
	public List<String> get_childern(String path, boolean watch) throws Exception
	{
		return zkobj.getChildren(zk,path,watch);
	}

	@Override
	public void mkdirs(String path) throws Exception
	{
		zkobj.mkdirs(zk,path);
	}

	@Override
	public void tryToBeLeader(String path, byte[] host) throws Exception
	{
		zkobj.createNode(zk,path,host,CreateMode.EPHEMERAL);
	}

	@Override
	public void close()
	{
		this.active.set(false);
		zk.close();
	}

	@Override
	public boolean node_existed(String path, boolean watch) throws Exception
	{
		return zkobj.existsNode(zk,path,watch);

	}

	@Override
	public UUID register(ClusterStateCallback callback) {
		UUID id = UUID.randomUUID();
		this.callbacks.put(id,callback);
		return id;
	}

	@Override
	public String set_sequentail_node_data(String path,byte[] data) throws Exception
	{
		if (data.length > Utils.SIZE_1_K * 800)
		{
			throw new Exception("Writing 800k+ data into ZK is not allowed!, data size is " + data.length);
		}
		String sequentailPath =  zkobj.createSequentailNode(zk,path,data);

		if (zkCache != null)
			zkCache.put(sequentailPath,data);

		return sequentailPath;

	}

	@Override
	public ClusterStateCallback unregister(UUID id) {
		return this.callbacks.remove(id);
	}
	public LeafCache getZkCache() { return zkCache;}
	public void setZkCache(LeafCache zkCache) {this.zkCache = zkCache;}


}