

import java.awt.event.*;
import java.awt.*;
import java.lang.*;
import java.util.LinkedList;


public class SnakeApplet extends java.applet.Applet implements ActorVisitor {
    Arena   arena;
    Snake   snake;
    int     direction;
    private UpdateInterface update;
    KeyListener keyListener;
    Image backBuffer;
    Graphics gBack;
    LinkedList keyboardQueue = new LinkedList();
       
    /** Initializes the applet SnakeApplet */
    public void init() {
        
        resize(640,480);
        
        keyListener = new KeyListener() {
            public void keyPressed(KeyEvent e) {
                handleKeys(e);
            }
            public void keyReleased(KeyEvent e) {}
            public void keyTyped(KeyEvent e) {}           
            
        };
        addKeyListener( keyListener );
        
        
        addFocusListener( new FocusListener() {
            public void focusLost(FocusEvent e) {
                update.suspend();
                repaint();
            }
            public void focusGained(FocusEvent e) {
                update.resume();
                repaint();
            }
        });
        
        /*
        KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        fm.addKeyEventDispatcher(new KeyEventDispatcher() {
            public boolean dispatchKeyEvent(KeyEvent e) {
                handleKeys(e);
                return true;
            }
        });
         */
        
    }
    
    public void start(){
        direction = Part.RIGHT;
        snake = new Snake( 15, 5, 10 );
        arena = new Arena( snake, 62, 46);
        
        backBuffer = createImage(620, 460);
        gBack = backBuffer.getGraphics();
           
        new Thread(
            update = new UpdateInterface() {
                private boolean alive = true;
                private boolean suspend = !hasFocus();
                
                public void run() {
                    for (;alive;) {
                        try {
                            Thread.sleep(100);
                        } catch (Exception e ) {};
                            if (!suspend)
                                snakeRun();
                    }
                }
                
                public void suspend() {
                    suspend = true;
                }
                public void resume() {
                    suspend = false;
                }
                public boolean isSuspended() {
                    return suspend;
                }
                public void playDead() {
                     alive = false;
                }
                public boolean isDead() {
                    return !alive;
                }
            }
        ).start();
    }
    
    public void stop() {
       // update.playDead();
    }
    
    private synchronized void handleKeys( KeyEvent e ) {

        if (e.getKeyCode() == KeyEvent.VK_S ) {
            if (update.isDead()) {
                keyboardQueue.clear();
                start();
            }
        }
        else {
            if ( keyboardQueue.size() < 5 )
                keyboardQueue.add( e );
        }
        
    }
    
    private synchronized void checkKeyboard() {
        if ( keyboardQueue.isEmpty() == false ) {
            KeyEvent e = (KeyEvent)keyboardQueue.removeFirst();
            switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                case KeyEvent.VK_H: // left
                    if (direction!=Part.RIGHT)
                        direction = Part.LEFT;
                    break;
                case KeyEvent.VK_RIGHT:
                case KeyEvent.VK_L: // right
                    if (direction!=Part.LEFT)
                        direction = Part.RIGHT;
                    break;
                case KeyEvent.VK_DOWN:
                case KeyEvent.VK_J: // down
                    if (direction!=Part.UP)
                        direction = Part.DOWN;
                    break;
                case KeyEvent.VK_UP:
                case KeyEvent.VK_K: // up
                    if (direction!=Part.DOWN)
                        direction = Part.UP;
                    break;
                
            }
        }
    }

    
    
    public synchronized void paint(Graphics g) {
        
        gBack.setColor(Color.black);
        gBack.fillRect(0, 0, 640, 480);
        arena.visit(this);
        gBack.setColor(Color.white);
        gBack.drawString( "Score: " + String.valueOf(arena.getScore()), 10, 450 );
      
        if ( update.isDead() ) {
            gBack.setColor(Color.darkGray);
            gBack.fillRect(0, 40, 640, 12);
            gBack.setColor(Color.red);
            gBack.drawString( "You died - game over - press 'S'", 50, 50 );    
        }
        if ( update.isSuspended() ) {
            gBack.setColor(Color.darkGray);
            gBack.fillRect(0, 90, 640, 12);
            gBack.setColor(Color.yellow);
            gBack.drawString( "Paused - click in applet window to resume", 100, 100 );
        }
        g.drawImage(backBuffer, 0, 0, null);
    }
    
    public void update(Graphics g) {
        paint(g);
    }
   
    private synchronized void snakeRun() {
        checkKeyboard();
        if ( arena.snakeRun( direction ) == true ) { // is it dead?
            Graphics g = getGraphics();
            update.playDead();
        }
        repaint();
    }
    
    // Drawing code
    
    public void visistHead( Head head ) {
        gBack.setColor(Color.orange);
        drawTailSegment(gBack, 10*head.getX(), 10*head.getY(), 10*head.getNext().getX(), 10*head.getNext().getY(), 8 );
        //gBack.drawLine( 10*head.getNext().getX(), 10*head.getNext().getY(), 10*head.getX(), 10*head.getY() );
        gBack.setColor(Color.red);
        gBack.fillOval(10*head.getX()-5, 10*head.getY()-5, 10, 10 );
    }
    
    private void drawTailSegment(Graphics g, int x, int y, int h, int k, int thick) {
        if ( h < x ) {
            int temp = x;
            x = h;
            h = temp;
        }
        if ( k < y ) {
            int temp = y;
            y = k;
            k = temp;
        }
        
        for (int a=x; a <= h; a+=10)
            for (int b=y; b <= k; b+=10) {
                gBack.fillOval(a-thick/2, b-thick/2, thick, thick );
            }    
        h -= x;
        k -= y;
        thick-=5;
        g.fillRect(x-thick/2, y-thick/2, h+thick, k+thick);
        
    }
    
    public void visitTurningPoint( TurningPoint tp ) {
        gBack.setColor(Color.orange);
        drawTailSegment(gBack, 10*tp.getX(), 10*tp.getY(), 10*tp.getNext().getX(), 10*tp.getNext().getY(), 8 );
        //gBack.drawLine( 10*tp.getX(), 10*tp.getY(), 10*tp.getNext().getX(), 10*tp.getNext().getY() );
    }
    
    public void visitTail( Tail tail ) {
        gBack.setColor( Color.yellow );
        gBack.fillOval(10*tail.getX()-4, 10*tail.getY()-4, 8, 8 );
    }
    
    public void visitFood( Food food ) {
       gBack.setColor( Color.yellow );
       gBack.drawString( String.valueOf( food.getValue() ), 10*food.getX()-5, 10*food.getY()+5 );
       
    }
    public void visitArena( Arena arena ) {
        gBack.setColor( Color.magenta );
        gBack.drawRect( 2, 2, 10*arena.getWidth()-6, 10*arena.getHeight()-6 );
    }
}
