package com.nms.server.thread.change;

import static com.nms.server.common.Constants.WEB_SOCKET_IP;
import static com.nms.server.common.Constants.WEB_SOCKET_PORT;
import static com.nms.server.thread.socket.SocketCMD.DOWNLOAD_PLUGIN_SCRIPT;
import static com.nms.server.thread.socket.SocketCMD.SEND_PLUGIN_SCRIPT_FILE;

import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.google.common.collect.Sets;
import com.nms.server.bean.EventRecordLibrary;
import com.nms.server.bean.NodeModel;
import com.nms.server.bean.SetInfo;
import com.nms.server.common.Common;
import com.nms.server.common.Constants;
import com.nms.server.dao.CommonDao;
import com.nms.server.service.ChangeService;
import com.nms.server.service.CommonService;
import com.nms.server.util.FileUtils;
import com.nms.server.util.StringUtil;
import com.nms.server.util.socket.SSLSocketCallable;
import com.nms.server.util.socket.SocketUtils;

/**
 * 下发第三方监测脚本文件
 */
public class ChangePluginScriptFile implements Runnable {
	private static final Logger logger = Logger.getLogger(ChangePluginScriptFile.class);

	/*
	 *  脚本文件临时存储目录
	 */
	private File pluginScriptDir;

	/*
	 *  接收脚本文件的NC节点列表
	 */
	private Set<String> ncNodesIpStrSet;
	
	/**
	 * 向DC管理的所有有效服务器节点发送脚本文件
	 * 
	 * @param dpluginScriptDir 脚本文件临时存储目录
	 */
	public ChangePluginScriptFile(File pluginScriptDir) {
		this.pluginScriptDir = pluginScriptDir;
	}
	
	/**
	 * 向指定的NC节点发送脚本文件
	 * 
	 * @param pluginScriptDir 脚本文件临时存储目录
	 * @param ncNodesIpStrSet 待接收脚本的NC节点列表
	 */
	public ChangePluginScriptFile(File pluginScriptDir, Set<String> ncNodesIpStrSet) {
		this.pluginScriptDir = pluginScriptDir;
		this.ncNodesIpStrSet = ncNodesIpStrSet;
	}
	
	
	/*
	 * 用于统计向特定节点发送的脚本文件
	 */
	private Map<String, List<File>> sendRecordList = new HashMap<String, List<File>>();

	/*
	 * 统计文件剩余发送次数（用于清理DC临时脚本文件）
	 */
	private Map<String, Integer> fileSendLastCount = new HashMap<String, Integer>();
	
	
	@SuppressWarnings("unchecked")
	public void run() {
		CommonDao dao = null;

		try {
			dao = new CommonDao();
			ChangeService changeService = new ChangeService(dao);

//			Thread.currentThread().setName("发送监测脚本文件");
			Thread.currentThread().setName("Send A Monitoring Script File");
			
			Collection<File> files = FileUtils.listFiles(pluginScriptDir, null, false);
			
			if (ncNodesIpStrSet == null) {
				// 未指定接收的NC节点时，根据检测设置确定待接收脚本的NC节点
				Map<String, SetInfo> setInfoMap = new HashMap<String, SetInfo>();
				List<SetInfo> setInfos = changeService.getAllSetInfo(1, 1);
				for (SetInfo setInfo : setInfos) {
					if("2".equals(setInfo.getIsControlStart())) {
						setInfoMap.put(setInfo.getProcessIden(), setInfo);
					}
				}
	
				for (File pluginFile : files) {
					String processIden = pluginFile.getName().replaceAll("\\w+_(\\w+)\\.\\w+", "$1");
					SetInfo setInfo = setInfoMap.get(processIden);
	
					// 校验 变更范围的节点IP
					List<NodeModel> ipNodeModelList = changeService.getNodeModelListBySetInfo(setInfo);
					for (NodeModel ip : ipNodeModelList) {
						// 仅向0服务器节点发送变更信息：0：服务器
						if (ip != null && ip.getNodeIp() != null && ip.getNodeType().longValue() == 0l
								&& Common.hasIpInIpSegment(ip.getNodeIp())) {
							addToSendRecordList(ip.getNodeIp(), pluginFile); //
							addTofileSendLastCount(pluginFile); //
						}
					}
				}
				ncNodesIpStrSet = sendRecordList.keySet();
			
			} else {
				// 已指定待接收脚本的NC节点
				for (File pluginFile : files) {
					for (String ip : ncNodesIpStrSet) {
						addToSendRecordList(ip, pluginFile); // 统计向特定节点发送的脚本文件
						addTofileSendLastCount(pluginFile); // 统计文件剩余发送次数
					}
				}
			}
			
			for (String ip : ncNodesIpStrSet) {
				Common.runChangeRunnable(new SendPluginScriptFile(ip, sendRecordList.get(ip)));
			}
			
			// 延时清理脚本
			new Thread(new DeletePluginScriptDirAfterSending()).start();

		} catch (Exception e) {
			logger.error("", e);
		} finally {
			if (dao != null) {
				dao.close();
				dao = null;
			}
		}
	}

	private void addTofileSendLastCount(File pluginFile) {
		Integer count = fileSendLastCount.get(pluginFile.getName());
		count = (count != null) ? count : 0;
		fileSendLastCount.put(pluginFile.getName(), count + 1);
	}
	
	private void addToSendRecordList(String nodeIp, File pluginFile) {
		if (sendRecordList.containsKey(nodeIp)) {
			sendRecordList.get(nodeIp).add(pluginFile);
		} else {
			List<File> files = new ArrayList<File>();
			files.add(pluginFile);
			sendRecordList.put(nodeIp, files);
		}
	}

	protected class SendPluginScriptFile extends SocketUtils implements Callable<Object> {
		private String cmd;
		private List<File> fileList;

		public SendPluginScriptFile(String ip, List<File> fileList) {
			super(ip, Constants.SSL_CLIENT_PORT);
			this.cmd = SEND_PLUGIN_SCRIPT_FILE;
			this.fileList = fileList;
		}

		@Override
		public Object call() throws Exception {
//			Thread.currentThread().setName("发送文件 To:>" + ip);
			Thread.currentThread().setName("Send File To:>" + ip);
			CommonDao dao = null;
			try {
				if(fileList == null || fileList.size()<1){
					logger.info("没有脚本文件需要发送");
					return null;
				}
				createClientSocket();
				this.sendMessage(cmd);
				logger.debug("cmd:> " + cmd + " 发送命令结果>> " + this.receiveMessage());
				StringBuffer sb = new StringBuffer();
				for (File file : fileList) {
					sb.append(",").append(file.getName());
				}
				this.sendMessage(sb.substring(1));
				this.receiveMessage();
				this.bpSendFileByBath(fileList, pluginScriptDir.getCanonicalPath());
				logger.debug("cmd:> " + cmd + " 发送脚本文件>> " + this.receiveMessage());

				return true;

			} catch (Exception e) {
				String errorInfo = "Target communication:>" + ip + " create failure：" + e.getMessage();
				logger.error(errorInfo, e);
				
				dao  = new CommonDao();
				ChangeService changeService = new ChangeService(dao);
				Map<String, String> recordContent = new HashMap<String, String>();
				HashSet<String> pluginFileNames = new HashSet<String>();
				for (File pluginFile : fileList) {
					pluginFileNames.add(pluginFile.getName());
				}
				String content = StringUtils.join(pluginFileNames.iterator(), "，");
				recordContent.put("scriptNames", content);
				
				changeService.saveEventRecordLibrary(cmd, Common.getIpSeqIdMap().get(ip) + ""
						, "S2C", JSONObject.fromObject(recordContent).toString());
				
				return false;
			
			} finally {
				close(); // close socket
				if(dao != null) {
					dao.close();
					dao = null;
				}
				
				// 文件剩余发送次数为0时，由DeletePluginScriptDirAfterSending线程清理脚本
				for (File file : fileList) {
					fileSendLastCount.put(file.getName(), fileSendLastCount.get(file.getName()) - 1);
				}
			}
		}
	}

	/**
	 * NC初始化时发送脚本文件
	 * 
	 * @param ip 接收脚本的NC节点
	 */
	public static void sendPluginFileWhenNcInit(final String ip) {	
		ChangePluginScriptFile task = null;
		File tempPluginDir = ChangePluginScriptFile.getNewTempPluginDirectory();
				
		CommonDao dao = null;
		List<String> prefixNameList = new ArrayList<String>();
		try {
			Set<String> ipList = new HashSet<String>(1);
			ipList.add(ip);
			task = new ChangePluginScriptFile(tempPluginDir, ipList);
		
			dao = new CommonDao();
			CommonService service = new CommonService(dao);
			List<SetInfo> setInfoList = service.selectSetInfoList(1l, null, Common.getIpSeqIdMap().get(ip), null, null, 1l, null, 0l, true);
			for (SetInfo setInfo : setInfoList) {
				if("2".equals(setInfo.getIsControlStart())) {
					String prefixName = setInfo.getCheckTypeName() + "_" + setInfo.getProcessIden() + ".";
					prefixNameList.add(prefixName);
				}
			}
		} catch (SQLException e) {
			logger.error(e.getMessage(), e);
		} finally {
			if (dao != null) {
				dao.close();
				dao = null;
			}
		}

		boolean bool = task.waitForGettingScriptFromWeb(tempPluginDir, prefixNameList);
		if (bool) {
			Common.service.execute(task);
		
		} else { // 向Web请求脚本失败，记录eventRecordLibrary
			try {
				dao  = new CommonDao();
				ChangeService changeService = new ChangeService(dao);
				Map<String, String> recordContent = new HashMap<String, String>();
				String content = StringUtils.join(prefixNameList.iterator(), "，");
				recordContent.put("scriptNames", content);
				changeService.saveEventRecordLibrary(SEND_PLUGIN_SCRIPT_FILE, Common.getIpSeqIdMap().get(ip) + "",
						"S2C", JSONObject.fromObject(recordContent).toString());

			} catch (SQLException e) {
				logger.error(e.getMessage(), e);
			} finally {
				if (dao != null) {
					dao.close();
					dao = null;
				}
			}
		}
	}
	
	/**
	 * 解析失败暂存信息，重新发送脚本文件<br/>
	 * 
	 * @param mrlList
	 */
	public static void sendPluginFileBaseOnEventRecord(List<EventRecordLibrary> recordList) {
		if(recordList == null || recordList.isEmpty()) {
			return;
		}
		
		// W2S [脚本名前缀,...]
		Set<String> w2sDetecSetInfos = new HashSet<String>();
				
		// S2C key:NC地址, value:[脚本名前缀,...]
		Map<String, Set<String>> s2cDetecSetInfos = new HashMap<String, Set<String>>();
		
		CommonDao dao = null;
		try {
			dao = new CommonDao();
			ChangeService changeService = new ChangeService(dao);
			
			for (EventRecordLibrary record : recordList) {
				if (SEND_PLUGIN_SCRIPT_FILE.equalsIgnoreCase(record.getRecordCommand())) { // 脚本下发
					Map<?, ?> map = StringUtil.getMapFromJsonObjStr(record.getRecordContent());
					String recordType = record.getRecordType();
					String scriptNames = (String) map.get("scriptNames");
					if("W2S".equals(recordType)) {
						for (String scriptName : scriptNames.split(",")) {
							w2sDetecSetInfos.add(scriptName);
						}
						
					} else if("S2C".equals(recordType)) {
						for (String scriptName : scriptNames.split(",")) {
							String ip = Common.getNodeIpByUUID(record.getSeqId());
							Set<String> scriptNameSet = s2cDetecSetInfos.get(ip);
							if(scriptNameSet == null) {
								scriptNameSet = new HashSet<String>();
							}
							scriptNameSet.add(scriptName);
							s2cDetecSetInfos.put(ip, scriptNameSet);
						}
					}
				}
				
				changeService.deleteEventRecordByIds(record.getId() + "");
			}
			
		} catch (SQLException e) {
			logger.error("Delete EventRecordLibrary", e);
		}
		
		
		// W2S, 向DC管理的所有有效节点发送脚本文件
		if(!w2sDetecSetInfos.isEmpty()) {
			File tempPluginDir = ChangePluginScriptFile.getNewTempPluginDirectory();
			ChangePluginScriptFile task = new ChangePluginScriptFile(tempPluginDir);
			boolean bool = task.waitForGettingScriptFromWeb(tempPluginDir, w2sDetecSetInfos);
			if(bool) {
				Common.service.execute(task);
				
			} else { // 向Web请求脚本失败，记录eventRecordLibrary
				try {
					dao  = new CommonDao();
					ChangeService changeService = new ChangeService(dao);
					Map<String, String> recordContent = new HashMap<String, String>();
					String content = StringUtils.join(w2sDetecSetInfos.iterator(), ",");
					recordContent.put("scriptNames", content);
					changeService.saveEventRecordLibrary(SEND_PLUGIN_SCRIPT_FILE, "",
							"W2S", JSONObject.fromObject(recordContent).toString());

				} catch (SQLException e) {
					logger.error(e.getMessage(), e);
				} finally {
					if (dao != null) {
						dao.close();
						dao = null;
					}
				}
			}
		}
		
		// S2C, 向指定节点发送脚本文件
		Iterator<Entry<String, Set<String>>> it = s2cDetecSetInfos.entrySet().iterator();
		while(it.hasNext()) {
			Entry<String, Set<String>> entry = it.next();
			Set<String> recvIpSet = Sets.newHashSet(entry.getKey());
			File tempPluginDir = ChangePluginScriptFile.getNewTempPluginDirectory();
			ChangePluginScriptFile task = new ChangePluginScriptFile(tempPluginDir, recvIpSet);
			Set<String> prefixNames = entry.getValue();
			boolean bool = task.waitForGettingScriptFromWeb(tempPluginDir, prefixNames);
			bool = false;
			if(bool) {
				Common.service.execute(task);
				
			} else { // 向Web请求脚本失败，记录eventRecordLibrary
				try {
					dao  = new CommonDao();
					ChangeService changeService = new ChangeService(dao);
					Map<String, String> recordContent = new HashMap<String, String>();
					String content = StringUtils.join(prefixNames.iterator(), ",");
					recordContent.put("scriptNames", content);
					String ips = Common.getIpSeqIdMap().get(recvIpSet.iterator().next())+"";
					changeService.saveEventRecordLibrary(SEND_PLUGIN_SCRIPT_FILE, ips,
							"S2C", JSONObject.fromObject(recordContent).toString());

				} catch (SQLException e) {
					logger.error(e.getMessage(), e);
				} finally {
					if (dao != null) {
						dao.close();
						dao = null;
					}
				}
			}
		}
	}
	
	/**
	 * 根据脚本名称从Web获取脚本文件，并等待脚本获取完成
	 * 
	 * @param dir
	 * @param prefixNames
	 * @return
	 */
	protected Boolean waitForGettingScriptFromWeb(File dir, Collection<String> prefixNames) {
		GetScriptFileFromWeb task = null;
		task = this.new GetScriptFileFromWeb(dir, prefixNames);
		FutureTask<Object> future = new FutureTask<Object>(task);
		new Thread(future).start();
		try {
			Object result = future.get();
			
			if(result == null) {
				logger.debug("从Web服务器获取脚本失败，脚本列表：" + prefixNames);
				return false;
			}
			return (Boolean) result;

		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		return false;
	}
	
	protected class GetScriptFileFromWeb extends SSLSocketCallable {
		
		Logger logger = Logger.getLogger(GetScriptFileFromWeb.class);

		private File tempPluginDir; // 脚本存储目录
		private String prefixNames; // 脚本名称列表(逗号分隔)

		public GetScriptFileFromWeb(File tempPluginDir, Collection<String> prefixNames) {
			super(WEB_SOCKET_IP, WEB_SOCKET_PORT);
			this.tempPluginDir = tempPluginDir;
			if(prefixNames != null && prefixNames.size() > 0){
				StringBuilder sb = new StringBuilder();
				for(String s : prefixNames){
					if(StringUtils.isNotBlank(s)){
						sb.append(",");
						sb.append(s);
					}
				}
				if(sb.length() >0){
					sb.deleteCharAt(0);
					this.prefixNames = sb.toString();
				}
			}
			
		}

		@Override
		protected Object toDo() throws Exception {
//			Thread.currentThread().setName("下载第三方监测脚本文件");
			Thread.currentThread().setName("Download Third Party Monitoring Script Files");
			if(StringUtils.isBlank(prefixNames)){
				return true;
			}
			this.sendMessage(DOWNLOAD_PLUGIN_SCRIPT);
			this.receiveMessage();
			this.sendMessage(prefixNames);
			String fileName = this.receiveMessage();

			// 重复请求脚本，Web无对应脚本时返回为空
			if(FAIL.equalsIgnoreCase(fileName) || StringUtils.isBlank(fileName)) {
				logger.error("The Web server does not monitor " + prefixNames + "the corresponding script file！");
				return false;
			} else {
				this.sendMessage(SUCCESS);
				this.bpReceiveFileByBath(tempPluginDir.getCanonicalPath());
				logger.info("已接收脚本文件:" + Arrays.toString(tempPluginDir.list()));
				return true;
			}
		}
	}
	
	protected class DeletePluginScriptDirAfterSending implements Runnable {
		@Override
		public void run() {
			while (!fileSendLastCount.isEmpty()) {
				Iterator<Entry<String, Integer>> iterator = fileSendLastCount.entrySet().iterator();
				while (iterator.hasNext()) {
					 Entry<String, Integer> entry = iterator.next();
					 if(entry.getValue() == 0) {
						 iterator.remove();
					 }
				}
				try {
					TimeUnit.SECONDS.sleep(2L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			FileUtils.deleteAllFiles(pluginScriptDir, true);
		}
	}
	
	/**
	 * web向DC下发脚本或DC向web请求脚本过程中，每次都创建新的临时目录<br/>
	 * （DC向NC下发脚本完成后将删除临时目录）
	 * 
	 * @return
	 */
	public static File getNewTempPluginDirectory() {
		File tempPluginDir = new File(Constants.PLUGIN_SCRIPT_FILE_DIR, System.currentTimeMillis()+"");
		if (!tempPluginDir.exists()) {
			tempPluginDir.mkdirs();
		}
		return tempPluginDir;
	}
}