import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.*;
import java.util.*;

/**


  <h2>EVTI Drag'n'drop exercise</h2>
  
  <p>
  (c) 2000 Tobias Poschwatta, tp@fonz.de
  </p>

*/


public class dadexc extends Applet implements MouseListener, MouseMotionListener {
	
  protected static final String version_string = "2000.05.07";
  
	/** the applet */
	dadexc myApplet;
	
	/** the background image */
	Image image = null;
  int imageWidth;
  int imageHeight;

	/** current dragged item */
	Word dragWord = null;
	
	/** original location of currently dragged item */
	Point wordStartLocation;
	
	/** component-relative mouse position when mouse was pressed */
	Point mouseStartLocation;

	/** Label for status information */
	protected Label lblStatus;
  
	/** Default 'Correct' feedback text */
  protected String default_correct_feedback = "Correct";
  
  /** Default 'Wrong' feedback text */
	protected String default_wrong_feedback = "Wrong";
  
  /** Number of wrong tries so far */
  protected int nWrong;
  
  /** Inidcates wheter the exercise is already finished. */
  protected boolean isFinished;

  /* sounds */
  protected AudioClip welcome_snd = null;
  protected AudioClip finished_snd = null;
  protected AudioClip correct_snd = null;
  protected AudioClip wrong_snd = null;
  protected AudioClip bumpy_snd = null;

  /* colors */  
  protected Color color_fg_nowhere = Color.black,
                  color_bg_nowhere = Color.orange,
                    
                  color_bg_correct = new Color(0x00, 0x66, 0x00),
                  color_bg_wrong = new Color(0x88, 0x00, 0x00);

  
  /**
    This is the feedback window, containing a textarea and a button.
  */
    
  protected class MyFeedback extends Frame implements ActionListener { 
    protected Button b;
    protected TextArea l;
    public Object owner = null;
        
    public MyFeedback() {
      super("Feedback window");

      addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          ((Frame)e.getSource()).setVisible(false);
        }
      });
      
      setLayout(new BorderLayout());
    
      setBackground(new Color(0xf0, 0xf0, 0xf0));
        
      b = new Button("Continue");
      b.addActionListener(this);
      add(b, BorderLayout.SOUTH);
      
      l = new TextArea();
      l.setFont(new Font("SansSerif", Font.PLAIN, 12));
      l.setEditable(false);
      add(l, BorderLayout.CENTER);
      
      setVisible(false);
    } 
    
    public void popup(Object owner, String buttonLabel, String feedback) {
      this.owner = owner;
      b.setLabel(buttonLabel == null ? "Continue" : buttonLabel);
      l.setText(feedback == null ? "No feedback available." : feedback);
      setVisible(true);
    }
    
    public void popup(Object owner, String s) {
      popup(owner, null, s);
    }
    
    public void popup(String s) {
      l.setText(s);
      popup();
    }
    
    public void setText(String s) {
      l.setText(s);
    }
    
    public void popup() {
      setVisible(true);
    }
    
    public void close() {
      setVisible(false);
      //myApplet.validate();
    }
    
    public void actionPerformed(ActionEvent e) {
      close();
    }
    
  }
  
  /** There should be only one feedback windows. This is it! */
  protected MyFeedback myFeedback;
   
   
	/**
		A simple layout manager that does nothing.
	*/
	
  protected class MyLayout2 implements LayoutManager {
  
    public void addLayoutComponent(String name, Component comp) {}
    public void layoutContainer(Container parent) {
      //parent.repaint();
    }
    public Dimension minimumLayoutSize(Container parent) {
      return parent.getSize();
    }
    public Dimension preferredLayoutSize(Container parent) {
      return parent.getSize();
    }
    public void removeLayoutComponent(Component comp) {}
    
  }
  
	/** A drop is not a component! */
	
	protected class Drop { 
		
		public int id;
		protected boolean visible;
		public int x, y, w, h;
		
		public int rx = 20;
		public int ry = 8;
		
    boolean doflash;
    
		public Drop(int id, String param) {
      super();
        
			this.id = id;
			int i = param.indexOf(',');
			x = Integer.parseInt(param.substring(0, i)) - rx;
			y = Integer.parseInt(param.substring(i+1)) - ry;
			w = 2 * rx + 1;
			h = 2 * ry + 1;
  
      doflash = false;
		}
		
    public boolean flash(boolean b) {
      if (b != doflash) {
        doflash = b;
        repaint();
        return true;
      }
      return false;
    }
    
    public void reset() {
      setVisible(true);
    }
    
		public void paint(Graphics g) {
			if (!isVisible()) 
				return;
				
      //g.setColor(Color.blue);
      //g.drawLine(x, y, x + w - 1, y + h - 1);
      //g.drawLine(x, y + h - 1, x + w - 1, y);
      g.setColor(Color.white);
      g.drawRect(x, y, w - 2, h - 2);
      g.setColor(Color.gray);
      g.drawRect(x + 1, y + 1, w - 4, h - 4);
      g.setColor(Color.black);
      g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
      g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
       
		}

    public void setVisible(boolean v) {
      visible = v;
    }
    public boolean isVisible() {
      return visible;
    }
        
		public boolean contains(Rectangle b) {
			Rectangle r = new Rectangle(x, y, w, h);
			return r.intersects(b);
		}

	}

	protected Vector drops = new Vector();

  /** The dragger thread repaints the currently dragged word in regular intervals. */
    
  protected class DraggerThread extends Thread {
  
    protected Component c;
    protected Point p;
    protected boolean changed;
    
    protected Object sync = new Object();
    
    protected boolean stopFlag;
    
    public DraggerThread() {
      p = new Point();
      changed = false;
    }
    
    public void run() {
      try {
        //System.out.println("DraggerThread started.");
        stopFlag = false;
        
        while (!stopFlag) {
          
          sleep(20);
          
          synchronized (sync) {
            if (changed) {
              changed = false;
              c.setLocation(p);
              //c.repaint();
              //System.out.println("Moved");
              
/*              boolean flashupdate = false;
              for (int i = 0; i < drops.size(); i++) {
                Drop d = (Drop)drops.elementAt(i);
                if (d.isVisible()) 
                  flashupdate |= d.flash(d.contains(c.getBounds()));
              }
*/              
              //if (flashupdate) 
              //  myApplet.repaint();
              //else
                c.repaint();
              
            }
          }
        }
        
        //System.out.println("DraggerThread stopped.");
        
      } catch (InterruptedException e) {
        System.out.println("DraggerThread is interrupted.");
      }
    }
  
    public void setStopFlag() {
      stopFlag = true;
    }
    
    public void setComponent(Component c) {
      synchronized (sync) {
        this.c = c;
        changed = false;
      }
    }
    
    public void stopDragging() {
      synchronized(sync) {
        if (c != null) c.repaint();
        this.c = null;
        changed = false;
      }
    }
    
    public void setLocation(int x, int y) {

      if (x < 0) x = 0;
      if (x + c.getSize().width >= imageWidth) x = imageWidth - c.getSize().width;
      if (y < 0) y = 0;
      if (y + c.getSize().height >= imageHeight) y = imageHeight - c.getSize().height;

      synchronized(sync) {
        changed = (x != p.x) || (y != p.y);
        p.x = x;
        p.y = y;
        //System.out.println("Move to " + x + ", " + y);
      }
      
    }
    
  }
  
  /** There should be only one dragger thread. This is it! */
  protected DraggerThread dragger;
  
  
	/** This is a draggable word.	*/
		
	protected class Word extends Canvas implements MouseListener, MouseMotionListener {
		public int id;
		
		public Drop drop;
		public boolean correct;  
		public boolean bumpy;    // overlaps with more than one drop ?
  
/*    public static final int ALONE = 1;
    public static final int WRONG = 2;
    public static final int CORRECT = 3;
    public static final int BUMPY = 4;
    
    protected int state;
*/      
    /* the word that's displayed  */
    String text;

		public Word(int id, String text) {
      this.id = id;

			//super(text, Label.CENTER);
      setFont(new Font("SansSerif", Font.BOLD, 12));
      setText(text);
      			
      reset();
      
      addMouseListener(this);
			addMouseMotionListener(this);
		}

    public void setText(String s) {
      this.text = s;
      FontMetrics fm = getFontMetrics(getFont());
      setSize(fm.stringWidth(s) + 4, 20);
//      System.out.println("Word " + s + " has width " + fm.stringWidth(s));
    }

    public void reset() {
      drop = null;
      correct = false;
      bumpy = false;
      
      setVisible(true);
      updateColors();
    }
/*    
    public void setState(int s) {
      state = s;
    }
    
    public int getState() {
      return state;
    }
*/    
		public void mouseClicked(MouseEvent e) {
      if (myFeedback.isVisible() && ((myFeedback.owner == this) || (myFeedback.owner == null)))
        myFeedback.close();
      else {
        if (bumpy)
          bumpy_feedback();
        else {
          if (drop != null) 
//            myFeedback.popup(this, myApplet.getFeedback(id, drop.id));
            myFeedback.popup(this, getFeedback(id, drop.id));
          else
            myFeedback.popup(this, "No feedback available.");
        }
      }
		}
		public void mouseEntered(MouseEvent e) {
      //myFeedback.setVisible(false);
		}
		public void mouseExited(MouseEvent e) {
		}
		public void mousePressed(MouseEvent e) {
      //myFeedback.setVisible(false);
			if (!correct) {
				wordStartLocation = getLocation();
				mouseStartLocation = new Point(
					wordStartLocation.x + e.getX(),
					wordStartLocation.y + e.getY());
				drop = null;
				dragWord = this;
        
        dragger.setComponent(this);
			}
		}
		public void mouseReleased(MouseEvent e) {
			if (dragWord != this) {
				dragWord = null;
				return;
			}
      onDrop();
      myApplet.repaint();
      //myApplet.validate();
		}

		public void onDrop() {
      dragWord = null;
      dragger.stopDragging();
      
			Drop d = null, dTemp;
      
			for (int i = 0; i < drops.size(); i++) {
				dTemp = (Drop)drops.elementAt(i);
				if (dTemp.isVisible() && dTemp.contains(getBounds())) {
					if (d != null) {
						bumpy_feedback();
						drop = null;
            correct = false;
            bumpy = true;
            updateColors();        
						return;
					}
					d = dTemp;
				}
			}
			
      bumpy = false;
      
			if (d == null) {
        updateColors();        
				return;
			}
				
			drop = d;
			correct = (drop.id == id);
      updateColors();        

			if (correct) {
        drop.setVisible(false);
				Dimension sz = getSize();
				setLocation(
					drop.x + drop.rx - (sz.width / 2),
					drop.y + drop.ry - (sz.height / 2));
        correct_feedback(id);
			} else {
        wrong_feedback(id, drop.id);
      }
		}

		public void mouseDragged(MouseEvent e) {
			if (dragWord == this) {
				Point p = getLocation();
        
        if (myFeedback.isVisible()) {
          myFeedback.close();
        }
        
        dragger.setLocation(
          wordStartLocation.x - mouseStartLocation.x + p.x + e.getX(),
          wordStartLocation.y - mouseStartLocation.y + p.y + e.getY());
			}
		}
		public void mouseMoved(MouseEvent e) {
		}
		
    public void updateColors() {
      if (bumpy) {
        setForeground(Color.black);
        setBackground(Color.red);
        setCursor(new Cursor(Cursor.HAND_CURSOR));
      } else {
        if (drop == null) {
          setForeground(color_fg_nowhere);
          setBackground(color_bg_nowhere);
          setCursor(new Cursor(Cursor.HAND_CURSOR));
        } else {
          if (correct) {
            setForeground(Color.white);
            setBackground(color_bg_correct);
            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
          }
          else {
            setForeground(Color.white);
            setBackground(color_bg_wrong);
            setCursor(new Cursor(Cursor.HAND_CURSOR));
          }
        }
      }
    }
  
  
    public void paint(Graphics g) {
      //g.setColor(Color.black);
      g.drawString(text, 2, (getSize().height + getFont().getSize()) / 2);
    }
    
        
	}

	protected Vector words = new Vector();	
	
	/** Reads the parameters and creates the applet's components. */
	public void init() {
    String s;

		myApplet = this;
    
		//System.out.println("Applet size is (" + getSize().width + ", " + getSize().height + ")");
    
    setBackground(Color.white);
		setLayout(new MyLayout2());

    myFeedback = new MyFeedback();
    //add(myFeedback);

		// paint image
		try {
			String imageParam = getParameter("image");
      //System.out.println("Image: " + imageParam);
			image = getImage(new URL(getCodeBase(), imageParam));
		} catch (MalformedURLException ex) {
      image = null;
			System.out.println("MalformedURLException: " + ex.getMessage());
			return;
		}

    // colors
    if ((s = getParameter("color_fg_nowhere")) != null)
      color_fg_nowhere = new Color(Integer.parseInt(s, 16));
    if ((s = getParameter("color_bg_nowhere")) != null)
      color_bg_nowhere = new Color(Integer.parseInt(s, 16));
    if ((s = getParameter("color_bg_correct")) != null)
      color_bg_correct = new Color(Integer.parseInt(s, 16));
    if ((s = getParameter("color_bg_wrong")) != null)
      color_bg_wrong = new Color(Integer.parseInt(s, 16));

    
		// words and drops
		for (int i = 1; i < 100; i++) {
			String wordParam = getParameter("word" + i);
			if (wordParam != null) {
				Word word = new Word(i, wordParam);
				words.addElement(word);
				add(word);
        word.setVisible(true);
			} else
				break;
				
		}

		for (int i = 1; i < 100; i++) {
			String dropParam = getParameter("drop" + i);
			if (dropParam != null) {
				Drop drop = new Drop(i, dropParam);
				drops.addElement(drop);
        //add(drop);
        //drop.setVisible(true);
			} else
				break;
		}

    //invalidate();
    
		// default feedback
    if ((s = getParameter("correct")) != null) default_correct_feedback = s;
		if ((s = getParameter("wrong")) != null) default_wrong_feedback = s;
      
		// status label
/*    
    (lblStatus = new Label()).addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        //System.out.println("feedback popup!");
        myFeedback.popup();
      }
    });
*/   
	  add(lblStatus = new Label());
    
    //add(myFeedback);

    // audio clips
    if ((s = getParameter("welcome_snd")) != null)
      welcome_snd = getAudioClip(getDocumentBase(), s);
    if ((s = getParameter("finished_snd")) != null)
      finished_snd = getAudioClip(getDocumentBase(), s);
    if ((s = getParameter("correct_snd")) != null)
      correct_snd = getAudioClip(getDocumentBase(), s);
    if ((s = getParameter("wrong_snd")) != null)
      wrong_snd = getAudioClip(getDocumentBase(), s);
    if ((s = getParameter("bumpy_snd")) != null)
      bumpy_snd = getAudioClip(getDocumentBase(), s);

    // listen to the mouse
    addMouseListener(this);
    addMouseMotionListener(this);
    
	}

  protected void init_feedback() {
    int feedback_width = getSize().width;
    int feedback_height = getSize().height;
    
/*
    String s;
    
    try {
      if ((s = getParameter("feedback_width")) != null) feedback_width = Integer.parseInt(s);
      System.out.println("feedback_width is " + feedback_width);
    } catch (Exception e) {
      System.err.println("Parameter 'feedback_width' invalid.");
    }
    try {
      if ((s = getParameter("feedback_height")) != null) feedback_height = Integer.parseInt(s);
      System.out.println("feedback_height is " + feedback_height);
    } catch (Exception e) {
      System.err.println("Parameter 'feedback_height' invalid.");
    }
    
    myFeedback.setSize(feedback_width, feedback_height);
*/    
    myFeedback.pack();

    Dimension d = getToolkit().getScreenSize();
    myFeedback.setLocation((d.width - feedback_width) / 3, (d.height - feedback_height) / 3);
    
//    System.err.println("Feedback size is (" + myFeedback.getSize().width + ", " +  myFeedback.getSize().height + ")");

  }
    
  public void start() {
    super.start();
    mylayout();

    for (int i = 0; i < words.size(); i++) 
      ((Word)words.elementAt(i)).reset();
    for (int i = 0; i < drops.size(); i++)
      ((Drop)drops.elementAt(i)).reset();
      
    nWrong = 0;
    isFinished = false;
    (dragger = new DraggerThread()).start();

    lblStatus.setText("Welcome to the EVTI Drag'n'drop exercise!");
    welcome_feedback();

  }
  
  public void stop() {
    dragger.setStopFlag();
    super.stop();
    myFeedback.close();
  }
  
  protected void mylayout() {
    
    // image
    imageWidth = image.getWidth(this);
    imageHeight = image.getHeight(this);
    
    // words
    for (int i = 0, y = 3, x = 3; i < words.size(); i++) {
      Word w = (Word)words.elementAt(i);
      w.setSize(w.getPreferredSize());
      
      if (x + w.getSize().width > imageWidth) {
        y += w.getSize().height;
        x = 3;
      }
      
      w.setLocation(x, y);
      w.setVisible(true);

      x += w.getSize().width + 1;
    }
    
    // status-line
    lblStatus.setSize(getSize().width, getSize().height - imageHeight);
    lblStatus.setLocation(0, imageHeight + 1);
    lblStatus.setVisible(true);
    
    // paint
    validate();
    repaint();

    // feedback window
    init_feedback();
  }

	public void paint(Graphics g) {
		//System.out.print(".");
    if (image != null) {
			g.drawImage(image, 0, 0, this);
      g.draw3DRect(0, 0, imageWidth - 1, imageHeight - 1, true);
		}
		
		for (int i = 0; i < drops.size(); i++)
			((Drop)drops.elementAt(i)).paint(g);

	}
		
	public void mouseClicked(MouseEvent e) {
    //System.out.println("Your mouse clicked at (" + e.getX() + "; " + e.getY() + ")");
    if (!myFeedback.isVisible())
        myFeedback.popup(null, 
          "You have made " + nWrong + " mistake" + (nWrong != 1 ? "s" : "") + " so far.\n\n" +
          "Press 'Reload' to restart.");
    else
      myFeedback.close();
	}
	public void mouseEntered(MouseEvent e) {
	}
	public void mouseExited(MouseEvent e) {
	}
	public void mousePressed(MouseEvent e) {
	}
	public void mouseReleased(MouseEvent e) {
		if (dragWord != null)
			dragWord.onDrop();
	}
	public void mouseDragged(MouseEvent e) {
		if (dragWord == null) 
			return;
			
		if (!contains(e.getX(), e.getY()))
			return;
			
		dragger.setLocation(
			wordStartLocation.x - mouseStartLocation.x + e.getX(),
			wordStartLocation.y - mouseStartLocation.y + e.getY());
	}
	public void mouseMoved(MouseEvent e) {
	}
	
	public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
    mylayout();
		return super.imageUpdate(img, flags, x, y, w, h);
	} 
	
 
  /* Feedback functions 
  */
  
  protected String getFeedback(int w, int d) {
    String s = getParameter(w + "on" + d);
    if (s == null) 
      s = (w == d ? default_correct_feedback : default_wrong_feedback);
    return s.replace('\\', '\n');
  }
 
  protected void welcome_feedback() {
    if (welcome_snd != null) welcome_snd.play();
    myFeedback.popup(null, "Start", 
      "Welcome to \n\n" +
      "EVTI English Via The Internet\n" +
      "- Drag'n'Drop Exercise -\n\n" + 
        
      "Move the words with your mouse to the right\n" +
      "placeholders.\n" +
      "Click on the words or the image for feedback.\n\n" +
        
      " Version " + version_string + "\n\n" + 
      " (c) 2000 T. Poschwatta, tp@fonz.de\n" +
      " Idea by Mr. H.-H.Müller, mueller@zems.tu-berlin.de\n" +
      "  (English teacher at University of Technology Berlin)");
  }
  
  protected void finished_feedback() {
    if (finished_snd != null) finished_snd.play();
    myFeedback.popup(null, "Close", 
      "Congratulations!\n" +
      "The exercise is now finished!\n" +
      "You have made " + nWrong + (nWrong == 1 ? " mistake." : " mistakes."));
  }
  
  
  protected void correct_feedback(int w) {
    if (correct_snd != null) correct_snd.play();
    update_status();

    for (int i = 0; i < words.size(); i++)
      if (!((Word)words.elementAt(i)).correct)
        return;
    isFinished = true; update_status();
    finished_feedback();
  }
  
  protected void wrong_feedback(int w, int d) {
    if (wrong_snd != null) wrong_snd.play();
    nWrong++; update_status();
    myFeedback.popup(null, null, getFeedback(w, d));
  }
  
  protected void bumpy_feedback() {
    if (bumpy_snd != null) bumpy_snd.play();
    myFeedback.popup(null, "Continue", 
      "The word overlaps with two or more placeholders, it's not\n" +
      "clear where you think the right position is.\n" +
      "Please move the word so that it only touches one single\n" + 
      "placeholder.");
  }
  
  protected void update_status() {
    if (isFinished) {
      lblStatus.setText(nWrong + " mistake" + (nWrong != 1 ? "s" : "") + ". Press 'Reload' to restart.");
    } else {
      lblStatus.setText(nWrong + " mistake" + (nWrong != 1 ? "s" : "") + " so far");
    }
  }
     
}
