/*
 * A puzzle game written in Java.
 *
 * Please read "http://juzzle.sourceforge.net/juzzle_licence.txt" for copyrights.
 * 
 * The sourcecode is designed and created with
 * Sun J2SDK 1.3 and Microsoft Visual J++ 6.0
 *
 * Juzzle homepage: http://juzzle.sourceforge.net
 *
 * autor: Slawa Weis
 * email: slawaweis@animatronik.net
 *
 */

package org.game.Juzzle;

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

import javax.swing.*;
import javax.swing.border.*;

/**
 * this panel paints the pieces of the puzzle
 *
 * @see     org.game.Juzzle.PuzzlePanel
 * @see     org.game.Juzzle.PuzzlePartList
 */
public class PuzzlePanel extends JPanel implements MouseListener, MouseMotionListener
{
 /**
  * thicknes of the outline for the piece for repaint rect
  */
 protected static final int PAINT_EXTEND = 2;
 /**
  * thicknes of the shadow while dragging and for repaint rect
  */
 protected static final int PAINT_SHADOW = 10;
 /**
  * cursor while dragging
  */
 protected static final Cursor CURSOR_HAND    = new Cursor(Cursor.HAND_CURSOR);
 /**
  * cursor normal
  */
 protected static final Cursor CURSOR_DEFAULT = new Cursor(Cursor.DEFAULT_CURSOR);
 /**
  * outline color
  */
 protected static final Color outlineColor = new Color(0, 0, 0, 128);
 /**
  * shadow color
  */
 protected static final Color shadowColor = new Color(0, 0, 0, 128);

 /**
  * reference to the image
  */
 protected BufferedImage bimage = null;
 /**
  * x division
  */
 protected int x_parts = 0;
 /**
  * y division
  */
 protected int y_parts = 0;

 /**
  * reference to the ControlPanel to set sthe solved pieces count
  *
  * @see     org.game.Juzzle.ControlPanel
  */
 protected ControlPanel controlPanel = null;

 /**
  * drag modus 1 mean that the user clicks the mouse button and then move the piece and clicks again to release.<br>
  * <br>
  * <br>
  * drag modus 2 mean that the user press the mouse button and then drag the piece and release the button.<br>
  * <br>
  * <br>
  */
 protected boolean dragModus1, dragModus2 = false;
 /**
  * last drag location for difference calculation
  */
 protected Point lastDragLocation = new Point();
 /**
  * source rect to repaint
  */
 protected Rectangle repaintRect1 = new Rectangle();
 /**
  * target rect to repaint
  */
 protected Rectangle repaintRect2 = new Rectangle();
 /**
  * needed to show only one time per game the win message
  */
 protected boolean endMessagePrinted = false;

 /**
  * reference to the pieces list model
  *
  * @see     org.game.Juzzle.IPuzzlePartList
  */
 protected IPuzzlePartList puzzlePartList = null;

 /**
  * antialiasing on/off
  */
 protected boolean antialiasing = true;
 /**
  * outline on/off
  */
 protected boolean outline = true;
 /**
  * shadow on/off
  */
 protected boolean shadow = true;

 /**
  * simply constructor
  *
  * @param controlPanel the reference to the ControlPanel
  */
 public PuzzlePanel(ControlPanel controlPanel)
  {
  super(null);

  this.controlPanel = controlPanel;

  setOpaque(false);

  addMouseListener(this);
  addMouseMotionListener(this);

//  setPreferredSize(new Dimension(2000, 2000));

  // set the vatiables
  emptyGame();
  }

 /**
  * reset the current game
  */
 public void resetGame()
  {
  if(bimage != null)
    {
    emptyGame();
    puzzlePartList = new PuzzlePartList(bimage, x_parts, y_parts, getSize());
    controlPanel.printPieces("" + puzzlePartList.getPartsCount());
    controlPanel.printSolved("0");
    controlPanel.startCounter();
    repaint();
    }
  }

 /**
  * clear all variables
  */
 public void emptyGame()
  {
  dragModus1 = dragModus2 = false;
  lastDragLocation = new Point();
  repaintRect1 = new Rectangle();
  repaintRect2 = new Rectangle();
  puzzlePartList = new DummyPuzzlePartList();
  endMessagePrinted = false;

  controlPanel.printPieces("");
  controlPanel.printSolved("");
  controlPanel.printTime("");

  repaint();
  }

 /**
  * set the new parameters to play the game
  */
 public void setGame(BufferedImage bimage, int x_parts, int y_parts)
  {
  // clear all variables
  emptyGame();

  this.bimage = bimage;
  this.x_parts = x_parts;
  this.y_parts = y_parts;

  puzzlePartList = new PuzzlePartList(bimage, x_parts, y_parts, getSize());
  controlPanel.printPieces("" + puzzlePartList.getPartsCount());
  controlPanel.printSolved("0");
  controlPanel.startCounter();

  repaint();
  }

 /**
  * set antialiasing on/off
  */
 public void setAntialiasing(boolean b)
  {
  antialiasing = b;
  repaint();
  }
 /**
  * set outline on/off
  */
 public void setOutline(boolean b)
  {
  outline = b;
  repaint();
  }
 /**
  * set shadow on/off
  */
 public void setShadow(boolean b)
  {
  shadow = b;
  repaint();
  }

 /**
  * overwrite paintComponent to paint the pieces
  */
 public void paintComponent(Graphics g)
  {
  super.paintComponent(g);

  Graphics2D g2 = (Graphics2D)g;

  // enable antialiasing
  if(antialiasing)
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

  // get the first part for enumeration
  PuzzlePartList.PuzzlePart part = puzzlePartList.getFirstNode();

  int tx, ty;

  // outline size
  g2.setStroke(new BasicStroke(3));

  while(part != null)
       {
//       g2.setColor(Color.black);
//       g2.fillOval(part.locationIn.x-5, part.locationIn.y-5, 10, 10);
//       g2.setColor(Color.black);
//       g2.drawOval(part.locationOut.x-5, part.locationOut.y-5, 10, 10);

       tx = part.locationIn.x - part.boundsIn.x;
       ty = part.locationIn.y - part.boundsIn.y;

       // translate the graphics context to the piece location
       g2.translate(tx, ty);

//       PuzzlePaint pp = new PuzzlePaint(bimage, part.boundsImage);

       // paint the shadow
       if(shadow && puzzlePartList.isSelectedPart(part))
         {
         g2.translate(PAINT_SHADOW, PAINT_SHADOW);
         g2.setPaint(shadowColor);
         g2.fill(part.generalPath);
         g2.translate(-PAINT_SHADOW, -PAINT_SHADOW);
         }

//       g2.setPaint(pp);

       // set the paint
       g2.setPaint(part.puzzlePaint);
       // fill the shape of the piece with the piece image part
       g2.fill(part.generalPath);

       // paint the outline
       if(outline)
         {
         g2.setColor(outlineColor);
         g2.draw(part.generalPath);
         }

//  g2.setStroke(new BasicStroke(1));
//  g2.setColor(Color.blue);
//  g2.draw(part.boundsIn);

//       g2.setStroke(new BasicStroke(1));
//       g2.setColor(Color.black);
//       g2.draw(s.getBounds());

       // translate the graphics context back
       g2.translate(-tx, -ty);

       // next in list
       part = part.next;
       }

//  g2.setStroke(new BasicStroke(1));
//  g2.setColor(Color.red);
//  g2.draw(repaintRect1);
//  g2.setColor(Color.green);
//  g2.draw(repaintRect2);
  }

 /**
  * if the user clicks one of the parts, start dragging
  */
 protected void startDragging(MouseEvent e)
  {
  if(puzzlePartList.selectPart(e.getPoint()))
    {
    setCursor(CURSOR_HAND);
    lastDragLocation.setLocation(e.getPoint());
    dragModus1 = true;
    mouseMoved(e);
    }
  }

 /**
  * the user put the piece on the desctop back
  */
 protected void stopDragging(MouseEvent e)
  {
  // ask the model for the part that has changed and should be repainted
  puzzlePartList.setRedrawRect(repaintRect1);
  // move selected part
  puzzlePartList.updatePart();
  // ask again
  puzzlePartList.setRedrawRect(repaintRect2);

  // deselect the selected part
  puzzlePartList.deselectPart();

  // if the repaintRect1 and repaintRect2 intersects repaint only one rect
  if(repaintRect1.intersects(repaintRect2))
    {
    repaintRect1.add(repaintRect2);
    repaint(repaintRect1.x - PAINT_EXTEND,
            repaintRect1.y - PAINT_EXTEND,
            repaintRect1.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
            repaintRect1.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
    }
  // if the repaintRect1 and repaintRect2 don't intersects repaint two rects
  else
    {
    repaint(repaintRect1.x - PAINT_EXTEND,
            repaintRect1.y - PAINT_EXTEND,
            repaintRect1.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
            repaintRect1.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
    repaint(repaintRect2.x - PAINT_EXTEND,
            repaintRect2.y - PAINT_EXTEND,
            repaintRect2.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
            repaintRect2.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
    }

  // update the solved count in ControlPanel
  controlPanel.printSolved("" + puzzlePartList.getSolvedPartsCount());

  // dragging end
  dragModus1 = false;
  setCursor(CURSOR_DEFAULT);

  // if solved the whole image, show the message one time
  if(!endMessagePrinted && puzzlePartList.getSolvedPartsCount() >= puzzlePartList.getPartsCount())
    {
    controlPanel.stopCounter();
    JOptionPane.showMessageDialog(this, "Congratulation! You solved the puzzle in " + controlPanel.getGameTime() + ".");
    endMessagePrinted = true;
    }
  }

 /**
  * from MouseListener
  */
 public void mouseClicked (MouseEvent e)
  {
  // drag modus 1 on if not on
  if(dragModus1 == false)
    {
    startDragging(e);
    }
  // drag modus 1 off if on
  else
    {
    stopDragging(e);
    }
  }
 /**
  * from MouseListener, not used
  */
 public void mouseEntered (MouseEvent e)
  {
  }
 /**
  * from MouseListener, not used
  */
 public void mouseExited  (MouseEvent e)
  {
  }
 /**
  * from MouseListener
  */
 public void mousePressed (MouseEvent e)
  {
  // drag modus 2 on if no mode is on
  if(!dragModus2 && !dragModus1)
    {
    dragModus2 = true;
    }
  }
 /**
  * from MouseListener
  */
 public void mouseReleased(MouseEvent e)
  {
  // if only mode 2 on do nothing
  // mouseClicked is called
  if(dragModus2 && !dragModus1)
    {
    dragModus2 = false;
    }
  // if both modes on stop dragging
  else if(dragModus2 && dragModus1)
    {
    dragModus2 = false;
    stopDragging(e);
    }
  }

 /**
  * from MouseMotionListener
  */
 public void mouseDragged(MouseEvent e)
  {
  // user use the modus 2
  if(dragModus2 && !dragModus1)
    {
    dragModus1 = true;
    startDragging(e);
    }
  mouseMoved(e);
  }
 /**
  * from MouseMotionListener
  */
 public void mouseMoved(MouseEvent e)
  {
  // user dragged or moved the piece
  if(dragModus1)
    {
    Point point = e.getPoint();

    int dx = point.x - lastDragLocation.x;
    int dy = point.y - lastDragLocation.y;

    // ask the model for the part that has changed and should be repainted
    puzzlePartList.setRedrawRect(repaintRect1);
    // move selected part
    puzzlePartList.moveLocation(dx, dy);
    // ask again
    puzzlePartList.setRedrawRect(repaintRect2);

    // if the repaintRect1 and repaintRect2 intersects repaint only one rect
    if(repaintRect1.intersects(repaintRect2))
      {
      repaintRect1.add(repaintRect2);
      repaint(repaintRect1.x - PAINT_EXTEND,
              repaintRect1.y - PAINT_EXTEND,
              repaintRect1.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
              repaintRect1.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
      }
    // if the repaintRect1 and repaintRect2 don't intersects repaint two rects
    else
      {
      repaint(repaintRect1.x - PAINT_EXTEND,
              repaintRect1.y - PAINT_EXTEND,
              repaintRect1.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
              repaintRect1.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
      repaint(repaintRect2.x - PAINT_EXTEND,
              repaintRect2.y - PAINT_EXTEND,
              repaintRect2.width  + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND,
              repaintRect2.height + PAINT_SHADOW + PAINT_EXTEND + PAINT_EXTEND);
      }

    lastDragLocation.setLocation(point);
    }
  }
}