package processor;

import java.util.Stack;

import processor.StackFrame.FrameType;

/**
 * manages the memory for a {@link PseudoCodeProcessor}
 * @author kolja
 *
 */
public class Memory {

    /**
     * The visibility of a variable
     * GLOBAL means that the variable can be accessed by using its name
     * LOCAL means that the variable can only be accessed if it is on the topmost {@link StackFrame}.
     * COMPLETE_STACK is the same as LOCAL for write access but for read access the whole {@link Stack} is searched.
     * @author kolja
     *
     */
    public enum Visibility
    {
        GLOBAL,
        LOCAL,
        COMPLETE_STACK
    }

    /**
     * a memory without the possibility to update or insert values.
     * physically this is the same storage as the corresponding {@link Memory} object so there the values can still be initialized.
     * @author kolja
     *
     */
    public class ReadOnlyMemory
    {
        public <T> T read( String name, Visibility visibility )
        {
            return Memory.this.read( name, visibility );
        }
        
        public boolean isSomewhereDefined( String name, Visibility visibility )
        {
            return Memory.this.isSomewhereDefined( name, visibility );
        }
        
        public boolean isDefined( String name, Visibility visibility )
        {
            return Memory.this.isDefined( name, visibility );
        }
    }
    
    private StackFrame global;
    private Stack< StackFrame > stack;
    
    
    public Memory()
    {
        stack = new Stack<StackFrame>();
        global = new StackFrame( FrameType.FUNCTION );
    }
    
    public synchronized ReadOnlyMemory createReadOnlyMemory()
    {
        return new ReadOnlyMemory();
    }
    
    public synchronized int getSize() 
    {
        return stack.size();
    }
    
    public synchronized void addFrame( StackFrame frame )
    {
        stack.push( frame );
    }
    
    public synchronized StackFrame removeFrame()
    {
        return stack.pop();
    }
    
    /**
     * declare a new variable
     * @param name the name of the variable
     * @param value the value of the variable
     * @param visibility visibility of the variable
     * @param <T> the type of the variable
     */
    public synchronized <T> void declare( String name, T value, Visibility visibility )
    {
        switch( visibility )
        {
        case GLOBAL:
            this.global.declare( name, value );
            break;
        case COMPLETE_STACK:
        case LOCAL:
        	if( stack.size() == 0 )
        		return;
            stack.peek().declare( name, value );
            break;
        default:
            assert false;
            break;
        }
    }
    
    /**
     * Writes a value to a previously declared variable.
     * If the variable hat not been declared yet, nothing happens.
     * @param name the name of the variable
     * @param value the value of the variable
     * @param visibility visibility of the variable
     * @param <T> the type of the variable
     */
    public synchronized <T> void write( String name, T value, Visibility visibility )
    {
        switch( visibility )
        {
        case GLOBAL:
            this.global.set( name, value );
            break;
        case COMPLETE_STACK:
        case LOCAL:
        	int index = stack.size() - 1;
        	while( index >= 0 ) {
        	   StackFrame stackF = stack.get( index-- );
        	   if( stackF.isDefined( name ) )
        	   {
        		   stackF.set( name, value );
        		   return;
        	   }
        	   if( stackF.getType() == FrameType.FUNCTION )
        		   break;
        	}
        	break;
        default:
            assert false;
            break;
        }
    }
    
    /**
     * 
     * @param name the name of the variable
     * @param visibility visibility of the variable
     * @param <T> the type of the variable
     * @return
     */
    public synchronized <T> T read( String name, Visibility visibility )
    {
        int index = stack.size() - 1;
        switch( visibility )
        {
        case GLOBAL:
            return this.global.get( name );
        case LOCAL:
            while( index >= 0 ) {
                StackFrame stackF = stack.get( index-- );
                if( stackF.isDefined( name ) )
                    return stackF.get( name );
                if( stackF.getType() == FrameType.FUNCTION )
                    break;
             }
            break;
        case COMPLETE_STACK:
            while( index >= 0 ) {
                StackFrame stackF = stack.get( index-- );
                if( stackF.isDefined( name ) )
                    return stackF.get( name );
             }
            break;
        default:
            assert false;
            break;
        }
        return null;
    }
    
    public synchronized boolean isSomewhereDefined( String name, Visibility visibility )
    {
        int index = stack.size() - 1;
        switch( visibility )
        {
        case GLOBAL:
            return this.global.isDefined( name );
        case LOCAL:
            while( index >= 0 ) {
                StackFrame stackF = stack.get( index-- );
                if( stackF.isDefined( name ) )
                    return true;
                if( stackF.getType() == FrameType.FUNCTION )
                    break;
             }
            break;
        case COMPLETE_STACK:
            while( index >= 0 ) {
                StackFrame stackF = stack.get( index-- );
                if( stackF.isDefined( name ) )
                    return true;
             }
            break;
        default:
            break;
        }
    	return false;
    }
    
    public synchronized boolean isDefined( String name, Visibility visibility )
    {
        switch( visibility )
        {
        case GLOBAL:
            return this.global.isDefined( name );
        case COMPLETE_STACK:
        case LOCAL:
            if( stack.size() == 0 )
                return false;
            return stack.peek().isDefined( name );
        default:
            break;
        }
    	return false;
    }
    
    public synchronized void undeclare( String name, Visibility visibility )
    {
        switch( visibility )
        {
        case GLOBAL:
            this.global.undeclare( name );
            break;
        case COMPLETE_STACK:
        case LOCAL:
        	if( stack.size() == 0 )
        		return;
            stack.peek().undeclare( name );
            break;
        default:
            break;
        }
    }
}
