function Gallery(viewer, items, options) {
  /**
   * Possible options:
   *  activators [default: empty]
   *  nexts [default: empty]
   *  prevs [default: empty]
   *  cyclic [default: false]
   */
  if (!options) optiosn = [];
  
  var cyclic = !!options.cyclic;
  var beforeOpenItem = (typeof(options.beforeOpenItem) == 'function' ? options.beforeOpenItem : function () {} )
  var beforeOpenViewer = (typeof(options.beforeOpenViewer) == 'function' ? options.beforeOpenViewer : function () {} )
  var beforeCloseViewer = (typeof(options.beforeCloseViewer) == 'function' ? options.beforeCloseViewer : function () {} )
  
  obj = this;
  obj.current = null;
  obj.viewer = viewer;
  obj.viewer.opened = false;
  obj.items = items;
  obj.activators = options.activators;
  obj.nexts = options.nexts;
  obj.prevs = options.prevs;
  
  if (items.length < 1) {
    alert('Expecting items! Empty array given.');
    return false;
  }
  if (obj.activators && obj.activators.length > 0 && obj.items.length != obj.activators.length) {
    alert('Amount of items must be equal to amount of their activators!');
    return false;
  }
  
  function nev2006() {
    return false;
  }
  
  obj.viewer.open = function() {
    if (obj.current) {
      if (!obj.viewer.opened) {
        beforeOpenViewer(obj);
        obj.viewer.opened = true;
        new Effect.BlindDown(obj.viewer);
      }
    } else {
      obj.openItem(0);
    }
  }
  
  obj.viewer.close = function() {
    if (obj.viewer.opened) {
      beforeCloseViewer(obj);
      obj.viewer.opened = false;
      new Effect.BlindUp(obj.viewer, {
        afterFinish: function() {
          obj.current.hide();
          obj.current = null;
        }
      });
    }
  }
  
  obj.openItem = function(index) {
    if (obj.viewer.opened && obj.current) {
      var previous = obj.current;
      obj.current = obj.items[index];
      previous.hide();
      beforeOpenItem(obj);
      obj.current.show();
    } else {
      obj.current = obj.items[index];
      beforeOpenItem(obj);
      obj.current.show();
      obj.viewer.open();
    }
  }
  
  obj.next = function() {
    if (canNext(obj.current)) obj.openItem((obj.current.index + 1) % (obj.items.length));
  }
  
  obj.prev = function() {
    if (canPrev(obj.current)) obj.openItem((obj.items.length + obj.current.index - 1) % (obj.items.length));
  }
  
  function canNext(item) {
    return item && (cyclic || (item.index + 1 < obj.items.length));
  }
  
  function canPrev(item) {
    return item && (cyclic || item.index > 0);
  }
  
  obj.rewind = function() {
    obj.openItem(0);
  }
  
  obj.items.each(function(item, index) {
    item.index = index;
    item.image = item.getElementsBySelector('img')[0];
  });
  
  obj.activators.each(function(activator, index) {
    activator.item = obj.items[index];
    activator.onclick = nev2006;
    Element.observe(activator, 'click', function() {
      obj.openItem(index);
    });
  });
  
  if (obj.nexts) {
    obj.nexts.each(function(nextObj) {
      nextObj.onclick = nev2006;
      //Element.observe(nextObj, 'click', obj.next);
    });
  }
  
  if (obj.prevs) {
    obj.prevs.each(function(prevObj) {
      prevObj.onclick = nev2006;
      //Element.observe(prevObj, 'click', obj.prev);
    });
  }
  
  displayNavigation = function() {
    obj.nexts.each(function(nextObj) {
      if (canNext(obj.current)) nextObj.show();
    });
    obj.prevs.each(function(prevObj) {
      if (canPrev(obj.current)) prevObj.show();
    });
  }
  
  undisplayNavigation = function() {
    obj.nexts.each(function(nextObj) {
      nextObj.hide();
    });
    obj.prevs.each(function(prevObj) {
      prevObj.hide();
    });
  }
  
  Element.observe(obj.viewer, 'mouseover', displayNavigation);
  Element.observe(obj.viewer, 'mouseout', undisplayNavigation);
  Element.observe(obj.viewer, 'click', function(event) {
    var snappedX = Math.round((Event.pointerX(event) - Position.cumulativeOffset(obj.viewer)[0]));
    if (snappedX < (obj.current.getWidth() / 2)) {
      obj.prev();
    } else {
      obj.next();
    }
  });
  
  return this;
}