/*******************************************************************************
 * Copyright (c) 2010, 2012 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.core.model;

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

import org.eclipse.core.runtime.URIUtil;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.wst.jsdt.debug.core.jsdi.Location;
import org.eclipse.wst.jsdt.debug.core.jsdi.ScriptReference;
import org.eclipse.wst.jsdt.debug.core.jsdi.StackFrame;
import org.eclipse.wst.jsdt.debug.core.jsdi.Variable;
import org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame;
import org.eclipse.wst.jsdt.debug.core.model.IJavaScriptValue;

/**
 * A JavaScript stack frame
 * 
 * @since 1.0
 */
public final class JavaScriptStackFrame extends JavaScriptDebugElement implements IJavaScriptStackFrame {

	/**
	 * Owning thread.
	 */
	private JavaScriptThread thread;

	/**
	 * The underlying {@link StackFrameReference}
	 */
	private StackFrame stackFrame = null;

	private ArrayList variables;
	
	private IVariable thisvar = null;

	/**
	 * Constructs a Rhino stack frame in the given thread.
	 * 
	 * @param thread
	 */
	public JavaScriptStackFrame(JavaScriptThread thread, StackFrame stackFrame) {
		super(thread.getJavaScriptDebugTarget());
		this.thread = thread;
		this.stackFrame = stackFrame;
	}

	/**
	 * Returns the underlying JSDI {@link StackFrame}
	 * 
	 * @return the underlying JSDI {@link StackFrame}
	 */
	StackFrame getUnderlyingStackFrame() {
		return this.stackFrame;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getCharEnd()
	 */
	public int getCharEnd() throws DebugException {
		return -1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getCharStart()
	 */
	public int getCharStart() throws DebugException {
		return -1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getLineNumber()
	 */
	public int getLineNumber() throws DebugException {
		return (stackFrame.location() != null ? stackFrame.location().lineNumber() : -1);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getName()
	 */
	public String getName() throws DebugException {
		return this.stackFrame.location().functionName();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getRegisterGroups()
	 */
	public IRegisterGroup[] getRegisterGroups() throws DebugException {
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getThread()
	 */
	public IThread getThread() {
		return this.thread;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#getVariables()
	 */
	public synchronized IVariable[] getVariables() throws DebugException {
		if (this.variables == null) {
			List underlyingVariables = this.stackFrame.variables();
			this.variables = new ArrayList(underlyingVariables.size() + 1);
			for (Iterator iterator = underlyingVariables.iterator(); iterator.hasNext();) {
				Variable variable = (Variable) iterator.next();
				JavaScriptVariable jsdiVariable = new JavaScriptVariable(this, variable);
				this.variables.add(jsdiVariable);
			}
			
			Collections.sort(variables, new Comparator() {		
				public int compare(Object arg0, Object arg1) {
					
					IVariable var0 = (IVariable) arg0;
					IVariable var1 = (IVariable) arg1;
					try {
						return var0.getName().compareToIgnoreCase(var1.getName());
					} catch (DebugException e) {
						return 0;
					}
				}
			});
			
			// add the "this" object at the front
			thisvar = new JavaScriptVariable(this, stackFrame.thisObject());
			variables.add(0, thisvar);			
		}
		return (IVariable[]) this.variables.toArray(new IVariable[this.variables.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#hasRegisterGroups()
	 */
	public boolean hasRegisterGroups() throws DebugException {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStackFrame#hasVariables()
	 */
	public boolean hasVariables() throws DebugException {
		return this.stackFrame.variables().size() > 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#canStepInto()
	 */
	public boolean canStepInto() {
		return this.thread.canStepInto();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#canStepOver()
	 */
	public boolean canStepOver() {
		return this.thread.canStepOver();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#canStepReturn()
	 */
	public boolean canStepReturn() {
		return this.thread.canStepReturn();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#isStepping()
	 */
	public boolean isStepping() {
		return this.thread.isStepping();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#stepInto()
	 */
	public void stepInto() throws DebugException {
		this.thread.stepInto();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#stepOver()
	 */
	public void stepOver() throws DebugException {
		this.thread.stepOver();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.IStep#stepReturn()
	 */
	public void stepReturn() throws DebugException {
		this.thread.stepReturn();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ISuspendResume#canResume()
	 */
	public boolean canResume() {
		return this.thread.canResume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ISuspendResume#canSuspend()
	 */
	public boolean canSuspend() {
		return this.thread.canSuspend();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ISuspendResume#isSuspended()
	 */
	public boolean isSuspended() {
		return this.thread.isSuspended();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ISuspendResume#resume()
	 */
	public void resume() throws DebugException {
		this.thread.resume();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ISuspendResume#suspend()
	 */
	public void suspend() throws DebugException {
		this.thread.suspend();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ITerminate#canTerminate()
	 */
	public boolean canTerminate() {
		return this.thread.canTerminate();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ITerminate#isTerminated()
	 */
	public boolean isTerminated() {
		return this.thread.isTerminated();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.debug.core.model.ITerminate#terminate()
	 */
	public void terminate() throws DebugException {
		if (this.thread.canTerminate()) {
			this.thread.terminate();
		} else {
			getJavaScriptDebugTarget().terminate();
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame#evaluate(java.lang.String)
	 */
	public IJavaScriptValue evaluate(String expression) {
		return this.thread.evaluate(expression);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame#getSourceName()
	 */
	public String getSourceName() {
		Location loc = getUnderlyingStackFrame().location();
		if(loc != null) {
			ScriptReference script = loc.scriptReference();
			if(script != null) {
				String segment = URIUtil.lastSegment(script.sourceURI());
				if (segment == null) {
					return script.sourceURI().toString();
				}
				return segment;
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame#getSourcePath()
	 */
	public String getSourcePath() {
		Location loc = getUnderlyingStackFrame().location();
		if(loc != null) {
			ScriptReference script = loc.scriptReference();
			if(script != null) {
				return script.sourceURI().toString();
			}
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame#getSource()
	 */
	public String getSource() {
		Location loc = getUnderlyingStackFrame().location();
		if(loc != null) {
			ScriptReference script = loc.scriptReference();
			if(script != null) {
				return script.source();
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptDebugElement#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class adapter) {
		if(IJavaScriptStackFrame.class == adapter) {
			return this;
		}
		return super.getAdapter(adapter);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame#getThisObject()
	 */
	public IVariable getThisObject() {
		return thisvar;
	}
}
