function cli (arg) {
  if (_debugMode)
    (function(){
      console.log(arg + ':');
      console.log(eval(arg));
    });
}

/*
  TODO setup options for the Gallery class that will allow for CSS classNames to be specified
*/
var Gallery = Class.create({
  initialize: function(id){
    this.id = id;
    this.loading = true;
    this.moving = false;
    document.observe('dom:loaded', this.setupDOM.bind(this));
  },
  setupDOM: function(){
    this.gallery = $(this.id);
    if (!this.gallery) return;
    
    this.galleryLinks = new GalleryLinks('galleryLinks');
    this.galleryTitlesAndDescriptions = new GalleryTextContent('galleryTitle', 'galleryDescription');
    
    this.pictureLinks = this.gallery.select('.picture');
    this.pictures = this.pictureLinks.invoke('down').flatten();
    
    this.category = $('left').down('h3').innerHTML;
    this.categorySansSpaces = this.category.remove(' ');
    
    this.stripHrefsOfSpaces();
    this.setupHrefsOfPictures();
    
    this.scroller = $('scroller');
    this.scrollerBar = $('scrollerBar');
    this.scrollerHandle = $('scrollerHandle');
    
    this.space = 10;
    this.pictureOpacity = '.25';
    this.imageHoverMorphOptions = { duration: .25 };
    
    this.hideStuffWhileLoading();
    
    this.setupImageHovers();
    
    this.setupHistoryManager();
    
    document.observe('link:clicked', this.showPicture.bind(this));
    document.observe('window:loaded', this.pageLoaded.bind(this));
  },
  pageLoaded: function(){
    this.loading = false;
    this.affectImage();
    this.showStuffAfterLoad();
    this.calculateDimensions();
    this.setScrollerWidth();
    
    this.scroller.observe('click', this.scrollerClicked.bind(this));
    Event.observe(window, 'resize', this.windowResized.bind(this));
    
    document.observe('picture:clicked', function(e){
      this.animateScrollerAndHandle(e);
      this.highlightClickedImage(e);
    }.bind(this));
    
    this.setupScroller();
    
    this.tryToShowPicture();
  },
  setupHistoryManager: function(){
    /*
    
    Connect the Gallery to the History Manager
    */
    this.historyManager = historyManager;
    this.historyKey = this.categorySansSpaces + '\/';
    
    this.historyManager.register(
      this.historyKey,
      [this.pictureLinks[0].readAttribute('href').remove('#')], // the href of the galleries first picture, something like 'Apartments-1'
      function(values) {
        this.showPicture(this.findPicture(values[0]));
      }.bind(this),
      function(values) {
        return String(this.historyKey + values[0]);
      }.bind(this),
      this.historyKey + '([\\w_-]*)' // had to modify regex to match structure of history state module
    );
    this.historyManager.start();
  },
  stripHrefsOfSpaces: function () {
    this.pictureLinks.each(function(p){
      p.writeAttribute('href', p.readAttribute('href').gsub(' ', ''));
    });
  },
  setupHrefsOfPictures: function(){
    var pictureHrefs = this.pictureLinks.invoke('readAttribute', 'href'); // [[somename], [anothername]]
    var groupedPictureHrefs = pictureHrefs.groupSort(); // [[somename, somename], [anothername]]
    groupedPictureHrefs.each(function(group, i){
      for (var i=0; i < group.length; i++) {
        group[i] = group[i] + '-' + (i + 1);
      };
    }).flatten();
    this.pictureLinks.each(function(pictureLink, i){
      pictureLink.writeAttribute('href', groupedPictureHrefs.flatten()[i]);
    });
  },
  hideStuffWhileLoading: function(){
    [this.pictures, this.scrollerHandle, this.scrollerBar].flatten()
      .invoke('setStyle', { display: 'none', opacity: 0 });
  },
  showStuffAfterLoad: function(){
    this.showingStuffAfterLoadEffect = new Effect.Parallel(
      [
        /* pictures appear */
        this.pictures.invoke('show').collect(function(p){
          var to = (p == this.affectedImage) ? '1' : this.pictureOpacity;
          return new Effect.Opacity(p, { to: to, sync: true });
        }.bind(this)),
        /* scroller stuff appear */
        [this.scrollerHandle, this.scrollerBar].invoke('show').collect(function(s){
          return new Effect.Appear(s, { sync: true });
        })
      ].flatten(),
      {
        duration: .5,
        afterFinish: function(){ this.showingStuffAfterLoadEffectComplete = true; }.bind(this)
      }
    );
    /* if there isn't an image name specified in the address, use the first link */
    if (!this.pictureName){
      this.pictureName = this.galleryLinks.linkNames[0];
      this.tryToShowPicture();
    }
  },
  affectImage: function(){
    this.affectedImage = this.scroller.down('a[href=#' + this.pictureName + '] img') || this.pictures.first();
    if (this.showingStuffAfterLoadEffectComplete){
      this.fadeInPicture(this.affectedImage);
    }
    this.activateImage(this.affectedImage);
  },
  activateImage: function(t){
    this.pictures.each(function(p){ p._active = false; });
    t._active = true;
  },
  setScrollerWidth: function(){
    this.scrollerWidth = this.widths.inject(0, function(acc, w){
      return acc + w
    }) + this.space;
    this.scroller.setStyle({ width: this.scrollerWidth + 'px' });
  },
  setupImageHovers: function(){
    this.scroller
      .observe('mouseover', function(e){
        var t = $(e.target);
        if (t.match('.picture img') && !t._active) this.fadeInPicture(t);
      }.bind(this))
      .observe('mouseout', function(e){
        var t = $(e.target);
        if (t.match('.picture img') && !t._active) this.fadeOutPicture(t);
      }.bind(this))
  },
  scrollerClicked: function(e){
    /* when a picture is clicked */
    var t = $(e.target);
    if (t.match('.picture img')){
      t.up().blur();
      t.fire('picture:clicked');
      var value = t.up().readAttribute('href').remove('#');
      this.historyManager.setValue(this.historyKey, 0, value); // manually update history
      this.historyManager.update();
      e.stop();
    }
  },
  setupScroller: function(redefined){
    this.scrollerHandle.setStyle({ width: this.calculateScrollerHandleWidth() + 'px' });
    /*
      TODO find perfect expression!
    */
    if (this.pictures.size() < 5) {
      var d = document.viewport.getWidth() / this.scroller.getWidth(); /* no idea what the significance of 3.2 is */
    } else {
      var d = document.viewport.getWidth() / this.scroller.getWidth()/ 3.2; /* no idea what the significance of 3.2 is */
    }
    if (this.scrollerObject) {
      var v = this.scrollerObject.value;
      this.scrollerObject.dispose();
    }
    this.scrollerObject = new Control.Slider(this.scrollerHandle, this.scrollerBar, {
      range: $R(0 - d, 1 + d), /* eureka! */
      onSlide: this.updateScroller.bind(this),
      onChange: this.updateScrollerFromHandleOrRelease.bind(this)
    });
    
    if (redefined) this.scrollerObject.setValue(v);
  },
  calculateScrollerHandleWidth: function(){
    /* size the scroller handle in proportion to the viewable gallery area */
    return this.scrollerHandleWidth = Math.round(this.scrollerBar.getWidth() * ((this.scrollerBar.getWidth() / this.scroller.getWidth())));
  },
  updateScroller: function(sliderValue){
    this.scroller.setStyle({ left: (sliderValue * this.rangeOfMotion()) + 'px' });
  },
  updateScrollerFromHandleOrRelease: function(sv, obj){
    this.updateScroller(sv, obj);
    /* if using the handle */
    if (!this.moving){
      /* find out what image is hitting the centerLine */
      var pic = this.pictures.find(function(picture, i){
        /* if the picture begins before the centerline and ends after the center point */
        if (picture.viewportOffset()[0] < this.centerlineValue() && picture.viewportOffset()[0] + picture.getWidth() > this.centerlineValue()){
          return picture;
        }
      }.bind(this));
      /* show the image, and associated links and text content */
      if (pic){
        pic.fire('picture:clicked');
        this.fadeInPicture(pic);
        var value = pic.up().readAttribute('href').remove('#');
        this.historyManager.setValue(this.historyKey, 0, value); // manually update history
        this.historyManager.update();
      }
    }
  },
  animateScrollerAndHandle: function(e){
    /* called from picture:clicked
       * used to slide the scroller
       * used only when an image or link is clicked
    */
    if (!this.moving){
      /* move left property to the sum of the widths of the previous pictures */
      this.moving = true;
      var _i = this.pictures.indexOf(e.target || e);
      var v = this.arrayOfLeftPositionExpressions[_i]() / this.rangeOfMotion();
      var t = new Effect.Tween(this.scrollerObject, this.scrollerObject.value, v, {
        duration: .6,
        beforeStart: function(){ this.moving = true; }.bind(this),
        afterFinish: function(){ this.moving = false; }.bind(this)
      }, 'setValue');
    }
  },
  calculateDimensions: function(){
    this.widths = this.pictures.invoke('getWidth');
    this.cumulativePictureWidths = [0];
    this.widths.inject(0, function(acc, value){
      this.cumulativePictureWidths.push(value + acc);
      return value + acc;
    }.bind(this));
    this.createArrayOfLeftPositionExpressions();
  },
  rangeOfMotion: function(){
    return this.arrayOfLeftPositionExpressions.first()() + this.arrayOfLeftPositionExpressions.last()();
  },
  createArrayOfLeftPositionExpressions: function(){
    /* This array of left position expressions is used to position the pictures so that they are centered horizontally,
       the page can vary in width, and requires the left positions to be dynamic */
    this.arrayOfLeftPositionExpressions = [];
    this.widths.each(function(width, i){
      this.arrayOfLeftPositionExpressions.push(
        function(){
          return this.centerlineValue() - (width / 2) - this.cumulativePictureWidths[i]
        }.bind(this)
      );
    }.bind(this));
  },
  centerlineValue: function(){
    return document.viewport.getWidth() / 2
  },
  highlightClickedImage: function(e){
    /* called from picture:clicked */
    /* image is already highlighted from the hover effect */
    this.activateImage(e.target);
    /* fade out the previously activated image */
    this.fadeOutPicture(this.pictures.find(function(p, i){
      if (p.getStyle('opacity') != this.pictureOpacity && !p._active) return p;
    }.bind(this)));
  },
  fadeInPicture: function(t){
    if (t) t.morph({ 'opacity': '1' }, this.imageHoverMorphOptions);
  },
  fadeOutPicture: function(t){
    if (t) t.morph({ 'opacity': this.pictureOpacity }, this.imageHoverMorphOptions);
  },
  tryToShowPicture: function(){
    /* let the actual picture fire the event */
    if (this.pictureName){
      this.pictures.find(function(p, i){
        return p._active;
      }).fire('picture:clicked');
    }
  },
  showPicture: function(e){
    /* called from links link:clicked */
    this.pictureName = $(e.target || e).readAttribute('href').selectFromTo('#'); // turns something like 'http://wat.com/#SomeCategoryPicture-1' into 'Picture-1'
    this.affectImage();
    this.tryToShowPicture();
  },
  findPicture: function(pictureName){
    /* name: Picture-1 */
    return this.pictureLinks.find(function(p){
      return (p.readAttribute('href') == '#' + pictureName);
    }.bind(this));
  },
  windowResized: function(e){
    this.setScrollerWidth();
    this.setupScroller(true);
  }
});



/* Gallery Link Controls */
var GalleryLinks = Class.create({
  initialize: function(id){
    this.nav = $(id);
    if (!this.nav) return;
    
    this.links = this.nav.select('a');
    
    /* formats href's */
    this.links.each(function(l, i){
      l.writeAttribute('href', l.readAttribute('href').remove(' '));
    }.bind(this));
    
    this.linkHrefs = this.links.pluck('href');
    /* this.linkNames is used in gallery.showStuffAfterLoad */
    this.linkNames = this.linkHrefs.invoke('selectFromTo', '#');
    
    this.tweakLineHeight();
    
    document.observe('picture:clicked', this.highlightLink.bind(this));
    this.nav.observe('click', this.navClicked.bind(this));
  },
  highlightLink: function(e){
    /* called from gallery picture:clicked */
    this.links
      .invoke('removeClassName', 'active')
      .find(function(link){
        var firstRel = $(e.target).up().readAttribute('href').split('-')[0] + '-1';
        return (link.readAttribute('href') == firstRel);
      }).addClassName('active');
  },
  navClicked: function(e){
    if ($(e.target).match('a') && !gallery.moving) {
      $(e.target).fire('link:clicked');
      var value = $(e.target).readAttribute('href').remove('#');
      gallery.historyManager.setValue(gallery.historyKey, 0, value); // manually update history
      gallery.historyManager.update();
    }
    e.target.blur();
    e.stop();
  },
  tweakLineHeight: function(){
    this.links.invoke('up').each(function(li, i){
      if (li.getHeight() > 35) li.addClassName('multiLine');
    });
  }
});



/* Gallery Text Content
  * used from within the Gallery class
*/
var GalleryTextContent = Class.create({
  initialize: function(titleClassName, descriptionClassName){
    this.titles = $$('.' + titleClassName);
    this.descriptions = $$('.' + descriptionClassName);
    
    if (this.titles.length < 1 || this.descriptions.length < 1 || this.titles.size() != this.descriptions.size()) return;
    
    this.hideGalleryContent();
    this.modifyRels();
    document.observe('picture:clicked', this.showContent.bind(this));
  },
  hideGalleryContent: function(){
    /* hide all gallery content, called prior to showing one */
    [this.titles, this.descriptions].flatten().invoke('hide');
  },
  modifyRels: function () {
    [this.titles, this.descriptions].flatten().each(function(p, i){
      p.writeAttribute('rel', p.readAttribute('rel').gsub(' ', ''));
    }.bind(this));
  },
  showContent: function(e){
    // cl('showContent');
    /* called from gallery picture:clicked */
    var rel = $(e.target).up().readAttribute('href').remove('#');
    var firstRel = rel.split('-')[0] + '-1';
    
    var title = this.titles.find(function(el){
      return (el.readAttribute('rel') == firstRel);
    });
    var description = this.descriptions.find(function(el){
      return (el.readAttribute('rel') == firstRel);
    });
    
    // if not already visible
    if (!title.visible() && !description.visible()){
      this.hideGalleryContent();
      [title, description].invoke('show');
    }
  }
});


/* Extending the String prototype with commonly used functions */
Object.extend(String.prototype, {
  remove: function(pattern){
    return this.gsub(pattern, '');
  },
  selectFromTo: function(fromPattern, toPattern){
    var to = (toPattern) ? this.indexOf(toPattern) : this.length;
    return this.substr(this.indexOf(fromPattern) + 1, to);
  }
});


var historyManager = new ProtoHistoryManager();
var gallery = new Gallery('gallery');
