RollingCounter = $.Class({
  initialize: function(element, callerSettings) {
    this.settings = $.extend({
      scale: 1,
      imageUrl: '/javascript/counter/filmstrip.png',
      commaImageUrl: '/javascript/counter/comma.png',
      dollarSignImageUrl: '/javascript/counter/dollarsign.png',
      baseImageWidth: 53,
      baseImageHeight: 6180,
      baseTileHeight: 103,
      baseDollarSignWidth: 62,
      baseDollarSignHeight: 104,
      baseCommaWidth: 6,
      baseCommaHeight: 11,
      useThousandsSeparator: true,
      showDollarSign: true,
      transitionSteps: 5, // Number of "transitional" frames between each number
      counterDigits: 10, // How many digits to display
      stepDuration: 30 // in milliseconds
    }, callerSettings || {});

    this.settings.tileHeight =
      this.settings.baseTileHeight * this.settings.scale;
    this.settings.imageWidth =
      parseInt(this.settings.baseImageWidth * this.settings.scale);
    this.settings.imageHeight =
      parseInt(this.settings.baseImageHeight * this.settings.scale);
    this.settings.dollarSignWidth =
      parseInt(this.settings.baseDollarSignWidth * this.settings.scale);
    this.settings.dollarSignHeight =
      parseInt(this.settings.baseDollarSignHeight * this.settings.scale);
    this.settings.dollarSignDivWidth = this.settings.dollarSignWidth + 5;
    this.settings.dollarSignDivHeight = this.settings.dollarSignHeight - 1;
    this.settings.commaWidth =
      parseInt(this.settings.baseCommaWidth * this.settings.scale);
    this.settings.commaHeight =
      parseInt(this.settings.baseCommaHeight * this.settings.scale);
    this.settings.commaDivWidth = this.settings.commaWidth + 5;
    this.settings.commaDivHeight =
      parseInt(this.settings.tileHeight - 21 * this.settings.scale);

    this.digits = [];
    
    // Keep a reference to the element to use as our container
    this.container = $(element);
    
    // Convert the element into our graphical counter, reading
    // in any text that might be contained and using it as our
    // starting value.
    this.container.addClass('rollingCounterGen');
    this.valueText = this.sanitizeValueText(this.container.html());
    this.container.html(''); // Delete the contents of the container

    if(this.settings.showDollarSign) {
      var img = new Image();
      $(img).attr("src", this.settings.dollarSignImageUrl)
        .css("width", this.settings.dollarSignWidth + "px")
        .css("height", this.settings.dollarSignHeight + "px");
      this.container.append(
        $('<div />').addClass('dollarSign')
          .css("width", this.settings.dollarSignDivWidth + "px")
          .css("height", this.settings.dollarSignDivHeight + "px")
           .append($(img))
      );
    }

    var paddedValue = this.getPaddedValue();

    // Loop to generate an instance of RollingCounterDigit for each
    // character in the number to display
    for(var i=0; i < paddedValue.length; i++) {

      // Insert thousands separator as appropriate
      if (this.settings.useThousandsSeparator &&
          i != 0 && (paddedValue.length - i) % 3 == 0) {
        var img = new Image();
        $(img).attr("src", this.settings.commaImageUrl)
          .css("width", this.settings.commaWidth + "px")
          .css("height", this.settings.commaHeight + "px");
        this.container.append(
          $('<div />').addClass('comma')
            .css("width", this.settings.commaDivWidth + "px")
            .css("height", this.settings.commaDivHeight + "px")
            .append($(img))
        );
      }
      var digit = new RollingCounterDigit(paddedValue.substr(i, 1),
																				  this.settings);
      this.digits.push(digit);

      // Insert the html for this digit into our container
      this.container.append(digit.element);
    }
    
    // Since each of the digits is set to "float:left" we need this
    // shim to give our container actual height in the page flow.
//    this.container.append(
//      $('<div />').css("clear", "left")
//        .css("width", "0px")
//        .css("height", "0px")
//    );
  },
  
  setValue: function(newValue) {
    this.valueText = this.sanitizeValueText(newValue + "");
    var paddedValue = this.getPaddedValue();
    for(var i=0; i < paddedValue.length; i++) {
      this.digits[i].setDigit(paddedValue.substr(i, 1));
    }
  },

  getValue: function() {
    return parseInt(this.valueText);
  },

  reset: function() {
    var savedValue = this.getValue();
    this.setValue(0);
    this.setValue(savedValue);
  },

  // *************************************************
  //   Private Functions
  // *************************************************
  
  // Remove all non-numeric digits from the valueText
  // Remove any leading zeros from the valueText
  // (When you use parseInt on something with a leading zero, it is
  // interpreted as an octal number incorrectly)
  sanitizeValueText: function(valueText) {
    valueText = valueText.replace(/[^\d]/g, '');
    valueText = valueText.replace(/^0*/, '');
    return valueText;
  },
  
  // Returns the zero-padded version of our valueText variable.
  getPaddedValue: function() {
    return this._padValue(this.valueText, this.settings.counterDigits);
  },

  // Recursive function to pad the number with leading zeros,
  // or whatever other character you specify.
  // If the incoming value is too long, it's trimmed, keeping
  // only the right-most digits.
  _padValue: function(value, length, padCharacter) {
    if (padCharacter == null) {
      padCharacter = "0";
    }
    value = value + "";
    if (value.length < length) {
      value = padCharacter + value;
      return this._padValue(value, length, padCharacter);
    }
    else if (value.length > length) {
      return value.substr(value.length - length, length);
    }
    else {
      return value;
    }
  }
});

RollingCounterDigit = $.Class({
  initialize: function(initialValue, callerSettings) {
    this.settings = callerSettings;

    // Create the DOM elements for this digit
    var img = new Image();
    $(img).attr("src", this.settings.imageUrl)
        .css("width", this.settings.imageWidth + "px")
        .css("height", this.settings.imageHeight + "px");
    this.element = $('<div />')
      .css("height", parseInt(this.settings.tileHeight) + "px")
      .addClass('digit')
      .append($(img));
    
    this.image = $(img);

    // Initialize the digit's state.
    this.value = parseInt(initialValue);
    this._initializeStates();
    this.setCurrentState(this.getStateForNumber(this.value));
  },

  setDigit: function(newValue) {
    this.value = parseInt(newValue);
    this.update();
  },

  // *************************************************
  //   Private Functions
  // *************************************************
 
  // When a digit changes, this function is called.
  update: function() {
    var obj = this;
    // We set the currentState to the nextState every time this
    // is called, until we reach the state corresponding to the
    // rest position of the number we're looking for.
    if (this.currentState.number != this.value) {
      this.setCurrentState(this.currentState.nextState);

      // The first time this function is called, we call setInterval to call
      // the update function repeatedly until the animation is complete.
      if (this.updateTimeout == null) {
        this.updateTimeout = setInterval(function() { obj.update() },
           this.settings.stepDuration);
      }
    }
    else {
      // the animation is complete, clear the interval.
      clearInterval(this.updateTimeout);
      this.updateTimeout = null;
    }
  },

  setCurrentState: function(newState) {
    // Set the current state, and update the display accordingly.
    this.currentState = newState;

    // We update which "frame" is showing by moving the image within
    // a clipping container that is just big enough to show one frame.
    var offset = -1 * this.settings.tileHeight * this.currentState.index;
    this.image.css("top", parseInt(offset) + "px");
  },

  getStateForNumber: function(number) {
    return this.states[number + this.settings.transitionSteps * number];
  },

  // Create the states for the counter animation
  _initializeStates: function() {

    // only generate them once, class variable
    if (!RollingCounterDigit.states) {

      RollingCounterDigit.states = [];

      for(var i=0; i < 10; i++) {

        // Create the "rest" state for each number
        RollingCounterDigit.states.push({
          kind: 'solidNumber',
          number: i
        });

        // Create the in-between states for the animation
        for(var j=0; j < this.settings.transitionSteps; j++) {
          RollingCounterDigit.states.push({
            kind: 'transition',
            number: null
          });
        }
      }

      for(var i=0; i < RollingCounterDigit.states.length; i++) {
        // Link all the states to each other via nextState
        // and prevState properties...
        var state = RollingCounterDigit.states[i];
        state.index = i;
        if (i > 0) {
          state.prevState = RollingCounterDigit.states[i-1];
        }
        else {
          state.prevState =
            RollingCounterDigit.states[RollingCounterDigit.states.length-1];
        }
        if (i == RollingCounterDigit.states.length-1) {
          state.nextState = RollingCounterDigit.states[0];
        }
        else {
          state.nextState = RollingCounterDigit.states[i+1];
        }
      }
    }

    // Keep a convenience instance variable reference to the
    // states in the class variable.
    this.states = RollingCounterDigit.states;
  }
});

