//  Starbox 0.3.0.4 - 17-12-2007

//  Copyright (c) 2007 Nick Stakenburg (http://www.nickstakenburg.com)
//
//  Permission is hereby granted, free of charge, to any person obtaining
//  a copy of this software and associated documentation files (the
//  "Software"), to deal in the Software without restriction, including
//  without limitation the rights to use, copy, modify, merge, publish,
//  distribute, sublicense, and/or sell copies of the Software, and to
//  permit persons to whom the Software is furnished to do so, subject to
//  the following conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
//  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//  More information on this project:
//  http://www.nickstakenburg.com/projects/starbox/

var Starboxes = {
  // Configuration for all starboxes
  inverse: false,
  locked: false,
  onRate: Prototype.emptyFunction,
  overlayImages: '../images/default/starbox/', // relative to starbox.js
  overlay: 'default.png',
  rerate: false,

  REQUIRED_Prototype: '1.6.0',
  REQUIRED_Scriptaculous: '1.8.0',

  load: function() {
    this.require('Prototype');
    var srcMatch = /starbox\.js$/;
    this.imageSource = (($$("head script[src]").find(function(s) {
      return s.src.match(srcMatch);
    }) || {}).src || '').replace(srcMatch, '') + this.overlayImages;
  },

  require: function(library) {
    if ((typeof window[library] == 'undefined') ||
      (this.convertVersionString(window[library].Version) < this.convertVersionString(this['REQUIRED_' + library])))
      throw('Starbox requires ' + library + ' >= ' + this['REQUIRED_' + library]);
  },

  convertVersionString: function(versionString) {
    var r = versionString.split('.');
    return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
  },

  fixIE: (function(agent) {
    var version = new RegExp('MSIE ([\\d.]+)').exec(agent);
    return version ? (parseFloat(version[1]) <= 6) : false;
  })(navigator.userAgent),

  imagecache: [],
  cacheImage: function(imageInfo) {
    if(!this.getCachedImage(imageInfo.src)) this.imagecache.push(imageInfo);
    return imageInfo;
  },

  getCachedImage: function(src) {
    return this.imagecache.find(function(imageInfo) { return imageInfo.src == src });
  },

  // speed up the initial load of the page by building in batches
  // images are cached to to minimize requests
  buildQueue: [],
  queueBuild: function(starbox) {
    this.buildQueue.push(starbox);
  },

  processBuildQueue: function() {
    // on empty queue, stop loading as batches
    if (!this.buildQueue[0]) { this.batchLoading = true; return; }
    this.cacheBuildBatch(this.buildQueue[0]);
  },

  cacheBuildBatch: function(starbox) {
    var set = [];
    var overlay = starbox.options.overlay;
    var imageInfo = this.getCachedImage(overlay);

    // create a batch based on images with the same overlay
    this.buildQueue.each(function(s) {
      if (s.options.overlay == overlay) {
        set.push(s);
        this.buildQueue = this.buildQueue.without(s);
      }
    }.bind(this));

    if (!imageInfo) {
      var starImage = new Image();
      starImage.onload=function() {
        var imageInfo = this.cacheImage({ src: overlay, height: starImage.height,
          width: starImage.width, fullsrc: starImage.src });
        this.buildBatch(set, imageInfo);
      }.bind(this);
      starImage.src = Starboxes.imageSource + overlay;
    }
    else { this.buildBatch(set, imageInfo); }
  },

  buildBatch: function(set, imageInfo) {
    set.each(function(s) {
      s.imageInfo = imageInfo;
      s.build();
    });
    this.processBuildQueue();
  }
};
Starboxes.load();
document.observe('dom:loaded', Starboxes.processBuildQueue.bind(Starboxes));

var Starbox = Class.create({
  initialize: function(element, average) {
    this.element = $(element),
    this.average = average;

    this.options = Object.extend({
      buttons: 5,
      className : 'default',
      color: false,
      duration: 0.6,
      effect: { mouseover: false , mouseout: (window.Effect && Effect.Morph) },
      hoverColor: false,
      hoverClass: 'hover',
      ghostColor: false,
      ghosting: false,
      ratedClass: 'rated',
      identity: false,
      indicator: false,
      inverse: Starboxes.inverse,
      locked: false,
      max: 5,
      onRate: Starboxes.onRate,
      rerate: Starboxes.rerate,
      rated: false,
      overlay: Starboxes.overlay,
      stars: 5,
      total : 0
    }, arguments[2] || {});

    this.rated = this.options.rated;
    this.total = this.options.total;
    this.locked =  this.options.locked || (this.rated && !this.options.rerate);

    if (this.options.effect && (this.options.effect.mouseover || this.options.effect.mouseout))
      Starboxes.require('Scriptaculous');

    Starboxes.queueBuild(this);
    if (Starboxes.batchLoading) Starboxes.processBuildQueue();
  },

  enable: function() {
    if (!Prototype.Browser.IE) {
      this.onMouseout = this.onMouseout.wrap(function(proceed, event) {
        var rel = event.relatedTarget, cur = event.currentTarget;
        if (rel && rel.nodeType == Node.TEXT_NODE) rel = rel.parentNode;
        if (rel && rel != cur && !(rel.descendantOf(cur)))
          proceed(event);
      });
    }

    $w('mouseout mouseover click').each(function(e) {
      var E = e.capitalize();
      this['on' + E + '_cached'] = this['on' + E].bindAsEventListener(this);
      this.starbar.observe(e, this['on' + E + '_cached']);
    }.bind(this));

    this.buttons.invoke('setStyle', { cursor: 'pointer' });
  },

  disable: function() {
    $w('mouseover mouseout click').each(function(e) {
      this.starbar.stopObserving(e, this['on' + e.capitalize() + '_cached']);
    }.bind(this));

    this.buttons.invoke('setStyle', { cursor: 'auto' });
  },

  build: function() {
    this.starWidth = this.imageInfo.width;
    this.starHeight = this.imageInfo.height;
    this.starSrc = this.imageInfo.fullsrc;
    this.boxWidth = this.starWidth * this.options.stars;
    this.buttonWidth = this.boxWidth / this.options.buttons;
    this.buttonRating = this.options.max / this.options.buttons;

    if(this.options.effect) {
      this.zeroPosition = this.getBarPosition(0);
      this.maxPosition = this.getBarPosition(this.options.max);
    }

    var styles = {
      absolute: { position: 'absolute', top: 0, left: 0, width: this.boxWidth + 'px', height: this.starHeight + 'px' },
      base: { position: 'relative', width: this.boxWidth + 'px', height: this.starHeight + 'px' },
      star: { position: 'absolute', top: 0, left: 0, width: this.starWidth + 'px', height: this.starHeight + 'px' }
    };

    this.element.addClassName('starbox');
    this.container = new Element('div', { 'class': this.options.className || '' }).setStyle({ position: 'relative' });

    this.status = this.container.appendChild(new Element('div'));
    if (this.rated) this.status.addClassName('rated');
    if (this.locked) this.status.addClassName('locked');

    this.hover = this.status.appendChild(new Element('div'));
    this.wrapper = this.hover.appendChild(new Element('div', { 'class': 'stars' }));
    this.wrapper.setStyle(Object.extend({ overflow: 'hidden' }, styles.base));

    if (this.options.ghosting) {
      this.ghost = this.wrapper.appendChild(new Element('div', { 'class': 'ghost' }).setStyle(styles.absolute));
      if (this.options.ghostColor) this.ghost.setStyle({ background: this.options.ghostColor });
      if (this.options.effect) this.ghost.scope = this.ghost.identify();
      this.setBarPosition(this.ghost, this.average, (window.Effect && Effect.Morph));
    }

    this.colorbar = this.wrapper.appendChild(new Element('div', { 'class': 'colorbar' }).setStyle(styles.absolute));
    if (this.options.color) this.colorbar.setStyle({ background: this.options.color });
    if (this.options.effect) this.colorbar.scope = this.colorbar.identify();

    var starWrapper = this.wrapper.appendChild(new Element('div').setStyle(styles.absolute));
    this.starbar = starWrapper.appendChild(new Element('div').setStyle(styles.base));

    this.options.stars.times(function(i) {
      var star = this.starbar.appendChild(new Element('div').setStyle(Object.extend({
        background: 'url(' + this.starSrc + ') top left no-repeat'
      }, styles.star)));
      star.setStyle({ left: this.starWidth * i + 'px' });

      if (Starboxes.fixIE) {
        star.setStyle({
          background: 'none', 'filter' : 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
            this.starSrc + '\'\', sizingMethod=\'scale\')'
        });
      }
    }.bind(this));

    this.buttons = [];
    this.options.buttons.times(function(i) {
      var leftPos = this.options.inverse ? this.boxWidth - this.buttonWidth * (i + 1) : this.buttonWidth * i;
      var button = this.starbar.appendChild(new Element('div', { href: 'javascript:;' }).setStyle({
        position: 'absolute',
        top: 0,
        left: leftPos + 'px',
        width: this.buttonWidth + (Prototype.Browser.IE ? 1 : 0) + 'px',
        height: this.starHeight + 'px'
      }));
      button.rating = this.buttonRating * i + this.buttonRating;
      this.buttons.push(button);
    }.bind(this));

    this.setBarPosition(this.colorbar, this.average);
    this.element.update(this.container);

    if (this.options.indicator) {
      this.indicator = this.hover.appendChild(new Element('div', { 'class' : 'indicator' }));
      this.updateIndicator();
    }

    if (!this.locked) this.enable();
  },

  updateAverage: function(increment) {
    if (this.rated && this.options.rerate)
      this.average = (this.total * this.average - this.rated) / (this.total-1 || 1);

    var total = this.rated ? this.total : this.total++;

    this.average = (this.average == 0) ? increment :
      (this.average * (this.rated ? total-1 : total) + increment) / (this.rated ? total : total+1);
  },

  updateIndicator: function() {
    this.indicator.update(new Template(this.options.indicator).evaluate({
      max: this.options.max,
      total: this.total,
      average: (this.average * 10).round() / 10
    }));
  },

  getBarPosition : function(rating) {
    var position = (this.boxWidth - (rating/this.buttonRating) * this.buttonWidth);
    return parseInt(this.options.inverse ? position.ceil() : -1 * position.floor());
  },

  setBarPosition: function(element, rating) {
    if (this.options.effect && this['activeEffect_' + element.scope])
      Effect.Queues.get(element.scope).remove(this['activeEffect_' + element.scope]);

    var left = this.getBarPosition(rating);
    if (arguments[2]) {
      var current = parseInt(element.getStyle('left'));
      var to = this.getBarPosition(rating);
      if (current == to) return;
      var mspeed = ((this.maxPosition - (current - to).abs()).abs() / this.zeroPosition.abs()).toFixed(2);

      this['activeEffect_' + element.scope] = new Effect.Morph(element, { style: { left: left + 'px' },
        queue: { position: 'end', limit: 1, scope: element.scope}, duration: (this.options.duration * mspeed) });
    }
    else { element.setStyle({ left: left + 'px' }); }
  },

  onClick: function(event) {
    var element = event.element();
    if (!element.rating) return;

    this.updateAverage(element.rating);
    if (this.options.indicator) this.updateIndicator();
    if (this.options.ghosting) this.setBarPosition(this.ghost, this.average, (window.Effect && Effect.Morph));

    if (!this.rated) this.status.addClassName('rated');
    var rerated = !!this.rated;
    this.rated = element.rating;

    if (!this.options.rerate) {
      this.disable();
      this.status.addClassName('locked');
      this.onMouseout(event);
    }

    var info = {
      average: this.average,
      identity: this.options.identity,
      max: this.options.max,
      rated: element.rating,
      rerated: rerated,
      total: this.total
    };
    this.options.onRate(this.element, info);
    this.element.fire('starbox:rated');
  },

  onMouseout: function(event) {
    this.setBarPosition(this.colorbar, this.average, (this.options.effect && this.options.effect.mouseout));
    this.hovered = false;
    if (this.options.hoverClass) this.hover.removeClassName(this.options.hoverClass);
    if (this.options.hoverColor) this.colorbar.setStyle({ background: this.options.color });
  },

  onMouseover: function(event) {
    var element = event.element();
    if (!element.rating) return;

    this.setBarPosition(this.colorbar, element.rating, (this.options.effect && this.options.effect.mouseover));
    if(!this.hovered && this.options.hoverClass) this.hover.addClassName(this.options.hoverClass);
    this.hovered = true;
    if (this.options.hoverColor) this.colorbar.setStyle({ background: this.options.hoverColor });
  }
});