package org.kurento.room.ttroomserver;

import static org.kurento.commons.PropertiesManager.getProperty;
import static org.kurento.commons.PropertiesManager.getPropertyJson;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.kurento.client.KurentoClient;
import org.kurento.commons.ConfigFileManager;
import org.kurento.room.api.KurentoClientProvider;
import org.kurento.room.api.KurentoClientSessionInfo;
import org.kurento.room.exception.RoomException;
import org.kurento.room.internal.AutoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

public class TTKmsManager implements KurentoClientProvider {
	private static final Logger log = LoggerFactory.getLogger(TTKmsManager.class);

	/**
	 * 存放kms服务信息对象
	 */
	class KurentoClientInfo {
		public KurentoClientInfo() {
			info = new ServerInfo();
			info.sServerKey = "";
			info.sServerIP = "";
			info.sServerName = "";
			client = null;
		}

		public ServerInfo info;
		public KurentoClient client;
	}

	// 获取配置文件
	private static final String TTROOM_CFG_FILENAME = "kurento-room-demo.conf.json";
	static {
		ConfigFileManager.loadConfigFile(TTROOM_CFG_FILENAME);
	}
	private List<KurentoClientInfo> _clientList = null;
	private List<String> _currentClientList = new ArrayList<String>();
	private ServerProvider _serverProvider = null;
	private boolean _bCreateClient = false;
	private String _sServerProviderUrl;
	private String _sUserName = "";
	private String _sUserPwd = "";
	private String _sServerType = "";
	private int _nMaxRoom = 10;
	private String _kmsPort = "8888";
	private String _kmsExtension = "kurento";

	public TTKmsManager() {
		InitServerProvider();
		_clientList = new ArrayList<KurentoClientInfo>();
	}

	public TTKmsManager(String user, String pwd, String url) {
		_sUserName = user;
		_sUserPwd = pwd;
		_sServerProviderUrl = url;
		_clientList = new ArrayList<KurentoClientInfo>();
		InitServerProvider();
	}

	public void InitServerProvider() {
		JsonElement jsEL = getPropertyJson("serverProvider", "http://127.0.0.1:8080", JsonElement.class);
		JsonObject jsObj = new JsonObject();
		jsObj = jsEL.getAsJsonObject();
		_sServerProviderUrl = jsObj.get("uri").getAsString();
		_sServerType = getProperty("serverType", "local").toLowerCase();

		if (_sServerType.equals("local")) {
			_serverProvider = ServerProviderFactory.CreateServerProvider(ProviderType.LOCAL_CONFIG);
		} else {
			_serverProvider = ServerProviderFactory.CreateServerProvider(ProviderType.REMOTE_ALI);
		}
		_serverProvider.SetUrl(_sServerProviderUrl);
		_serverProvider.SetUser(_sUserName);
		_serverProvider.SetPasswd(_sUserPwd);

		// 设置kms服务器的最大房间数 TODO 应该设置最大人数（或者最大节点数）
		_nMaxRoom = Integer.parseInt(getProperty("maxRoom", "6").toString());

		_kmsPort = getProperty("kms.port", "8888").toString();
		_kmsExtension = getProperty("kms.extension", "kurento").toString();
	}

	/**
	 * 获取client
	 */
	@Override
	public KurentoClient getKurentoClient(KurentoClientSessionInfo sessionInfo) throws RoomException {
		KurentoClient client = GetMinRoomClient();
		return client;
	}

	private KurentoClient GetMinRoomClient() {
		KurentoClient client = null;
		ServerType type = _serverProvider.GetServerType();
		if (type == ServerType.TYPE_LOCAL) {
			client = GetLocalMinRoomClient();
		} else if (type == ServerType.TYPE_REMOTE) {
			client = GetRemoteMinRoomClient();
		}
		return client;
	}

	/**
	 * 获取本地的client连接 TODO 比较不够准确（只判断了pipeline个数，没有判断具体连接数）
	 */
	private KurentoClient GetLocalMinRoomClient() {
		KurentoClient client = null;
		do {
			if (NeedCreateLocalServer()) {
				initLocakClient();
			}
			client = getMinKMSClient();

		} while (false);

		return client;
	}

	/**
	 * 初始化配置文件里面所有kms服务器连接 添加的kms服务器 自动添加
	 */
	private void initLocakClient() {
		// TODO 这个地方应该是去查询redis或者数据库，不应该走配置文件
		// JsonArray kmsUris = getPropertyJson(KurentoRoomServerApp.KMSS_URIS_PROPERTY,
		// "", JsonArray.class);
		// List<String> kmsServerurls = JsonUtils.toStringList(kmsUris);
		// 从application.properties 中读取kms的uris配置
		List<String> kmsServerurls = JSONArray.parseArray(AutoUtil.getEnv().getProperty("kms.uris"), String.class);
		log.info("方法:{}中, 表达式{}的值: {}", "roomServer:initLocakClient", "kmsServerurls", kmsServerurls);

		List<String> newKmsServerurls = new ArrayList<>();
		newKmsServerurls.addAll(kmsServerurls);
		KurentoClientInfo clientInfo = null;
		if (_clientList.size() > 0) {
			for (KurentoClientInfo client : _clientList) {
				if (kmsServerurls.contains(client.info.sServerIP)) {
					newKmsServerurls.remove(client.info.sServerIP);
				}
			}
		}
		for (String clientUrl : newKmsServerurls) {
			// 这里必须重新new 一个对象, 不能复用原来的
			clientInfo = new KurentoClientInfo();
			clientInfo.info.sServerIP = clientUrl;
			clientInfo.info.sServerKey = _serverProvider.GetServerKey();
			String sServer = "ws://" + clientInfo.info.sServerIP + ":" + _kmsPort + "/" + _kmsExtension;
			KurentoClient client = KurentoClient.create(sServer);
			clientInfo.client = client;
			synchronized (_clientList) {
				_clientList.add(clientInfo);
				_currentClientList.add(clientUrl);
			}

		}
	}

	/**
	 * 清楚现有的kms服务器
	 */
	private void cleanKmsServer(String kmsIp) {
		for (KurentoClientInfo _client : _clientList) {
			if (_client.info.sServerIP.equals(kmsIp)) {
				_clientList.remove(_client);
			}
		}
	}

	// /**
	// * 新建client(kms连接) TODO 要求要有空余的kms服务器,都满了的时候就报错了
	// */
	// private KurentoClient CreateLocalClient() {
	// // 检查是否有新的kms连接添加
	// String sServerIP = _serverProvider.CreateNewServer();
	// if (sServerIP.isEmpty()) {
	// return null;
	// }
	//
	// KurentoClientInfo clientInfo = new KurentoClientInfo();
	// clientInfo.info.sServerIP = sServerIP;
	// clientInfo.info.sServerKey = _serverProvider.GetServerKey();
	// String sServer = "ws://" + clientInfo.info.sServerIP + ":" + _kmsPort + "/" +
	// _kmsExtension;
	// KurentoClient client = KurentoClient.create(sServer);
	// clientInfo.client = client;
	// synchronized (_clientList) {
	// _clientList.add(clientInfo);
	// }
	//
	// return client;
	// }

	/**
	 * 获取远端的client连接 TODO 比较不够准确（只判断了pipeline个数，没有判断具体连接数）
	 */
	private KurentoClient GetRemoteMinRoomClient() {
		KurentoClient client = null;
		do {
			boolean bCreate = NeedCreateRemoteClient();
			if (bCreate) {
				CreateNewKurentoServer();
				break;
			}

			client = getMinKMSClient();

			// fix bug when now is creating...
			if (_bCreateClient) {
				if (null != client) {
					int nRooms = client.getServerManager().getPipelines().size();
					if (nRooms >= _nMaxRoom) {
						// 没有服务器是合适的
						client = null;
					}
				}
			}
		} while (false);

		return client;
	}

	/**
	 * 获取现有的kms是否都达到上限了
	 * 
	 * @return true 没有初始化或者都达到上限了 false 有可能用的kms
	 */
	private boolean NeedCreateRemoteClient() {
		boolean bCreate = true;
		do {
			if (_bCreateClient) {
				bCreate = false;
				break;
			}
			if (_clientList.size() == 0)
				break;

			for (KurentoClientInfo kcInfo : _clientList) {
				KurentoClient client = kcInfo.client;
				int nTmpSize = client.getServerManager().getPipelines().size();
				if (nTmpSize < _nMaxRoom) {
					bCreate = false;
					break;
				}
			}

		} while (false);

		return bCreate;
	}

	/**
	 * 创建一个新的远端kms
	 */
	private void CreateNewKurentoServer() {
		Thread t = new Thread(new Runnable() {
			public void run() {
				_bCreateClient = true;
				// create server
				String sServerInfo = _serverProvider.CreateNewServer();
				KurentoClientInfo clientInfo = new KurentoClientInfo();
				clientInfo.info.sServerIP = _serverProvider.GetServerIP();
				clientInfo.info.sServerKey = _serverProvider.GetServerKey();
				clientInfo.info.sServerName = _serverProvider.GetServerName();
				// open server
				_serverProvider.OpenServer(clientInfo.info.sServerKey);

				// create kurentoClient
				String sServer = "ws://" + clientInfo.info.sServerIP + ":" + _kmsPort + "/" + _kmsExtension;
				KurentoClient client = KurentoClient.create(sServer);
				clientInfo.client = client;
				synchronized (_clientList) {
					_clientList.add(clientInfo);
				}
				_bCreateClient = false;
			}
		});

		t.run();
	}

	/**
	 * 获取一个最小负载的kms Client
	 */
	private KurentoClient getMinKMSClient() {
		KurentoClient client = null;

		for (KurentoClientInfo kcInfo : _clientList) {
			if (null == client) {
				client = kcInfo.client;
				continue;
			}

			int nOriRooms = client.getServerManager().getPipelines().size();
			int nTmpRooms = kcInfo.client.getServerManager().getPipelines().size();
			if (nOriRooms > nTmpRooms) {
				client = kcInfo.client;
			}
		}
		log.info("方法:{}中, 表达式{}的值: {}", "roomServer:getMinKMSClient", "client", client);
		return client;
	}

	/**
	 * 检查是否可有client TODO 不能只是单纯的检查pipeline个数
	 * 
	 * @return true 有可用的client false 没有可用的client
	 */
	private Boolean NeedCreateLocalServer() {
		Boolean bCreate = true;
		for (KurentoClientInfo kcInfo : _clientList) {
			KurentoClient client = kcInfo.client;
			int nRooms = client.getServerManager().getPipelines().size();
			if (nRooms < _nMaxRoom) {
				bCreate = false;
				break;
			}
		}

		return bCreate;
	}

	@Override
	public boolean destroyWhenUnused() {
		return false;
	}

	@Override
	public ServerInfo GetServerInfo(KurentoClient client) {
		ServerInfo info = null;

		for (KurentoClientInfo kcInfo : _clientList) {
			KurentoClient tmpClient = kcInfo.client;
			if (tmpClient == client) {
				info = kcInfo.info;
				break;
			}
		}

		return info;
	}

	@Override
	public boolean CloseServer(KurentoClient client) {
		// local server must not close server
		ServerType type = _serverProvider.GetServerType();
		if (ServerType.TYPE_LOCAL == type) {
			return true;
		}

		// query the client if it has pipelines...
		boolean bUsed = isServerUsed(client);
		if (bUsed) {
			return false;
		}

		client.destroy();
		ServerInfo info = GetServerInfo(client);
		String sServerKey = info.sServerKey;
		_serverProvider.CloseServer(sServerKey);

		RemoveKurentoClient(client);

		DeleteServer(info);
		// TODO...should add remove strategy

		return true;
	}

	@Override
	public boolean DeleteServer(ServerInfo info) {
		// TODO: should extend the delete strategy
		String sServerKey = info.sServerKey;
		// need put delete operator into thread
		Thread t = new Thread(new Runnable() {
			public void run() {
				boolean bDelete = false;
				int nRetryTimes = 0;
				while (!bDelete) {
					bDelete = _serverProvider.DeleteServer(sServerKey);
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// in case it is freeze
					if (nRetryTimes++ > 20) {
						break;
					}
				}
			}
		});
		t.run();

		return true;
	}

	/*
	 * 清除一个KurentoClient
	 */
	private void RemoveKurentoClient(KurentoClient client) {
		synchronized (_clientList) {
			Iterator<KurentoClientInfo> iter = _clientList.iterator();
			while (iter.hasNext()) {
				KurentoClientInfo clientInfo = (KurentoClientInfo) iter.next();
				if (clientInfo.client == client) {
					iter.remove();
				}
			}
		}
	}

	/**
	 * 判断一个kms服务器是不是有连接（一个服务一个kurentoClient）
	 */
	private boolean isServerUsed(KurentoClient client) {
		boolean flag = true;
		do {
			if (null == client) {
				break;
			}
			if (client.getServerManager().getPipelines().size() > 0) {
				break;
			}

			flag = false;
		} while (false);

		return flag;
	}
}
