/*
 * PhotonQueue.cs
 *
 * This class implements a "particle system" that manages the photons fired
 * by either ship in the game.  When a ship fires a photon, it adds it to this
 * particle system.  The particle system is responsible for moving (and drawing)
 * the photon particle.  It also keeps track of the age of the photon.  Photons
 * that are too old are deleted, so that they are not bouncing about the game
 * forever.
 *
 * The PhotonQueue is exactly what it sounds like: a queue. In this implementation
 * we use the circular array implementation of a queue (which you may have learned
 * in CS 2110).  Why do we do this when C# has perfectly good Collection classes?
 * Because in game programming it is considered bad form to have "new" statements
 * in an update or a graphics loop if you can easily avoid it.  Each "new" is
 * a potentially expensive memory allocation.  It is (often) much better to
 * allocate all the memory that you need at start-up, so that all you do is
 * assign variables during game time. If you notice, all the Photon objects
 * are declared and initialized in the constructor; we just reassign the fields
 *
 * Based on original GameX Ship Demo by Rama C. Hoetzlein, 2002
 *
 * @author: Walker M. White
 * @date: 1/10/25
 */
package edu.cornell.cis3152.shipdemo;

import com.badlogic.gdx.math.*;
import com.badlogic.gdx.graphics.*;
import edu.cornell.gdiac.graphics.SpriteBatch;

/**
 * Model class representing an "particle system" of photons.
 *
 * Note that the graphics resources in this class are static. That is because
 * all photons share the same image file, and it would waste memory to load the
 * same image file for each photon.
 */
public class PhotonQueue {
    // Private constants to avoid use of "magic numbers"
	/** Fixed velocity for a photon */
	private static final float PHOTON_VELOCITY = 5.0f;
	/** Number of animation frames a photon lives before deleted */
	private static final int MAX_AGE = 48;
	/** Maximum number of photons allowed on screen at a time. */
	private static final int MAX_PHOTONS = 512;

	/** Graphic asset representing a single photon. */
	private static Texture texture;
    /** The drawing transform */
    private Affine2 transform = new Affine2();

	// QUEUE DATA STRUCTURES
	/** Array implementation of a circular queue. */
	protected Photon[] queue;
	/** Index of head element in the queue */
	protected int head;
	/** Index of tail element in the queue */
	protected int tail;
	/** Number of elements currently in the queue */
	protected int size;

	/**
	 * An inner class that represents a single Photon.
	 *
	 * To count down on memory references, the photon is "flattened" so that
	 * it contains no other objects.
     */
	public class Photon {
		/** X-coordinate of photon position */
		public float x;
		/** Y-coordinate of photon position */
		public float y;
		/** X-coordinate of photon velocity */
		public float vx;
		/** X-coordinate of photon velocity */
		public float vy;
		/** Age for the photon in frames (for decay) */
		public int age;

		/**
		 * Creates a new empty photon with age -1.
		 *
		 * Photons created this way "do not exist". This constructor is
		 * solely for pre-allocation.  To actually use a photon, use the
		 * allocate() method.
		 */
		public Photon() {
			this.x  = 0.0f; this.y  = 0.0f;
			this.vx = 0.0f; this.vy = 0.0f;
			this.age = -1;
        }

		/**
		 * Allocates a photon by setting its position and velocity.
		 *
		 * A newly allocated photon starts with age 0.
		 *
		 * @param x  The x-coordinate of the position
		 * @param y  The y-coordinate of the position
		 * @param vx The x-coordinate of the velocity
		 * @param vy The y-coordinate of the velocity
		 */
		public void allocate(float x, float y, float vx, float vy) {
			this.x  = x;  this.y  = y;
			this.vx = vx; this.vy = vy;
			this.age = 0;
		}

		/**
		 * Moves the photon within the given boundary.
		 *
		 * If the photon goes outside of bounds, it bounces off the edge.
		 * This method also advances the age of the photon.
		 */
		public void move(Rectangle bounds) {
			x += vx;
			y += vy;

            // Bounce photons off the left/right walls if they hit.
            if (x <= bounds.x) {
            	vx = -vx;
            	x  = bounds.x;
            } else if (x >= bounds.width) {
            	vx = -vx;
            	x  = bounds.width - 1.0f;
            }

            // Bounce photons off the top/bottom walls if they hit.
            if (y <= bounds.y) {
            	vy = -vy;
            	y  = bounds.y;
            } else if (y >= bounds.height) {
            	vy = -vy;
            	y  = bounds.height - 1.0f;
            }

            // Finally, advance the age of the photon.
            age++;
		}
	}

	/**
	 *  Constructs a new (empty) PhotonQueue
	 */
	public PhotonQueue() {
		// Construct the queue.
		queue = new Photon[MAX_PHOTONS];

        head = 0;
        tail = -1;
        size = 0;

        // "Predeclare" all the photons for efficiency
        for (int ii = 0; ii < MAX_PHOTONS; ii++) {
        	queue[ii] = new Photon();
        }
	}

	/**
	 * Returns the image for a single photon; reused by all photons.
	 *
     * This value should be loaded by the GameScene and set there. However, we
     * have to be prepared for this to be null at all times
     *
	 * @return the image for a single photon; reused by all photons.
	 */
	public Texture getTexture() {
		return texture;
	}

	/**
	 * Sets the image for a single photon; reused by all photons.
	 *
     * This value should be loaded by the GameScene and set there. However, we
     * have to be prepared for this to be null at all times
     *
	 * @param value the image for a single photon; reused by all photons.
	 */
	public void setTexture(Texture value) {
		texture = value;
	}

	/**
	 * Adds a photon to the active queue.
	 *
	 * When adding a photon, we assume that it is fired from a ship with the
	 * given position, velocity, and facing (angle).  We could have a general
	 * photon adding function, but this will make refactoring easier in
	 * Exercise 9.
	 *
	 * As all Photons are predeclared, this involves moving the head and the
	 * tail, and reseting the values of the object in place. This is a simple
	 * implementation of a memory pool. It works because we delete objects in
	 * the same order that we allocate them.
	 *
	 * @param position  The velocity of the ship firing the photon
	 * @param velocity  The position of the ship firing the photon
	 * @param angle		The facing of the ship firing the photon
	 */
	public void addPhoton(Vector2 position, Vector2 velocity, float angle) {
		// Determine direction and velocity of the photon.
		float fire_x = velocity.x + (float)Math.cos(Math.toRadians(angle))  * PHOTON_VELOCITY;
		float fire_y = velocity.y + (float)-Math.sin(Math.toRadians(angle)) * PHOTON_VELOCITY;

		// Check if any room in queue.
		// If maximum is reached, remove the oldest photon.
        if (size == MAX_PHOTONS) {
        	head = ((head + 1) % MAX_PHOTONS);
        	size--;
        }

        // Add a new photon at the end.
        // Already declared, so just initialize.
        tail = ((tail + 1) % MAX_PHOTONS);
        queue[tail].allocate(position.x,position.y,fire_x,fire_y);
        size++;
	}

	/**
	 * Moves all the photons in the active queu.
	 *
	 * Each photon is advanced according to its velocity. Photons out of bounds
	 * are rebounded into view. Photons which are too old are deleted.
	 */
	public void move(Rectangle bounds) {
		// First, delete all old photons.
        // INVARIANT: Photons are in queue in decending age order.
        // That means we just remove the head until the photons are young enough.
        while (size > 0 && queue[head].age > MAX_AGE) {
        	// As photons are predeclared, all we have to do is move head forward.
            head = ((head + 1) % MAX_PHOTONS);
            size--;
        }

        // Now, step through each active photon in the queue.
        for (int ii = 0; ii < size; ii++) {
        	// Find the position of this photon.
            int idx = ((head+ii) % MAX_PHOTONS);

            // Move the photon according to velocity.
            queue[idx].move(bounds);
        }
	}

	/**
	 * Draws the photons to the sprite batch.
	 *
	 * You should NOT modify this method for Exercise 4. Instead, create a
	 * method in GameScene that calls this method several times.
     *
	 * This method uses additive blending, which is set before this method is
	 * called (in GameScene).
	 *
	 * @param batch The sprite batch to draw to.
	 */
	public void draw(SpriteBatch batch) {
		if (texture == null) {
			return;
		}

		// Get photon texture origin
		float ox = texture.getWidth()/2.0f;
		float oy = texture.getHeight()/2.0f;

		// Step through each active photon in the queue.
        for (int ii = 0; ii < size; ii++) {
        	// Find the position of this photon.
            int idx = ((head + ii) % MAX_PHOTONS);

            // How big to make the photon.  Decreases with age.
            float scale = 1.25f - (float)queue[idx].age * 0.5f / (float)MAX_AGE;

			// Use this information to draw.
            SpriteBatch.computeTransform( transform, ox, oy, queue[idx].x,queue[idx].y,0,scale,scale);
            batch.setColor( Color.WHITE );
            batch.draw( texture, transform );
        }
	}
}
