// ============================================================================
// 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.rammi;

import de.caff.asteroid.*;

/**
 *  Trying to see the future, basic implementation.
 */
public abstract class AbstractBasicFuturologist
        implements Futurologist,
                   GameData
{
  /** Identity for ufo. */
  protected static final Integer IDENT_UFO = -1;
  /** Identity for space ship. */
  protected static final Integer IDENT_SPACE_SHIP = -2;
  /** Minimal identity for bullet. */
  protected static final int IDENT_BULLET_OFFSET = Integer.MIN_VALUE;

  /** Identity mapper. */
  private static class IdentMapper
    implements GameObjectVisitor
  {
    /** The objects identity (or <code>null</code>). */
    private Integer ident;

    /**
     *  Get the identity for an object.
     *  @param obj object
     *  @return identity
     */
    Integer getIdent(MovingGameObject obj)
    {
      if (obj != null) {
        obj.visitedBy(this);
        return ident;
      }
      return null;
    }


    /**
     * Handle an asteroid.
     *
     * @param asteroid asteroid to handle
     */
    public void handle(Asteroid asteroid)
    {
      ident = asteroid.getIdentity();
    }

    /**
     * Handle a bullet.
     *
     * @param bullet bullet to handle
     */
    public void handle(Bullet bullet)
    {
      ident = bullet.getIdentity().intValue() + IDENT_BULLET_OFFSET;
    }

    /**
     * Handle an explosion.
     *
     * @param explosion explosion to handle
     */
    public void handle(Explosion explosion)
    {
      ident = null;
    }

    /**
     * Handle a ship explosion.
     *
     * @param explosion ship explosion to handle
     */
    public void handle(ShipExplosion explosion)
    {
      ident = null;
    }

    /**
     * Handle a space ship.
     *
     * @param ship space ship to handle
     */
    public void handle(SpaceShip ship)
    {
      ident = IDENT_SPACE_SHIP;
    }

    /**
     * Handle a text.
     *
     * @param text text to handle
     */
    public void handle(Text text)
    {
      ident = null;
    }

    /**
     * Handle an ufo.
     *
     * @param ufo ufo to handle
     */
    public void handle(Ufo ufo)
    {
      ident = IDENT_UFO;
    }
  }

  /** Identity mapper. */
  private IdentMapper identMapper = new IdentMapper();

  /**
   *  Calculate the number of frames when a bullet will hit an asteroid.
   *  @param bullet the bullet
   *  @param obj    object which is hit
   *  @return the number of frames or {@link java.lang.Integer#MAX_VALUE} if no hit occurs
   */
  protected static int calcHitTime(Bullet bullet, MovingGameObject obj)
  {
    int frames = AsteroidPlayer.getFramesUntilCollision(bullet, obj, Bullet.MIN_LIFETIME);
    if (frames > 0  &&  frames + bullet.getLifetime() < Bullet.MIN_LIFETIME) {
      return frames;
    }
    return Integer.MAX_VALUE;
  }

  /**
   *  Handle an incoming frame info.
   *  @param info frame info
   */
  protected void handleFrameInfo(FrameInfo info)
  {
    forgetEverything();
    Ufo ufo = info.getUfo();
    SpaceShip ship = info.getSpaceShip();
    for (Bullet bullet: info.getBullets()) {
      int hit = Integer.MAX_VALUE;
      Integer hitted = null;
      for (Asteroid ast: info.getAsteroids()) {
        int hitFrames = calcHitTime(bullet, ast);
        if (hitFrames < hit) {
          hit = hitFrames;
          hitted = ast.getIdentity();
        }
      }
      if (ufo != null) {
        int hitFrames = calcHitTime(bullet, ufo);
        if (hitFrames < hit) {
          hit = hitFrames;
          hitted = IDENT_UFO;
        }
      }
      if (ship != null) {
        int hitFrames = calcHitTime(bullet, ship);
        if (hitFrames < hit) {
          hit = hitFrames;
          hitted = IDENT_SPACE_SHIP;
        }
      }
      if (hitted != null) {
        addHit(hitted, info.getIndex() + hit);
        addHit(identMapper.getIdent(bullet), info.getIndex() + hit);
      }
    }
  }

  /**
   * Get the number of frames until a game object is destroyed if it doesn't alter its movements.
   *
   * @param obj       game object
   * @param frameInfo frame info of game object
   * @return number of frames till destruction or less than zero if frame is unknown
   */
  public int getFramesTillDestroyed(MovingGameObject obj, FrameInfo frameInfo)
  {
    return getFramesTillDestroyed(identMapper.getIdent(obj), frameInfo);
  }

  /**
   * Get the number of frames until a game object is destroyed if it doesn't alter its movements.
   *
   * @param ident     ident of game object
   * @param frameInfo frame info of game object
   * @return number of frames till destruction or less than zero if frame is unknown
   */
  protected abstract int getFramesTillDestroyed(Integer ident, FrameInfo frameInfo);

  /**
   *  Add a known hit for an asteroid.
   *  @param astIdent      asteroid ID
   *  @param frameCounter  frame counter when hit happens
   */
  protected abstract void addHit(Integer astIdent, int frameCounter);

  /**
   *  Forget all knowledge about any asteroids.
   */
  protected abstract void forgetEverything();
}
