/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.core.internal.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.wst.sse.core.internal.Logger;
import org.eclipse.wst.sse.core.internal.SSECorePlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

/**
 * This responds to memory events.
 * 
 * Create an instance of a child of this class with the events you are interested in.
 * Then call connect() to start listening. To stop listening call disconnect();
 */
public abstract class AbstractMemoryListener implements EventHandler {
	/**
	 * The event that indicates that memory is running low at the lowest severity.
	 * Listeners are requested to release caches that can easily be recomputed.
	 * The Java VM is not seriously in trouble, but process size is getting higher than 
	 * is deemed acceptable.
	 */
	public static final String SEV_NORMAL = "org/eclipse/equinox/events/MemoryEvent/NORMAL"; //$NON-NLS-1$
	
	/**
	 * The event that indicates that memory is running low at medium severity. 
	 * Listeners are requested to release intermediate build results, complex models, etc.
	 * Memory is getting low and may cause operating system level stress, such as swapping.
	 */
	public static final String SEV_SERIOUS = "org/eclipse/equinox/events/MemoryEvent/SERIOUS"; //$NON-NLS-1$
	
	/**
	 * The event that indicates that memory is running low at highest severity.
	 * Listeners are requested to do things like close editors and perspectives, close database connections, etc.
	 * Restoring these resources and caches constitutes lots of work, but memory is so low that
	 * drastic measures are required.
	 */
	public static final String SEV_CRITICAL = "org/eclipse/equinox/events/MemoryEvent/CRITICAL"; //$NON-NLS-1$
	
	/**
	 * All of the valid memory severities
	 */
	public static final String[] SEV_ALL = { SEV_NORMAL, SEV_SERIOUS, SEV_CRITICAL };

	/**
	 * Used to register the {@link EventAdmin} listener
	 */
	private static BundleContext CONTEXT =
		(SSECorePlugin.getDefault() != null) ?
				SSECorePlugin.getDefault().getBundle().getBundleContext() : null;

	/**
	 * the severities that will be reacted to
	 */
	private final List fSeverities;
	
	/**
	 * service used to register this listener
	 */
	private ServiceRegistration fRegisterService;

	/**
	 * Will listen to all memory events
	 */
	public AbstractMemoryListener() {
		this(AbstractMemoryListener.SEV_ALL);
	}

	/**
	 * Will listen to memory events of the given <code>severity</code>
	 * 
	 * @param severity listen for memory events of this severity
	 */
	public AbstractMemoryListener(String severity) {
		Assert.isNotNull(severity, "Severity can not be null"); //$NON-NLS-1$
		
		List severities = new ArrayList(1);
		severities.add(severity);
		fSeverities = severities;
	}
	
	/**
	 * Will listen to memory events of the given <code>severities</code>
	 * 
	 * @param severities listen for memory events for any of these severities
	 */
	public AbstractMemoryListener(String[] severities) {
		Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$
		Assert.isLegal(severities.length > 0, "Severities must specify at least one severity"); //$NON-NLS-1$
		
		fSeverities = Arrays.asList(severities);
	}

	/**
	 * Will listen to memory events of the given <code>severities</code>
	 * 
	 * @param severities listen for memory events for any of these severities
	 */
	public AbstractMemoryListener(List severities) {
		Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$
		Assert.isLegal(!severities.isEmpty(), "Severities must specify at least one severity"); //$NON-NLS-1$
		fSeverities = severities;
	}

	/**
	 * Connect this listener to the {@link EventAdmin}
	 */
	public final void connect() {
		if (CONTEXT != null) {
			// NOTE: This is TEMPORARY CODE needed to load the plugin
			// until its done automatically by the product
			// TODO: Remove me
			Bundle b = Platform.getBundle("org.eclipse.equinox.event"); //$NON-NLS-1$
			if (b != null && b.getState() == Bundle.RESOLVED) {
				try {
					b.start(Bundle.START_TRANSIENT);
				}
				catch (BundleException e) {
					e.printStackTrace();
				}
			}
			// end remove me
			
			//register this handler
			String[] severities = (String[])fSeverities.toArray(new String[fSeverities.size()]);
			Hashtable prop = new Hashtable(1);
			prop.put(EventConstants.EVENT_TOPIC, severities);
			fRegisterService = CONTEXT.registerService(EventHandler.class.getName(), this, prop);
			
			//call any implementer specific connect code
			doConnect();
		} else {
			Logger.log(Logger.WARNING, "Error accessing bundle context. Is Platform running? Not tracking memory events. "); //$NON-NLS-1$
		}
	}

	/**
	 * Disconnect this listener to the {@link EventAdmin}
	 */
	public final void disconnect() {
		if (fRegisterService != null) {
			fRegisterService.unregister();
			fRegisterService = null;
		}
		
		//call any implementer specific disconnect code
		doDisconnect();
	}

	/**
	 * <p>Filter out any events that are not of the type that this listener handles</p>
	 * 
	 * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
	 */
	public final void handleEvent(Event event) {
		if (fSeverities.contains(event.getTopic())) {
			handleMemoryEvent(event);
		}
	}

	/**
	 * Implementing child classes may assume that only {@link Event}s of the types
	 * given to the constructor will be given to this method.
	 * 
	 * @param event the {@link Event} with a topic equal to one of the memory
	 * severities that this listener is listening for
	 */
	protected abstract void handleMemoryEvent(Event event);
	
	/**
	 * Implementers may overrun this method to do setup after connection of this listener
	 */
	protected void doConnect() {
		//do nothing by default
	}
	
	/**
	 * Implementers may overrun this method to do tear down after disconnection of this listener
	 */
	protected void doDisconnect() {
		//do nothing by default
	}
}
