/Users/petercappello/NetBeansProjects/56-2014/56-2014-2-GraphicsVM/src/GVM.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;

/**
 * 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;
    
    private final static int STOP     = 0;
    private final static int SET      = 1;
    private final static int LOAD     = 2;
    private final static int STORE    = 3;
    private final static int ADD      = 4;
    private final static int ZERO     = 5;
    private final static int GOTO     = 6;
    private final static int SETCOLOR = 7;
    private final static int DRAWLINE = 8;
    private final static int DRAWRECT = 9;
    private final static int FILLRECT = 10;
    private final static int DRAWOVAL = 11;
    private final static int FILLOVAL = 12;
    
    private final static int ACC    = 0;
    private final static int X      = 1;
    private final static int Y      = 2;
    private final static int WIDTH  = 3;
    private final static int HEIGHT = 4;
    private final static int RED    = 5;
    private final static int GREEN  = 6;
    private 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 = 0;
    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; }
    
    void load()
    {
        // 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;

        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
        {
            System.out.print(" STOP. " );
        }
    }
    
    void run()
    {
        while ( programMemory[ instructionAddress ][ OPCODE]  != STOP )
        {
            executeInstruction( programMemory[ instructionAddress ] );
        }
        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( opcodeMneumonics[ 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[] opcodeMneumonics = 
    {
        "stop", 
        "set", 
        "load", 
        "store", 
        "add", 
        "zero", 
        "goto", 
        "setcColor", 
        "drawLine", 
        "drawRect", 
        "fillRect", 
        "drawOval", 
        "fillOval" 
    };
}