/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix

import java.util.Random;

/**
 * The environment in which an operator is applied.  This contains
 * information such as global animation parameters used by some
 * operators.
 *<P>
 *
 * These values should be read by expressions, but never set by expressions.
 *
 */
public class ELEnvironment 
{
    /**
     * The parameter controlling interpolation between behaviors in a
     * genetic morph.
     */
    private double morphInterpolationParameter = 0.0;

    private double time = 0.0;

    private ELMatrix previousFrame;

    /**
     * Frequency decomposition.  Values range from 0 - 1.
     */
    public double frequency[] = new double[288];

    // Used for simulating frequencies
    private double frFunc[] = new double[288];
    private double dFrFunc[] = new double[288];
    
    private final static int numParticles = 75;

    /**
     * width of particle grid - 1 (must be a power of 2 - 1)
     */
    private final static int particleMax = 16383;
    private final static int particleMaxSpeed = 400;

    private int particleX[] = new int[numParticles];
    private int particleY[] = new int[numParticles];
    private int particleAngle[] = new int[numParticles];
    private int particledAngle[] = new int[numParticles];
    
    /**
     * Block averages over the frequencies. Values range from 0 - 1.
     */
    public double filteredFrequency[] = new double[64];
    
    /**
     * When true, evaluation of a single expression is multithreaded for
     * performance.  Setting this to false may improve performance on
     * machines with a small number of processors. 
     */
    public boolean multiThreadEvaluation = false;
    
    /**
     * When true, evaluation of an ELMorphInterpolator expression is
     * multithreaded for performance.  Setting this to false may improve
     * performance on a single processor machine.
     */
    public boolean multiThreadInterpolation = true;

    final private Random random = new java.util.Random(1);
    private int _hash;

    public ELEnvironment()
    {
	for(int f = 0; f < 288; f++) {
	    dFrFunc[f] = (random.nextDouble() - 0.5) * 0.1;
	    frFunc[f] = random.nextDouble();
	}
	simulateFrequencies();
	time = random.nextDouble() * 1e6;
	_hash = hashCode();

	for(int p = 0; p < numParticles; p++) {
	    particleX[p] = (int)(random.nextDouble() * particleMax);
	    particleY[p] = (int)(random.nextDouble() * particleMax);
	    particleAngle[p] = (int)(random.nextDouble() * 65536);
	    particledAngle[p] = (int)((random.nextDouble() - 0.5) * 10000);
	}
    }

    public void setTime(double s) 
    {
	time = s;
	_hash++;
    }

    /**
     * Returns the time, in seconds.
     */
    public double getTime()
    {
	return time;
    }

    public void setMorphInterpolationParameter(double m) 
    {
	morphInterpolationParameter = m;
	_hash++;
    }

    public double getMorphInterpolationParameter()
    {
	return morphInterpolationParameter;
    }

    /**
     * Clones the previous frame into m.
     */
    public void getPreviousFrame(ELMatrix m) 
    {
	if ((previousFrame == null) || (previousFrame.getWidth() != m.getWidth()) ||
	    (previousFrame.getHeight() != m.getHeight())) {
	    m.setAll(0f, 0f, 0f);
	} else {
	    m.set(previousFrame);
	}
    }

    /**
     * Clones m into the previousFrame field.
     */
    public void setPreviousFrame(ELMatrix m) 
    {
	if (m == null) {
	    previousFrame = null;
	    return;
	}

	if ((previousFrame == null) || (previousFrame.getWidth() != m.getWidth()) ||
	    (previousFrame.getHeight() != m.getHeight())) {
	    previousFrame = (ELMatrix)m.clone();
	} else {
	    previousFrame.set(m);
	}
    }

    /**
     * Clocks the system.  Call this once per frame for animations.
     */
    public void clock(double time, ELMatrix previousValue) {
	setTime(time);
	moveParticles();
	simulateFrequencies();
	setPreviousFrame(previousValue);
    }

    /**
     * Returns a number in the range [0, 16383]
     */
    public int getParticleX(int p) 
    {
	return particleX[p];
    }

    /**
     * Returns a number in the range [0, 16383]
     */
    public int getParticleY(int p) 
    {
	return particleY[p];
    }

    public int getNumParticles() 
    {
	return numParticles;
    }

    /**
     * Moves the particles about (clocks them).
     */
    public void moveParticles()
    {
	for(int p = 0; p < numParticles; p++) {
	    // Position
	    int speed = (particleMaxSpeed * p + numParticles) / numParticles;
	    particleAngle[p] = (particleAngle[p] + particledAngle[p]) & 65535;

	    double angle = Math.PI * 2.0 * (particleAngle[p] / 65536.0);
	    particleX[p] = (int)(Math.cos(angle) * speed + particleX[p]) & particleMax;
	    particleY[p] = (int)(Math.sin(angle) * speed + particleY[p]) & particleMax;

  	    if (random.nextDouble() > 0.95) {
  		particledAngle[p] = (int)(Math.pow((random.nextDouble() - 0.5), 3) * 40000);
  	    }
	}
    }

    /**
     * Sets frequencies using a simulated audio input.
     */
    public void simulateFrequencies() 
    {
	if (random.nextDouble() > 0.9) {
	    // Change the frequencies
	    for(int f = 0; f < 288; f++) {
		dFrFunc[f] = (random.nextDouble() - 0.5) * 0.1;
	    }
	}

	// Compute some random frequency data.  Increase the contrast on the data
	// to encourage all on & off
	for(int f = 0; f < 288; f++) {
	    frequency[f] = Math.max(Math.min(frFunc[f] * 4.0 - 2.0, 1.0), 0.0);
	    frFunc[f] += dFrFunc[f];
	    if (frFunc[f] > 1.0) {
		dFrFunc[f] = -dFrFunc[f];
		frFunc[f] = 1.0;
	    } else if (frFunc[f] < 0.0) {
		dFrFunc[f] = -dFrFunc[f];
		frFunc[f] = 0.0;
	    }
	}

	recomputeFilteredFrequencies();
	_hash++;
    }

    /**
   * Recomputes the filtered frequencies from the unfiltered ones
   */
    public void recomputeFilteredFrequencies() 
    {
	// Compute averages of every frequency block
	for(int f = 0; f < 64; f++) {
	    int g0 = f * (288 / 64);
	    int g1 = (f + 1) * (288 / 64);
	    double sum = 0;
	    for(int g = g0; g < g1; g++) { 
		sum += frequency[g];
	    }
	    filteredFrequency[f] = sum / (g1 - g0);

	    // Increase the contrast
	    filteredFrequency[f] = Math.max(Math.min(filteredFrequency[f] * 4.0 - 2.0, 1.0), 0.0);
	}
    }

    /**
   * Returns a hash code.  This hash is unique for each environment
   * and changes each time the environment changes.  If a behavior is
   * evaluated twice with an environment where the hash has not changed,
   * it can safely assume that any cached values are still valid.
   */
    public int hash()
    {
	return _hash;
    }
}


