/**********************************************************************
* This file is part of iDempiere ERP Open Source                      *
* http://www.idempiere.org                                            *
*                                                                     *
* Copyright (C) Contributors                                          *
*                                                                     *
* This program is free software; you can redistribute it and/or       *
* modify it under the terms of the GNU General Public License         *
* as published by the Free Software Foundation; either version 2      *
* of the License, or (at your option) any later version.              *
*                                                                     *
* This program 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 General Public License for more details.                        *
*                                                                     *
* You should have received a copy of the GNU General Public License   *
* along with this program; if not, write to the Free Software         *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
* MA 02110-1301, USA.                                                 *
*                                                                     *
* Contributors:                                                       *
* - Trek Global Corporation                                           *
* - Heng Sin Low                                                      *
**********************************************************************/
package com.trekglobal.idempiere.rest.api.v1.resource.impl;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.logging.Level;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.compiere.model.MUser;
import org.compiere.server.LogFileInfo;
import org.compiere.server.SystemInfo;
import org.compiere.util.CLogMgt;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.compiere.util.Util;
import org.idempiere.distributed.IClusterMember;
import org.idempiere.distributed.IClusterService;
import org.idempiere.server.cluster.callable.DeleteLogsCallable;
import org.idempiere.server.cluster.callable.RotateLogCallable;
import org.idempiere.server.cluster.callable.SetTraceLevelCallable;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.trekglobal.idempiere.rest.api.util.ClusterUtil;
import com.trekglobal.idempiere.rest.api.util.ErrorBuilder;
import com.trekglobal.idempiere.rest.api.v1.resource.NodeResource;

/**
 * @author hengsin
 *
 */
public class NodeResourceImpl implements NodeResource {

	private static final CLogger log = CLogger.getCLogger(NodeResourceImpl.class);
	
	/**
	 * default constructor 
	 */
	public NodeResourceImpl() {
	}

	@Override
	public Response getNodes() {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for get nodes request");

		JsonArray nodes = new JsonArray();
		IClusterService service = ClusterUtil.getClusterService();
		if (service == null) {
			JsonObject localNode = new JsonObject();
			localNode.addProperty("id", LOCAL_ID);
			try {
				localNode.addProperty("name", InetAddress.getLocalHost().getCanonicalHostName());
			} catch (UnknownHostException e) {
				log.log(Level.WARNING, e.getMessage(), e);
			}
			nodes.add(localNode);
			return Response.ok(localNode.toString()).build();
		}
		
		Collection<IClusterMember> members = service.getMembers();
		for(IClusterMember member : members) {
			JsonObject node = new JsonObject();
			node.addProperty("id", member.getId());
			node.addProperty("hostName", member.getAddress().getCanonicalHostName());
			node.addProperty("port", member.getPort());
			nodes.add(node);
		}
		JsonObject json = new JsonObject();
		json.add("nodes", nodes);
		return Response.ok(json.toString()).build();
	}

	@Override
	public Response getNodeInfo(String id) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for get node info request");

		SystemInfo info = getSystemInfo(id);
		
		if (info == null) {
			return Response.status(Status.NOT_FOUND)
					.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
					.build(); 
		}
		
		JsonObject json = new JsonObject();
		json.addProperty("id", id);
		InetAddress address = null;
		try {
			address = info.getAddress() != null ? info.getAddress() : InetAddress.getLocalHost();
		} catch (UnknownHostException e) {
		}
		if (address != null)
			json.addProperty("hostName", address.getCanonicalHostName());
		json.addProperty("home", info.getIDempiereHome());
		json.addProperty("os", info.getOperatingSystem());
		json.addProperty("jvm", info.getJavaVM());
		json.addProperty("databaseDescription", info.getDatabaseDescription());
		json.addProperty("databaseConnectionURL", info.getDatabaseConnectionURL());
		json.addProperty("databaseStatus", info.getDatabaseStatus());
		json.addProperty("availableProcessors", info.getAvailableProcessors());
		json.addProperty("averageSystemLoad", info.getAverageSystemLoad());
		json.addProperty("memoryUsage", info.getMemoryUsage());
		json.addProperty("heapMemoryUsage", info.getHeapMemoryUsage());
		json.addProperty("runtime", info.getRuntimeName());
		json.addProperty("runtimeUptime", TimeUtil.formatElapsed(info.getRuntimeUpTime()));
		json.addProperty("threadCount", info.getThreadCount());
		json.addProperty("peakThreadCount", info.getPeakThreadCount());
		json.addProperty("daemonThreadCount", info.getDaemonThreadCount());
		json.addProperty("totalStartedThreadCount", info.getTotalStartedThreadCount());
		json.addProperty("logLevel", info.getLogLevel().getName());
		json.addProperty("currentLogFile", info.getCurrentLogFile());
		json.addProperty("sessionCount", info.getSessionCount());
		json.addProperty("garbageCollectionCount", info.getGarbageCollectionCount());
		json.addProperty("garbageCollectionTime", info.getGarbageCollectionTime());
		
		return Response.ok(json.toString()).build();
	}

	private SystemInfo getSystemInfo(String id) {
		SystemInfo info = null;
		if (id.equals(LOCAL_ID)) {
			info = SystemInfo.getLocalSystemInfo();
		} else {
			IClusterService service = ClusterUtil.getClusterService();
			if (service != null && service.getLocalMember().getId().equals(id))
				info = SystemInfo.getLocalSystemInfo();
			else
				info = SystemInfo.getClusterNodeInfo(id);
		}
		return info;
	}

	@Override
	public Response getNodeLogs(String id) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for get node logs request");

		SystemInfo info = getSystemInfo(id);
		
		if (info == null) {
			return Response.status(Status.NOT_FOUND)
					.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
					.build(); 
		}
		
		LogFileInfo[] logInfos = info.getLogFileInfos();
		JsonArray logs = new JsonArray();
		for(LogFileInfo logInfo : logInfos) {
			JsonObject log = new JsonObject();
			log.addProperty("fileName", logInfo.getFileName());
			log.addProperty("fileSize", logInfo.getFileSize());
			logs.add(log);
		}
		JsonObject json = new JsonObject();
		json.add("logs", logs);
		return Response.ok(json.toString()).build();
	}

	@Override
	public Response getNodeLogFile(String id, String fileName, String asJson) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for get node log file request");
		FileResourceImpl fileResource = new FileResourceImpl();
		return fileResource.getFile(fileName, id, asJson);
	}

	@Override
	public Response deleteNodeLogs(String id) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for delete logs request");
		DeleteLogsCallable callable = new DeleteLogsCallable();
		Boolean result = null;
		try {
			if (id.equals(LOCAL_ID)) {
				result = callable.call();
			} else {
				IClusterService service = ClusterUtil.getClusterService();
				if (service != null && service.getLocalMember().getId().equals(id)) {
					result = callable.call();
				} else if (service == null) {
					return Response.status(Status.NOT_FOUND)
							.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
							.build();
				} else {
					IClusterMember member = ClusterUtil.getClusterMember(id);
					if (member == null) {
						return Response.status(Status.NOT_FOUND)
								.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
								.build();
					}
					result = service.execute(callable, member).get();
				}
					
			}
		} catch (Exception ex) {
			log.log(Level.SEVERE, ex.getMessage(), ex);
			return Response.status(Status.INTERNAL_SERVER_ERROR)
					.entity(new ErrorBuilder().status(Status.INTERNAL_SERVER_ERROR).title("Server error").append("Server error with exception: ").append(ex.getMessage()).build().toString())
					.build();
		}
		
		SystemInfo info = getSystemInfo(id);
		JsonObject json = new JsonObject();
		json.addProperty("currentLogFile", info.getCurrentLogFile());
		if (result != null && result) {			
			return Response.ok(json.toString()).build();
		} else {
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(json.toString()).build();
		}
	}

	@Override
	public Response rotateNodeLogs(String id) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for rotate log request");
		RotateLogCallable callable = new RotateLogCallable();
		Boolean result = null;
		try {
			if (id.equals(LOCAL_ID)) {
				result = callable.call();
			} else {
				IClusterService service = ClusterUtil.getClusterService();
				if (service != null && service.getLocalMember().getId().equals(id)) {
					result = callable.call();
				} else if (service == null) {
					return Response.status(Status.NOT_FOUND)
							.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
							.build();
				} else {
					IClusterMember member = ClusterUtil.getClusterMember(id);
					if (member == null) {
						return Response.status(Status.NOT_FOUND)
								.entity(new ErrorBuilder().status(Status.NOT_FOUND).title("Invalid Node Id").append("No match found for node id: ").append(id).build().toString())
								.build();
					}
					result = service.execute(callable, member).get();
				}
					
			}
		} catch (Exception ex) {
			log.log(Level.SEVERE, ex.getMessage(), ex);
			return Response.status(Status.INTERNAL_SERVER_ERROR)
					.entity(new ErrorBuilder().status(Status.INTERNAL_SERVER_ERROR).title("Server error").append("Server error with exception: ").append(ex.getMessage()).build().toString())
					.build();
		}
		
		SystemInfo info = getSystemInfo(id);
		JsonObject json = new JsonObject();
		json.addProperty("currentLogFile", info.getCurrentLogFile());
		if (result != null && result)
			return Response.ok(json.toString()).build();
		else
			return Response.status(Status.INTERNAL_SERVER_ERROR).entity(json.toString()).build();
	}

	@Override
	public Response updateNodeLogLevel(String id, String logLevel) {
		MUser user = MUser.get(Env.getCtx());
		if (!user.isAdministrator())
			return forbidden("Access denied", "Access denied for set log level request");

		if (Util.isEmpty(logLevel, true)) {
			return Response.status(Status.BAD_REQUEST)
					.entity(new ErrorBuilder().status(Status.BAD_REQUEST).title("No Log Level").append("No log level parameter").build().toString())
					.build(); 
		}
		
		String levelName = null;
		for(Level level : CLogMgt.LEVELS) {
			if (level.getName().equalsIgnoreCase(logLevel)) {
				levelName = level.getName();
				break;
			}
		}
		if (levelName == null) {
			return Response.status(Status.BAD_REQUEST)
					.entity(new ErrorBuilder().status(Status.BAD_REQUEST).title("Invalid Log Level").append("Invalid log level parameter: "+logLevel).build().toString())
					.build(); 
		}
		
		SetTraceLevelCallable callable = new SetTraceLevelCallable(levelName);
		try 
		{
			if (!Util.isEmpty(id, true)) 
			{
				ClusterUtil.getClusterService().execute(callable, ClusterUtil.getClusterMember(id)).get();
			} 
			else 
			{
				callable.call();				
			}
		} 
		catch (Exception ex) 
		{
			log.log(Level.SEVERE, ex.getMessage(), ex);
			return Response.status(Status.INTERNAL_SERVER_ERROR)
					.entity(new ErrorBuilder().status(Status.INTERNAL_SERVER_ERROR).title("Server error").append("Server error with exception: ").append(ex.getMessage()).build().toString())
					.build();
		}
		
		SystemInfo info = getSystemInfo(id);
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty("logLevel", info.getLogLevel().getName());
		
		return Response.ok(jsonObject.toString()).build();
	}

	private Response forbidden(String title, String detail) {
		return Response.status(Status.FORBIDDEN)
				.entity(new ErrorBuilder().status(Status.FORBIDDEN).title(title).append(detail).build().toString())
				.build();
	}

}
