// Copyright (c) Corporation for National Research Initiatives
package org.python.core;


/**
 * A python frame object.
 */

public class PyFrame extends PyObject
{
    public PyFrame f_back;
    public PyTableCode f_code;
    public PyObject f_locals;
    public PyObject f_globals;
    public int f_lineno;
    public PyObject f_builtins;
    public PyObject[] f_fastlocals;
    public PyCell[] f_env; // nested scopes: cell + free env
    public int f_ncells;
    public int f_nfreevars;

    // an interface to functions suitable for tracing, e.g. via sys.settrace()
    public TraceFunction tracefunc;

    public static PyClass __class__;

    private static final String[] __members__ = {
        "f_back", "f_code", "f_locals", "f_globals", "f_lineno",
        "f_builtins", "f_trace"
    };

    public PyFrame(PyTableCode code, PyObject locals, PyObject globals,
                   PyObject builtins)
    {
        super(__class__);
        f_code = code;
        f_locals = locals;
        f_globals = globals;
        f_builtins = builtins;
        // This needs work to be efficient with multiple interpreter states
        if (locals == null && code != null) {
            // ! f_fastlocals needed for arg passing too
            if ((code.co_flags&PyTableCode.CO_OPTIMIZED)!=0 ||
                                        code.nargs > 0) {
                if (code.co_nlocals > 0) {
                    // internal: may change
                    f_fastlocals = new PyObject[
                                code.co_nlocals-code.jy_npurecell];
                }
            } else
                f_locals = new PyStringMap();
        }
        if (code != null) { // reserve space for env
            int env_sz = 0;
            if (code.co_freevars != null)
                env_sz += (f_nfreevars = code.co_freevars.length);
            if (code.co_cellvars != null)
                env_sz += (f_ncells = code.co_cellvars.length);
            if (env_sz > 0)
                f_env = new PyCell[env_sz];
        }
    }

    public PyFrame(PyTableCode code, PyObject globals) {
        this(code, null, globals, null);
    }

    public String toString() {
        if (f_code == null) {
            return "<frame (unknown code) at line "+f_lineno+">";
        } else {
            return "<frame in \""+f_code.co_name+"\" at line "+f_lineno+">";
        }
    }

    public PyObject __dir__() {
        PyString members[] = new PyString[__members__.length];
        for (int i = 0; i < __members__.length; i++)
            members[i] = new PyString(__members__[i]);
        return new PyList(members);
    }

    private void throwReadonly(String name) {
        for (int i = 0; i < __members__.length; i++)
            if (__members__[i] == name)
                throw Py.TypeError("readonly attribute");
        throw Py.AttributeError(name);
    }

    public void __setattr__(String name, PyObject value) {
        // In CPython, some of the frame's attributes are read/writeable
        if (name == "f_trace")
            tracefunc = new PythonTraceFunction(value);
        // not yet implemented:
        // f_exc_type
        // f_exc_value
        // f_exc_traceback
        else throwReadonly(name);
    }

    public void __delattr__(String name) {
        if (name == "f_trace")
            tracefunc = null;
        // not yet implemented:
        // f_exc_type
        // f_exc_value
        // f_exc_traceback
        else throwReadonly(name);
    }

    public PyObject __findattr__(String name) {
        if (name == "f_locals")
            return getf_locals();
        else if (name == "f_trace") {
            if (tracefunc instanceof PythonTraceFunction) {
                return ((PythonTraceFunction)tracefunc).tracefunc;
            }
            return Py.None;
        }
        return super.__findattr__(name);
    }

    public PyObject getf_locals() {
        if (f_locals == null)
            f_locals = new PyStringMap();
        if (f_code!=null && f_code.co_nlocals>0) {
            int i;
            if (f_fastlocals != null) {
                for (i=0; i<f_fastlocals.length; i++) {
                    PyObject o = f_fastlocals[i];
                    if (o != null)
                        f_locals.__setitem__(f_code.co_varnames[i], o);
                }
                if ((f_code.co_flags&PyTableCode.CO_OPTIMIZED) == 0)
                    f_fastlocals = null;
            }
            int j = 0;
            for (i=0; i<f_ncells; i++,j++) {
                PyObject v = f_env[j].ob_ref;
                if (v != null) f_locals.__setitem__(f_code.co_cellvars[i],v);
            }
            for (i=0; i<f_nfreevars; i++,j++) {
                PyObject v = f_env[j].ob_ref;
                if (v != null) f_locals.__setitem__(f_code.co_freevars[i],v);
            }
        }
        return f_locals;
    }

    public void setline(int line) {
        f_lineno = line;
        if (tracefunc != null)
            tracefunc = tracefunc.traceLine(this, line);
    }

    public int getline() {
        return f_lineno;
    }

    public PyObject getlocal(int index) {
        if (f_fastlocals != null) {
            PyObject ret = f_fastlocals[index];
            if (ret != null)
                return ret;
        }
        return getlocal(f_code.co_varnames[index]);
    }

    public PyObject getlocal(String index) {
        // System.err.println("getlocal: "+index);
        if (f_locals == null)
            getf_locals();
        PyObject ret = f_locals.__finditem__(index);
        if (ret != null)
            return ret;

        throw Py.UnboundLocalError("local: '"+index+"'");
        //return getglobal(index);
    }

    public PyObject getname(String index) {
        if (f_locals == null) getf_locals();
        if (f_locals == f_globals) return getglobal(index);

        PyObject ret = f_locals.__finditem__(index);
        if (ret != null) return ret;
        return getglobal(index);
    }

    public PyObject getglobal(String index) {
        PyObject ret = f_globals.__finditem__(index);
        if (ret != null) {
            return ret;
        }

        // Set up f_builtins if not already set
        if (f_builtins == null) {
            f_builtins = Py.getSystemState().builtins;
        }
        ret = f_builtins.__finditem__(index);
        if (ret != null) return ret;

        throw Py.NameError(index);
    }

    public void setlocal(int index, PyObject value) {
        if (f_fastlocals != null)
            f_fastlocals[index] = value;
        else
            setlocal(f_code.co_varnames[index], value);
    }

    public void setlocal(String index, PyObject value) {
        if (f_locals == null)
            getf_locals();
        f_locals.__setitem__(index, value);
    }

    public void setglobal(String index, PyObject value) {
        f_globals.__setitem__(index, value);
    }

    public void dellocal(int index) {
        if (f_fastlocals != null) {
            if (f_fastlocals[index] == null) {
              throw Py.UnboundLocalError("local: '"+
                                         f_code.co_varnames[index]+"'");
            }
            f_fastlocals[index] = null;
        } else
            dellocal(f_code.co_varnames[index]);
    }

    public void dellocal(String index) {
        if (f_locals == null)
            getf_locals();
        try {
            f_locals.__delitem__(index);
        } catch(PyException e) {
          if (!Py.matchException(e,Py.KeyError)) throw e;
          throw Py.UnboundLocalError("local: '"+index+"'");
        }
    }

    public void delglobal(String index) {
        f_globals.__delitem__(index);
    }

    // nested scopes helpers

    public PyObject getclosure(int index) {
        return f_env[index];
    }

    public PyObject getderef(int index) {
        PyObject obj=f_env[index].ob_ref;
        if (obj != null) return obj;
        String name;
        if (index >= f_ncells) name = f_code.co_freevars[index-f_ncells];
        else name = f_code.co_cellvars[index];
        throw Py.UnboundLocalError("local: '"+name+"'");
    }

    public void setderef(int index,PyObject value) {
        f_env[index].ob_ref = value;
    }

    public void to_cell(int parm_index,int env_index) {
        f_env[env_index].ob_ref=f_fastlocals[parm_index];
    }
}
