/***********************************************************************
 * MicrochipsPlayer.java - Source Code (requires java 1.1)             *
 * Version release date : June 24, 2003                                *
 * Copyright (C) 2001-2003  Neural Semantics sprl, Belgium             *
 * Author : Michel Petre mpetre@neuralsemantics.com                    *
 *                                                                     *
 *     http://www.neuralsemantics.com/                                 *
 *                                                                     *
 * This code is released under the GNU GPL license, version 2 or       *
 * later, for educational and non-commercial purposes only.            *
 * If any part of the code is to be included in a commercial           *
 * software, please contact us first for a clearance at                *
 * info@neuralsemantics.com                                            *
 *                                                                     *
 *   This notice must remain intact in all copies of this code.        *
 *   This code is distributed WITHOUT ANY WARRANTY OF ANY KIND.        *
 *   The GNU GPL license can be found at :                             *
 *           http://www.gnu.org/copyleft/gpl.html                      *
 *                                                                     *
 ***********************************************************************/

 /* History & changes **************************************************
 *                                                                     *
 ******** July 20, 2001 ************************************************
 *   - First release                                                   *
 ******** June 24, 2003 ************************************************
 *   - Enhanced motion speed control                                   *
 *   - Added motionJump control                                        *
 *   - Added layer control                                             *
 *   - Added welcome text                                              *
 ***********************************************************************/


import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

class MicrochipsPlayer extends Canvas 
                       implements MouseListener, 
                                  MouseMotionListener, 
                                  Runnable {

  public final static int LOCATE = 0;
  public final static int MOVE = 1;
  private int motionMode = MOVE;

  public final static int MOTION_SLOW = 0;
  public final static int MOTION_MEDIUM = 1;
  public final static int MOTION_FAST = 2;
  private int motionSpeed = MOTION_MEDIUM;

  private long timer;
  private long fastTimer = 30;
  private long slowTimer = 350;
  private long moveTimer = 3;

  private int border = 1;
  private int motionJump = 1;

  private int cw, ch, ow, oh, sx, sy;
  private int lastX, lastY, mouseX, mouseY;
  private int clipX, clipY, clipW = 40, clipH = 40;

  private boolean isMoving, isReleased, isCleared, isSingleLayer;

  private Color bgColor = new Color(0x00, 0x00, 0x00);
  private Color borderColor = new Color(0x40, 0x40, 0x40);

  private Image memImg, offImg, backImg;
  private Graphics graph, offGraph;
  private MemoryImageSource source;
  private int[] pix;

  private int currentDelta, currentMask;
  private int pixDelta;
  private int pixMask = 0x00FFFFFF;

  private String sStart;
  private boolean isText;
  private Color textColor = new Color(0x00, 0xC0, 0xFF);
  private Font startFont = new Font("Dialog", Font.PLAIN, 12);

  private Thread runner;

//**********************************************************************
//                            Constructors
//**********************************************************************
  MicrochipsPlayer() {
    addMouseListener(this);
    addMouseMotionListener(this);
  }

//**********************************************************************
//                              Methods
//**********************************************************************
  private int getCode(int x, int y) {
    int code = -1;
    if (x < lastX) {
      if (y < lastY)
        code = 6;
      else if (y == lastY)
        code = 5;
      else if (y > lastY)
        code = 4;
    }
    else if (x == lastX) {
      if (y < lastY)
        code = 7;
      else if (y == lastY)
        code = 0;
      else if (y > lastY)
        code = 3;
    }
    else if (x > lastX) {
      if (y < lastY)
        code = 8;
      else if (y == lastY)
        code = 1;
      else if (y > lastY)
        code = 2;
    }
    return code;
  }
//------------------------------------------------------------------------
  private int changeX(int code) {
    switch(code) {
      case 8 :
      case 1 :
      case 2 : 
        return lastX + motionJump;
      case 4 :
      case 5 :
      case 6 :
        return lastX - motionJump;
    }
    return lastX;
  }
//------------------------------------------------------------------------
  private int changeY(int code) {
    switch(code) {
      case 2 :
      case 3 :
      case 4 : 
        return lastY + motionJump;
      case 6 :
      case 7 :
      case 8 :
        return lastY - motionJump;
    }
    return lastY;
  }
//------------------------------------------------------------------------
  private void moveMotion() {
    int code = this.getCode(mouseX, mouseY);
    if (code > 0) {
      isMoving = true;
      int xNo = changeX(code);
      int yNo = changeY(code);
      if ((xNo >= 0 - clipW) && (xNo < cw + clipW) && 
          (yNo >= 0 - clipH) && (yNo < ch + clipH)) {
        lastX = xNo;
        lastY = yNo;
        clipX = lastX - (int)(clipW / 2);
        clipY = lastY - (int)(clipH / 2);
        makeImage();
      }
    }
    else {
      isMoving = false;
      if (isReleased)
        timer = slowTimer;
    }
  }
//------------------------------------------------------------------------
  private void locateMotion() {
    lastX = mouseX;
    lastY = mouseY;
    clipX = lastX - (int)(clipW / 2);
    clipY = lastY - (int)(clipH / 2);
    makeImage();
    isMoving = false;
    if (isReleased)
      timer = slowTimer;
  }
//------------------------------------------------------------------------
  private int[] getPixels(Image im) {
    int[] px = new int[cw * ch];
    PixelGrabber pg = new PixelGrabber(im, 0, 0, cw, ch, px, 0, cw);
    try {
      pg.grabPixels();
      return px;
    } 
    catch (InterruptedException e) { }
    return null;
  }
//------------------------------------------------------------------------
  private synchronized void handleMouseOn(int x, int y, int mod) {
    mouseX = x;
    mouseY = y;
    if (isReleased) {
      if (mod != InputEvent.BUTTON2_MASK) {
        lastX = mouseX - 1;
        lastY = mouseY - 1;
      }
      isReleased = false;
      isCleared = false;
      isText = false;
    }
    timer = fastTimer;
    isMoving = true;
  }

//**********************************************************************
//                               Thread
//**********************************************************************
  public void start() {
    if (runner == null) {
      runner = new Thread(this);
      runner.start();
    }
    isReleased = true;
    isCleared = true;
    timer = slowTimer;
  }
//------------------------------------------------------------------------
  public void run() {
    int pass = 0;
    while (true) {
      repaint();
      while (isMoving) {
        if (isReleased) {
          isMoving = false;
          timer = slowTimer;
        }
        else {
          if (motionMode == MOVE)
            this.moveMotion();
          else if (motionMode == LOCATE)
            this.locateMotion();
          if (motionSpeed == MOTION_SLOW) {
            try { Thread.sleep(moveTimer); }
            catch (InterruptedException e) { }
          }
          else if (motionSpeed == MOTION_MEDIUM) {
            pass += 1;
            if (pass == 3) {
              try { Thread.sleep(moveTimer); }
              catch (InterruptedException e) { }
              pass = 0;
            }
          }
        }
      }
      if (isReleased)
        timer = slowTimer;
      try { Thread.sleep(timer); }
      catch (InterruptedException e) { }
    }
  }
//------------------------------------------------------------------------
  public void stop() {
    this.cleanup();
  }
//------------------------------------------------------------------------
  public void destroy() {
    this.cleanup();
  }
//------------------------------------------------------------------------
  private synchronized void cleanup() {
    if (runner != null) {
      try {
        runner.stop();
        runner.join();
        runner = null;
      }
      catch(Exception e) { }
    }
  }

//**********************************************************************
//                     MouseListener Interface
//**********************************************************************
  public void mouseClicked(MouseEvent e) {
    this.handleMouseOn(e.getX(), e.getY(), e.getModifiers());
  }
//------------------------------------------------------------------------
  public void mouseEntered(MouseEvent e) {
  }
//------------------------------------------------------------------------
  public void mouseExited(MouseEvent e) {
  }
//------------------------------------------------------------------------
  public void mousePressed(MouseEvent e) {
  }
//------------------------------------------------------------------------
  public void mouseReleased(MouseEvent e) {
    isReleased = true;
  }


//**********************************************************************
//                   MouseMotionListener Interface
//**********************************************************************
  public void mouseDragged(MouseEvent e) {
    this.handleMouseOn(e.getX(), e.getY(), e.getModifiers());
  }
//-------------------------------------------
  public void mouseMoved(MouseEvent e) {
  }

//**********************************************************************
//                               Painter
//**********************************************************************
  private boolean checkImage() {
    Dimension d = getSize();
    int w = d.width;
    int h = d.height;
    if ((w == 0) || (h == 0))
      return false;

    w -= 2 * border;
    h -= 2 * border;

    if ((memImg == null) || (w != cw) || (h != ch)) {
      cw = w;
      ch = h;
      Image img = createImage(cw, ch);
      Graphics g = img.getGraphics();
      if (backImg == null) {
        g.setColor(bgColor);
        g.fillRect(0, 0, cw, ch);
      }
      else {
        g.setColor(Color.black);
        g.fillRect(0, 0, cw, ch);
        g.drawImage(backImg, 0, 0, cw, ch, bgColor, null);
      }
      pix = this.getPixels(img);
      if (pix != null) {
        source = new MemoryImageSource(cw, ch, pix, 0, cw);
        source.setAnimated(true);
        memImg = createImage(source);
      }
      else
        memImg = null;
    }
    return true;
  }
//------------------------------------------------------------------------
  private void makeImage() {
    this.checkImage();

    int linePos, pos;
    int minY = Math.max(0, clipY);
    int maxY = Math.min(ch, clipY + clipH);
    int minX = Math.max(0, clipX);
    int maxX = Math.min(cw, clipX + clipW);

    for (int i=minY; i<maxY; i++) {
      linePos = i * cw;
      for (int j=minX; j<maxX; j++) {
        pos = linePos + j;
        pix[pos] = ((pix[pos] + pixDelta) & pixMask) + 0xFF000000;
      }
    }
    source.newPixels(clipX, clipY, clipW, clipH);
  }
//------------------------------------------------------------------------
  public void update(Graphics g) {
    this.checkImage();
    if ((offImg == null) || (cw != ow) || (ch != oh)) {
      ow = cw;
      oh = ch;
      offImg = createImage(ow, oh);
      offGraph = offImg.getGraphics();
      FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(startFont);
      sx = (int)((cw - fm.stringWidth(sStart)) / 2);
      sy = (int)((ch - fm.getMaxDescent()) / 2) - 10;
    }
    if (isSingleLayer) {
      offGraph.setColor(bgColor);
      offGraph.fillRect(0, 0, cw - 1, ch - 1);
    }
    if (memImg != null) {
      offGraph.drawImage(memImg, 1, 1, this);
    }
    if (isText) {
      offGraph.setFont(startFont);
      offGraph.setColor(textColor);
      offGraph.drawString(sStart, sx, sy);
    }
    g.setColor(borderColor);
    g.drawRect(0, 0, cw + 2*border - 1, ch + 2*border - 1);
    g.drawImage(offImg, border, border, this);
  }
//------------------------------------------------------------------------
  public void paint(Graphics g) {
    this.checkImage();
    if (memImg != null)
      g.drawImage(memImg, 1, 1, this);
    g.setColor(borderColor);
    g.drawRect(0, 0, cw + 2*border - 1, ch + 2*border - 1);
  }

//**********************************************************************
//                            Public Interface
//**********************************************************************
  public void setBgColor(Color c) { 
    bgColor = c;
  }
//------------------------------------------------------------------------
  public void clear() {
    memImg = null;
    timer = slowTimer;
    isMoving = false;
    repaint();
    isCleared = true;
  }
//------------------------------------------------------------------------
  public void setDelta(int d) {
    currentDelta = d;
    if ((currentMask == 0) && (currentDelta == 0))
      d += 1;
    int r = d + ((currentMask & 4) >> 2);
    int g = d + ((currentMask & 2) >> 1);
    int b = d + (currentMask & 1);
    pixDelta = (r<<16) | (g<<8) | b;
  }
//------------------------------------------------------------------------
  public void setMask(int i) {
    currentMask = i;
    setDelta(currentDelta);
  }
//------------------------------------------------------------------------
  public void setChipSize(int w, int h) {
    clipW = w;
    clipH = h;
  }
//------------------------------------------------------------------------
  public void setSingleLayer(boolean b) {
    isSingleLayer = b;
  }
//------------------------------------------------------------------------
  public void setMotionMode(int i) {
    if ((i >= 0) && (i <= 1))
      motionMode = i;
  }
//------------------------------------------------------------------------
  public void setMotionSpeed(int i) {
    if ((i >= 0) && (i <= 2))
      motionSpeed = i;
  }
//------------------------------------------------------------------------
  public void setMotionJump(int i) {
    if (i > 0)
      motionJump = i;
  }
//------------------------------------------------------------------------
  public void setText(String s) {
    if (s == null)
      isText = false;
    else
      isText = true;
    sStart = s;
  }

//**********************************************************************
//                                Properties
//**********************************************************************
  public Dimension getPreferredSize() { return getSize(); }
//------------------------------------------------------------------------
  public Dimension getImageSize() { return new Dimension(cw, ch); }
//------------------------------------------------------------------------
  public Image getImage() { return offImg; }
//------------------------------------------------------------------------
  public boolean isCleared() { return isCleared; }
//------------------------------------------------------------------------
  public int[] getRGBPixels() { return pix; }
}