package jdraw.std;

import jdraw.framework.*;

import java.util.*;
import java.util.List;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * Standard implementation of interface DrawView.
 *
 * @see DrawView
 *
 * @author  Dominik Gruntz
 * @version 2.0, 26.04.01
 */
public class StdDrawView 
	extends JComponent 
	implements DrawView, MouseListener, MouseMotionListener, KeyListener {

	private DrawEditor editor;
	private DrawModel  model;
	private DrawTool   tool;
	private PointConstrainer constrainer;
	
	private Dimension size;
	
	private List selection = new LinkedList();
	private List handles   = new LinkedList();
	
	private Cursor currentCursor;
	
	
	public StdDrawView(DrawEditor editor, DrawModel model, int width, int height){
		this.editor = editor;
		this.model = model;
		this.size  = new Dimension(width, height);
		
		checkMinimumSize();
		
		setDefaultTool();
			
		
		model.addModelChangeListener(
			new DrawModelListener(){
				public void modelChanged(DrawModelEvent e){
					checkMinimumSize();
					
					if(e.getType() == DrawModelEvent.FIGURE_REMOVED){
						removeFromSelection(e.getFigure());
					}
					
					repaint();
				}
			}
		);
		
		
		addMouseListener(this);
		addMouseMotionListener(this);
		
		addKeyListener(this);
	}	
	


	public DrawModel getModel(){
		return model;
	}
	
	public DrawEditor getEditor(){
		return editor;
	}
	
	public DrawTool getTool(){
		return tool;
	}
	
	public void setTool(DrawTool tool){
		if(tool == null)
			throw new IllegalArgumentException("DrawTool must not be null");
		
		if(this.tool != tool){
			if(this.tool != null) this.tool.deactivate();
			this.tool = tool;
			tool.activate();
		
			clearSelection();
			editor.toolChanged();
			repaint();
		}
	}
	
	DrawTool defaultTool = new DefaultTool();
	public void setDefaultTool(){
		setTool(defaultTool);
	}
	
	// Constrainer
	//////////////
	
	public void setConstrainer(PointConstrainer constrainer) {
		this.constrainer = constrainer;
	}

	public PointConstrainer getConstrainer() {
		return constrainer;
	}

	protected Point constrainPoint(Point p) {
	    if (constrainer != null )
	        return constrainer.constrainPoint(p);
	    return p;
	}


	public void paintComponent(Graphics g){
		Iterator it = model.getFigures();
		while(it.hasNext()){
			((Figure)it.next()).draw(g);
		}
		Iterator hit = handles.iterator();
		while(hit.hasNext()){
			((Handle)hit.next()).draw(g);
		}
	}
	
	
	// Selection
	////////////
	
	public Set getSelection(){
		return new HashSet(selection);
	}
	
	public void clearSelection(){
		selection.clear();
		handles.clear();
	}
	
	public void addToSelection(Figure f){
		setDefaultTool();
		if(!selection.contains(f)){
			selection.add(f);
			List hList = f.getHandles();
			if(hList != null)
				handles.addAll(hList);
		}	
	}
	
	public void removeFromSelection(Figure f){
		if(selection.contains(f)){
			selection.remove(f);
			Iterator it = handles.iterator();
			while(it.hasNext()){
				Handle h = (Handle)it.next();
				if(h.getOwner() == f)it.remove();
			}
		}
	}


	// Size
	///////
	
	private void checkMinimumSize(){
		Iterator it = model.getFigures();
		java.awt.Rectangle r = new java.awt.Rectangle();
		while(it.hasNext()){
			r.add(((Figure)it.next()).getBounds());
		}
		Dimension d = r.getSize();
		size.height = r.height+r.y + 10;
		size.width  = r.width+r.x  + 10;
		setPreferredSize(size);
		revalidate();
	}
	
	public Dimension getMinimumSize() {
	    return size;
	}

	public Dimension getPreferredSize() {
	    return size;
	}
	
	
	// Cursor
	/////////

	public void setCursor(Cursor c){
		currentCursor = c;
		super.setCursor(c);
	}
	
		
	// KeyListener 
	//////////////

	public void keyPressed(KeyEvent e){
		int code = e.getKeyCode();
		
		if(code==KeyEvent.VK_ESCAPE){
			setDefaultTool();
		}
		
		if(code==KeyEvent.VK_DELETE){
			// delete selected figures
			
			Iterator it=getSelection().iterator();
			while(it.hasNext()){
				model.removeFigure((Figure)it.next());
				// as a consequence, the figure is also removed from the selection
			}
			repaint();
		}
		else {		
			int dx = 0;
			int dy = 0;
			if(code == KeyEvent.VK_LEFT){
				if(constrainer != null)
					dx = -constrainer.getStepX();
				else
					dx = -1;
			}
			else if(code == KeyEvent.VK_RIGHT){
				if(constrainer != null)
					dx = constrainer.getStepX();
				else
					dx = +1;
			}
			else if(code == KeyEvent.VK_UP){
				if(constrainer != null)
					dy = -constrainer.getStepY();
				else
					dy = -1;
			}
			else if(code == KeyEvent.VK_DOWN){
				if(constrainer != null)
					dy = constrainer.getStepY();
				else
					dy = +1;
			}
			if(dx != 0 || dy != 0){
				Iterator iterator = selection.iterator();
				while(iterator.hasNext()){
					Figure figure = (Figure)iterator.next();
					figure.move(dx, dy);
				}
			}
		}
	}
	
	public void keyReleased(KeyEvent keyevent) { }
    public void keyTyped(KeyEvent keyevent){ }


	// MouseListener
	///////////////////////////
	
	public void mousePressed(MouseEvent e) {
        requestFocus();
		if((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0){
			Point p = constrainPoint(new Point(e.getX(), e.getY()));
	        tool.mouseDown(p.x, p.y, e);
	    }
    }

		
	public void mouseReleased(MouseEvent e){
		if((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0){
			Point p = constrainPoint(new Point(e.getX(), e.getY()));
			tool.mouseUp(p.x, p.y, e);
		}
	}

	public void mouseClicked(MouseEvent e){	}	
	public void mouseEntered(MouseEvent e){	}
	public void mouseExited(MouseEvent e){	}

	// MouseMotionListener 
	////////////////////////
		
	public void mouseDragged(MouseEvent e){
		Point p = constrainPoint(new Point(e.getX(), e.getY()));
		tool.mouseDrag(p.x, p.y, e);
	}
	
	public void mouseMoved(MouseEvent e){
		int x = e.getX();
		int y = e.getY();
	
		Iterator it = handles.iterator();
		while(it.hasNext()){
			Handle h = (Handle)it.next();
			if(h.contains(x, y)){
				super.setCursor(h.getCursor());
				return;
			}
		}
		setCursor(currentCursor);
	}
	
	

private class DefaultTool implements DrawTool {

    int x0, y0;
    int x,  y;
	
    private Figure lastSelectedFigure;
    private Handle currentHandle;

    public void activate() { }
    public void deactivate() { }

    public void mouseDown(int i, int j, MouseEvent e)
    {
        x0 = e.getX();
        y0 = e.getY();
        x = i;
        y = j;
		
		// 0. check, whether a handle is selected
        lastSelectedFigure = null;
		
		Iterator iterator = handles.iterator();
		while(iterator.hasNext()){
            Handle handle = (Handle)iterator.next();
            if(handle.contains(x0, y0)) {
                currentHandle = handle;
                handle.startInteraction(i, j, e, StdDrawView.this);
                return;
            }
        }

        // 1. check, whether mouse position is near by an already
        //    selected figure; in this case keep selection
        boolean keepSelection = false;
        Iterator it = getModel().getFigures();
        Figure  f = null;
        while(it.hasNext()) {
            f = (Figure)it.next();
            if(selection.contains(f) && f.contains(x0, y0)) {
                keepSelection = true;
                break;
            }
        }
		
        
        // 2. if click is outside of existing selection then
        //    deselect all figures - except if shift is down
        //    (modifier used to extend selection)
        if(!keepSelection && !e.isShiftDown()){
            clearSelection();
        }
			
        // 3. if the existing selection is not kept then
        //    look for new figures (which are not already
        //    selected) and select them. Only one figure.
        if(!keepSelection)
        {
            for(it = getModel().getFigures(); it.hasNext();)
            {
                Figure figure1 = (Figure)it.next();
                if(figure1.contains(x0, y0) && !selection.contains(figure1))
                {
                    addToSelection(figure1);
                    lastSelectedFigure = figure1;
                    break;
                }
            }

        }
		
		repaint();
    }

    public void mouseDrag(int i, int j, java.awt.event.MouseEvent e)
    {
        int k = i - x;
        int l = j - y;
        if(currentHandle != null)
        {
            currentHandle.dragInteraction(i, j, e, StdDrawView.this);
            return;
        }
		
		Iterator iterator = selection.iterator();
		while(iterator.hasNext()){
			Figure figure = (Figure)iterator.next();
			figure.move(k, l);
		}

        x = i;
        y = j;
        repaint();
    }

    public void mouseUp(int i, int j, MouseEvent e)
    {
        if(currentHandle != null)
        {
            currentHandle.stopInteraction(i, j, e, StdDrawView.this);
            currentHandle = null;
            return;
        }
        if(e.isShiftDown() && e.getX() == x0 && e.getY() == y0)
        {
			Iterator it = getModel().getFigures();
            while(it.hasNext()) {
                Figure figure = (Figure)it.next();
                if(figure.contains(x0, y0)) {
                    if(selection.contains(figure) && figure != lastSelectedFigure)
                        removeFromSelection(figure);
                    repaint();
                    break;
                }
            }

        }
    }
}

}
