// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.scheme.compiler;

import bdc.scheme.GlobalEnvironment;
import bdc.scheme.Symbol;
import bdc.scheme.Variable;
import bdc.scheme.VariableArray;
import bdc.scheme.expression.Expression;
import bdc.scheme.expression.LexicalAddress;
import bdc.scheme.expression.LocalAddress;
import bdc.util.Assert;

/**
    CompileTimeEnvironment are used to support LexicalAddress compilation.
*/
public class CompileTimeEnvironment
{
    /**
        The global environment if this is the top level lexical environment
    */
    private GlobalEnvironment globalEnvironment;

    /**
        The closing environment. A null implies the global environment.
    */
    private CompileTimeEnvironment enclosing;

    /**
        The variables in this environment
    */
    private VariableArray variables;

    /**
        Create an empty top-level CompileTimeEnvironment
    */
    CompileTimeEnvironment (GlobalEnvironment globalEnvironment)
    {
        this(null, new VariableArray());
        this.globalEnvironment = globalEnvironment;
    }

    /**
        Create a new enclosing CompileTimeEnvironment
    */
    private CompileTimeEnvironment (CompileTimeEnvironment enclosing,
                                    VariableArray          variables)
    {
        this.enclosing = enclosing;
        this.variables = variables;

        this.variables.arguments = variables.inUse;
    }

    /**
        create a new CompileTimeEnvironment by extending an existing
        one with new variables.
    */
    CompileTimeEnvironment extend (VariableArray variables)
    {
        return new CompileTimeEnvironment(this, variables);
    }

    /**
        add a local to the list of variables for this frame
    */
    void addLocal (Variable local)
    {
        variables.addElement(local);
    }

    /**
        remove locals by marking them dead
    */
    void removeLocals (int n)
    {
        if (n == 0) {
            return;
        }
        Variable[] v = variables.array();
        for (int i = variables.inUse-1; i >= 0; i--) {
            if (!v[i].dead) {
                v[i].dead = true;
                n--;
                if (n == 0) {
                    return;
                }
            }
        }
    }

    public VariableArray variables ()
    {
        return variables;
    }

    /**
        Search for variable starting in the current lexical environment.
        If found
        then return a LexicalAddress that describes where it was found
        else return a GlobalVariable for the variable
    */
    Expression findVariable (Symbol name)
    {
            // Lexical
        CompileTimeEnvironment environment = this;
        int frameIndex = 0;
        while (true) {
            Variable[] variables  = environment.variables.array();
            for (int displacement = environment.variables.inUse-1;
                 displacement >= 0;
                 displacement--)
            {
                Variable variable = variables[displacement];
                if (variable.dead) {
                    continue;
                }
                if (name != variable.name) {
                    continue;
                }

                /*
                    If we are going back more than the current frame,
                    we are inside a closure and mark the variable to be
                    heap allocated
                */
                if (frameIndex != 0) {
                    variable.heap = true;
                }

                /*
                    If we know the variable is heap allocated,
                    make a LexicalAddress. Just fill in the
                    displacement as -1. We will have to make a
                    second pass to fill in the displacement
                    because the variables can change from local to
                    lexical as we compile various subexpressions
                    of lamdba
                */
                if (variable.heap) {
                    return new LexicalAddress(variable, -1, -1);
                }

                /*
                    If we aren't sure that a variable is to be heap
                    allocated, be optimistic and make a LocalAddress.
                    We need to fix up the -1 on the next pass.
                */
                return new LocalAddress(variable, -1);

            }

            if (environment.enclosing == null) {
                    // Global
                return environment.globalEnvironment.define(name);
            }

            environment = environment.enclosing;
            frameIndex++;
        }
    }

    /**
        Interface for the second pass where we pass in the existing
        address so that the we can fix it up.
    */
    public Expression fixupVariable (Variable variable, Object address)
    {
        CompileTimeEnvironment environment = this;

        /*
            Our optimistic assumption of LocalAddress paid off...
        */
        if (!variable.heap) {
            LocalAddress localAddress = (LocalAddress)address;
            Variable[] variables = environment.variables.array();
            int index = -1;
            int i;
            for (i = 0; i < environment.variables.arguments; i++) {
                if (variable == variables[i]) {
                    localAddress.index = index;
                    return localAddress;
                }
                index--;
            }
            index = 0;
            for (; i < environment.variables.inUse; i++) {
                if (variable == variables[i]) {
                    localAddress.index = index;
                    return localAddress;
                }
                if (!variables[i].heap) {
                    index++;
                }
            }
            Assert.that(false, "not reached: would be lexical variable");
        }


        /*
            Now take care of LexicalAddresses
        */
        int frame = 0;
        while (environment != null) {
            Variable[] variables = environment.variables.array();
            int displacement = 0;
            for (int i = 0; i < environment.variables.inUse; i++)
            {
                if (variable != variables[i]) {
                    if (variables[i].heap) {
                        displacement++;
                    }
                    continue;
                }
                if (address instanceof LexicalAddress) {
                    LexicalAddress lexicalAddress = (LexicalAddress)address;
                    lexicalAddress.frame        = frame;
                    lexicalAddress.displacement = displacement;
                    return lexicalAddress;
                }
                return new LexicalAddress(variable, frame, displacement);
            }
                // only count non-empty heaps as we would at run time
            if (environment.variables.heap != 0) {
                frame++;
            }
            environment = environment.enclosing;
        }

        Assert.that(false, "not reached: would be global variable");
        return null;
    }
}
