/*******************************************************************************
 * ===========================================================
 * Ankush : Big Data Cluster Management Solution
 * ===========================================================
 * 
 * (C) Copyright 2014, by Impetus Technologies
 * 
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License (LGPL v3) as
 * published by the Free Software Foundation;
 * 
 * This software is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this software; if not, write to the Free Software Foundation, 
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ******************************************************************************/
/**
 * 
 */
package com.impetus.ankush.agent.sigar;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hyperic.sigar.Cpu;
import org.hyperic.sigar.CpuInfo;
import org.hyperic.sigar.CpuPerc;
import org.hyperic.sigar.FileSystem;
import org.hyperic.sigar.FileSystemUsage;
import org.hyperic.sigar.Mem;
import org.hyperic.sigar.NetInterfaceStat;
import org.hyperic.sigar.OperatingSystem;
import org.hyperic.sigar.ProcCpu;
import org.hyperic.sigar.ProcMem;
import org.hyperic.sigar.ProcState;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
import org.hyperic.sigar.Swap;
import org.hyperic.sigar.Uptime;
import org.hyperic.sigar.Who;

import com.impetus.ankush.agent.utils.AgentLogger;
import com.impetus.ankush.agent.utils.CommandExecutor;
import com.impetus.ankush.agent.utils.Result;

/**
 * The Class SigarNodeInfoProvider.
 * 
 * @author Hokam Chauhan
 */
public class SigarNodeInfoProvider {

	/** The log. */
	private static final AgentLogger LOGGER = new AgentLogger(
			SigarNodeInfoProvider.class);

	/** The Constant SLEEP. */
	public static final char SLEEP = 'S';

	/** The Constant RUN. */
	public static final char RUN = 'R';

	/** The Constant STOP. */
	public static final char STOP = 'T';

	/** The Constant ZOMBIE. */
	public static final char ZOMBIE = 'Z';

	/** The Constant IDLE. */
	public static final char IDLE = 'D';

	/** The Constant HUNDRED. */
	public static final int HUNDRED = 100;

	/** The Constant NUMBER_1024. */
	public static final int NUMBER_1024 = 1024;

	/** The sigar. */
	private Sigar sigar;

	private DecimalFormat dFormatter = new DecimalFormat("##.##");

	/**
	 * Instantiates a new sigar node info provider.
	 */
	public SigarNodeInfoProvider() {
		sigar = new Sigar();
	}

	/**
	 * Method getNodeMemoryInfo.
	 * 
	 * @return Map<Object,Object>
	 */
	public Map<Object, Object> getNodeMemoryInfo() {
		Map<Object, Object> nodeMemoryInfo = new HashMap<Object, Object>();
		Mem mem = null;

		try {
			mem = sigar.getMem();
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}

		nodeMemoryInfo.put("total", mem.getTotal());
		nodeMemoryInfo.put("free", mem.getFree());
		nodeMemoryInfo.put("used", mem.getUsed());
		nodeMemoryInfo.put("actualFree", mem.getActualFree());
		nodeMemoryInfo.put("actualUsed", mem.getActualUsed());
		nodeMemoryInfo.put("freePercentage", mem.getFreePercent());
		nodeMemoryInfo.put("usedPercentage", mem.getUsedPercent());

		return nodeMemoryInfo;
	}

	/**
	 * Method getNodeCpuInfos.
	 * 
	 * @return List<Map<Object,Object>>
	 */
	public List<Map<Object, Object>> getNodeCpuInfos() {

		List<Map<Object, Object>> nodeCpuInfoList = new ArrayList<Map<Object, Object>>();
		Cpu[] cpuList = null;
		CpuInfo[] cpuInfoList = null;
		Map<Object, Object> nodeCpuInfo = null;

		try {
			cpuList = sigar.getCpuList();

		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		try {
			cpuInfoList = sigar.getCpuInfoList();

		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}

		for (int i = 0; i < cpuList.length; i++) {
			nodeCpuInfo = new HashMap<Object, Object>();
			nodeCpuInfo.put("vendor", cpuInfoList[i].getVendor());
			nodeCpuInfo.put("clock", cpuInfoList[i].getMhz());
			nodeCpuInfo.put("model", cpuInfoList[i].getModel());
			nodeCpuInfo.put("cores", cpuInfoList[i].getTotalCores());
			nodeCpuInfo.put("coresPerSocket",
					cpuInfoList[i].getCoresPerSocket());
			nodeCpuInfo.put("sockets", cpuInfoList[i].getTotalSockets());
			nodeCpuInfo.put("cacheSize", cpuInfoList[i].getCacheSize());
			nodeCpuInfo.put("idleTime", cpuList[i].getIdle());
			nodeCpuInfo.put("interruptHandlingTime", cpuList[i].getIrq());
			nodeCpuInfo.put("niceTime", cpuList[i].getNice());
			nodeCpuInfo.put("softRequestTime", cpuList[i].getSoftIrq());
			nodeCpuInfo.put("kernelTime", cpuList[i].getSys());
			nodeCpuInfo.put("cpuTime", cpuList[i].getTotal());
			nodeCpuInfo.put("userTime", cpuList[i].getUser());
			nodeCpuInfo.put("waitTime", cpuList[i].getWait());
			nodeCpuInfoList.add(nodeCpuInfo);
		}

		return nodeCpuInfoList;
	}

	/**
	 * Method getNodeSwapInfo.
	 * 
	 * @return Map<Object,Object>
	 */
	public Map<Object, Object> getNodeSwapInfo() {

		Swap swap = null;
		Map<Object, Object> nodeSwapInfo = new HashMap<Object, Object>();

		try {
			swap = sigar.getSwap();
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}

		nodeSwapInfo.put("freeSystemSwap", swap.getFree());
		nodeSwapInfo.put("pageIn", swap.getPageIn());
		nodeSwapInfo.put("pageOut", swap.getPageOut());
		nodeSwapInfo.put("totalSystemSwap", swap.getTotal());
		nodeSwapInfo.put("usedSystemSwap", swap.getUsed());

		return nodeSwapInfo;
	}

	/**
	 * Method getNodeNetworkInfos.
	 * 
	 * @return List<Map<Object,Object>>
	 */
	public List<Map<Object, Object>> getNodeNetworkInfos() {

		List<Map<Object, Object>> nodeNetworkInfoList = new ArrayList<Map<Object, Object>>();
		Map<Object, Object> nodeNetworkInfo = null;
		String[] netInterfaceList = null;
		NetInterfaceStat netInterfaceStat = null;

		try {
			netInterfaceList = sigar.getNetInterfaceList();

		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}

		for (String netInterface : netInterfaceList) {
			try {
				netInterfaceStat = sigar.getNetInterfaceStat(netInterface);

			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}
			nodeNetworkInfo = new HashMap<Object, Object>();
			nodeNetworkInfo.put("interfaceName", netInterface);
			nodeNetworkInfo.put("receivedBytes", netInterfaceStat.getRxBytes());
			nodeNetworkInfo.put("receivedDropped",
					netInterfaceStat.getRxDropped());
			nodeNetworkInfo.put("receivedErrors",
					netInterfaceStat.getRxErrors());
			nodeNetworkInfo.put("receivedFrame", netInterfaceStat.getRxFrame());
			nodeNetworkInfo.put("receivedOverruns",
					netInterfaceStat.getRxOverruns());
			nodeNetworkInfo.put("receivedPackets",
					netInterfaceStat.getRxPackets());
			nodeNetworkInfo.put("transmittedBytes",
					netInterfaceStat.getTxBytes());
			nodeNetworkInfo.put("transmittedDropped",
					netInterfaceStat.getTxDropped());
			nodeNetworkInfo.put("transmittedErrors",
					netInterfaceStat.getTxErrors());
			nodeNetworkInfo.put("transmittedCarrier",
					netInterfaceStat.getTxCarrier());
			nodeNetworkInfo.put("transmittedCollision",
					netInterfaceStat.getTxCollisions());
			nodeNetworkInfo.put("transmittedOverruns",
					netInterfaceStat.getTxOverruns());
			nodeNetworkInfo.put("transmittedPackets",
					netInterfaceStat.getTxPackets());
			nodeNetworkInfo.put("speed", netInterfaceStat.getSpeed());

			nodeNetworkInfoList.add(nodeNetworkInfo);
		}

		return nodeNetworkInfoList;
	}

	/**
	 * Method getNodeOSInfo.
	 * 
	 * @return Map<Object,Object>
	 */
	public Map<Object, Object> getNodeOSInfo() {
		OperatingSystem os = null;
		Map<Object, Object> nodeOSInfo = new HashMap<Object, Object>();

		os = OperatingSystem.getInstance();

		nodeOSInfo.put("arch", os.getArch());
		nodeOSInfo.put("cpuEndian", os.getCpuEndian());
		nodeOSInfo.put("dataModel", os.getDataModel());
		nodeOSInfo.put("description", os.getDescription());
		nodeOSInfo.put("machineName", os.getMachine());
		nodeOSInfo.put("patchlevel", os.getPatchLevel());
		nodeOSInfo.put("systemVersion", os.getVersion());
		nodeOSInfo.put("vendor", os.getVendor());
		nodeOSInfo.put("vendorCodeName", os.getVendorCodeName());
		nodeOSInfo.put("vendorVersion", os.getVendorVersion());

		return nodeOSInfo;
	}

	/**
	 * Method getNodeUpTimeInfo.
	 * 
	 * @return Map<Object,Object>
	 */
	public Map<Object, Object> getNodeUpTimeInfo() {

		Uptime upTime = null;
		Who[] who = null;
		Map<Object, Object> nodeUpTimeInfo = new HashMap<Object, Object>();
		double[] loadAverage = { 0, 0, 0 };

		try {
			upTime = sigar.getUptime();
			nodeUpTimeInfo.put("upTime", upTime.getUptime());
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		try {
			who = sigar.getWhoList();
			String loggedUsers;
			List<String> userList = new ArrayList<String>();
			for (Who w : who) {
				userList.add(w.getUser());
			}
			loggedUsers = getDelimitedValues(userList, ",");

			nodeUpTimeInfo.put("loggedUsers", loggedUsers);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		try {
			loadAverage = sigar.getLoadAverage();
			nodeUpTimeInfo.put("loadAverage1", loadAverage[0]);
			nodeUpTimeInfo.put("loadAverage2", loadAverage[1]);
			nodeUpTimeInfo.put("loadAverage3", loadAverage[2]);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}

		nodeUpTimeInfo.put("cpuUsage", getCpuUsage());
		return nodeUpTimeInfo;
	}

	/**
	 * Gets the cpu usage.
	 * 
	 * @return the cpu usage
	 */
	private Object getCpuUsage() {
		try {
			// cpu percentage object.
			CpuPerc per = sigar.getCpuPerc();

			// getting the string value of percentage.
			String p = String.valueOf(per.getCombined() * 100.0);
			// getting index of the .
			int ix = p.indexOf('.') + 1;
			// getting only one digit after .
			return p.substring(0, ix) + p.substring(ix, ix + 1);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		return null;
	}

	/**
	 * Method getDelimitedValues.
	 * 
	 * @param list
	 *            List
	 * @param delimiter
	 *            String
	 * @return String
	 */
	private static String getDelimitedValues(List list, String delimiter) {
		StringBuffer sBuf = new StringBuffer();
		int size = list.size();
		for (int index = 0; index < size; ++index) {
			if (index != 0) {
				sBuf.append(delimiter);
			}
			sBuf.append(list.get(index));
		}
		return sBuf.toString();
	}

	/**
	 * Method getNodeDiskInfos.
	 * 
	 * @return List<Map<Object,Object>>
	 */
	public List<Map<Object, Object>> getNodeDiskInfos() {

		List<Map<Object, Object>> nodeInfoList = new ArrayList<Map<Object, Object>>();
		Map<Object, Object> nodeDiskInfo;
		FileSystem[] fileSystemList = null;
		FileSystemUsage fileSystemUsage = null;

		try {
			fileSystemList = sigar.getFileSystemList();
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		for (FileSystem fileSystem : fileSystemList) {
			nodeDiskInfo = new HashMap<Object, Object>();
			nodeDiskInfo.put("deviceName", fileSystem.getDevName());
			nodeDiskInfo.put("dirName", fileSystem.getDirName());
			nodeDiskInfo.put("fileSystemEnvironmentType",
					fileSystem.getTypeName());
			nodeDiskInfo.put("fileSystemType", fileSystem.getSysTypeName());
			nodeDiskInfo.put("partitionFlags", fileSystem.getFlags());
			nodeDiskInfo.put("options", fileSystem.getOptions());

			fileSystemUsage = new FileSystemUsage();
			try {
				fileSystemUsage.gather(sigar, fileSystem.getDirName());

			} catch (SigarException e) {
				LOGGER.error(e.getMessage(), e);
			}
			nodeDiskInfo.put("availableMemory", fileSystemUsage.getAvail());
			nodeDiskInfo.put("freeMemory", fileSystemUsage.getFree());
			nodeDiskInfo.put("totalMemory", fileSystemUsage.getTotal());
			nodeDiskInfo.put("usedMemory", fileSystemUsage.getUsed());
			nodeDiskInfo.put("readBytes", fileSystemUsage.getDiskReadBytes());
			nodeDiskInfo.put("reads", fileSystemUsage.getDiskReads());
			nodeDiskInfo.put("writeBytes", fileSystemUsage.getDiskWriteBytes());
			nodeDiskInfo.put("writes", fileSystemUsage.getDiskWrites());

			nodeInfoList.add(nodeDiskInfo);
		}

		return nodeInfoList;
	}

	/**
	 * Method getProcessState.
	 * 
	 * @param state
	 *            char
	 * @return String
	 */
	private static String getProcessState(char state) {
		String strState;
		switch (state) {
		case SLEEP:
			strState = "Sleeping";
			break;
		case RUN:
			strState = "Running";
			break;
		case STOP:
			strState = "Terminated";
			break;
		case ZOMBIE:
			strState = "Zombie";
			break;
		case IDLE:
			strState = "Idle";
			break;
		default:
			strState = "Unknown";
			break;
		}
		return strState;
	}

	/**
	 * Method getNodeProcessInfo.
	 * 
	 * @return List<Map<Object,Object>>
	 */
	private List<Map<Object, Object>> getNodeProcessInfo() {
		List<Map<Object, Object>> nodeProcessInfos = new ArrayList<Map<Object, Object>>();
		try {
			long[] procIdList = sigar.getProcList();

			Mem mem = sigar.getMem();
			float totalMem = mem.getTotal() / NUMBER_1024;

			Result rs = new Result();

			for (long pid : procIdList) {
				try{

				ProcCpu procCpu = sigar.getProcCpu(pid);
				ProcMem procMem = sigar.getProcMem(pid);
				ProcState procState = sigar.getProcState(pid);
				String pName = procState.getName();
				double cpuUsage = 0;
				try {
					// executing jps command.
					String command = "ps -p " + pid + " -o%cpu";
					rs = CommandExecutor.executeCommand(command);
					if (rs.getExitVal() == 0) {
						String usage = rs.getOutput().split("\n")[1].trim();
						cpuUsage = Double.parseDouble(usage);
					}
				} catch (Exception e) {
					LOGGER.error(e.getMessage(), e);
					cpuUsage = procCpu.getPercent();
				}

				double residentMemory = procMem.getResident() / NUMBER_1024;
				double sharedMemory = procMem.getShare() / NUMBER_1024;
				double virtualMemory = procMem.getSize() / NUMBER_1024;
				long threads = procState.getThreads();
				String state = getProcessState(procState.getState());
				double memUsage = (residentMemory / totalMem) * HUNDRED;
				Date startSince = new Date(procCpu.getStartTime());

				Map<Object, Object> nodeProcInfoStatus = new HashMap<Object, Object>();
				nodeProcInfoStatus.put("pid", pid);
				nodeProcInfoStatus.put("pName", pName);
				nodeProcInfoStatus.put("memUsage", dFormatter.format(memUsage));
				nodeProcInfoStatus.put("cpuUsage", cpuUsage);
				nodeProcInfoStatus.put("residentMemory", residentMemory);
				nodeProcInfoStatus.put("sharedMemory", sharedMemory);
				nodeProcInfoStatus.put("virtualMemory", virtualMemory);
				nodeProcInfoStatus.put("threads", threads);
				nodeProcInfoStatus.put("startSince", startSince);
				nodeProcInfoStatus.put("state", state);
				nodeProcInfoStatus.put("ioUsage", 0);
				nodeProcessInfos.add(nodeProcInfoStatus);
				} catch (Exception e) {
					LOGGER.error(e.getMessage(), e);
				}
			}

		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		return nodeProcessInfos;
	}

	/**
	 * Method getNodeProcessCPUInfos.
	 * 
	 * @return List<Map<Object,Object>>
	 */
	public List<Map<Object, Object>> getSortedListByItem(final String item) {
		List<Map<Object, Object>> nodeProcessInfos = getNodeProcessInfo();
		try {
			Collections.sort(nodeProcessInfos,
					new Comparator<Map<Object, Object>>() {

						@Override
						public int compare(Map<Object, Object> arg1,
								Map<Object, Object> arg2) {
							Double d2 = Double.parseDouble(arg2.get(item)
									.toString());
							Double d1 = Double.parseDouble(arg1.get(item)
									.toString());
							return Double.compare(d2, d1);
						}

					});
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
		return nodeProcessInfos;
	}

	/**
	 * Get the Map of all metrics information.
	 * 
	 * @param topProcessCount
	 *            the top process count
	 * @return the node info
	 */
	public Map<String, Object> getNodeInfo(int topProcessCount) {

		Map<String, Object> infoMap = new HashMap<String, Object>();
		infoMap.put("memory", Collections.singletonList(getNodeMemoryInfo()));
		infoMap.put("cpu", getNodeCpuInfos());
		infoMap.put("disk", getNodeDiskInfos());
		infoMap.put("network", getNodeNetworkInfos());
		infoMap.put("swap", Collections.singletonList(getNodeSwapInfo()));
		infoMap.put("os", Collections.singletonList(getNodeOSInfo()));
		infoMap.put("uptime", Collections.singletonList(getNodeUpTimeInfo()));
		infoMap.put("processCPU",
				getSortedListByItem("cpuUsage").subList(0, topProcessCount));
		infoMap.put("processMemory",
				getSortedListByItem("memUsage").subList(0, topProcessCount));
		infoMap.put("processIO",
				getSortedListByItem("ioUsage").subList(0, topProcessCount));
		return infoMap;
	}
}