/**
 * Copyright 2001 Jean-Francois Doue
 * 
 * This file is part of Asteroid Zone. Asteroid Zone 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 of the License, or (at your option) any later version.
 * Asteroid Zone 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 Asteroid Zone; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 */

package asteroids;

import javax.microedition.lcdui.*;
import java.util.*;

/**
 * Class to implement an asteroid. Asteroids come in three sizes.
 * @author Jean-Francois Doue
 * @version 1.2, 2001/10/24
 */
public class Asteroid extends Mobile {
    /**
     * A size constant for small asteroids.
     */
    public static final byte SIZE_SMALL = 0;

    /**
     * A size constant for medium asteroids.
     */
    public static final byte SIZE_MEDIUM = 1;

    /**
     * A size constant for large asteroids.
     */
    public static final byte SIZE_LARGE = 2;

    /**
     * The x coordinates of the asteroid's vertices
     */
    public static byte[][] xcoords;

    /**
     * The y coordinates of the asteroid's vertices
     */
    public static byte[][] ycoords;

    /**
     * The radius of the asteroid.
     */
    public static byte[] radii;

    /**
     * The diameter of the asteroid.
     */
    public static int[] diameters;


    /**
     * Scan converted asteroids used for collision detection.
     */
    public static boolean[][][] masks;

    /**
     * The asteroids currently existing in the game.
     */
    public static Pool asteroids;

    private byte _angle;                  // Orientation of the asteroid.
    private byte _size;                   // Small, medium or large.
    private static byte[] _value = {5, 2, 1};   // Points earned when shot.
    private static final byte _FIELD_TOP = 0;
    private static final byte _FIELD_BOTTOM = 1;
    private static final byte _FIELD_LEFT = 2;
    private static final byte _FIELD_RIGHT = 3;


    static {
        // Create and populate the asteroid pool.
        Asteroid[] array = new Asteroid[20];
        for (int i = array.length - 1; i >= 0; i--) {
            array[i] = new Asteroid();
        }
        asteroids = new Pool(array);

        final int[] allRadii = {3, 5, 8};
        final byte[][] allAngles = {
          {3, 11, 19, 25, 3},       // small
          {2, 7, 17, 19, 26, 2},    // medium
          {1, 4, 9, 15, 21, 23, 29, 1} // large
        };
        radii = new byte[3];
        diameters = new int[3];
        xcoords = new byte[3][];
        ycoords = new byte[3][];
        masks = new boolean[3][][];
        for (int i = SIZE_SMALL; i <= SIZE_LARGE; i++) {
         // Precompute the asteroid vertices.
            radii[i] = (byte)(allRadii[i] * ratioNum / ratioDenom);
            byte[] angles = allAngles[i];
            byte[] pointx = new byte[angles.length];
            xcoords[i] = pointx;
            byte[] pointy = new byte[angles.length];
            ycoords[i] = pointy;
            for (int j = angles.length - 1; j >= 0; j--) {
                pointx[j] = (byte)((radii[i] * Mobile.cos[angles[j]]) >> 6);
                pointy[j] = (byte)((radii[i] * Mobile.sin[angles[j]]) >> 6);
            }

         // Scan convert the asteroid polygon.        
            diameters[i] = radii[i] << 1;
            masks[i] = new boolean[diameters[i]][];
            for (int j = 0; j < diameters[i]; j++) {
               masks[i][j] = new boolean[diameters[i]];
            }
            Geometry.scanConvertPolygon(masks[i], pointx, pointy);
        }
     }

    /**
     * Initializes an Asteroid instance to make it large, positionned
     * on a screen boundary and going in the direction of opposite boundary.
     */
    public static void randomInit(Asteroid asteroid) {
        int x = 0, y = 0;
        byte angle = 0;
        switch(Math.abs(Game.random.nextInt()) % 4) {
            case _FIELD_TOP:
                y = 1;
                x = Math.abs(Game.random.nextInt()) % width;
                angle = (byte)(15 + Math.abs(Game.random.nextInt()) % 15);
                break;
            case _FIELD_LEFT:
                x = 1;
                y =  Math.abs(Game.random.nextInt()) % height;
                angle = (byte)((23 + Math.abs(Game.random.nextInt()) % 15) % 32);
                break;
            case _FIELD_RIGHT:
                x = width - 1;
                y =  Math.abs(Game.random.nextInt()) % height;
                angle = (byte)(7 + Math.abs(Game.random.nextInt()) % 15);
                break;
            case _FIELD_BOTTOM:
                y = height - 1;
                x = Math.abs(Game.random.nextInt()) % width;
                angle = (byte)(Math.abs(Game.random.nextInt()) % 15);
                break;
        }
        asteroid.init(x, y, angle, SIZE_LARGE);
    }

    /**
     * Initializes a Asteroid instance by setting its position, angle
     * and size.
     */
    public final void init(int x, int y, byte angle, byte size) {
        _angle = angle;
        _size = size;
        moveTo(x, y);
        setVelocity(cos[angle] << 2, sin[angle] << 2);
    }

    public Asteroid() {
    }

    /**
     * Move the asteroid to its next position.
     */
    public static final void move() {
      for (int i = 0; i < asteroids.count; i++) {
            Asteroid a = (Asteroid)asteroids.pool[i];
           a._x += a.vx;
           a._y += a.vy;
           a.x = a._x >> 8;
           a.y = a._y >> 8;

           // If a border has been hit, wrap the trajectory around
           // the screen. The new origin is the projection of the
           // intersection point on the opposite border.
           if (a.x <= 0) {
               a.moveTo(width - 2, a.y);
           } else if (a.x >= width - 1) {
               a.moveTo(1, a.y);
           } else if (a.y <= 0) {
               a.moveTo(a.x, height - 2);
           } else if (a.y >= height - 1) {
               a.moveTo(a.x, 1);
           }
        }
    }

    /**
     * Compute collisions between the asteroids and
     * the ship and the rockets.
     */
     public static final void collisionDetection() {
      Ship ship = Ship.ship;
      Field field = Game.field;

      // Collision detection makes sense only while the game is
      // being played and the ship is alive.
        if ((field != null) && (field.getState() == Field.GAME_STATE) && (ship.isAlive)) {

         int score = field.getScore();
         int newScore = score;
         Pool rockets = Rocket.rockets;
         Pool explosions = Explosion.explosions;

         for (asteroids.current = asteroids.count - 1; asteroids.current >= 0;) {
            Asteroid a = (Asteroid)asteroids.pool[asteroids.current--];

            // Detect ship - asteroid collisions.
            int offset = ship.angle << 2;
            for (int i = 0; i < 4; i++) {
                 int sx = ship.x + Ship.xcoords[offset + i] - a.x + radii[a._size];
                 if ((sx >= 0) && (sx < diameters[a._size])) {
                  int sy = ship.y + Ship.ycoords[offset + i] - a.y + radii[a._size];
                  if ((sy >= 0) && (sy < diameters[a._size])) {

                     // Use the mask for collision detection
                     if (masks[a._size][sx][sy]) {
                              int lives = field.getLives();
                             if ((lives > 0) && (ship.isAlive)) {
                                 ship.explode();
                                 field.setLives(lives - 1);
                             }
                     }
                  }
                 }
            }


            // Detect asteroid - rocket collisions.
            for (rockets.current = rockets.count - 1; rockets.current >= 0;) {
               Rocket r = (Rocket)rockets.pool[rockets.current--];

               // Transform the r into asteroid coordinates.
                 int rx = r.x - a.x + radii[a._size];
                 if ((rx >= 0) && (rx < diameters[a._size])) {
                  int ry = r.y - a.y + radii[a._size];
                  if ((ry >= 0) && (ry < diameters[a._size])) {

                     // Use the mask for collision detection
                     if (masks[a._size][rx][ry]) {
                               asteroids.removeCurrent();
                               rockets.removeCurrent();
                               newScore += _value[a._size];
                               a.split(r);
                               Explosion explosion = (Explosion)explosions.addNewObject();
                               if (explosion != null) {
                                   explosion.init(r.x, r.y);
                               }
                               break;
                     }
                    }
                 }
                }
            }
            if (newScore != score) {
                field.setScore(newScore);
            }
        }
    }

    /**
     * Draws an asteroid of the specified size and location
     * in the specified graphic context.
     */
    public static final void draw(byte size, int xpos, int ypos, Graphics g) {
        byte[] xcoord = xcoords[size];
        byte[] ycoord = ycoords[size];
        for (int i = 0; i < xcoord.length - 1; i++) {
             g.drawLine(xcoord[i] + xpos, ycoord[i] + ypos, xcoord[i + 1] + xpos, ycoord[i + 1] + ypos);
         }
    }


    /**
     * Draws all the asteroids of the supplied object pool using the
     *  specified graphic context.
     */
    static public final void draw(Graphics g) {
      for (int i = 0; i < asteroids.count; i++) {
         Asteroid a = (Asteroid)asteroids.pool[i];
           byte[] xcoord = xcoords[a._size];
           byte[] ycoord = ycoords[a._size];
         for (int j = 0; j < xcoord.length - 1; j++) {
            g.drawLine(
               xcoord[j] + a.x,
               ycoord[j] + a.y,
               xcoord[j + 1] + a.x,
               ycoord[j + 1] + a.y);
         }
        }
    }


    /**
     * Splits the asteroid in two smaller pieces and set their
     * direction depending on the asteroid direction and the hitting
     * rocket direction. The pieces are added to the supplied collection.
     */
    public final void split(Rocket rocket) {
        if (_size >= SIZE_MEDIUM) {
            byte newSize = (byte)(_size - 1);
            int angle = (cos[_angle] * sin[rocket.angle] - cos[rocket.angle] * sin[_angle]) >> 10;
            byte angle1 = (byte)(_angle + angle + Math.abs(Game.random.nextInt()) % 5);
            if (angle1 < 0) {
                angle1 += 32;
            }
            if (angle1 >= 32) {
                angle1 -= 32;
            }
            byte angle2 = (byte)(_angle + angle - Math.abs(Game.random.nextInt()) % 5);
            if (angle2 < 0) {
                angle2 += 32;
            }
            if (angle2 >= 32) {
                angle2 -= 32;
            }
            Asteroid a = (Asteroid)asteroids.addNewObject();
            if (a != null) {
                a.init(x, y, angle1, newSize);
            }
            a = (Asteroid)asteroids.addNewObject();
            if (a != null) {
                a.init(x, y, angle2, newSize);
            }
        }
    }
}