/*******************************************************************************
 * Copyright (c) 2010 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.jsdt.debug.internal.rhino.jsdi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.wst.jsdt.debug.core.jsdi.StackFrame;
import org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference;
import org.eclipse.wst.jsdt.debug.internal.rhino.RhinoDebugPlugin;
import org.eclipse.wst.jsdt.debug.internal.rhino.transport.JSONConstants;
import org.eclipse.wst.jsdt.debug.internal.rhino.transport.RhinoRequest;
import org.eclipse.wst.jsdt.debug.internal.rhino.transport.RhinoResponse;
import org.eclipse.wst.jsdt.debug.transport.exception.DisconnectedException;
import org.eclipse.wst.jsdt.debug.transport.exception.TimeoutException;

/**
 * Rhino implementation of {@link ThreadReference}
 * 
 * @since 1.0
 */
public class ThreadReferenceImpl extends MirrorImpl implements ThreadReference {

	private Number threadId;
	private List frames;
	private int status = THREAD_STATUS_RUNNING;
	private boolean suspended;
	private boolean atBreakpoint = false;
	private String name;
	private String step;

	/**
	 * Constructor
	 * 
	 * @param vm
	 * @param jsonThread
	 */
	public ThreadReferenceImpl(VirtualMachineImpl vm, Map jsonThread) {
		super(vm);
		this.threadId = (Number) jsonThread.get(JSONConstants.THREAD_ID);
		this.name = (String) jsonThread.get(JSONConstants.NAME);
		if (this.name == null) {
			// TODO NLS this
			this.name = "Rhino - " + threadId; //$NON-NLS-1$
		}
		this.suspended = JSONConstants.SUSPENDED.equals(jsonThread.get(JSONConstants.STATE));
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#frameCount()
	 */
	public synchronized int frameCount() {
		frames();
		return (frames == null ? 0 : frames.size());
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#frame(int)
	 */
	public synchronized StackFrame frame(int index) {
		frames();
		return (StackFrame) frames.get(index);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#frames()
	 */
	public synchronized List frames() {
		if (!suspended || status == THREAD_STATUS_ZOMBIE) {
			return Collections.EMPTY_LIST;
		}
		if (frames != null) {
			return frames;
		}
		RhinoRequest request = new RhinoRequest(JSONConstants.FRAMES);
		request.getArguments().put(JSONConstants.THREAD_ID, threadId);
		try {
			RhinoResponse response = vm.sendRequest(request, 30000);
			List frameIds = (List) response.getBody().get(JSONConstants.FRAMES);
			if (frameIds.isEmpty()) {
				return Collections.EMPTY_LIST;
			}
			frames = new ArrayList();
			for (Iterator iterator = frameIds.iterator(); iterator.hasNext();) {
				Long frameId = new Long(((Number) iterator.next()).longValue());
				StackFrameImpl frame = createStackFrame(frameId);
				frames.add(frame);
			}
		} catch (DisconnectedException e) {
			handleException(e.getMessage(), e);
		} catch (TimeoutException e) {
			RhinoDebugPlugin.log(e);
		}
		if(frames != null) {
			return frames;
		}
		return Collections.EMPTY_LIST;
	}

	/**
	 * Sends a request to make a new {@link StackFrameReference} in the current debugger.
	 * Returns a new {@link StackFrameReference} on success, <code>null</code> on failure
	 * 
	 * @param frameId
	 * @return a new {@link StackFrameReference} or <code>null</code>
	 */
	private StackFrameImpl createStackFrame(Long frameId) {
		RhinoRequest request = new RhinoRequest(JSONConstants.FRAME);
		request.getArguments().put(JSONConstants.THREAD_ID, threadId);
		request.getArguments().put(JSONConstants.FRAME_ID, frameId);
		try {
			RhinoResponse response = vm.sendRequest(request);
			Map jsonFrame = (Map) response.getBody().get(JSONConstants.FRAME);
			return new StackFrameImpl(vm, jsonFrame);
		} catch (DisconnectedException e) {
			handleException(e.getMessage(), e);
		} catch (TimeoutException e) {
			RhinoDebugPlugin.log(e);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#isAtBreakpoint()
	 */
	public synchronized boolean isAtBreakpoint() {
		return suspended && atBreakpoint;
	}

	/**
	 * Marks the current thread reference as suspended
	 * 
	 * @param atBreakpoint
	 */
	public synchronized void markSuspended(boolean atBreakpoint) {
		this.suspended = true;
		this.atBreakpoint = atBreakpoint;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#isSuspended()
	 */
	public synchronized boolean isSuspended() {
		return suspended;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#name()
	 */
	public String name() {
		return name;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#resume()
	 */
	public synchronized void resume() {
		if (status == THREAD_STATUS_ZOMBIE) {
			return;
		}
		RhinoRequest request = new RhinoRequest(JSONConstants.CONTINUE);
		request.getArguments().put(JSONConstants.THREAD_ID, threadId);
		if (step != null) {
			request.getArguments().put(JSONConstants.STEP, step);
		}
		try {
			RhinoResponse response = vm.sendRequest(request);
			if (response.isSuccess()) {
				step = null;
				frames = null;
				suspended = false;
				atBreakpoint = false;
			}
		} catch (DisconnectedException e) {
			handleException(e.getMessage(), e);
		} catch (TimeoutException e) {
			RhinoDebugPlugin.log(e);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#suspend()
	 */
	public synchronized void suspend() {
		if (status == THREAD_STATUS_ZOMBIE) {
			return;
		}
		RhinoRequest request = new RhinoRequest(JSONConstants.SUSPEND);
		request.getArguments().put(JSONConstants.THREAD_ID, threadId);
		try {
			RhinoResponse response = vm.sendRequest(request);
			if(response.isSuccess()) {
				markSuspended(false);
			}
		} catch (DisconnectedException e) {
			handleException(e.getMessage(), e);
		} catch (TimeoutException e) {
			RhinoDebugPlugin.log(e);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#interrupt()
	 */
	public void interrupt() {
		throw new UnsupportedOperationException();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.jsdi.ThreadReference#status()
	 */
	public synchronized int status() {
		return status;
	}

	/**
	 * Returns the step type
	 * 
	 * @return the step type
	 * @see JSONConstants#STEP_IN
	 * @see JSONConstants#STEP_NEXT
	 * @see JSONConstants#STEP_OUT
	 */
	public synchronized String getStep() {
		return step;
	}

	/**
	 * Sets the current step type
	 * 
	 * @param step
	 * @see JSONConstants#STEP_IN
	 * @see JSONConstants#STEP_NEXT
	 * @see JSONConstants#STEP_OUT
	 */
	public synchronized void setStep(String step) {
		this.step = step;
	}
}
