package animation;

import java.util.Stack;

import animation.StackFrame.FrameType;

public class Memory {

    public enum MemoryType
    {
        GLOBAL,
        LOCAL,
        COMPLETE_STACK
    }
    
    public class ReadOnlyMemory
    {
        public <T> T read( String name, MemoryType type )
        {
            return Memory.this.read( name, type );
        }
        
        public boolean isSomewhereDefined( String name, MemoryType type )
        {
            return Memory.this.isSomewhereDefined( name, type );
        }
        
        public boolean isDefined( String name, MemoryType type )
        {
            return Memory.this.isDefined( name, type );
        }
    }
    
    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();
    }
    
    public synchronized <T> void declare( String name, T value, MemoryType type )
    {
        switch( type )
        {
        case GLOBAL:
            this.global.declare( name, value );
            break;
        case COMPLETE_STACK:
        case LOCAL:
        	if( stack.size() == 0 )
        		return;
            stack.peek().declare( name, value );
            break;
        }
    }
    
    public synchronized <T> void write( String name, T value, MemoryType type )
    {
        switch( type )
        {
        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;
        	}
        }
    }
    
    public synchronized <T> T read( String name, MemoryType type )
    {
        int index = stack.size() - 1;
        switch( type )
        {
        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 );
             }
        }
        return null;
    }
    
    public synchronized boolean isSomewhereDefined( String name, MemoryType type )
    {
        int index = stack.size() - 1;
        switch( type )
        {
        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;
             }
        }
    	return false;
    }
    
    public synchronized boolean isDefined( String name, MemoryType type )
    {
        switch( type )
        {
        case GLOBAL:
            return this.global.isDefined( name );
        case COMPLETE_STACK:
        case LOCAL:
            if( stack.size() == 0 )
                return false;
            return stack.peek().isDefined( name );
        }
    	return false;
    }
    
    public synchronized void undeclare( String name, MemoryType type )
    {
        switch( type )
        {
        case GLOBAL:
            this.global.undeclare( name );
            break;
        case COMPLETE_STACK:
        case LOCAL:
        	if( stack.size() == 0 )
        		return;
            stack.peek().undeclare( name );
        }
    }
}
