// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid.rammi;

import de.caff.asteroid.*;
import de.caff.util.Tools;

import java.awt.geom.Point2D;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 *  Do not make a point.
 *  Do not use HYPERSPACE.
 */
public class KleinstplanetenschutzvereinPlayer
        extends AbstractBasicAsteroidPlayer
        implements FrameListener,
                   DrawableProvider,
                   GameData
{
  private static final int RADIUS = 20;
  private ShootingDirectionFixer shootingDirectionFixer;

  /**
   *  Default constructor.
   */
  public KleinstplanetenschutzvereinPlayer()
  {
    this(null);
  }

  /**
   *  Constructor.
   *  @param com  communication access
   */
  public KleinstplanetenschutzvereinPlayer(Communication com)
  {
    super(com);
    if (com != null) {
      shootingDirectionFixer = new ShootingDirectionFixer();
      com.setFramePreparer(new FramePreparerSequence(new ImprovedVelocityPreparer(),
                                                     new ScoreFixer(),
                                                     shootingDirectionFixer));
      com.addDatagramListener(shootingDirectionFixer);
    }
  }

  /**
   * Call this to cleanup any resources taken by this object.
   */
  public void destroy()
  {
    com.removeDatagramListener(shootingDirectionFixer);
  }

  /**
   * Called each time a frame is received.
   * <p/>
   * <b>ATTENTION:</b> this is called from the communication thread!
   * Implementing classes must be aware of this and take care by synchronization or similar!
   *
   * @param frame the received frame
   */
  public void frameReceived(FrameInfo frame)
  {
    SpaceShip ship = frame.getSpaceShip();
    if (ship != null) {
      SortedMap<Integer, List<MovingGameObject>> dangerous = getDangerousObjects(frame, ship);
      if (!dangerous.isEmpty()) {
        // System.out.println("danger: "+dangerous.size()+" (frames: "+dangerous.firstKey()+")");
        Integer frames = dangerous.firstKey();
        if (frames.intValue() < 4) {
          pushButton(BUTTON_HYPERSPACE);
        }
        else {
          double dirX = ship.getDirX();
          double dirY = ship.getDirY();
          double dirLen = Tools.getLength(dirX, dirY);
          MovingGameObject obj = dangerous.get(frames).get(0);
          Point delta = ship.getDelta(obj.getPredictedLocation(frames));
          double deltaLen = Tools.getLength(delta);
          double cross  = Tools.crossProduct(delta.x, delta.y, dirX, dirY)/(dirLen*deltaLen);
          double scalar = Tools.scalarProduct(delta.x, delta.y, dirX, dirY)/(dirLen*deltaLen);
          if (Math.abs(scalar) < 0.2) {
            pushButton(BUTTON_THRUST);
          }
          else {
            // turn more away
            if (cross < 0) {
              //System.out.println(scalar < 0 ? "LEFT1" : "RIGHT1");
              pushButton(scalar < 0 ? BUTTON_LEFT : BUTTON_RIGHT);
            }
            else {
              //System.out.println(scalar > 0 ? "LEFT2" : "RIGHT2");
              pushButton(scalar < 0 ? BUTTON_RIGHT : BUTTON_LEFT);
            }
          }
        }
      }
      else {
        Point2D averageVelocity = getAverageVelocity(frame.getAsteroids());
        double dvx = averageVelocity.getX() - ship.getVelocityX();
        double dvy = averageVelocity.getY() - ship.getVelocityY();
        if (dvx != 0  ||  dvy != 0) {
          double dvlen = Tools.getLength(dvx, dvy);
          double dirX = ship.getDirX();
          double dirY = ship.getDirY();
          double dirLen = Tools.getLength(dirX, dirY);
          double cross = Tools.crossProduct(dvx, dvy, dirX, dirY)/(dvlen*dirLen);
          if (Math.abs(cross) > 0.1) {
            pushButton(cross < 0 ? BUTTON_LEFT : BUTTON_RIGHT);
          }
          else {
            if (dvlen > 0.2) {
              pushButton(BUTTON_THRUST);
            }
          }
        }
      }
    }
  }

  private static SortedMap<Integer, List<MovingGameObject>> getDangerousObjects(FrameInfo frame, SpaceShip ship)
  {
    SortedMap<Integer, List<MovingGameObject>> dangerous = new TreeMap<Integer, List<MovingGameObject>>();
    checkCollisions(ship, new ArrayList<MovingGameObject>(frame.getBullets()), dangerous);
    checkCollisions(ship, new ArrayList<MovingGameObject>(frame.getAsteroids()), dangerous);
    if (frame.getUfo() != null) {
      List<MovingGameObject> ufoList = new ArrayList<MovingGameObject>(1);
      ufoList.add(frame.getUfo());
      checkCollisions(ship, ufoList, dangerous);
    }
    return dangerous;
  }

  private static void checkCollisions(SpaceShip ship,
                                      Collection<MovingGameObject> objects,
                                      SortedMap<Integer, List<MovingGameObject>> result)
  {
    final int maxCheck = 240;
    for (MovingGameObject obj: objects) {
      int frames = AsteroidPlayer.getFramesUntilCollision(ship, obj, maxCheck);
      if (frames > 0  &&  frames < maxCheck) {
        Integer iFrames = frames;
        List<MovingGameObject> entries = result.get(iFrames);
        if (entries == null) {
          entries = new LinkedList<MovingGameObject>();
          result.put(iFrames, entries);
        }
        entries.add(obj);
      }
    }
  }

  private static Point2D getAverageVelocity(Collection<Asteroid> asteroids)
  {
    double vx = 0;
    double vy = 0;
    for (Asteroid ast: asteroids) {
      vx += ast.getVelocityX();
      vy += ast.getVelocityY();
    }
    return asteroids.size() > 0  ?
            new Point2D.Double(vx/asteroids.size(),
                               vy/asteroids.size())  :
            new Point2D.Double();
  }

  /**
   * Draw information of a frame.
   *
   * @param g     graphics context
   * @param frame frame information
   */
  public void draw(Graphics2D g, FrameInfo frame)
  {
    SpaceShip ship = frame.getSpaceShip();
    if (ship != null) {
      SortedMap<Integer, List<MovingGameObject>> dangerous = getDangerousObjects(frame, ship);
      Color color = Color.red;
      g.setStroke(new BasicStroke(2));
      for (List<MovingGameObject> objList: dangerous.values()) {
        g.setColor(color);
        color = color.brighter();
        for (MovingGameObject obj: objList) {
          g.drawOval(obj.getX() - RADIUS,
                     obj.getY() - RADIUS,
                     2*RADIUS, 2*RADIUS);
        }
      }
    }
  }
}
