/Users/petercappello/NetBeansProjects/56-2014/56-2014-5-Gala/src/GVM.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Arrays;

/**
 * Graphics Virtual Machine
 *
 * @author Peter Cappello
 */
public class GVM 
{
    private final static int DATA_MEMORY_SIZE = 100;
    
    private final static int OPCODE   = 0;
    private final static int OPERAND  = 1;
    
    public final static int STOP     = 0;
    public final static int SET      = 1;
    public final static int LOAD     = 2;
    public final static int STORE    = 3;
    public final static int ADD      = 4;
    public final static int ZERO     = 5;
    public final static int GOTO     = 6;
    public final static int SETCOLOR = 7;
    public final static int DRAWLINE = 8;
    public final static int DRAWRECT = 9;
    public final static int FILLRECT = 10;
    public final static int DRAWOVAL = 11;
    public final static int FILLOVAL = 12;
    
    public final static int ACC    = 0;
    public final static int X      = 1;
    public final static int Y      = 2;
    public final static int WIDTH  = 3;
    public final static int HEIGHT = 4;
    public final static int RED    = 5;
    public final static int GREEN  = 6;
    public final static int BLUE   = 7;
        
    final static int IMAGE_SIZE = 800;
    
    private final Image image;
    private final Graphics graphics;
    private final int[] dataMemory = new int[ DATA_MEMORY_SIZE ];
    
    private int[][] programMemory;
    private int instructionAddress;
    private Color color;
        
    GVM() 
    {         
        image = new BufferedImage( IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB );
        graphics = image.getGraphics();
        graphics.setColor( Color.white );
        graphics.fillRect( 0, 0, IMAGE_SIZE, IMAGE_SIZE );
        color = Color.black;
        graphics.setColor( color );
    }
    
    public Image getImage() { return image; }
    
    int[][] getProgramMemory() { return programMemory; }
    
    void load( int[][]  programMemory )
    {
        this.programMemory = programMemory;
        Arrays.fill( dataMemory, 0 );
        instructionAddress = 0;
        
//        // GVM data memory addresses
//        int DECREMENT      = 20;
//        int ROW_COUNTER    = 21;
//        int COLUMN_COUNTER = 22;
//        int DELTA          = 23;
//
//        // GVM program memory addresses
//        int FILL_SQUARE_OVAL_BLOCK = 9;
//        int IS_RED = 16;
//        int IF_ADVANCE_COLUMN = 20;
//        int ADVANCE_COLUMN = 29;
//        int ADVANCE_ROW = 33;
//    
//        // GVM program constants
//        int N           = 17;
//        int DELTA_VALUE = 40;

//        this.programMemory = new int[][]        
//        {
//            // initialize variables: 
//            //  DECREMENT, DELTA, N, ROW_COUNTER, COLUMN_COUNTER
//            { SET, -1 },
//            { STORE, DECREMENT }, 
//            { SET, DELTA_VALUE }, 
//            { STORE, DELTA },
//            { STORE, WIDTH }, 
//            { STORE, HEIGHT },
//            { SET, N },
//            { STORE, ROW_COUNTER },
//            { STORE, COLUMN_COUNTER },
//            // end loacations 8
//            
//            // BeginFillSquare/Oval Block: 9
//            { LOAD, RED },
//            { ZERO,  IS_RED },
//                // is black
//                { SET, 255 },
//                { STORE, RED },
//                { SETCOLOR },
//                { FILLOVAL },
//                { GOTO, IF_ADVANCE_COLUMN },
//                // end location 15
//            
//                // is red: 16
//                { SET, 0 },
//                { STORE, RED },
//                { SETCOLOR },
//                { FILLRECT },
//            // end FillSquare/Oval Block: location 19
//                
//            // begin If Advance Column Block: 20
//            { LOAD, COLUMN_COUNTER },
//            { ADD, DECREMENT },
//            { STORE, COLUMN_COUNTER },
//            { ZERO, ADVANCE_COLUMN },
//                
//            // Finished columns. Advance row?
//            { LOAD, ROW_COUNTER },
//            { ADD, DECREMENT },
//            { STORE, ROW_COUNTER },
//            { ZERO, ADVANCE_ROW },
//            // Finished rows.
//            { STOP },
//            // loction 28
//            
//            // Advance column block: 29
//            { LOAD, X },
//            { ADD, DELTA },
//            { STORE, X },
//            { GOTO, FILL_SQUARE_OVAL_BLOCK },
//            // end location 32
//            
//            // Advance row block: 33
//            { LOAD, Y },
//            { ADD, DELTA },
//            { STORE, Y },
//                // reset column
//            { SET, 0 },
//            { STORE, X },
//            { SET, N },
//            { STORE, COLUMN_COUNTER },
//            { GOTO, FILL_SQUARE_OVAL_BLOCK }
//        };
        
//        programMemory = new int[][]
//        {
//            { SET, 20 },  // ACC <- 10
//            { STORE, X },   // STORE ACC -> X
//            { STORE, Y },   // STORE ACC -> Y
//            { STORE, WIDTH }, // STORE ACC -> WIDTH
//            { STORE, HEIGHT }, // STORE ACC -> HEIGHT
//            { DRAWRECT }, // DRAWRECT
//            { SET, 255 },  // ACC <- 255
//            { STORE, RED }, // ACC -> RED
//            { SETCOLOR }, // SETCOLOR to red
//            { LOAD, X }, // ACC <- X
//            { ADD, X }, // ACC += X
//            { STORE, X }, // ACC -> X
//            { STORE, Y }, // ACC -> Y
//            { STORE, WIDTH }, // STORE ACC -> WIDTH
//            { STORE, HEIGHT }, // STORE ACC -> HEIGHT
//            { DRAWOVAL }, // FILLRECT
//            { LOAD, X }, // ACC <- X
//            { ADD, X }, // ACC += X
//            { STORE, X }, // ACC -> X
//            { STORE, Y }, // ACC -> Y
//            { STORE, WIDTH }, // STORE ACC -> WIDTH
//            { STORE, HEIGHT }, // STORE ACC -> HEIGHT
//            { FILLOVAL }, // FILLRECT
//            { LOAD, X }, // ACC <- X
//            { ADD, X }, // ACC += X
//            { STORE, X }, // ACC -> X
//            { STORE, Y }, // ACC -> Y
//            { STORE, WIDTH }, // STORE ACC -> WIDTH
//            { STORE, HEIGHT }, // STORE ACC -> HEIGHT
//            { FILLRECT }, // FILLRECT
//            { LOAD, X }, // ACC <- X
//            { ADD, X }, // ACC += X
//            { STORE, X }, // ACC -> X
//            { STORE, Y }, // ACC -> Y
//            { STORE, WIDTH }, // STORE ACC -> WIDTH
//            { STORE, HEIGHT }, // STORE ACC -> HEIGHT
//            { DRAWLINE }, // FILLRECT
//            { 0 }       // STOP
//        };
    }
        
    void step()
    { 
        if ( programMemory[ instructionAddress ][ OPCODE]  != STOP )
        {
            executeInstruction( programMemory[ instructionAddress ] );
        }
        else
        {
            instructionAddress = 0;
            System.out.print(" STOP. " );
        }
    }
    
    void run()
    {
        while ( programMemory[ instructionAddress ][ OPCODE]  != STOP )
        {
            executeInstruction( programMemory[ instructionAddress ] );
        }
        instructionAddress = 0;
        System.out.println(" Program has executed a STOP instruction. " );
    }
    
    private void executeInstruction( int[] instruction )
    {
        System.out.println( "instruction address: " + instructionAddress 
                + " OPCODE: " + instruction[ OPCODE ] 
                + " "
                + instructionToString( instruction ) );
        int nextInstructionAddress = instructionAddress + 1;
        switch ( instruction[ OPCODE ] )
        { 
            case SET:
                dataMemory[ ACC ] = instruction[ OPERAND ];
                break;
        
            case LOAD:
                dataMemory[ ACC ] = dataMemory[ instruction[ OPERAND ] ];
                break;
                
            case STORE:
                dataMemory[ instruction[ OPERAND ] ] = dataMemory[ ACC ];
                break;
                
            case ADD:
                dataMemory[ ACC ] += dataMemory[ instruction[ OPERAND ] ];
                break;
        
            case ZERO:
                if ( dataMemory[ ACC ] != 0 )
                {
                    nextInstructionAddress = instruction[ OPERAND ];
                }
                break;
                
            case GOTO:
                nextInstructionAddress = instruction[ OPERAND ];
                break;
                
            case SETCOLOR:
                color = new Color( dataMemory[ RED ], dataMemory[ GREEN ], dataMemory[ BLUE ] );
                graphics.setColor( color );
                break;
                
            case DRAWLINE:
                graphics.drawLine( dataMemory[ X ], dataMemory[ Y ], dataMemory[ X ] + dataMemory[ WIDTH ], dataMemory[ Y ] + dataMemory[ HEIGHT ] );
                break;
                
            case DRAWRECT:
                graphics.drawRect( dataMemory[ X ], dataMemory[ Y ], dataMemory[ WIDTH ], dataMemory[ HEIGHT ] );
                break;
                
            case FILLRECT:
                graphics.fillRect( dataMemory[ X ], dataMemory[ Y ], dataMemory[ WIDTH ], dataMemory[ HEIGHT ] );
                break;
        
            case DRAWOVAL:
                graphics.drawOval( dataMemory[ X ], dataMemory[ Y ], dataMemory[ WIDTH ], dataMemory[ HEIGHT ] );
                break;
                
            case FILLOVAL:
                graphics.fillOval( dataMemory[ X ], dataMemory[ Y ], dataMemory[ WIDTH ], dataMemory[ HEIGHT ] );
                break;
                
            default : 
                assert false : "Invalid operation code: " + instruction[ OPCODE ];
        }
        instructionAddress = nextInstructionAddress;
    }
    
    private String instructionToString( int[] instruction )
    {
        StringBuilder string = new StringBuilder();
        string.append( opcodeMnemonics[ instruction[ OPCODE ] ] ).append( ' ');
        switch ( instruction[ OPCODE ] )
        {
            case SET: case GOTO:
                string.append( instruction[ OPERAND ] );
                break;
        
            case LOAD:
                string.append(instruction[ OPERAND ]).append(" memory cell value: ").append( dataMemory[ instruction[ OPERAND ] ]);
                break;
                
            case STORE:
                string.append(dataMemory[ ACC ]).append(" in memory cell ").append(instruction[ OPERAND ]).append(" cell: ").append( dataMemory[ ACC ]);
                break;
                
            case ADD:
                string.append("datamemory[ ").append(instruction[ OPERAND ]).append(" ] of value: ").append( instruction[ OPERAND ] ).append(" to ACC: ").append( dataMemory[ ACC ]);
                break;
        
            case ZERO:
                string.append("ACC: [ ").append( dataMemory[ ACC ]).append( " ] ");
                if ( dataMemory[ ACC ] != 0 )
                {
                    string.append(" branching to ").append( instruction[ OPERAND ]);
                }
                break;
                
            case SETCOLOR:
                string.append( color );
                break;
                
            case DRAWLINE: case DRAWRECT: case FILLRECT: case DRAWOVAL: case FILLOVAL:
                string.append(" X: ").append(dataMemory[ X ]).append(" Y: ").append(dataMemory[ Y ]).append(" W: ").append(dataMemory[ WIDTH ]).append(" H: ").append(dataMemory[ HEIGHT ]);
                break;
                
            default : 
                assert false : "Invalid operation code: " + instruction[ OPCODE ];
        }
        return string.toString();
    }
    
    private final String[] opcodeMnemonics = 
    {
        "stop", 
        "set", 
        "load", 
        "store", 
        "add", 
        "zero", 
        "goto", 
        "setcColor", 
        "drawLine", 
        "drawRect", 
        "fillRect", 
        "drawOval", 
        "fillOval" 
    };
}