/*
  Silly (JS)

  20110224 by Reto Kohli, based on the Java version (19970714)

  Generates arrays of floats in the range [-1.0 .. 1.0]
  that change smoothly from call to call

  - Constructor Silly(options) builds a Silly object with options:
      selector:   Selector; empty by default!
      property:   CSS property to be sillied; empty by default!
      interval:   Interval between updates, in ms
      count:      Size of tuple (n)
      complexity: Number of offsets and deltas per element (o)
      speed:      Tuple speed (s)
      polygon:    Return closed polygon if true (add first element to tail) (p)
      callback:   Callback function to call with the resulting float array,
        or
      mul:        Scale floats first,
      add:        then add some offset individually for each value
    that produces n-tuples using o offsets which change with speed s.
    if the boolean flag p is true, the first tuple element is appended to the
    end of the resulting array again.
  - Method get() returns the next array of floats
  - Variable current contains the current array of floats

  TODO:
  - Interface for changing properties on the fly
  - Interface for attaching a Silly to one or more elements and
    any style property, jQuery style, like:
      $J(".groovy").Silly(3, 2, 1, 0, "color", interval);
      $J(".groovy").Silly(1, 2, 3, 0, "width", interval);
*/

function Silly(options)
{
  var speedFactor = 100; // const
  var running;
  var current = [];
  var size;
  var offsets;
  var deltas;

  // Defaults
  var opt = {
    selector: '',   // Selector; empty by default!
    property: '',   // CSS property to be sillied; empty by default!
    interval: 100,  // Interval between updates, in ms
    count: 1,       // Size of tuple
    complexity: 1,  // Number of offsets and deltas per element
    speed: 1,       // Tuple speed
    polygon: 0,     // Return closed polygon if true (add first element to tail)
    callback: '',   // Callback function to call with the resulting float array
    mul: [1],       // Scale floats first,
    add: [0],       // then add some offset individually for each value
    method: 'normal', // Calculation method (need better names...)
  };

  function start() {
     running = setInterval(update, opt.interval);
  };

  function stop() {
    if (running) clearInterval(running);
  };

  function update() {
    var tuple = get();
    if (opt.callback) {
      // Use a callback for sophisticated stuff
      opt.callback(tuple);
    } else {
      // This works for single (count == 1) values (like width),
      // and for tuples in parentheses (like rgb(r,g,b)) *only*!
      var style = (opt.count > 1 ? "(" : "");
      for (var i = 0; i < opt.count; ++i) {
        style = style +
          (i > 0 ? ", " : "") +
          Math.floor(tuple[i]*opt.mul[i]+opt.add[i]);
      }
      style = style + (opt.count > 1 ? ")" : "");
      $J(opt.selector).css(opt.property, style);
    }
//    $J(opt.selector).text(
//      "$J("+opt.selector+").css("+opt.property+", "+style+")");
//      $J(opt.selector).css(opt.property+":"+style);
  };

  function get() {
    for (var offset = 0; offset < size; ++offset) {
      offsets[offset] += deltas[offset];
      if (offsets[offset] > 2) {
        offsets[offset] -= 2;
      }
    }
    if (opt.method == 'extra') {
      calculate_extra();
    } else {
      calculate_normal();
    }
    return current;
  };

  function calculate_normal() {
    for (var element = 0; element < opt.count; ++element) {
      var elementIndex = element*opt.complexity;
      var tmp = 0;
      for (var offset = 0; offset < opt.complexity; ++offset) {
        tmp = Math.sin(tmp + offsets[elementIndex + offset]*Math.PI);
      }
      current[element] = tmp;
//document.write("element "+element+", current "+current[element]+"<br />");
    }
    if (opt.polygon) {
      current[count] = current[0];
    }
  };

  function calculate_extra() {
    for (var element = 0; element < opt.count; ++element) {
      var elementIndex = element*opt.complexity;
      var tmp = 0;
      for (var offset = 0; offset < opt.complexity; ++offset) {
        tmp += Math.sin(offsets[elementIndex + offset]*Math.PI);
      }
      current[element] = Math.sin(tmp*Math.PI/opt.complexity);
//document.write("element "+element+", current "+current[element]+"<br />");
    }
    if (opt.polygon) {
      current[count] = current[0];
    }
  };

  // Construction is here:
  for (option in opt) {
    if (options[option] != undefined) opt[option] = options[option];
//alert("Set "+option+" to "+opt[option]);
  }
  if (opt.mul.length != opt.count) {
    opt.mul = new Array(opt.count);
    for (var i = 0; i < opt.count; ++i) {
      opt.mul[i] = 1;
    }
  }
  if (opt.add.length != opt.count) {
    opt.add = new Array(opt.count);
    for (var i = 0; i < opt.count; ++i) {
      opt.add[i] = 0;
    }
  }

  //document.write("count "+count+"<br />");
  size = opt.count*opt.complexity; // size of internal arrays
  //alert("Size "+size);
  offsets = new Array(size);
  deltas = new Array(size);
  for (var offset = 0; offset < size; ++offset) {
    offsets[offset] = (Math.random()*2);
    deltas[offset] = (Math.random()/speedFactor*opt.speed);
  }

  start();

}

