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

/**
 * An image generator with state entirely parameterized by time and a
 * pixel function.
 */
public class Behavior
{
  ELExpression expression;

  ELMatrix currentValue;

  /**
   * True when currentValue is out of sync with expression.
   */
  boolean dirty = true;
  
  int lastEnvironmentHash = 0;

  // Interesting statistics
  int numMutations;
  int numGenerations;

  String expressionCode = "";
    long evaluationTime = 0;


  /**
   * @param gp The parameters that should be used to generate the
   * initial expression.
   */
  public Behavior(GeneticParameters gp, int width, int height)
  {
    numMutations = 0;
    numGenerations = 0;
    currentValue = ELMatrix.alloc(width, height);
    setRandomExpression(gp);
  }

  public Behavior(ELExpression e, int width, int height, int numGenerations)
  {
    currentValue = ELMatrix.alloc(width, height);
    setExpression(e);
    this.numGenerations = numGenerations;
  }

  public Behavior(ELExpression e, int width, int height, boolean fast)
  {
      if (fast == false) {
	  currentValue = ELMatrix.alloc(width, height);
	  setExpression(e);
	  this.numGenerations = numGenerations;
      } else {
	  currentValue = ELMatrix.alloc(width, height);
	  expression = e;
	  expressionCode = e.unparse();
	  dirty = true;
	  long start = System.currentTimeMillis();    
	  evaluate(new ELEnvironment());
	  evaluationTime = (System.currentTimeMillis() - start);
      }
  }

  /**
   * Sets this behavior to a new, non-boring expression.
   */
  public void setRandomExpression(GeneticParameters gp)
  {
      boolean done = false;
      do {
	  ELExpression e = gp.randomExpression();
	  if (e.numNestedSubexpressions() >= 3) {
	      setExpression(e);
	      done = ! isBoring();
	  }
    } while (! done);
    System.out.print(".");
  }

  public void setExpression(ELExpression e) {
      setExpression(e, new ELEnvironment(), false);
  }

    public void setExpression(ELExpression e, ELEnvironment environment) {
	setExpression(e, environment, true);
    }

  private void setExpression(ELExpression e, ELEnvironment environment, boolean fast)
  {
    expression = e;
    expressionCode = e.unparse();
    dirty = true;

    int n = 1;
    
    // Let animations work up a bit.
    if (e.hasExternalInfluences() && !fast) {
	n = 6;
    }
    
    long start = System.currentTimeMillis();

    for(int i = 0; i < n; i++) {
	environment.clock(System.currentTimeMillis() / 1000.0, currentValue);
	evaluate(environment);
    }

    evaluationTime = (System.currentTimeMillis() - start) / n;

    numMutations = 0;
    numGenerations = 0;
  }

  /**
   * Returns true if this behavior is boring.
   */
  public boolean isBoring()
  {
    // An expression is boring if it has less than three nodes.
    if (expression.numNestedSubexpressions() < 3) {
	System.out.print("<");
	return true;
    }

    // An expression is boring if it has more than 18 nodes because
    // it takes too long to render 
    if (expression.numNestedSubexpressions() > 25) {
	System.out.print(">");
	return true;
    }

    if (evaluationTime > 300) {
	System.out.print("T");
	return true;
    }
    
    // Images that are almost constant are boring.    
    int constantChannels = 0;

    // Loop over all color channels
    for(int c = 0; c < 3; c++) {
      float min = 1.0f;
      float max = -1.0f;
      float channel[] = currentValue.channel[c];
      float sum = 0.0f;

      // All elements
      for(int i = currentValue.numElements - 1; i >= 0; i--) {
        float v = channel[i];

        // Clamp values
        if (v < 0.0f) {
          v = 0.0f;
        } else if (v > 1.0f) {
          v = 1.0f;
        }

        sum += v;

        if (min > v) {
          min = v;
        }
        if (max < v) {
          max = v;
        }
      }
      
      float average = sum / currentValue.numElements;

      // compute variance
      float variance = 0.0f;
      for(int i = currentValue.numElements - 1; i >= 0; i--) {
        float v = channel[i];
        if (v < 0.0f) {
          v = 0.0f;
        } else if (v > 1.0f) {
          v = 1.0f;
        }
        variance += (float)Math.abs(v - average);
      }

      variance /= currentValue.numElements;
      
      // Values less than 0 aren't visible.
      if (min < 0.0) {
        min = 0.0f;
      }
      if (max < 0.0) {
        max = 0.0f;
      }

      // If the difference between the max and the min is less than
      // 0.1 (out of a possible 2.0) this channel is nearly constant.
      if (((max - min) < 0.1) || (variance < 0.2)) {
        constantChannels++;
      }
    }
    
    if (constantChannels == 3) {
	System.out.print("x");
	return true;
    }

    return false;
  }


  /**
   * Deallocates resources consumed by this behavior.
   */
  public Behavior free()
  {
    currentValue = currentValue.free();
    return null;
  }

  public String toString()
  {
    return expression.unparse();
  }

  public void evaluate()
  {
    evaluate(new ELEnvironment());
  }

  /**
   * Evaluates the expression.
   */
  public void evaluate(ELEnvironment environment)
  {
    // Don't reevaluate if nothing has changed
    if ((dirty) || (environment.hash() != lastEnvironmentHash)) {
      lastEnvironmentHash = environment.hash();

      dirty = false;

      // Clear the image.  This isn't techically necessary, but it
      // helps flush bugs where a filter doesn't write to all pixels.
      //currentValue.setAll(0f, 0f, 0f);

      expression.evaluate(currentValue, environment, null);
      currentValue.setDirty();
    }
  }

  /**
   * Returns true if both behaviors share an expression.
   */
  public boolean sameExpression(Behavior b) 
  {
    return expressionCode.equals(b.expressionCode);
  }

  /**
   * Invoke to tell the behavior that its environment has been mutated
   * and it should redraw if necessary.  This will automatically trigger
   * re-evaluation and redraw on the invoking thread.
   */
  public void environmentChanged()
  {
    if ((expression != null) && (expression.hasExternalInfluences())) {
      dirty = true;
      
    }
  }
}
