package Ragedemo01;

import processing.core.*;

/**
 * Draws a splash of boxes on the parent programs' screen. Intended for background use, 
 * so there's some distance between the boxes and the 'camera'.
 * 
 * Usage examples
 * ----------------
 *
 * this one draws 500 yellow wireframe boxes in an one-shot manner:
 * -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 * myBoxEffect = new RageBoxes(this, 500, -10.0f, false, false, true, 255, new int[]{255,255,0});
 * myBoxEffect.draw();
 * 
 * 
 * and this one draws 1000 colorful solid boxes with black edges in a looping manner, far away from the screen:  (can get slow on old machines)
 * -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 * myBoxEffect = new RageBoxes(this, 1000, -1000.0f, true, true, true, 255, new int[]{0,0,0});
 * myBoxEffect.draw();
 * 
 * 
 * @version 004
 * @author RageManifesto
 * 
 * 
 * TODO for next versions
 * ----------------------
 *   - possibility to change boxes' looks on the fly
 *   - possibility to define the spawning area and movement direction for the boxes
 *   - possibility to use some other shapes, too
 *   - no hard-coded screen size (1280 x 720)
 * 
 */
public class RageBoxes extends PApplet{

	private PApplet parent;
	private Item[] items;
	private float nextDistance;
	
	
	private class Item {
		
		private PApplet parent;
		private float a;
		private float xspeed;
		private float yspeed;
		private float xpos;
		private float ypos;
		private float distance;
		private float[] boxMeasures;
		private int[] color_rgb;
		private float rotationModifier;
		private int alpha;
		private boolean isRespawning;
		private boolean isFilled;
		private boolean isStroke;
		private int[] strokeColor_rgb;
		
		
		public Item(PApplet p,
				float distance,
				boolean isLooping,
				boolean isFilled,
				boolean isStroke,
				int alpha,
				int[] strokeColor_rgb) {
			
			this.parent = p;
			this.strokeColor_rgb = strokeColor_rgb;
			this.a = parent.random(0.0f, 25.0f);
			this.xspeed = parent.random(-5.0f, 25.0f);
			this.yspeed = parent.random(12.0f, 25.0f);
			this.distance = distance;
			this.xpos = parent.random(-400f, 800f) + (distance * 0.8f);
			this.ypos = parent.random(-1000f, -800f) + (distance * 0.8f);
			this.boxMeasures = new float[]{parent.random(30f, 150f), parent.random(30f, 150f), parent.random(30f, 150f)};
			this.color_rgb = new int[]{(int) parent.random(10,255), (int) parent.random(10,255), (int) parent.random(10,255)};
			this.rotationModifier = parent.random(-10.00f, 10.00f);
			this.alpha = alpha;
			this.isRespawning = isLooping;	
			this.isFilled = isFilled;
			this.isStroke = isStroke;
		}
		
		
		@SuppressWarnings("static-access")
		public void draw(){
			
			a++;
			this.xpos += this.xspeed;
			this.ypos += this.yspeed;
			
			
			//Box respawning
			if ( this.isRespawning == true ) {
			    if (this.ypos > (1500.0 + Math.abs(distance) * 0.45 ) ) {
			    	this.xpos = parent.random(-400f, 800f) + (this.distance * 0.8f);
			    	this.ypos = parent.random(-1000f, -800f) + (this.distance * 0.8f);
			    	this.color_rgb = new int[]{(int) parent.random(10,255), (int) parent.random(10,255), (int) parent.random(10,255)};
			    	this.rotationModifier = parent.random(-10.00f, 10.00f);
			    }
			}
						
			//drawing
			parent.pushMatrix();
			parent.translate(this.xpos, this.ypos, this.distance);
			//parent.imageMode(parent.CENTER);
			parent.rotateX(this.a*parent.TWO_PI/360 * this.rotationModifier);
			parent.rotateY(this.a*parent.TWO_PI/360 * this.rotationModifier);
			parent.rotateZ(this.a*parent.TWO_PI/360 * this.rotationModifier);
			
			if ( this.isFilled ) parent.fill(this.color_rgb[0], this.color_rgb[1], this.color_rgb[2], alpha);
			else parent.noFill();
			
			if ( this.isStroke ) {
			    parent.stroke(this.strokeColor_rgb[0], this.strokeColor_rgb[1], this.strokeColor_rgb[2], alpha);
			    parent.strokeWeight(4);
			}else parent.noStroke();
			
			parent.box(this.boxMeasures[0], this.boxMeasures[1], this.boxMeasures[2]);
			parent.popMatrix();	
		}	
	}
	
    
	/**
	 * Initializes the effect
	 * 
	 * @param p Reference to the parent program with a screen to draw to
	 * @param itemAmount Amount of boxes. Values over 500 can start to slow down on older (like 2007s) computers
	 * @param distance distance of things "from the camera". Can be used to layer this nicely to the background of things happening on screen
	 * @param isLooping Does the effect loop
	 * @param isFilled Are the boxes filled
	 * @param isStroke Do the edges of the boxes have lines
	 * @param alpha Transparency of the boxes (valid values: 0-255, where 255 is completely solid). Be careful: slows the effect down massively if there are many boxes
	 * @param strokeColor an int[] array of rgb value for box edge lines
	 */
	public RageBoxes(PApplet p, int itemAmount, float distance, boolean isLooping, boolean isFilled, boolean isStroke, int alpha, int[] strokeColor_rgb){
		//super(p);
		this.parent = p;
		initialize(itemAmount, distance, isLooping, isFilled, isStroke, alpha, strokeColor_rgb);
	}
	
	
	/**
	 * Nice short-signatured constructor for some easy automated goodness
	 * 
	 * @param p
	 */
	public RageBoxes(PApplet p){
		//super(p);
		this.parent = p;
		initialize(500, -10.0f, false, true, true, 255, new int[]{0,0,0});
		
	}
	
	
	private void initialize(int itemAmount, float distance, boolean isLooping, boolean isFilled, boolean isStroke, int alpha, int[] strokeColor_rgb){
		this.nextDistance = distance;
		this.items = new Item[itemAmount];
		for (int i = 0; i < itemAmount; i++ ) {
			this.items[i] = new Item(this.parent, this.nextDistance, isLooping, isFilled, isStroke, alpha, strokeColor_rgb);
			this.nextDistance -= 4.0;	
		}
		
	}
	
	
	/**
	 * Resets the effect for another go with new settings
	 * The method signature is the same as in the constructor, except the constructor's 1st parameter is to be left out
	 */
	public void reset(int itemAmount, float distance, boolean isLooping, boolean isFilled, boolean isStroke, int alpha, int[] strokeColor_rgb){
        this.items = null;
		initialize(itemAmount, distance, isLooping, isFilled, isStroke, alpha, strokeColor_rgb);
		
	}
	
	
	/**
	 * Update method. In this case, it's enough to call just the draw() method.
	 * Implemented in this extended class to make sure the method is completely empty.
	 */
	public void update(){
		//To be left blank
	}
	
	/**
	 * Drawing method. In this case, It's not necessary to call update() separately but it doesn't do any harm.
	 */
	public void draw(){
		
		for (int i = 0; i < items.length; i++ ) {
			
			items[i].draw();
		}
	}
}
