// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid;

import java.awt.*;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.LinkedList;

/**
 *  Generic game object in Asteroid game.
 *
 *  This class is part of a solution for a
 *  <a href="http://www.heise.de/ct/creativ/08/02/details/">competition by the German computer magazine c't</a>
 */
public abstract class GameObject
        implements GameData,
                   PropertyProvider,
                   Drawable
{
  /** The x coordinate. */
  protected final int x;
  /** The y coordinate (y is point upwards). */
  protected final int y;
  /** The correction for x. */
  private double correctionX = 0;
  /** The correction for y. */
  private double correctionY = 0;

  /**
   *  Constructor.
   *  @param x x coordinate
   *  @param y y coordinate (points upwards in game)
   */
  public GameObject(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

  /**
   *  Get the x coordinate of the objects center.
   *  @return center x
   */
  public int getX()
  {
    return x;
  }

  /**
   *  Get the y coordinate of the objects center.
   *  @return center y
   */
  public int getY()
  {
    return y;
  }

  /**
   *  Get the possibly corrected x coordinate.
   *  @return corrected x
   */
  public double getCorrectedX()
  {
    return x + correctionX;
  }

  /**
   *  Get the possibly corrected x coordinate.
   *  @return corrected x
   */
  public double getCorrectedY()
  {
    return y + correctionY;
  }

  /**
   *  Get the squared length of the shortest vector from this to another game object.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param obj other object
   *  @return squared length of shortest vector from this to the other object
   */
  public int getSquaredDistance(GameObject obj)
  {
    return getSquaredDistance(obj.getX(), obj.getY());
  }

  /**
   *  Get the squared length of the shortest vector from this objects position to another position.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param px other position's x
   *  @param py other position's y
   *  @return squared length of shortest vector from this object to the other position
   */
  public int getSquaredDistance(int px, int py)
  {
    int dx = px - getX();
    if (dx < -EXTENT_X/2) {
      dx += EXTENT_X;
    }
    else if (dx >= EXTENT_X/2) {
      dx -= EXTENT_X;
    }
    int dy = py - getY();
    if (dy < -EXTENT_Y/2) {
      dy += EXTENT_Y;
    }
    else if (dy >= EXTENT_Y/2) {
      dy -= EXTENT_Y;
    }
    return dx*dx + dy*dy;
  }

  /**
   *  Access method for visitor pattern.
   *  Has to be overwritten in each concrete implementation with the following code:
   *  <pre>
   *  public void visitedBy(GameObjectVisitor visitor)
   *  {
   *    visitor.handle(this);
   *  }
   *  </pre>
   *  @param visitor visitor
   */
  public abstract void visitedBy(GameObjectVisitor visitor);

  /**
   *  Get the squared length of the shortest vector from this objects position to another position.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param px other position's x
   *  @param py other position's y
   *  @return squared length of shortest vector from this object to the other position
   */
  public double getSquaredDistance(double px, double py)
  {
    double dx = normalizeDeltaX(px - getCorrectedX());
    double dy = normalizeDeltaY(py - getCorrectedY());
    return dx*dx + dy*dy;
  }

  /**
   *  Normalize a component.
   *  @param v      component
   *  @param extent extent
   *  @return a value mapped to <code>-extent/2 &lt;= value &lt;extent/2</code>
   */
  private static int normalizeDelta(int v, int extent)
  {
    while (v < -extent/2) {
      v += extent;
    }
    while (v >= extent/2) {
      v -= extent;
    }
    return v;
  }

  /**
   *  Normalize a x vector component.
   *  @param x component
   *  @return restricted value
   */
  public static int normalizeDeltaX(int x)
  {
    return normalizeDelta(x, EXTENT_X);
  }

  /**
   *  Normalize a y vector component.
   *  @param y component
   *  @return restricted value
   */
  public static int normalizeDeltaY(int y)
  {
    return normalizeDelta(y, EXTENT_Y);
  }

  /**
   *  Normalize a component.
   *  @param v      component
   *  @param extent extent
   *  @return a value mapped to <code>-extent/2 &lt;= value &lt;extent/2</code>
   */
  private static double normalizeDelta(double v, double extent)
  {
    while (v < -extent/2) {
      v += extent;
    }
    while (v >= extent/2) {
      v -= extent;
    }
    return v;
  }

  /**
   *  Normalize a x vector component.
   *  @param x component
   *  @return restricted value
   */
  public static double normalizeDeltaX(double x)
  {
    return normalizeDelta(x, EXTENT_X);
  }

  /**
   *  Normalize a y vector component.
   *  @param y component
   *  @return restricted value
   */
  public static double normalizeDeltaY(double y)
  {
    return normalizeDelta(y, EXTENT_Y);
  }

  /**
   *  Is this object overlapping with a given rectangle?
   *  @param rect rectangle
   *  @return the answer
   */
  public boolean isOverlappingWith(Rectangle rect)
  {
    Rectangle bounds = getBounds();
    return rect.intersects(bounds);
  }

  /**
   *  Get the bounding box of this rectangle.
   *  @return the bounding box
   */
  public abstract Rectangle getBounds();

  /**
   *  Get bounds for picking operations.
   *  @return picking bounds
   */
  public Rectangle getPickBounds()
  {
    return getBounds();
  }

  /**
   *  Correct the location.
   *  @param dx correction offset in x
   *  @param dy correction offset in y
   */
  public void correctLocation(double dx, double dy)
  {
    correctionX = dx;
    correctionY = dy;
  }

  /**
   *  Get the properties of this object.
   *  @return collection of properties
   */
  public Collection<Property> getProperties()
  {
    Collection<Property> props = new LinkedList<Property>(); // NOTE: linked list so sub classes can easily add
    props.add(new Property<String>("Type", getObjectType()));
    props.add(new Property<Point>("Position", getLocation()));
    props.add(new Property<Point2D>("Corrected Position", getCorrectedLocation()));
    return props;
  }

  /**
   *  Get the location of the object.
   *  @return object location
   */
  public Point getLocation()
  {
    return new Point(getX(), getY());
  }

  /**
   *  Get the possibly corrected location of the object.
   *  @return corrected object location
   */
  public Point2D getCorrectedLocation()
  {
    return new Point2D.Double(getCorrectedX(), getCorrectedY());
  }

  /**
   *  Get the type of game object.
   *  @return game object type
   */
  public abstract String getObjectType();

  /**
   *  Get the shortest vector from this to another game object.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param obj other object
   *  @return shortest vector from this to the other object
   */
  public Point getDelta(GameObject obj)
  {
    return getDelta(obj.getX(), obj.getY());
  }

  public Point2D getCorrectedDelta(GameObject obj)
  {
    return getDelta(obj.getCorrectedX(), obj.getCorrectedY());
  }

  /**
   *  Get the shortest vector from this object to a given point.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param p point
   *  @return shortest vector from this object's location to the point
   */
  public Point getDelta(Point p)
  {
    return getDelta(p.x, p.y);
  }

  /**
   *  Get the shortest vector from this object to a given point.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param px point's x coordinate
   *  @param py point's y coordinate
   *  @return shortest vector from this object's location to the point
   */
  public Point getDelta(int px, int py)
  {
    return getTorusDelta(px, py, getX(), getY());
  }

  /**
   *  Get the shortest vector from this object to a given point.
   *
   *  Because of the torus topology it may be shorter going across the borders.
   *  @param px point's x coordinate
   *  @param py point's y coordinate
   *  @return shortest vector from this object's location to the point
   */
  public Point2D getDelta(double px, double py)
  {
    return getTorusDelta(px, py, getCorrectedX(), getCorrectedY());
  }

  /**
   *  Get the shortest vector between two points taking care of the torus topology.
   *  @param p1 first point
   *  @param p2 second point
   *  @return delta vector pointing from point 2 to point 1
   */
  public static Point getTorusDelta(Point p1, Point p2)
  {
    return getTorusDelta(p1.x, p1.y, p2.x, p2.y);
  }

  /**
   *  Get the shortest vector between two points taking care of the torus topology.
   *  @param p1 first point
   *  @param p2 second point
   *  @return delta vector pointing from point 2 to point 1
   */
  public static Point2D getTorusDelta(Point2D p1, Point2D p2)
  {
    return getTorusDelta(p1.getX(), p1.getY(), p2.getX(), p2.getY());
  }

  /**
   *  Get the shortest vector between two points taking care of the torus topology.
   *  @param p1x first point's x
   *  @param p1y first point's y
   *  @param p2x second point's x
   *  @param p2y second point's y
   *  @return delta vector pointing from point 2 to point 1
   */
  public static Point getTorusDelta(int p1x, int p1y, int p2x, int p2y)
  {
    int dx = p1x - p2x;
    if (dx < -EXTENT_X/2) {
      dx += EXTENT_X;
    }
    else if (dx >= EXTENT_X/2) {
      dx -= EXTENT_X;
    }
    int dy = p1y - p2y;
    if (dy < -EXTENT_Y/2) {
      dy += EXTENT_Y;
    }
    else if (dy >= EXTENT_Y/2) {
      dy -= EXTENT_Y;
    }
    return new Point(dx, dy);
  }

  /**
   *  Get the shortest vector between two points taking care of the torus topology.
   *  @param p1x first point's x
   *  @param p1y first point's y
   *  @param p2x second point's x
   *  @param p2y second point's y
   *  @return delta vector
   */
  public static Point2D getTorusDelta(double p1x, double p1y, double p2x, double p2y)
  {
    double dx = p1x - p2x;
    if (dx < -EXTENT_X/2) {
      dx += EXTENT_X;
    }
    else if (dx >= EXTENT_X/2) {
      dx -= EXTENT_X;
    }
    double dy = p1y - p2y;
    if (dy < -EXTENT_Y/2) {
      dy += EXTENT_Y;
    }
    else if (dy >= EXTENT_Y/2) {
      dy -= EXTENT_Y;
    }
    return new Point2D.Double(dx, dy);
  }
}
