/* PlayingDesk.java --
 Copyright (C) 2005 Free Software Foundation, Inc.

 This file is part of GNU Classpath.

 GNU Classpath is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2, or (at your option)
 any later version.

 GNU Classpath is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GNU Classpath; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301 USA.

 Linking this library statically or dynamically with other modules is
 making a combined work based on this library.  Thus, the terms and
 conditions of the GNU General Public License cover the whole
 combination.

 As a special exception, the copyright holders of this library give you
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */


package gnu.classpath.examples.CORBA.swing.x5;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import javax.swing.JComponent;

import org.omg.CORBA.ORB;

/**
 * Manages actions, related to the game rules and also does all painting.
 *
 * @author Audrius Meskauskas, Lithuania (AudriusA@Bioinformatics.org)
 */
public class PlayingDesk
  extends JComponent
  implements MouseListener, State
{
  /**
   * Use serialVersionUID for interoperability.
   */
  private static final long serialVersionUID = 1;

  /**
   * Indicates that the field point state is the red oval.
   */
  public static final int RED = 0;

  /**
   * Indicates that the field point state is the black cross.
   */
  public static final int BLACK = 1;

  /**
   * Indicates that the field point state is the hint, suggested by the fan.
   */
  public static final int HINT = 2;

  /**
   * The access to the main frame methods.
   */
  ClientFrame frame;

  /**
   * The access to the player communicator.
   */
  PlayerImpl player;

  /**
   * The game manager.
   */
  GameManager manager;

  /**
   * The player ORB.
   */
  ORB orb;

  /**
   * The player IOR.
   */
  String ior;

  /**
   * True if the player ORB started ok.
   */
  boolean ok;

  /**
   * The grid spacing.
   */
  static int W = 16;

  /**
   * The radius of the dots being painted.
   */
  static int R = W / 3;

  /**
   * The collection of the red dots.
   */
  ArrayList reds = new ArrayList();

  /**
   * The collection of the black dots.
   */
  ArrayList blacks = new ArrayList();

  /**
   * The array of hints.
   */
  ArrayList hints = new ArrayList();

  /**
   * When the game is completed, obtains the value of the two end points of the
   * created line.
   */
  Point[] endOfGame;

  public PlayingDesk()
  {
    try
      {
        player = new PlayerImpl();
        player.desk = this;

        OrbStarter.startPlayer(player, this);

        jbInit();
      }
    catch (Exception e)
      {
        e.printStackTrace();
      }
  }

  /**
   * Paint this component.
   */
  public void paintComponent(Graphics g)
  {
    int w = getWidth();
    int h = getHeight();

    g.setColor(Color.white);
    g.fillRect(0, 0, w, h);

    drawGrid(w, h, g);
    drawFigures(g);
  }

  /**
   * Check maybe a game is finished after setting the point N
   */
  public Point[] checkFinished(Collection x, Point N)
  {
    Iterator iter = x.iterator();
    Point p;

    // The victory, if happens, must occur inside these boundaries:
    int ax = N.x - 5;
    int bx = N.x + 5;

    int ay = N.y - 5;
    int by = N.y + 5;

    while (iter.hasNext())
      {
        p = (Point) iter.next();

        if (p.x > ax && p.x < bx && p.y > ay && p.y < by)
          {
            // Check the vertical line down
            if (pointPresent(p.x, p.y + 1, x))
              if (pointPresent(p.x, p.y + 2, x))
                if (pointPresent(p.x, p.y + 3, x))
                  if (pointPresent(p.x, p.y + 4, x))
                    return new Point[] { p, new Point(p.x, p.y + 4) };

            // Check the horizontal line left
            if (pointPresent(p.x + 1, p.y, x))
              if (pointPresent(p.x + 2, p.y, x))
                if (pointPresent(p.x + 3, p.y, x))
                  if (pointPresent(p.x + 4, p.y, x))
                    return new Point[] { p, new Point(p.x + 4, p.y) };

            // Check the diagonal line right down.
            if (pointPresent(p.x + 1, p.y + 1, x))
              if (pointPresent(p.x + 2, p.y + 2, x))
                if (pointPresent(p.x + 3, p.y + 3, x))
                  if (pointPresent(p.x + 4, p.y + 4, x))
                    return new Point[] { p, new Point(p.x + 4, p.y + 4) };

            // Check the diagonal line left down.
            if (pointPresent(p.x - 1, p.y + 1, x))
              if (pointPresent(p.x - 2, p.y + 2, x))
                if (pointPresent(p.x - 3, p.y + 3, x))
                  if (pointPresent(p.x - 4, p.y + 4, x))
                    return new Point[] { p, new Point(p.x - 4, p.y + 4) };
          }
      }
    return null;
  }

  /**
   * Called when the "end of the game" situation is detected.
   */
  public void drawFinishLine(int xa, int ya, int xb, int yb, Graphics g)
  {
    g.setColor(Color.blue);

    int hW = W / 2;
    g.drawLine(xa * W + hW, ya * W + hW, xb * W + hW, yb * W + hW);
  }

  /**
   * Check for the presence of the given point in the collection.
   */
  public final boolean pointPresent(int x, int y, Collection in)
  {
    Iterator iter = in.iterator();
    Point p;
    while (iter.hasNext())
      {
        p = (Point) iter.next();
        if (p.x == x && p.y == y)
          return true;
      }
    return false;
  }

  public void drawGrid(int w, int h, Graphics g)
  {
    g.setColor(Color.lightGray);

    // Draw vertical lines:
    for (int x = 0; x < w; x += W)
      {
        g.drawLine(x, 0, x, h);
      }

    // Draw horizontal lines:
    for (int y = 0; y < h; y += W)
      {
        g.drawLine(0, y, w, y);
      }

    g.setColor(Color.gray);
    g.drawRect(0,0, frame.DESK_SIZE.width, frame.DESK_SIZE.height);
    g.drawRect(0,0, frame.DESK_SIZE.width+3, frame.DESK_SIZE.height+3);
  }

  public void drawFigures(Graphics g)
  {
    g.setColor(Color.red);
    drawDots(reds, g, RED);

    g.setColor(Color.black);
    drawDots(blacks, g, BLACK);

    g.setColor(Color.lightGray);
    drawDots(hints, g, HINT);

    if (endOfGame != null)
      drawFinishLine(endOfGame[0].x, endOfGame[0].y, endOfGame[1].x,
        endOfGame[1].y, g);
  }

  public Point makePoint(int x, int y)
  {
    return new Point(x / W, y / W);
  }

  /**
   * Draw a collection of dots (the collor must be set before calling the
   * method).
   */
  public void drawDots(Collection dots, Graphics g, int mode)
  {
    Iterator iter = dots.iterator();
    int x;
    int y;

    int hW = W / 2;
    int RR = R * 2;
    int hR = R / 2;
    Point p;
    while (iter.hasNext())
      {
        p = (Point) iter.next();
        x = p.x * W + hW;
        y = p.y * W + hW;

        if (mode == RED)
          g.drawOval(x - R, y - R, RR, RR);
        else if (mode == BLACK)
          {
            g.drawLine(x - R, y - R, x + R, y + R);
            g.drawLine(x - R, y + R, x + R, y - R);
          }
        else
          {
            // Hint.
            g.drawOval(x - hR, y - hR, R, R);
          }
      }
  }

  private void jbInit()
    throws Exception
  {
    addMouseListener(this);
  }

  public void mouseClicked(MouseEvent e)
  {
    try
      {
        int state = player.get_current_state();

        // Check if the state is correct.
        if (state == I_WAIT_FOR_YOUR_MOVE)
          {
            frame.talk(Color.black,
              "It is now time for our partner's move, not ours. Please wait.");
          }
        else if (state == DISCONNECTED)
          {
            frame.talk(Color.black,
              "We are not connected to the playing partner yet.");
          }
        else if (state == I_HAVE_LOST)
          {
            frame.talk(Color.black,
              "We have already lost this battle, but why not to try again?");
          }
        else if (state == I_HAVE_WON)
          {
            frame.talk(Color.black,
              "The victory is ours, nothing more to do here.");
          }
        else if (player.partner == null)
          frame.talk(Color.black, "No other player so far.");
        else
          {
            int x = e.getX();
            int y = e.getY();

            if (x>frame.DESK_SIZE.width ||
                y>frame.DESK_SIZE.height)
            {
              frame.talk(Color.black,"Outside the game area.");
              return;
            }

            Point p = makePoint(x, y);

            // Ignore clicks on the occupied cells.
            if (pointPresent(p.x, p.y, reds)
              || (pointPresent(p.x, p.y, blacks)))
              {
                frame.talk(Color.black,
                  "This is against the rules, select the unoccupied cell.");
                return;
              }

            reds.add(p);

            endOfGame = checkFinished(reds, p);
            repaint();

            if (endOfGame != null)
              {
                frame.talk(Color.red, "Our move " + p.x + "-" + p.y
                  + " and we win!");
                player.set_current_state(I_HAVE_WON);
              }
            else
              {
                frame.talk(Color.black, "Our move " + p.x + "-" + p.y
                  + ". Waiting for the other side move...");
                player.set_current_state(I_WAIT_FOR_YOUR_MOVE);
              }

            player.partner.receive_move(p.x, p.y, endOfGame);
          }
      }
    catch (RemoteException ex)
      {
        // We will print the exception because this is a demo application
        // that may be modified for learning purposes.
        ex.printStackTrace();
      }
  }

  /**
   * Handle the move of the other playing side.
   */
  public void friendsMove(int x, int y, Point[] victory)
  {
    try
      {
        int state = player.get_current_state();
        if (state != I_WAIT_FOR_YOUR_MOVE || pointPresent(x, y, blacks))
          {
            stateFailed("Move " + x + "-" + y);
          }
        else
          {
            blacks.add(new Point(x, y));

            if (victory != null)
              {
                frame.talk(Color.red,
                  " We have lost this time, unfortunately..");
                player.set_current_state(I_HAVE_LOST);
                endOfGame = victory;
              }
            else
              {
                frame.talk(Color.black, "Partner goes " + x + "-" + y
                  + ". Your move?");
                player.set_current_state(I_THINK);
              }
            repaint();
          }
      }
    catch (RemoteException rex)
      {
        rex.printStackTrace();
      }
  }

  /**
   * Prepare for the new game.
   */
  public void reset()
  {
    blacks.clear();
    reds.clear();
    hints.clear();
    endOfGame = null;
    repaint();
  }

  public void mouseEntered(MouseEvent m)
  {
    // Nothing to do.
  }

  public void mousePressed(MouseEvent m)
  {
    // Nothing to do.
  }

  public void mouseReleased(MouseEvent m)
  {
    // Nothing to do.
  }

  public void mouseExited(MouseEvent m)
  {
    // Nothing to do.
  }

  /**
   * The systems detected the error conditions. The game cannot continue (the
   * chat is still possible).
   */
  public void stateFailed(String reason)
  {
    try
      {
        player.receive_chat(ChatConstants.REMOTE_PLAYER,
          "Wrong move, game cannot continue (our state was "
            + player.get_current_state() + ")");
        frame.talk(Color.red, "The remote side violates communicating rules.");
        player.set_current_state(State.ERROR);
      }
    catch (RemoteException ex)
      {
        // We will print the exception because this is a demo application
        // that may be modified for learning purposes.
        ex.printStackTrace();
      }
  }
}
