/*******************************************************************************
 * Copyright (c) 2003, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.server.core.internal;

import java.util.*;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.wst.server.core.*;
/**
 * 
 */
public class ServerMonitorManager implements IServerMonitorManager {
	private static final String MONITOR_DATA_FILE = "monitors.xml";
	
	protected static ServerMonitorManager instance;

	protected List<MonitoredPort> ports = new ArrayList<MonitoredPort>(); 
	protected ServerMonitor monitor;
	
	class MonitoredPort implements IMonitoredServerPort {
		protected IServer server;
		protected ServerPort port;
		protected int newPort = -1;
		protected String[] content;
		public boolean started;
		
		public MonitoredPort(IServer server, ServerPort port, int newPort, String[] content) {
			this.server = server;
			this.port = port;
			this.newPort = newPort;
			this.content = content;
		}
		
		public MonitoredPort(IMemento memento, IProgressMonitor monitor) {
			load(memento, monitor);
		}
		
		public IServer getServer() {
			return server;
		}
		
		public ServerPort getServerPort() {
			return port;
		}
		
		public int getMonitorPort() {
			return newPort;
		}
		
		public void setMonitorPort(int p) {
			newPort = p;
		}
		
		public String[] getContentTypes() {
			if (content == null)
				return new String[0];
			return content;
		}
		
		public boolean isStarted() {
			return started;
		}
		
		protected void setStarted(boolean s) {
			started = s;
		}
		
		public boolean equals(Object obj) {
			if (!(obj instanceof MonitoredPort))
				return false;
			MonitoredPort mp = (MonitoredPort) obj;
			if (!mp.server.equals(server))
				return false;
			if (!mp.port.equals(port))
				return false;
			if (newPort != mp.newPort)
				return false;
			if (content == null && mp.content != null)
				return false;
			if (content != null && mp.content == null)
				return false;
			if (content != null) {
				int size = content.length;
				if (size != mp.content.length)
					return false;
				for (int i = 0; i < size; i++)
					if (!content[i].equals(mp.content[i]))
						return false;
			}
			return true;
		}
		
		protected boolean canSave() {
			return (port.getId() != null);
		}
		
		protected void save(IMemento memento) {
			memento.putString("serverId", server.getId());
			if (newPort != -1)
				memento.putString("port", newPort + "");
			memento.putString("portId", port.getId());
			memento.putBoolean("started", started);
			
			if (content != null) {
				StringBuffer sb = new StringBuffer();
				int size = content.length;
				for (int i = 0; i < size; i++) {
					if (i > 0)
						sb.append(",");
					sb.append(content[i]);
				}
				memento.putString("contentTypes", sb.toString());
			}
		}
		
		protected void load(IMemento memento, IProgressMonitor monitor2) {
			String serverId = memento.getString("serverId");
			server = null;
			if (serverId != null)
				server = ServerCore.findServer(serverId);
			if (server == null)
				throw new RuntimeException("Server could not be found: " + serverId + " " + server);
			String newPortStr = memento.getString("port");
			if (newPortStr != null && newPortStr.length() > 0)
				newPort = Integer.parseInt(newPortStr);
			String portId = memento.getString("portId");
			
			ServerPort[] ports2 = server.getServerPorts(monitor2);
			if (ports2 != null) {
				int size = ports2.length;
				for (int i = 0; port == null && i < size; i++) {
					ServerPort sp = ports2[i];
					if (sp.getId() != null && sp.getId().equals(portId))
						port = sp;
				}
			}
			if (port == null)
				throw new RuntimeException("Could not relocate port: " + serverId + " " + server + " " + portId);
			
			String s = memento.getString("contentTypes");
			if (s != null)
				content = ServerPlugin.tokenize(s, ",");
			
			Boolean b = memento.getBoolean("started");
			if (b != null && b.booleanValue()) {
				try {
					newPort = monitor.startMonitoring(server, port, newPort);
					started = true;
				} catch (CoreException e) {
					if (Trace.WARNING) {
						Trace.trace(Trace.STRING_WARNING, "Could not restart server monitor", e);
					}
				}
			}
		}
	}

	public ServerMonitorManager() {
		IServerMonitor[] monitors = ServerPlugin.getServerMonitors();
		if (monitors != null && monitors.length > 0)
			monitor = (ServerMonitor) monitors[0];
		
		instance = this;
		loadMonitors();
	}
	
	public static ServerMonitorManager getInstance() {
		if (instance == null)
			new ServerMonitorManager();
		return instance;
	}
	
	public static void shutdown() {
		if (instance == null)
			return;
		instance.saveMonitors();
	}

	/**
	 * Returns the monitor that is currently being used.
	 *  
	 * @return the current server monitor
	 */
	public IServerMonitor getCurrentServerMonitor() {
		return monitor;
	}

	/**
	 * Switch to use a different server monitor. All existing monitors will be
	 * removed from the current monitor and added to the new monitor.
	 * 
	 * @param newMonitor
	 * @throws org.eclipse.core.runtime.CoreException
	 */
	public void setServerMonitor(IServerMonitor newMonitor) throws CoreException {
		throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0, "Not implemented yet", null));
	}

	/**
	 * Returns the list of ports that are currently being monitored.
	 *
	 * @param server a server
	 * @return a possibly empty array of monitored server ports
	 */
	public IMonitoredServerPort[] getMonitoredPorts(IServer server) {
		List<IMonitoredServerPort> list = new ArrayList<IMonitoredServerPort>();
		Iterator iterator = ports.iterator();
		while (iterator.hasNext()) {
			MonitoredPort mp = (MonitoredPort) iterator.next();
			if (mp.server.equals(server))
				list.add(mp);
		}
		
		return list.toArray(new IMonitoredServerPort[list.size()]);
	}

	/**
	 * Starts monitoring the given port, and returns the new port # to use that will
	 * route to the monitored port.
	 * 
	 * @param server a server
	 * @param port a port
	 * @param monitorPort the port used for monitoring
	 * @param content the content
	 * @return a monitored server port
	 */
	public IMonitoredServerPort createMonitor(IServer server, ServerPort port, int monitorPort, String[] content) {
		if (port == null || monitor == null)
			return null;
		
		MonitoredPort mp = new MonitoredPort(server, port, monitorPort, content);
		ports.add(mp);
		return mp;
	}

	/**
	 * Stop monitoring the given port. Throws a CoreException if there was a problem
	 * stopping the monitoring
	 *
	 * @param port
	 */
	public void removeMonitor(IMonitoredServerPort port) {
		if (port == null)
			return;

		try {
			ports.remove(port);
			if (port.isStarted())
				monitor.stopMonitoring(port.getServer(), port.getServerPort());
		} catch (Exception e) {
			if (Trace.SEVERE) {
				Trace.trace(Trace.STRING_SEVERE, "Could not remove monitor", e);
			}
		}
	}

	/**
	 * Start the monitor. If the msp port is -1, it will be updated to the port that is actually in use.
	 * 
	 * @param msp
	 * @throws CoreException
	 */
	public void startMonitor(IMonitoredServerPort msp) throws CoreException {
		if (msp == null || msp.isStarted())
			return;
		
		MonitoredPort port = (MonitoredPort) msp;
		port.setMonitorPort(monitor.startMonitoring(msp.getServer(), msp.getServerPort(), msp.getMonitorPort()));
		port.setStarted(true);
	}

	/**
	 * Stop monitoring.
	 * 
	 * @param msp
	 */
	public void stopMonitor(IMonitoredServerPort msp) {
		if (msp == null || !msp.isStarted())
			return;
		MonitoredPort port = (MonitoredPort) msp;
		monitor.stopMonitoring(msp.getServer(), msp.getServerPort());
		port.setStarted(false);
	}

	/**
	 * Returns the mapped port to use when making requests to the given server
	 * and port number. Returns the existing port number if the port is not being
	 * monitored.
	 * 
	 * @param server a server
	 * @param port a port number
	 * @param content the content
	 * @return the port used for monitoring
	 */
	public int getMonitoredPort(IServer server, int port, String content) {
		try {
			Iterator iterator = ports.iterator();
			while (iterator.hasNext()) {
				MonitoredPort mp = (MonitoredPort) iterator.next();
				if (mp.isStarted() && mp.server.equals(server) && mp.port.getPort() == port) {
					String[] contentTypes = mp.getContentTypes();
					boolean found = false;
					if (content != null && contentTypes != null && contentTypes.length > 0) {
						int size = contentTypes.length;
						for (int i = 0; i < size; i++)
							if (content.equals(contentTypes[i]))
								found = true;
					} else
						found = true;
					if (found && mp.newPort != -1)
						return mp.newPort;
				}
			}
		} catch (Exception e) {
			// ignore
		}
		return port;
	}
	
	protected void saveMonitors() {
		String filename = ServerPlugin.getInstance().getStateLocation().append(MONITOR_DATA_FILE).toOSString();
		try {
			XMLMemento memento = XMLMemento.createWriteRoot("monitors");

			Iterator iterator = ports.iterator();
			while (iterator.hasNext()) {
				MonitoredPort mp = (MonitoredPort) iterator.next();
				if (mp.canSave()) {
					IMemento child = memento.createChild("monitor");
					mp.save(child);
				}
			}
			
			memento.saveToFile(filename);
		} catch (Exception e) {
			if (Trace.SEVERE) {
				Trace.trace(Trace.STRING_SEVERE, "Error saving monitor info", e);
			}
		}
	}
	
	protected void loadMonitors() {
		if (Trace.FINEST) {
			Trace.trace(Trace.STRING_FINEST, "Loading monitor info");
		}
		String filename = ServerPlugin.getInstance().getStateLocation().append(MONITOR_DATA_FILE).toOSString();
		
		try {
			IMemento memento = XMLMemento.loadMemento(filename);
			
			IMemento[] children = memento.getChildren("monitor");
			int size = children.length;
			
			for (int i = 0; i < size; i++) {
				try {
					MonitoredPort mp = new MonitoredPort(children[i], null);
					ports.add(mp);
				} catch (Exception e) {
					if (Trace.WARNING) {
						Trace.trace(Trace.STRING_WARNING, "Could not load monitor: " + e);
					}
				}
			}
		} catch (Exception e) {
			if (Trace.WARNING) {
				Trace.trace(Trace.STRING_WARNING, "Could not load monitor info", e);
			}
		}
	}
}
