package control;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;

import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.ArrayList;
import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;


/**
 * A Screen is a panel that manages Sprite objects, mouse and keyboard input, and simplified graphics commands for drawing
 * on a background.
 */
public class Screen extends JFrame {
    private JFrame frame;
    protected ScreenPanel panel;
    private JLabel label;
    
    int width;
    int height;
    
    List text = new ArrayList();
    
    int mouseX=-999;
    int mouseY=-999;
    boolean waitingForClick=false;
    boolean mouseWasClicked=false;
    boolean mousePressed = false;
    boolean pause=false;
    boolean[] keys = new boolean[525];
    boolean mouseDown = false;
    LinkedList keyEvents;
    LinkedList keyDown;
    
    double mousex2;
    double mousey2;
    
    
    // performance testing -- paintCount should be much smaller than repaintCount if java
    // is properly coalescing repaint events.
    private int paintCount=0;
    private int repaintCount=0;
    
    private Image overlayImage=null;
    
    private List<Paintable> paintables= new ArrayList<Paintable>();
    
    private class ScreenPanel extends JPanel {
        private BufferedImage image;
        
        private ArrayList sprites;
        
        private ScreenPanel(Screen screen) {
            image= new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );
            Graphics2D g= (Graphics2D)image.getGraphics();
            g.setColor(Color.white);
            g.fillRect( 0, 0, width, height );
            sprites= new ArrayList();
            addMouseListener( mouseAdapter );
            addKeyListener( getKeyListener() );
            addMouseMotionListener( new MouseMotionAdapter() {
                public void mouseMoved(MouseEvent e) {
                    Screen.this.mousex2 = e.getPoint().getX();
                    Screen.this.mousey2 = e.getPoint().getY();
                }
                public void mouseDragged(MouseEvent e) {
                    Screen.this.mousex2 = e.getPoint().getX();
                    Screen.this.mousey2 = e.getPoint().getY();
                }
            }
            );
        }
        
        public void paint(Graphics g1) {
            super.paint(g1);
            paintCount++;
            //System.err.println("paintCount="+paintCount+"  repaintCount="+repaintCount);
            Graphics2D g= ( Graphics2D ) g1;
            if ( image!=null ) {
                g.drawImage(image,0,0,this);
            }
            for ( int i=0; i<sprites.size(); i++ ) {
                ((Sprite)sprites.get(i)).paint(g);
            }
            if ( overlayImage!=null ) {
                g.drawImage(overlayImage,0,0,this);
            }
            synchronized (paintables) {
                for ( Paintable p: paintables ) {
                    p.paint((Graphics2D) g1);
                }
            }
        }
        
        // MouseMotionAdapter mouseListener =
        MouseInputAdapter mouseAdapter= new MouseInputAdapter() {
            public void mouseClicked( MouseEvent e ) {
                Screen.this.mousePressed = true;
                mouseX= e.getX();
                mouseY= e.getY();
                if ( Screen.this.keyDown( 32 ) ) {
                    label.setText( " x:" + mouseX + " y:" + mouseY );
                }
                waitingForClick= false;
                mouseWasClicked= true;
                requestFocus();
            }
            public void mouseReleased( MouseEvent e ) {
                Screen.this.mouseDown = false;
                Screen.this.mousePressed = false;
            }
            public void mousePressed( MouseEvent e ) {
                Screen.this.mouseDown = true;
            }
        };
        private KeyListener getKeyListener() {
            return new java.awt.event.KeyAdapter() {
                public void keyPressed( KeyEvent e ) {
                    Screen.this.keyEvents.add( e );
                    Screen.this.keys[e.getKeyCode()] = true;
                }
                public void keyReleased( KeyEvent e ) {
                    Screen.this.keyEvents.remove( e );
                    Screen.this.keys[e.getKeyCode()] = false;
                }
            };
        }
        
        public void saveAsPng() {
            File f=null;
            try {
                Iterator writers = ImageIO.getImageWritersByFormatName("png");
                ImageWriter writer = (ImageWriter)writers.next();
                
                JFileChooser jf= new JFileChooser();
                jf.setSelectedFile(new File("drawing.png"));
                
                int i = jf.showSaveDialog(this);
                if ( i==JFileChooser.APPROVE_OPTION ) {
                    f= jf.getSelectedFile();
                    ImageOutputStream ios = ImageIO.createImageOutputStream(f);
                    writer.setOutput(ios);
                    
                    BufferedImage simage= new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );
                    paint( simage.getGraphics() );
                    
                    writer.write(simage);
                    
                }
            } catch ( IOException e ) {
                if ( f==null ) {
                    JOptionPane.showConfirmDialog(this,"Unable to write to file "+f);
                } else {
                    JOptionPane.showConfirmDialog(this,"Unable to write gif, here's the exception: "+e );
                }
            }
            
        }
        
        public Action getSaveAsPngAction() {
            Action result= new AbstractAction("Save as .png") {
                public void actionPerformed( ActionEvent e ) {
                    saveAsPng();
                }
            };
            return result;
        }
    }
    
    public  void addPaintable( Paintable p ) {
        synchronized( paintables ) {
                paintables.add( p );
        }
    }
    
    /**
     * Constructs a Screen of the given dimensions.  In general the screen can be resized, but the
     * background image is not resized (as of version 1.4).  A special mode is provided to allow the
     * Screen to live inside an Applet.  In a future version, you might be able to place the Screen in
     * any java Container, allowing gui controls to be added to the Screen using Netbeans Matisse.
     * @param width the width in pixels of the panel.
     * @param height the height in pixels of the panel.
     * @param applet If non-null, then the screen is placed inside the applet and the quit menu option is not provided.
     */
    protected Screen( int width, int height, JApplet applet ) {
        this.width= width;
        this.height= height;
        if ( applet==null ) frame= new JFrame("Screen");
        
        JPanel content= new JPanel();
        content.setLayout( new BorderLayout( ) );
        
        panel= new ScreenPanel(this);
        
        
        panel.setPreferredSize(new Dimension( width, height ) );
        
        content.add(panel,BorderLayout.CENTER);
        
        JMenuBar menuBar= new JMenuBar();
        
        JMenu menu= new JMenu("File");
        menu.add( new JMenuItem( panel.getSaveAsPngAction() ) );
        if ( applet==null ) menu.add( new JMenuItem( getExitAction() ) );
        
        menuBar.add(menu);
        
        content.add( menuBar, BorderLayout.NORTH );
        
        JPanel controlPanel= new JPanel();
        controlPanel.setLayout( new BoxLayout( controlPanel, BoxLayout.X_AXIS ) );
        
        label= new JLabel("");
        label.setPreferredSize( new Dimension( 200, 20 ) );
        controlPanel.add( label );
        
        content.add(controlPanel,BorderLayout.SOUTH);
        
        if ( applet!=null ) {
            applet.getContentPane().add( content );
            applet.validate();
            
        } else {
            frame.getContentPane().add(content);
            
            frame.addWindowListener( new WindowAdapter() {
                public void windowGainedFocus( WindowEvent e ) {
                    panel.requestFocus();
                }
                public void windowOpened( WindowEvent e ) {
                    panel.requestFocus();
                }
            } );
            
            frame.pack();
            frame.setVisible(true);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        keyEvents= new LinkedList();
        keyDown = new LinkedList();
    }
    
    /**
     * constructs a default screen.
     */
    protected Screen() {
        this( 400, 300, null );
    }
    
    public boolean isMouseDown() {
        return Screen.this.mouseDown;
    }
    /**
     * creates a turtle object that draws on this screen
     * @return a Turtle object.
     */
    public Turtle newTurtle() {
        Turtle result= new Turtle(this);
        panel.sprites.add( result );
        repaint();
        return result;
    }
    
    /**
     * creates a sprite object that lives on this screen
     * @param filename the image to use for the sprite.
     * @return a Sprite object.
     */
    public Sprite newSprite( String filename ) {
        Sprite result= new Sprite( filename, this );
        panel.sprites.add( result );
        // report("spritescount="+panel.sprites.size());
        repaint();
        return result;
    }
    public double getRTMouseX() {
        return Screen.this.mousex2;
    }
    public double getRTMouseY() {
        return Screen.this.mousey2;
    }
    /**
     * creates a sprite object that lives on this screen, initially at x,y
     * @param filename the image to use for the sprite, a .gif or .png file.
     *
     * @param x the initial x position
     * @param y the initial y position
     *
     * @return a sprite object
     */
    public Sprite newSprite( String filename, int x, int y ) {
        Sprite result= new Sprite( filename, this, x, y, -99, -99 );
        panel.sprites.add( result );
        repaint();
        return result;
    }
    
    
    /**
     * adds a sprite to the screen.  Usually you will just use
     * newSprite() method to create new Sprites.
     * @param sprite the sprite object to add
     * @skillLevel week 3
     */
    public void addSprite( Sprite sprite ) {
        panel.sprites.add( sprite );
        repaint();
    }
    
    /**
     * removes the sprite from the screen.  This should be used if you know that the sprite is no longer needed.
     * Using the sprite's hide() method still consumes Screen object resources, while destroy will free up the
     * resources and improve performance.
     * @param sprite the sprite to remove
     */
    public void removeSprite( Sprite sprite ) {
        panel.sprites.remove( sprite );
        // report( "spriteremoved="+panel.sprites.size());
        repaint();
    }
    
    /**
     * removes the sprite from the screen after waiting a little while.  This
     * allows the game programmer to replace the sprite with an image that
     * indicates why the sprite is being removed (such as an explosion).
     * This should be used if you know that the sprite is no longer needed.
     * Using the sprite's hide() method still consumes Screen object resources,
     * while removeSprite will free up the resources and improve performance.
     * @param sprite the sprite to remove
     * @since 1.5
     */
    public void removeSprite( final Sprite sprite, final int afterMillis ) {
        Runnable run= new Runnable() {
            public void run() {
                try {
                    Thread.sleep(afterMillis);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                removeSprite(sprite);
            }
        };
        new Thread(run).start();
    }
    
    /**
     * removes the sprite from the screen.  This should be used if you know that the sprite is no longer needed.
     * Using the sprite's hide() method still consumes Screen object resources, while destroy will free up the
     * resources and improve performance.
     * @param sprite removes the sprite from the screen.
     * @depricated use removeSprite instead.
     */
    public void destroySprite( Sprite sprite ) {
        removeSprite( sprite );
        
    }
    
    /**
     * returns the java graphics object that draws on this screen.  Note that
     * the graphics may not be repainted immediately, so call repaint() when
     * you are done.
     * @return a Graphics2D object
     */
    public Graphics2D getGraphics() {
        Graphics2D g= (Graphics2D) panel.image.getGraphics();
        g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        g.setColor(color);
        g.setStroke( stroke );
        g.setFont( font );
        return g;
    }
    
    /**
     * forces the canvas to repaint
     */
    public void repaint() {
        if ( pause ) return;
        repaintCount++;
        panel.repaint(0,0,getWidth(),getHeight());
    }
    
    /**
     * @return whether or not the
     * key of the key code is being held down.
     */
    public boolean keyDown( int keyCode ) {
        if ( keys[keyCode] ) {
            return true;
        }
        return false;
    }
    /**
     * @return whether or not the
     * mouse is being held down.
     */
    public boolean mouseDown() {
        if ( mouseDown ) {
            return true;
        }
        return false;
    }
    /**
     * returns the width of the screen in pixels
     * @return the width in pixels
     */
    public int getWidth() {
        return panel.getWidth(); }
    
    /**
     * returns the height of the screen in pixels
     * @return the height in pixels
     */
    public int getHeight() {
        return panel.getHeight(); }
    
    private Stroke stroke= new BasicStroke(1.0f);
    private Color color= Color.black;
    private Font font= Font.decode("PLAIN");
    
    /**
     * Sets the pen thickness for the line and circle methods.  Note this does not affect turtles.
     * @param thickness the line thickness in pixels.
     */
    public void setLineThickness( double thickness ) {
        this.stroke= new BasicStroke((float)thickness);
    }
    
    /**
     * Sets the pen color for circle and line methods.
     * @param color java.awt.Color of the pen, for example Color.red or Color.black
     */
    public void setColor( Color color ) {
        this.color= color;
    }
    
    /**
     * set the font for drawString()
     * @param font a Font object to specify the font to use for drawString(), such as
     * <pre>new Font( "SansSerif", Font.PLAIN, 12 )</pre> or
     * <pre>Font.decode( "SansSerif-12" )</pre>
     * Beware that unusual fonts may not be available on every system.
     */
    public void setFont( Font font ) {
        // null is returned for Font.decodeFont("unavailable-font")
        if ( font!=null ) this.font= font;
    }
    
    /**
     * set the size of the current font
     * @param pointSize int pointSize of the font, which is roughly the height
     * of the letter T in pixels.
     */
    public void setFontSize( double pointSize ) {
        this.font= font.deriveFont( (float)pointSize );
    }
    
    
    /**
     * draw a circle with color and stroke.  This is to support the turtle.
     */
    void circle(int x, int y, int radius, Color color, Stroke stroke) {
        Graphics2D g= getGraphics();
        g.setColor( color );
        g.setStroke( stroke );
        g.drawOval( x-radius, y-radius, 2*radius, 2*radius );
        repaint();
    }
    
    /**
     * support for the turtle.
     */
    void line( int x1, int y1, int x2, int y2, Color color, Stroke stroke ) {
        Graphics2D g= getGraphics();
        g.setColor( color );
        g.setStroke( stroke );
        g.drawLine( x1, y1, x2, y2 );
        repaint();
    }
    
    /**
     * draws a circle on the screen, centered at x,y.
     * @param x the center X coordinate of the circle in pixels.
     * @param y the center Y coordinate of the circle in pixels.
     * @param radius the radius in pixels.
     */
    public void drawCircle( int x, int y, int radius ) {
        Graphics2D g= getGraphics();
        g.drawOval( x-radius, y-radius, 2*radius, 2*radius );
        repaint();
    }
    
    /**
     * draws a circle on the screen, centered at x,y.
     * @param x the center X coordinate of the circle in pixels.
     * @param y the center Y coordinate of the circle in pixels.
     * @param radius the radius in pixels.
     */
    public void drawCircle( double x, double y, double radius ) {
        Graphics2D g= getGraphics();
        g.drawOval( (int)(x-radius), (int)(y-radius), (int)(2*radius), (int)(2*radius) );
        repaint();
    }
    
    /**
     * draws a filled circle on the screen, centered at x,y.
     * @param x the center X coordinate of the circle in pixels.
     * @param y the center Y coordinate of the circle in pixels.
     * @param radius the radius in pixels.
     */
    public void fillCircle( int x, int y, int radius ) {
        Graphics2D g= getGraphics();
        g.fillOval( x-radius, y-radius, 2*radius, 2*radius );
        repaint();
    }
    
    /**
     * draws a filled circle on the screen, centered at x,y.
     * @param x the center X coordinate of the circle in pixels.
     * @param y the center Y coordinate of the circle in pixels.
     * @param radius the radius in pixels.
     */
    public void fillCircle( double x, double y, double radius ) {
        Graphics2D g= getGraphics();
        g.fillOval( (int)(x-radius), (int)(y-radius), (int)(2*radius), (int)(2*radius) );
        repaint();
    }
    
    /**
     * draw a rectangle with its upper left corner at x, y.
     * @param x the upper-left corner X coordinate
     * @param y the upper-left corner X coordinate
     * @param height the height in pixels.
     * @param width the width in pixels
     */
    public void drawRect( int x, int y, int height, int width ) {
        Graphics2D g= getGraphics();
        g.drawRect( x, y, height, width );
        repaint();
    }
    
    /**
     * draw a rectangle with its upper left corner at x, y.
     * @param x the upper-left corner X coordinate
     * @param y the upper-left corner X coordinate
     * @param height the height in pixels.
     * @param width the width in pixels
     */
    public void drawRect( double x, double y, double height, double width ) {
        drawRect( (int)x, (int)y, (int)height, (int)width );
    }
    
    /**
     * draw a filled rectangle with its upper left corner at x, y.
     * @param x the upper-left corner X coordinate
     * @param y the upper-left corner X coordinate
     * @param width the width in pixels
     * @param height the height in pixels.
     */
    public void fillRect( int x, int y, int width, int height ) {
        Graphics2D g= getGraphics();
        g.fillRect( x, y, width, height );
        repaint();
    }
    
    /**
     * draw a filled rectangle with its upper left corner at x, y.
     * @param x the upper-left corner X coordinate
     * @param y the upper-left corner X coordinate
     * @param width the width in pixels
     * @param height the height in pixels.
     */
    public void fillRect( double x, double y, double width, double height ) {
        fillRect( (int)x, (int)y, (int)width, (int)height );
    }
    
    /**
     * draws a line from (x1,y1) to (x2,y2)
     * @param x1 the X coordinate of the first end point.
     * @param y1 the Y coordinate of the first end point.
     * @param x2 the X coordinate of the second end point.
     * @param y2 the Y coordinate of the second end point.
     */
    public void drawLine( int x1, int y1, int x2, int y2 ) {
        Graphics2D g= getGraphics();
        g.drawLine( x1, y1, x2, y2 );
        repaint();
    }
    
    /**
     * draws a line from (x1,y1) to (x2,y2)
     * @param x1 the X coordinate of the first end point.
     * @param y1 the Y coordinate of the first end point.
     * @param x2 the X coordinate of the second end point.
     * @param y2 the Y coordinate of the second end point.
     */
    public void drawLine( double x1, double y1, double x2, double y2 ) {
        drawLine( (int) x1, (int) y1, (int) x2, (int) y2 ) ;
    }
    
    /**
     * draws a string on the screen using the current font and color.
     * @param s The string to draw
     * @param x the x coordinate of the beginning of the string.
     * @param y the y coordinate of the bottom of the string
     */
    public void drawString( String s, double x, double y ) {
        Graphics2D g= getGraphics();
        g.drawString( s, (float)x, (float)y );
        repaint();
    }
    
    /**
     * returns an image
     * @param resource String of the resource within the code space.
     * @return an Image object.
     */
    protected Image getImage( String resource ) {
        
        URL url= Chess.findResource(resource);
        if ( url==null ) {
            throw new IllegalArgumentException("file does not exist: "+resource);
        }
        ImageIcon icon= new ImageIcon( url );
        return icon.getImage();
    }
    
    /**
     * draws the image in filename with its upper left corner at x,y
     * @param filename the image filename, a .png or .gif file.
     * @param x the X coordinate where the image's upper-left corner will be drawn.
     * @param y the Y coordinate where the image's upper-left corner will be drawn.
     */
    public void drawImage( String filename, int x, int y ) {
        Image i= getImage( filename );
        getGraphics().drawImage( i, x, y, null );
        repaint();
    }
    
    /**
     * draws the image in filename with its upper left corner at x,y
     * @param filename the image filename, a .png or .gif file.
     * @param x the X coordinate where the image's upper-left corner will be drawn.
     * @param y the Y coordinate where the image's upper-left corner will be drawn.
     */
    public void drawImage( String filename, double x, double y ) {
        drawImage( filename, (int)x, (int)y );
    }
    
    /**
     * waits until the user clicks on the screen, and stores the coordinates of
     * the click.  Use getMouseX() and getMouseY() to get the coordinates of
     * the click.
     */
    public void waitForMouseClick() {
        waitingForClick= true;
        label.setText("  waiting for mouse click");
        panel.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
        while ( waitingForClick ) {
            try {
                Thread.sleep(50);
            } catch ( InterruptedException e ) {
                throw new RuntimeException(e);
            }
        }
        panel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * returns true if the mouse has been clicked since the last time
     * getMouseX was called.
     * @return true if the mouse has been clicked.
     */
    public boolean mouseWasClicked() {
        return mouseWasClicked;
    }
    
    /**
     * returns the X coordinate of the last mouse click, or -999 if the
     * mouse has not been clicked.  Also resets mouseWasClicked.
     * @return the click X-coordinate
     */
    public int getMouseX() {
        mouseWasClicked= false;
        return mouseX;
    }
    
    /**
     * returns the Y coordinate of the last mouse click, or -999 if the
     * mouse has not been clicked.
     * @return the click X-coordinate
     */
    public int getMouseY() {
        mouseWasClicked= false;
        return mouseY;
    }
    
    /**
     * stop the thread of execution until a key is pressed.
     */
    public void waitForKey() {
        label.setText("  waiting for key stroke");
        while ( keyEvents.size()==0 ) {
            try {
                Thread.sleep(50); } catch ( InterruptedException e ) {
                };
        }
    }
    
    /**
     * the key code, not the char, for the keystroke.  Compare this
     * to java.awt.event.KeyEvent.  For example:
     * <pre>
     *    if ( Chess.getKeyCode() == java.awt.event.KeyEvent.KeyEvent.VK_UP ) {
     *       Chess.report("Up was pressed.");
     *    }
     * </pre>
     * This will return 0 if a key has not been pressed.
     * @return the key code for the keystroke, or 0 if a key has not been pressed.
     */
    public int getKeyCode() {
        try {
            Thread.sleep(20); } catch ( InterruptedException e ) {
            }
        if ( keyEvents.size()==0 ) return 0;
        KeyEvent e= (KeyEvent)keyEvents.remove(0);
        
        return e.getKeyCode();
    }
    
    /**
     * return a char of the last key pressed.  Note this does not allow
     * for arrow keys and such, but can be compared to constant chars like
     * 'a'.  This will return 0 if a key has not been pressed.
     * @return char of the last key pressed or 0 if a key has not been pressed.
     */
    public char getKey() {
        // wait a short while so a loop doesn't lock up computer
        try {
            Thread.sleep(20); } catch ( InterruptedException e ) {
            }
        if ( keyEvents.size()==0 ) return 0;
        KeyEvent e= (KeyEvent)keyEvents.remove(0);
        return (char)e.getKeyChar();
    }
    
    
    /**
     * returns the topmost Sprite at (x,y), or null if no Sprite is at this
     * position.
     * @param x the X coordinate to check
     * @param y the Y coordinate to check
     * @return the sprite object, or null if no sprite is at (X,Y).
     */
    public Sprite getSpriteAt( int x, int y ) {
        for ( int i=panel.sprites.size()-1; i>=0; i-- ) {
            Sprite s= (Sprite)panel.sprites.get(i);
            if ( s.overlaps( x, y ) ) return s;
        }
        return null;
    }
    public LinkedList getAllSprites() {
        LinkedList list = new LinkedList();
        for ( int a = 0; a < panel.sprites.size() - 1; a ++ ) {
            list.add( panel.sprites.get( a ) );
        }
        return list;
    }
    public LinkedList getAllPaintables() {
        LinkedList list = new LinkedList();
        for ( int a = 0; a < paintables.size() - 1; a ++ ) {
            list.add( paintables.get( a ) );
        }
        return list;
    }
    /**
     * returns the topmost Sprite at (x,y), or null if no Sprite is at this
     * position (only comment).
     * @param x the X coordinate to check
     * @param y the Y coordinate to check
     * @return the sprite object, or null if no sprite is at (X,Y).
     */
    public Sprite getSpriteAtWithComment( int x, int y, String comment ) {
        for ( int i=panel.sprites.size()-1; i>=0; i-- ) {
            Sprite s= (Sprite)panel.sprites.get(i);
            if ( s.overlaps( x, y ) ) {
                if ( s.getComment() != null ) {
                    if ( s.getComment().equals( comment ) ) {
                        return s;
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * erase the screen, using color for the new background.  This is
     * equivalent to
     * <pre>
     *   Screen myScreen;
     *   myScreen.setColor( color );
     *   myScreen.fillRect( 0, 0, myScreen.getWidth(), myScreen.getHeight() );
     * </pre>
     * @param color Color of the background.
     */
    public void clear( Color color ) {
        Graphics2D g= getGraphics();
        g.setColor(color);
        g.fillRect( 0, 0, panel.getWidth(), panel.getHeight() );
        repaint();
    }
    
    /**
     * erase the screen with a white background.  This is equivalent to
     * <pre>
     *   Screen myScreen;
     *   myScreen.setColor( Color.white );
     *   myScreen.fillRect( 0, 0, myScreen.getWidth(), myScreen.getHeight() );
     * </pre>
     */
    public void clear() {
        clear( Color.white );
    }
    
    /**
     * return the Color of the background pixel at (x,y).  Don't use = to compare Colors,
     * use the equals method:
     * <pre>
     * if ( screen.getPixelColor().equals( Color.black ) ) {
     *     // do something
     * }
     * </pre>
     * Color.grey will be returned if x and y are outside the background image.
     * @param x the x coordinate of the pixel.
     * @param y the y coordinate of the pixel.
     * @return the Color at the pixel
     */
    public Color getPixelColor( double x, double y ) {
        return getPixelColor( (int)x, (int)y ) ;
    }
    
    /**
     * return the Color of the background pixel at (x,y).  Don't use = to compare Colors,
     * use the equals method:
     * <pre>
     * if ( screen.getPixelColor().equals( Color.black ) ) {
     *     // do something
     * }
     * </pre>
     * Color.grey will be returned if x and y are outside the background image.
     * @param x the x coordinate of the pixel.
     * @param y the y coordinate of the pixel.
     * @return the Color at the pixel
     */
    public Color getPixelColor( int x, int y ) {
        try {
            int argb= panel.image.getRGB( x, y );
            return new Color( argb, true );
        } catch ( IndexOutOfBoundsException ex ) {
            return Color.gray;
        }
    }
    
    /**
     * save the screen as a .png image file, bringing up a dialog to choose the
     * file name.
     */
    public void saveAsPng() {
        panel.saveAsPng();
    }
    
    private Action getExitAction() {
        return new AbstractAction("Exit") {
            public void actionPerformed( ActionEvent e ) {
                System.exit(0);
            }
        };
    }
    
    /**
     * prints the string at the bottom of the screen
     * @param message the message to print.
     */
    public void report( String message ) {
        label.setText( message );
    }
    
    /**
     * adds an additional MouseInputAdapter to the screen.  This is used when
     * you need to have more complex mouse interaction, and introduces more
     * complex java idioms.<br>
     * For example:
     * <pre>
     * Screen myScreen= Chess.newScreen();
     * Sprite s= myScreen.newSprite( "/sprites/coin1.png" );
     * MouseInputAdapter mia= new MouseInputAdapter() {
     *    public void mouseDragged( MouseEvent event ) {
     *        s.setPosition( event.getX(), event.getY() );
     *    }
     * }
     * myScreen.addMouseInputAdapter( mia );
     * </pre>
     * @param mouseInputAdapter the mouseInputAdapter object.  Override MouseInputAdapter to add mouse event handlers.
     * @skillLevel week 4
     */
    public void addMouseInputAdapter( MouseInputAdapter mouseInputAdapter ) {
        panel.addMouseListener(mouseInputAdapter);
        panel.addMouseMotionListener(mouseInputAdapter);
    }
    /**
     * returns the topmost Sprite at (x,y), or null if no Sprite is at this
     * position.
     * @param x the X coordinate to check
     * @param y the Y coordinate to check
     * @return the sprite object, or null if no sprite is at (X,Y).
     */
    /**
     * adds an image that is drawn on top of all the sprites.
     * This image will generally have transparency though which that the game
     * underneath is visible. For example, this could be a bridge that a car
     * drives under, or clouds above a plane.  This can also be used for
     * fade-in/fade-out effects.
     * <pre>
     *   BufferedImage overlay= new BufferedImage(600,400,BufferedImage.TYPE_INT_ARGB);
     *   Graphics2D g= (Graphics2D)overlay.getGraphics();
     *   g.setColor( new Color(255,0,0,100) ); // 100 is 40% transparent bar
     *   g.fillRect(0,200,600,100);
     *   Screen myScreen= Chess.newScreen(600,400);
     *   Sprite coin= myScreen.newSprite("sprites/coin1.png",300,200);
     *   myScreen.setOverlayImage( overlay );
     * </pre>
     * @param overlay java.awt.Image to be overlayed.
     * @since 1.4
     */
    public void setOverlayImage(Image overlay) {
        this.overlayImage= overlay;
        repaint();
    }
    
    /**
     * adds an image that is drawn on top of all the sprites.
     * This image will generally have transparency though which that the game
     * underneath is visible. For example, this could be a bridge that a car
     * drives under, or clouds above a plane.  This can also be used for
     * fade-in/fade-out effects.
     * <pre>
     *   Screen myScreen= Chess.newScreen(600,400);
     *   Sprite coin= myScreen.newSprite("sprites/coin1.png",300,200);
     *   myScreen.setOverlayImage( "clouds.png" );
     * </pre>
     * @param filename the .png or .gif file containing the image to be loaded.
     * @since 1.4
     */
    public void setOverlayImage( String filename ) {
        setOverlayImage( getImage( filename ) );
    }
    
    /**
     * adds an image that is drawn on top of all the sprites.  This method
     * creates an image all of one color to make it easy to do fade-in and
     * fade-out effects.
     * <pre>
     *   Screen myScreen= Chess.newScreen(600,400);
     *   Sprite coin= myScreen.newSprite("sprites/coin1.png",300,200);
     *   for ( int i=255; i>=0; i-=10 ) {
     *       myScreen.setOverlayImage( new Color(255,255,255,i) );
     *       Chess.waitForNextFrame(30);
     *   }
     * </pre>
     */
    /*public void setOverlayImage( Color color ) {
        int h= getHeight();
        int w= getWidth();
        BufferedImage overlay= new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
        Graphics2D g= (Graphics2D)overlay.getGraphics();
        g.setColor( color );
        g.fillRect(0,0,w,h);
        setOverlayImage( overlayImage );
    }*/
    
    /**
     * Holds value of property gameOver.
     */
    private boolean gameOver;
    
    /**
     * Getter for property gameOver.
     * @return Value of property gameOver.
     */
    public boolean isGameOver() {
        return this.gameOver;
    }
    
    /**
     * Setter for property gameOver.
     * @param gameOver New value of property gameOver.
     */
    public void setGameOver(boolean gameOver) {
        this.gameOver = gameOver;
    }
    
    /**
     * Holds value of property coins.
     */
    private int coins;
    
    /**
     * Getter for property coins.
     * @return Value of property coins.
     */
    public int getCoins() {
        return this.coins;
    }
    
    /**
     * Setter for property coins.
     * @param coins New value of property coins.
     */
    public void setCoins(int coins) {
        this.coins = coins;
    }
    
    /**
     * Holds value of property car.
     */
    private Car car;
    
    /**
     * Getter for property car.
     * @return Value of property car.
     */
    public Car getCar() {
        return this.car;
    }
    
    /**
     * Setter for property car.
     * @param car New value of property car.
     */
    public void setCar(Car car) {
        this.car = car;
    }
    /**
     * Holds value of property paused.
     */
    private boolean paused;
    
    /**
     * Getter for property paused.
     * @return Value of property paused.
     */
    public boolean isPaused() {
        return this.paused;
    }
    
    /**
     * Setter for property paused.
     * @param paused New value of property paused.
     */
    public void setPaused(boolean paused) {
        this.paused = paused;
    }
    
    /**
     * Holds value of property frameRate.
     */
    private int frameRate = 40; // 40 is default
    
    /**
     * Getter for property frameRate.
     * @return Value of property frameRate.
     */
    public int getFrameRate() {
        return this.frameRate;
    }
    
    /**
     * Setter for property frameRate.
     * @param frameRate New value of property frameRate.
     */
    public void setFrameRate(int frameRate) {
        this.frameRate = frameRate;
    }
}
