import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JPanel;
import javax.swing.JTextArea;

public class Display extends JPanel
        implements MouseListener, MouseMotionListener, KeyListener {
    /** The minimum size of a window. */
    private static final int MIN_SIZE = 25;

    /** The threshold mouse drag distance to distinguish between
     * a click on a window (to bring it to the front) or a drag
     * on a window (to move it). */
    private static final int MIN_MOVEMENT = 10;
    
    /** The colors to use for successively created windows. */
    private static final Color[] colors = {
        Color.RED, Color.GREEN, Color.BLUE,
        Color.YELLOW, Color.CYAN, Color.MAGENTA,
        Color.ORANGE, new Color(64, 64, 0), Color.PINK, new Color(128, 128, 255),
        new Color(128, 0, 0), new Color(0, 128, 0), new Color(0, 0, 128),
        Color.DARK_GRAY, Color.WHITE, Color.BLACK 
    };

    /** The names to use for successively created windows. */
    private static final String[] names = {
        "Red", "Green", "Blue",
        "Yellow", "Cyan", "Magenta",
        "Orange", "Brown", "Pink", "Lt Blue",
        "Dk Red", "Dk Green", "Dk Blue",
        "Dk Gray", "White", "Black"
    };
    
    /** The log area where to display messages about actions. */
    private JTextArea log;

    /** The window manager. */
    private Manager manager;

    /** The number of windows created thus far. */
    private int windowsCreated;

    /** The last window manipulated. */
    private Window current;

    /** Whether the last mouse press indicated that we are
     * resizing the current window. */
    private boolean isResizing;

    /** Whether the user has yet dragged far enough to indicate
     * that we are moving the current window. */
    private boolean isMoving;

    /** The x-coordinate where the mouse was pressed. */
    private int startX;

    /** The y-coordinate where the mouse was pressed. */
    private int startY;

    /** Whether to draw a ghost indicating the action when the
     * mouse is released. */
    private boolean ghostValid;

    /** The x-coordinate of the ghost's left edge. */
    private int ghostX;

    /** The y-coordinate of the ghost's top edge. */
    private int ghostY;

    /** The width of the ghost, in pixels. */
    private int ghostWidth;

    /** The height of the ghost, in pixels. */
    private int ghostHeight;
    
    public Display(JTextArea myLog) {
        log = myLog;
        manager = new Manager();
        windowsCreated = 0;
        current = null;
        ghostValid = false;
        
        setBackground(Color.LIGHT_GRAY);
        addMouseListener(this);
        addMouseMotionListener(this);
        log.addKeyListener(this);
        addKeyListener(this);
        setPreferredSize(new Dimension(320, 240));
    }
    
    /** Paints the display area. */
    public void paintComponent(Graphics g) {
        super.paintComponent(g); // this will paint the background color
        
        // paint windows
        manager.paintAll(g);
        
        // paint ghost
        if(ghostValid) {
            g.setColor(Color.GRAY);
            g.drawRect(ghostX, ghostY, ghostWidth, ghostHeight);
        }
    }

    /** Does nothing; required for the MouseListener interface. */
    public void mouseClicked(MouseEvent event) { }

    /** Starts a drag by determining which window the user is
     * manipulating (if any). */
    public void mousePressed(MouseEvent event) {
        startX = event.getX();
        startY = event.getY();
        log.append("Manager.find " + startX + "," + startY + ": ");
        current = manager.find(startX, startY);
        log.append(current + "\n");
        if(current == null) {
            ghostX = startX;
            ghostY = startY;
            ghostWidth = MIN_SIZE;
            ghostHeight = MIN_SIZE;
        } else {
            ghostX = current.getX();
            ghostY = current.getY();
            ghostWidth = current.getWidth();
            ghostHeight = current.getHeight();
            isResizing = current.isInResizeRegion(startX, startY);
            isMoving = false;
        }
        ghostValid = true;
        repaint();
    }

    /** Completes a mouse drag by performing the requested action. */
    public void mouseReleased(MouseEvent event) {
        mouseDragged(event); // update the ghost for the final location
        if(current == null) {
            int index = windowsCreated % colors.length;
            windowsCreated++;
            current = new Window(names[index], colors[index],
                    ghostX, ghostY,
                    ghostWidth, ghostHeight);
            log.append("Manager.add " + current + "\n");
            manager.add(current);
        } else if(isResizing) {
            log.append("Window.setSize " + current + "\n");
            current.setSize(ghostWidth, ghostHeight);
        } else if(isMoving) {
            log.append("Window.setLocation " + current + "\n");
            current.setLocation(ghostX, ghostY);
        } else {
            log.append("Manager.toFront " + current + "\n");
            manager.toFront(current);
        }
        ghostValid = false;
        repaint();
    }

    /** Does nothing; required for the MouseListener interface. */
    public void mouseEntered(MouseEvent event) { }

    /** Does nothing; required for the MouseListener interface. */
    public void mouseExited(MouseEvent event) { }

    /** Updates the ghost to reflect the current mouse location.  */
    public void mouseDragged(MouseEvent event) {
        int x = Math.max(0, Math.min(getWidth(), event.getX()));
        int y = Math.max(0, Math.min(getHeight(), event.getY()));
        if(current == null) {
            ghostX = Math.min(x, startX);
            ghostY = Math.min(y, startY);
            ghostWidth = Math.max(MIN_SIZE, Math.abs(x - startX));
            ghostHeight = Math.max(MIN_SIZE, Math.abs(y - startY));
        } else if(isResizing) {
            ghostWidth = Math.max(MIN_SIZE, current.getWidth() + x - startX);
            ghostHeight = Math.max(MIN_SIZE, current.getHeight() + y - startY);
        } else {
            int newX = Math.max(0, current.getX() + (x - startX));
            int newY = Math.max(0, current.getY() + (y - startY));
            int moveDist = Math.abs(newX - current.getX())
                + Math.abs(newY - current.getY());
            if(moveDist >= MIN_MOVEMENT) isMoving = true;
            if(isMoving) {
                ghostX = newX;
                ghostY = newY;
            }
        }
        repaint();
    }

    /** Does nothing; required for the MouseMotionListener interface. */
    public void mouseMoved(MouseEvent event) { }

    /** Processes a key typed by the user. */
    public void keyTyped(KeyEvent event) {
        char c = event.getKeyChar();
        if(c == '\n' || c == ' ') {
            log.append("Manager.getList:\n");
            log.append(manager.getList());
        } else if(c == '\b' || c == '\u007f' || c == '\u0008') {
            if(current != null) {
                log.append("Manager.remove " + current + "\n");
                manager.remove(current);
                current = null;
                repaint();
            }
        }
    }

    /** Does nothing; required for the KeyListener interface. */
    public void keyPressed(KeyEvent arg0) { }

    /** Does nothing; required for the KeyListener interface. */
    public void keyReleased(KeyEvent arg0) { }
    
}
