/***** BEGIN LICENSE BLOCK *****
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/epl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2007 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2006 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby.runtime;

import org.jruby.RubyModule;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * A Frame holds per-call information that needs to persist outside the
 * execution of a given method. Currently a frame holds the following:
 * <ul>
 * <li>The class against which this method is being invoked. This is usually
 * (always?) the class of "self" within this call.</li>
 * <li>The current "self" for the call.</li>
 * <li>The name of the method being invoked during this frame, used for
 * backtraces and "super" invocations.</li>
 * <li>The block passed to this invocation. If the given code body can't
 * accept a block, it will be Block.NULL_BLOCK.</li>
 * <li>Whether this is the frame used for a binding-related call like eval. This
 * is used to determine where to terminate evaled code's backtrace.</li>
 * <li>The current visibility for methods defined during this call. Starts out
 * as PUBLIC by default (in most cases) and can be modified by appropriate
 * Kernel.public/private/protected calls.</li>
 * <li>The jump target marker for non-local returns.</li>
 * </ul>
 * Frames are allocated for all Ruby methods (in compatibility mode, default)
 * and for some core methods. In general, a frame is required for a method to
 * show up in a backtrace, and so some methods only use frame for backtrace
 * information (so-called "backtrace frames").
 * 
 * @see ThreadContext
 */
public final class Frame {
    /** The class against which this call is executing. */
    private RubyModule klazz;
    
    /** The 'self' for this frame. */
    private IRubyObject self;
    
    /** The name of the method being invoked in this frame. */
    private String name;

    /**
     * The block that was passed in for this frame (as either a block or a &amp;block argument).
     * The frame captures the block for super/zsuper, but also for Proc.new (with no arguments)
     * and also for block_given?.  Both of those methods needs access to the block of the 
     * previous frame to work.
     */ 
    private Block block = Block.NULL_BLOCK;

    /** The current visibility for anything defined under this frame */
    private Visibility visibility = Visibility.PUBLIC;
    
    /** backref **/
    private IRubyObject backRef;
    
    /** lastline **/
    private IRubyObject lastLine;
    
    /** whether this frame has been captured into a binding **/
    private boolean captured;
    
    /**
     * Empty constructor, since Frame objects are pre-allocated and updated
     * when needed.
     */
    public Frame() {
    }
    
    /**
     * Copy constructor, since Frame objects are pre-allocated and updated
     * when needed.
     */
    private Frame(Frame frame) {
        assert frame.block != null : "Block uses null object pattern.  It should NEVER be null";

        this.self = frame.self;
        this.name = frame.name;
        this.klazz = frame.klazz;
        this.block = frame.block;
        this.visibility = frame.visibility;
    }

    /**
     * Update the frame with just filename and line, used for top-level frames
     * and method.
     */
    public void updateFrame() {
        updateFrame(null, null, null, Block.NULL_BLOCK, 0);
    }

    /**
     * Update the frame with caller information and method name, so it will
     * show up correctly in call stacks.
     * 
     * @param name The name of the method being called
     */
    public void updateFrame(String name) {
        this.name = name;
    }

    /**
     * Update the frame based on information from another frame. Used for
     * cloning frames (for blocks, usually) and when entering class bodies.
     * 
     * @param frame The frame whose data to duplicate in this frame
     */
    public void updateFrame(Frame frame) {
        assert frame.block != null : "Block uses null object pattern.  It should NEVER be null";

        this.self = frame.self;
        this.name = frame.name;
        this.klazz = frame.klazz;
        this.block = frame.block;
        this.visibility = frame.visibility;
    }

    /**
     * Update the frame based on the given values.
     * 
     * @param klazz The class against which the method is being called
     * @param self The 'self' for the method
     * @param name The name under which the method is being invoked
     * @param block The block passed to the method
     * @param jumpTarget The target for non-local jumps (return in block)
     */
    public void updateFrame(RubyModule klazz, IRubyObject self, String name,
                 Block block, int jumpTarget) {
        assert block != null : "Block uses null object pattern.  It should NEVER be null";

        this.self = self;
        this.name = name;
        this.klazz = klazz;
        this.block = block;
        this.visibility = Visibility.PUBLIC;
    }

    /**
     * Update the frame based on the given values.
     * 
     * @param self The 'self' for the method
     * @param jumpTarget The target for non-local jumps (return in block)
     */
    public void updateFrameForEval(IRubyObject self, int jumpTarget) {
        this.self = self;
        this.name = null;
        this.visibility = Visibility.PRIVATE;
    }

    /**
     * Clear the frame, as when the call completes. Clearing prevents cached
     * frames from holding references after the call is done.
     */
    public Frame clear() {
        this.self = null;
        this.klazz = null;
        this.block = Block.NULL_BLOCK;
        this.backRef = null;
        this.lastLine = null;
        
        return this;
    }
    
    /**
     * Clone this frame.
     * 
     * @return A new frame with duplicate information to the target frame
     */
    public Frame duplicate() {
        return new Frame(this);
    }

    /**
     * Clone this frame for use in backtraces only (avoiding long-lived
     * references to other elements.
     *
     * @return A new frame with identical backtrace information to this frame
     */
    public Frame duplicateForBacktrace() {
        Frame backtraceFrame = new Frame();
        backtraceFrame.name = name;
        return backtraceFrame;
    }

    /** 
     * Return class that we are calling against
     * 
     * @return The class we are calling against
     */
    public RubyModule getKlazz() {
        return klazz;
    }

    /**
     * Set the class we are calling against.
     * 
     * @param klazz the new class
     */
    public void setKlazz(RubyModule klazz) {
        this.klazz = klazz;
    }

    /**
     * Set the method name associated with this frame
     * 
     * @param name the new name
     */
    public void setName(String name) {
        this.name = name;
    }

    /** 
     * Get the method name associated with this frame
     * 
     * @return the method name
     */
    public String getName() {
        return name;
    }

    /**
     * Get the self associated with this frame
     * 
     * @return The self for the frame
     */
    public IRubyObject getSelf() {
        return self;
    }

    /** 
     * Set the self associated with this frame
     * 
     * @param self The new value of self
     */
    public void setSelf(IRubyObject self) {
        this.self = self;
    }
    
    /**
     * Get the visibility at the time of this frame
     * 
     * @return The visibility
     */
    public Visibility getVisibility() {
        return visibility;
    }
    
    /**
     * Change the visibility associated with this frame
     * 
     * @param visibility The new visibility
     */
    public void setVisibility(Visibility visibility) {
        this.visibility = visibility;
    }
    
    /**
     * Retrieve the block associated with this frame.
     * 
     * @return The block of this frame or NULL_BLOCK if no block given
     */
    public Block getBlock() {
        return block;
    }
    
    public IRubyObject getBackRef(IRubyObject nil) {
        IRubyObject backRef = this.backRef;
        return backRef == null ? nil : backRef;
    }
    
    public IRubyObject setBackRef(IRubyObject backRef) {
        return this.backRef = backRef;
    }
    
    public IRubyObject getLastLine(IRubyObject nil) {
        IRubyObject lastLine = this.lastLine;
        return lastLine == null ? nil : lastLine;
    }
    
    public IRubyObject setLastLine(IRubyObject lastLine) {
        return this.lastLine = lastLine;
    }
    
    public void setCaptured(boolean captured) {
        this.captured = captured;
    }
    
    public Frame capture() {
        captured = true;
        return this;
    }
    
    public boolean isCaptured() {
        return captured;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(50);
        
        sb.append(klazz);
        if (name != null) sb.append(" in ").append(name);

        return sb.toString();
    }
}
