/*******************************************************************************
 * ===========================================================
 * 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.ankush2.cassandra.monitor;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import com.impetus.ankush.common.exception.AnkushException;
import com.impetus.ankush.common.utils.JmxUtil;
import com.impetus.ankush2.cassandra.utils.CassandraConstants;
import com.impetus.ankush2.logger.AnkushLogger;

public class CassandraJMX {
	/** The logger. */
	private static AnkushLogger LOG = new AnkushLogger(CassandraJMX.class);
	private static DecimalFormat df = new DecimalFormat("###.##");

	private JmxUtil jmxUtil;
	private MBeanServerConnection connection;

	public CassandraJMX(String hostname, int jmxPort) throws Exception {

		jmxUtil = new JmxUtil(hostname, jmxPort);

		// connect to node
		connection = jmxUtil.connect();
		if (connection == null) {
			throw new AnkushException("Could not create JMX connection.");
		}
	}

	private List<String> getKeyspaces() {

		ObjectName mObjNameStorageService;
		try {
			mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);
			Object attrKeyspace = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_KEYSPACES);

			return (List<String>) attrKeyspace;
		} catch (MalformedObjectNameException e) {
			LOG.error("Could not fetch keyspaces.", e);
		}
		return null;
	}

	private Map<String, String> getOwnershipMap() {
		Map<String, String> ownershipMap = null;

		try {
			ObjectName mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);

			Object attrOwnership = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_OWNERSHIP);
			LinkedHashMap ownership = (LinkedHashMap) attrOwnership;

			ownershipMap = new HashMap<String, String>();
			for (Object key : ownership.keySet()) {
				ownershipMap
						.put(key.toString().substring(
								key.toString().lastIndexOf("/") + 1),
								String.valueOf(df.format(((Float) ownership
										.get(key)) * 100)) + " %");
			}

		} catch (MalformedObjectNameException e) {
			LOG.error("Could not get ownership details.", e);
		}

		return ownershipMap;
	}

	private Map<String, String> getLoadMap() {
		Map<String, String> loadMap = null;
		ObjectName mObjNameStorageService;
		try {
			mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);
			// Getting load
			Object attrLoadMap = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_LOADMAP);
			Map<String, String> load = (HashMap<String, String>) attrLoadMap;
			loadMap = new HashMap<String, String>();
			for (Map.Entry<String, String> entry : load.entrySet()) {
				loadMap.put(entry.getKey().trim(), entry.getValue().trim());
			}
		} catch (MalformedObjectNameException e) {
			LOG.error("Could not get load details.", e);
		}
		return loadMap;
	}

	private Map<String, String> getHostIdMap() {
		Map<String, String> hostIdMap = null;
		try {
			ObjectName mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);
			Object attrHostIdMap = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_HOSTIDMAP);
			Map<String, String> hostId = (HashMap<String, String>) attrHostIdMap;
			hostIdMap = new HashMap<String, String>();
			for (Map.Entry<String, String> entry : hostId.entrySet()) {
				hostIdMap.put(entry.getKey().trim(), entry.getValue().trim());
			}
		} catch (MalformedObjectNameException e) {
			LOG.error("Could not get host Id details.", e);
		}
		return hostIdMap;
	}

	private Map<String, String> getStateMap() {
		Map<String, String> stateMap = null;
		try {
			ObjectName mObjNameFailureDetector = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "net:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_FAILUREDETECTOR);
			// Getting status
			Object attrStatus = jmxUtil
					.getAttribute(
							mObjNameFailureDetector,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_SIMPLESTATES);
			Map<String, String> status = (Map) attrStatus;
			;
			stateMap = new HashMap<String, String>();
			for (Map.Entry<String, String> entry : status.entrySet()) {

				stateMap.put(
						entry.getKey().substring(
								entry.getKey().lastIndexOf("/") + 1),
						entry.getValue());
			}
		} catch (MalformedObjectNameException e) {
			LOG.error("Could not get host Id details.", e);
		}
		return stateMap;
	}

	// TODO: Can be eliminated using keys retrun from above functions.
	private List<String> getNodeList() {
		List<String> nodes = null;

		try {
			ObjectName mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);
			Object attributeLiveNodes = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_LIVE_NODES);
			Object attributeUnreachableNodes = jmxUtil
					.getAttribute(
							mObjNameStorageService,
							CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_ATTRIBUTE_UNREACHABLE_NODES);

			nodes = new ArrayList<String>();
			nodes.addAll((List<String>) attributeLiveNodes);
			nodes.addAll((List<String>) attributeUnreachableNodes);
		} catch (MalformedObjectNameException e) {
			LOG.error("Could not get node list.", e);
		}

		return nodes;
	}

	public List<CassandraNode> getCassandraNodeList() {
		List<CassandraNode> nodeList = null;
		Map<String, String> ownershipMap = getOwnershipMap();
		Map<String, String> loadMap = getLoadMap();
		Map<String, String> hostIdMap = getHostIdMap();
		Map<String, String> stateMap = getStateMap();
		List<String> nodes = getNodeList();

		if (ownershipMap == null || loadMap == null || hostIdMap == null
				|| stateMap == null || nodes == null) {
			LOG.error("Missing details.");
			return null;
		}

		try {

			ObjectName mObjNameStorageService = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_STORAGESERVICE);

			ObjectName mObjNameEndPointSnitchInfo = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "db:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_ENDPOINT_SNITCH_INFO);

			ObjectName mObjNameFailureDetector = new ObjectName(
					CassandraConstants.ORG_APACHE_CASSANDRA
							+ "net:type="
							+ CassandraConstants.Cassandra_JMX_Attributes.CASSANDRA_JMX_OBJECT_FAILUREDETECTOR);

			nodeList = new ArrayList<CassandraNode>();

			// getting topology information

			for (String cNodeConf : nodes) {

				Object opParams[] = { cNodeConf };
				String opSig[] = { String.class.getName() };

				CassandraNode node = new CassandraNode();
				Object result = connection
						.invoke(mObjNameEndPointSnitchInfo,
								CassandraConstants.JMX_Operations.CASSANDRA_JMX_ATTRIBUTE_GETRACK,
								opParams, opSig);
				node.setRack((String) result);

				result = connection
						.invoke(mObjNameEndPointSnitchInfo,
								CassandraConstants.JMX_Operations.CASSANDRA_JMX_ATTRIBUTE_GETDATACENTER,
								opParams, opSig);
				node.setDataCenter((String) result);

				node.setHost(cNodeConf);
				node.setOwnership(ownershipMap.get(cNodeConf));
				node.setLoad((loadMap.get(cNodeConf) != null
						&& !loadMap.get(cNodeConf).isEmpty() ? loadMap
						.get(cNodeConf) : "?"));
				node.setStatus(stateMap.get(cNodeConf));
				node.setHostId(hostIdMap.get(cNodeConf));

				result = connection
						.invoke(mObjNameFailureDetector,
								CassandraConstants.JMX_Operations.CASSANDRA_JMX_ATTRIBUTE_ENDPOINTSTATE,
								opParams, opSig);
				String strResult = (String) result;

				List<String> sysoutList = new ArrayList<String>(
						Arrays.asList(strResult.split("\n")));
				for (String outData : sysoutList) {
					List<String> paramList = new ArrayList<String>(
							Arrays.asList(outData.split(":")));
					String key = paramList.get(0).trim();
					if (key.equals("STATUS")) {
						node.setState((!paramList.get(1).trim().split(",")[0]
								.isEmpty() ? paramList.get(1).trim().split(",")[0]
								: "?"));
						break;
					}
				}

				// Getting tokens
				result = connection
						.invoke(mObjNameStorageService,
								CassandraConstants.JMX_Operations.CASSANDRA_JMX_ATTRIBUTE_TOKENS,
								opParams, opSig);
				List<String> tokens = (List<String>) result;
				node.setTokenCount(String.valueOf(tokens.size()));

				nodeList.add(node);
			}
		} catch (Exception e) {
			LOG.error("Could not get Cassandra node list.", e);
			nodeList = null;
		}

		return nodeList;
	}

	@Override
	protected void finalize() throws Throwable {
		if (jmxUtil != null) {
			jmxUtil.disconnect();
		}
		super.finalize();
	}
}