Element.implement({
   isDisplayed: function() {
      return this.getStyle('display') != 'none';
   },
   toggle: function() {
      return this[this.isDisplayed() ? 'hide': 'show']();
   },
   hide: function() {
      var d;
      try {
         d = this.getStyle('display');
      } catch(e) {}
      return this.store('originalDisplay', d || 'block').setStyle('display', 'none');
   },
   show: function(display) {
      return this.setStyle('display', display || this.retrieve('originalDisplay') || 'block');
   }
});

Array.implement({
   intersect: function(other) {
      var cpy = this.slice();
      this.each(function(el) {
         if (other.indexOf(el) < 0) {
            cpy.splice(cpy.indexOf(el), 1);
         }
      },
      this);
      return cpy;
   },
   differentiate: function(other) {
      var src = this.slice();
      var cmp = other.slice();
      other.each(function(elem) {
         if (src.indexOf(elem) > -1) {
            src.splice(src.indexOf(elem), 1);
            cmp.splice(cmp.indexOf(elem), 1);
         }
      },
      this);
      return src.combine(cmp);
   },
   getRange: function(start, elements) {
      var res = [];
      var j = 0;
      var upper = start + elements > this.length ? this.length: start + elements;
      if (start >= 0) {
         for (var i = start; i < upper; i++) {
            res[j++] = this[i];
         }
      }
      return res;
   },
   invoke: function(methodName) {
      var args = Array.slice(arguments, 1),
      results = [];
      for (var i = 0,
      j = this.length; i < j; i++) {
         var item = this[i];
         results.push(item[methodName].apply(item, args));
      }
      return results;
   },
   searchBinary: function(value, insert) {
      var h = this.length,
      l = -1,
      m;
      while (h - l > 1) {
         if (this[m = h + l >> 1] < value) {
            l = m;
         } else {
            h = m;
         }
      }
      return this[h] != value ? insert ? h: -1 : h;
   },
   unique: function() {
      return [].combine(this);
   }
});

Element.implement({
   measure: function(fn) {
      var vis = function(el) {
         return !! (!el || el.offsetHeight || el.offsetWidth);
      };
      if (vis(this)) return fn.apply(this);
      var parent = this.getParent(),
      restorers = [],
      toMeasure = [];
      while (!vis(parent) && parent != document.body) {
         toMeasure.push(parent.expose());
         parent = parent.getParent();
      }
      var restore = this.expose();
      var result = fn.apply(this);
      restore();
      toMeasure.each(function(restore) {
         restore();
      });
      return result;
   },
   expose: function() {
      if (this.getStyle('display') != 'none') return $empty;
      var before = this.style.cssText;
      this.setStyles({
         display: 'block',
         position: 'absolute',
         visibility: 'hidden'
      });
      return function() {
         this.style.cssText = before;
      }.bind(this);
   },
   getDimensions: function(options) {
      options = $merge({
         computeSize: false
      },
      options);
      var dim = {};
      var getSize = function(el, options) {
         return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
      };
      var parent = this.getParent('body');
      if (parent && this.getStyle('display') == 'none') {
         dim = this.measure(function() {
            return getSize(this, options);
         });
      } else if (parent) {
         try {
            dim = getSize(this, options);
         } catch(e) {}
      } else {
         dim = {
            x: 0,
            y: 0
         };
      }
      return $chk(dim.x) ? $extend(dim, {
         width: dim.x,
         height: dim.y
      }) : $extend(dim, {
         x: dim.width,
         y: dim.height
      });
   },
   getComputedSize: function(options) {
      options = $merge({
         styles: ['padding', 'border'],
         plains: {
            height: ['top', 'bottom'],
            width: ['left', 'right']
         },
         mode: 'both'
      },
      options);
      var size = {
         width: 0,
         height: 0
      };
      switch (options.mode) {
      case 'vertical':
         delete size.width;
         delete options.plains.width;
         break;
      case 'horizontal':
         delete size.height;
         delete options.plains.height;
         break;
      }
      var getStyles = [];
      $each(options.plains,
      function(plain, key) {
         plain.each(function(edge) {
            options.styles.each(function(style) {
               getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width': style + '-' + edge);
            });
         });
      });
      var styles = {};
      getStyles.each(function(style) {
         styles[style] = this.getComputedStyle(style);
      },
      this);
      var subtracted = [];
      $each(options.plains,
      function(plain, key) {
         var capitalized = key.capitalize();
         size['total' + capitalized] = size['computed' + capitalized] = 0;
         plain.each(function(edge) {
            size['computed' + edge.capitalize()] = 0;
            getStyles.each(function(style, i) {
               if (style.test(edge)) {
                  styles[style] = styles[style].toInt() || 0;
                  size['total' + capitalized] = size['total' + capitalized] + styles[style];
                  size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
               }
               if (style.test(edge) && key != style && (style.test('border') || style.test('padding')) && !subtracted.contains(style)) {
                  subtracted.push(style);
                  size['computed' + capitalized] = size['computed' + capitalized] - styles[style];
               }
            });
         });
      }); ['Width', 'Height'].each(function(value) {
         var lower = value.toLowerCase();
         if (!$chk(size[lower])) return;
         size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
         size['total' + value] = size[lower] + size['total' + value];
         delete size['computed' + value];
      },
      this);
      return $extend(styles, size);
   }
});

Element.Properties.csstext = {
   set: function(css_text) {
      if (this.get('tag') == 'style') {
         this.erase('csstext');
         if (Browser.Engine.trident && this.styleSheet) {
            this.styleSheet.cssText = css_text;
         } else if (this.sheet) {
            this.appendChild(document.createTextNode(css_text));
         }
      }
      return this;
   },
   get: function() {
      if (this.get('tag') == 'style') {
         var css_text = '';
         if (Browser.Engine.trident && this.styleSheet) {
            css_text = this.styleSheet.cssText;
         } else if (this.sheet) {
            css_text = this.innerHTML;
         }
         return css_text;
      }
   },
   erase: function() {
      if (this.get('tag') == 'style') {
         if (Browser.Engine.trident && this.styleSheet) {
            this.styleSheet.cssText = '';
         } else if (this.sheet && this.firstChild) {
            while (this.firstChild) {
               this.removeChild(this.firstChild);
            }
         }
      }
      return this;
   }
};

Fx.Scroll = new Class({
   Extends: Fx,
   options: {
      offset: {
         'x': 0,
         'y': 0
      },
      wheelStops: true
   },
   initialize: function(element, options) {
      this.element = this.subject = $(element);
      this.parent(options);
      var cancel = this.cancel.bind(this, false);
      if ($type(this.element) != 'element') this.element = $(this.element.getDocument().body);
      var stopper = this.element;
      if (this.options.wheelStops) {
         this.addEvent('start',
         function() {
            stopper.addEvent('mousewheel', cancel);
         },
         true);
         this.addEvent('complete',
         function() {
            stopper.removeEvent('mousewheel', cancel);
         },
         true);
      }
   },
   set: function() {
      var now = Array.flatten(arguments);
      this.element.scrollTo(now[0], now[1]);
   },
   compute: function(from, to, delta) {
      var now = [];
      var x = 2;
      x.times(function(i) {
         now.push(Fx.compute(from[i], to[i], delta));
      });
      return now;
   },
   start: function(x, y) {
      if (!this.check(arguments.callee, x, y)) return this;
      var offsetSize = this.element.getSize(),
      scrollSize = this.element.getScrollSize();
      var scroll = this.element.getScroll(),
      values = {
         x: x,
         y: y
      };
      for (var z in values) {
         var max = scrollSize[z] - offsetSize[z];
         if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z].limit(0, max) : max;
         else values[z] = scroll[z];
         values[z] += this.options.offset[z];
      }
      return this.parent([scroll.x, scroll.y], [values.x, values.y]);
   },
   toTop: function() {
      return this.start(false, 0);
   },
   toLeft: function() {
      return this.start(0, false);
   },
   toRight: function() {
      return this.start('right', false);
   },
   toBottom: function() {
      return this.start(false, 'bottom');
   },
   toElement: function(el) {
      var position = $(el).getPosition(this.element);
      return this.start(position.x, position.y);
   }
});

var Drag = new Class({
   Implements: [Events, Options],
   options: {
      snap: 6,
      unit: 'px',
      grid: false,
      style: true,
      limit: false,
      handle: false,
      invert: false,
      preventDefault: false,
      modifiers: {
         x: 'left',
         y: 'top'
      }
   },
   initialize: function() {
      var params = Array.link(arguments, {
         'options': Object.type,
         'element': $defined
      });
      this.element = $(params.element);
      this.document = this.element.getDocument();
      this.setOptions(params.options || {});
      var htype = $type(this.options.handle);
      this.handles = (htype == 'array' || htype == 'collection') ? $$(this.options.handle) : $(this.options.handle) || this.element;
      this.mouse = {
         'now': {},
         'pos': {}
      };
      this.value = {
         'start': {},
         'now': {}
      };
      this.selection = (Browser.Engine.trident) ? 'selectstart': 'mousedown';
      this.bound = {
         start: this.start.bind(this),
         check: this.check.bind(this),
         drag: this.drag.bind(this),
         stop: this.stop.bind(this),
         cancel: this.cancel.bind(this),
         eventStop: $lambda(false)
      };
      this.attach();
   },
   attach: function() {
      this.handles.addEvent('mousedown', this.bound.start);
      return this;
   },
   detach: function() {
      this.handles.removeEvent('mousedown', this.bound.start);
      return this;
   },
   start: function(event) {
      if (this.options.preventDefault) event.preventDefault();
      this.fireEvent('beforeStart', this.element);
      this.mouse.start = event.page;
      var limit = this.options.limit;
      this.limit = {
         'x': [],
         'y': []
      };
      for (var z in this.options.modifiers) {
         if (!this.options.modifiers[z]) continue;
         if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
         else this.value.now[z] = this.element[this.options.modifiers[z]];
         if (this.options.invert) this.value.now[z] *= -1;
         this.mouse.pos[z] = event.page[z] - this.value.now[z];
         if (limit && limit[z]) {
            for (var i = 2; i--; i) {
               if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
            }
         }
      }
      if ($type(this.options.grid) == 'number') this.options.grid = {
         'x': this.options.grid,
         'y': this.options.grid
      };
      this.document.addEvents({
         mousemove: this.bound.check,
         mouseup: this.bound.cancel
      });
      this.document.addEvent(this.selection, this.bound.eventStop);
   },
   check: function(event) {
      if (this.options.preventDefault) event.preventDefault();
      var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
      if (distance > this.options.snap) {
         this.cancel();
         this.document.addEvents({
            mousemove: this.bound.drag,
            mouseup: this.bound.stop
         });
         this.fireEvent('start', this.element).fireEvent('snap', this.element);
      }
   },
   drag: function(event) {
      if (this.options.preventDefault) event.preventDefault();
      this.mouse.now = event.page;
      for (var z in this.options.modifiers) {
         if (!this.options.modifiers[z]) continue;
         this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
         if (this.options.invert) this.value.now[z] *= -1;
         if (this.options.limit && this.limit[z]) {
            if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])) {
               this.value.now[z] = this.limit[z][1];
            } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])) {
               this.value.now[z] = this.limit[z][0];
            }
         }
         if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);
         if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
         else this.element[this.options.modifiers[z]] = this.value.now[z];
      }
      this.fireEvent('drag', this.element);
   },
   cancel: function(event) {
      this.document.removeEvent('mousemove', this.bound.check);
      this.document.removeEvent('mouseup', this.bound.cancel);
      if (event) {
         this.document.removeEvent(this.selection, this.bound.eventStop);
         this.fireEvent('cancel', this.element);
      }
   },
   stop: function(event) {
      this.document.removeEvent(this.selection, this.bound.eventStop);
      this.document.removeEvent('mousemove', this.bound.drag);
      this.document.removeEvent('mouseup', this.bound.stop);
      if (event) this.fireEvent('complete', this.element);
   }
});
Element.implement({
   makeResizable: function(options) {
      return new Drag(this, $merge({
         modifiers: {
            'x': 'width',
            'y': 'height'
         }
      },
      options));
   }
});

if (!$defined(console)) {
   var console = {
      log: function() {}
   };
}

if (typeof Galleries == 'undefined') {
   Galleries = {
      Components: {},
      Plugins: {},
      Effects: {},
      Constants: {
         notificationGeneratingMessage: '',
         placeholder_width: 10,
         effects: {
            reflex: {
               min_height: 30,
               percents_height: 15,
               opacity: 0.25
            },
            polaroid: {}
         },
         slide_duration: 1000,
         fade_duration: 1000,
         slideshow: {
            pages_to_preload: 5
         },
         thumbnails: {
            pages_to_preload: 1
         },
         slider: {
            pixels_per_second: 50,
            frames_per_second: 50,
            pages_to_preload: 2,
            fast_speed_factor: 8
         },
         viewer: {
            pages_to_preload: 5
         },
         css: {
            trident: {},
            gecko: {},
            webkit: {}
         }
      },
      Utils: {
         instances: {},
         saveInstance: function(id, instance) {
            this.instances['id_' + id] = instance;
         },
         getInstance: function(id) {
            return this.instances['id_' + id];
         },
         log: function() {
            if ($defined(window.console) && $type(window.console.log) == 'function') {
               window.console.log.apply(window.console, arguments);
            }
         },
         stringToBool: function(str) {
            switch (true) {
            case ! $defined(str) : case str === false: case str == 'none': return false;
               break;
            case str == 'true': return true;
               break;
            case ! isNaN(str) : return !! Number(str);
               break;
            }
            return true;
         },
         convertDataType: function(obj) {
            for (var key in obj) {
               if (!$defined(obj[key])) {
                  obj[key] = false;
                  continue;
               }
               if (obj[key] === true || obj[key] === false) {
                  continue;
               }
               if ($type(obj[key]) == 'array') {} else if ($type(obj[key]) == 'object') {
                  obj[key] = this.convertDataType(obj[key]);
               } else {
                  if (!isNaN(obj[key])) {
                     obj[key] = parseFloat(obj[key]);
                  } else {
                     switch (true) {
                     case obj[key] == 'false': case obj[key] == 'none': case obj[key] == '': obj[key] = false;
                        break;
                     case obj[key] == 'true': obj[key] = true;
                        break;
                     }
                  }
               }
            }
            return obj;
         },
         Element: {
            getClassSelector: function(element) {
               return element.get('class').clean().split(' ').map(function(str) {
                  return str.length > 0 ? str.replace(/^/, '.') : ''
               }).join('');
            },
            getComputedSizeSum: function(element, properties) {
               var result = 0,
               value;
               for (var i = 0; i < properties.length; i++) {
                  var value = element.getComputedStyle(properties[i]).toInt();
                  result += isNaN(value) ? 0 : value;
               }
               return result;
            },
            getMarginWidth: function(element) {
               return this.getComputedSizeSum(element, ['margin-left', 'margin-right']);
            },
            getMarginHeight: function(element) {
               return this.getComputedSizeSum(element, ['margin-top', 'margin-bottom']);
            },
            getBorderWidth: function(element) {
               return this.getComputedSizeSum(element, ['border-left-width', 'border-right-width']);
            },
            getBorderHeight: function(element) {
               return this.getComputedSizeSum(element, ['border-top-width', 'border-bottom-width']);
            },
            getPaddingWidth: function(element) {
               return this.getComputedSizeSum(element, ['padding-left', 'padding-right']);
            },
            getPaddingHeight: function(element) {
               return this.getComputedSizeSum(element, ['padding-top', 'padding-bottom']);
            },
            getHorizontalOffsets: function(element) {
               return this.getComputedSizeSum(element, ['padding-left', 'padding-right', 'margin-left', 'margin-right', 'border-left-width', 'border-right-width']);
            },
            getVerticalOffsets: function(element) {
               return this.getComputedSizeSum(element, ['padding-top', 'padding-bottom', 'margin-top', 'margin-bottom', 'border-top-width', 'border-bottom-width']);
            }
         }
      },
      PerformanceTest: {
         tests: {},
         start: function(test_id) {
            if (this.tests[test_id] == undefined) {
               this.tests[test_id] = {
                  start: new Date(),
                  end: null,
                  result: null
               };
            }
         },
         end: function(test_id) {
            if (this.tests[test_id] && this.tests[test_id].end == null) {
               this.tests[test_id].end = new Date();
               this.tests[test_id].result = this.tests[test_id].end.getTime() - this.tests[test_id].start.getTime();
            }
         },
         dump: function(dom_id) {
            if (Browser.Engine.trident && this.resultIE) {
               this.resultIE.value = '';
            }
            for (test_id in this.tests) {
               if (Browser.Engine.trident) {
                  if (!this.resultIE) {
                     this.resultIE = new Element('textarea', {
                        style: 'width: 800px; height: 200px;'
                     }).inject(document.body, 'top');
                  }
                  this.resultIE.value += test_id + '=' + this.tests[test_id].result + 'ms\n';
               }
               Galleries.Utils.log(test_id, '=', this.tests[test_id].result, 'ms');
               delete this.tests[test_id];
            }
         }
      }
   }
   if (!window.location.href.test('gallery_performance_test')) {
      for (var property in Galleries.PerformanceTest) {
         if ($type(Galleries.PerformanceTest[property] == 'function')) {
            Galleries.PerformanceTest[property] = $empty;
         }
      }
   }
}
if (!$defined(Galleries.styleSheets)) {
   Galleries.styleSheets = {
      styles: new Element('style', {
         type: 'text/css',
         id: 'ig_css_styles'
      }).inject(document.documentElement.firstChild),
      index: 0,
      writecss: function(selector, properties) {
         var css = selector + ' {';
         for (var property in properties) {
            css += this.createCssProperty(property, properties[property]);
         }
         css += '}';
         if (Browser.Engine.trident && this.styles.styleSheet) {
            Galleries.PerformanceTest.start(this.index + ' ' + css);
            this.styles.styleSheet.cssText += css;
            Galleries.PerformanceTest.end(this.index + ' ' + css);
            this.index++;
         } else if (this.styles.sheet) {
            this.styles.appendChild(document.createTextNode(css));
         }
      },
      createCssProperty: function(property, value) {
         if ($type(value) != 'string') {
            var map = (Element.Styles.get(property.camelCase()) || '@').split(' ');
            value = $splat(value).map(function(val, i) {
               if (!map[i]) return '';
               return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
            }).join(' ');
         } else if (value == String(Number(value))) {
            value = Math.round(value);
         }
         return property + ':' + value + ';'
      }
   }
   if ($defined(Galleries.Constants.css[Browser.Engine.name])) {
      var browser_css = Galleries.Constants.css[Browser.Engine.name];
      for (var selector in browser_css) {
         Galleries.styleSheets.writecss(selector, browser_css[selector]);
      }
   }
}

Galleries.Components.ComponentBase = new Class({
   domNode: null,
   options: {},
   bounds: {},
   initialize: function(options) {
      for (var key in options) {
         this.options[key] = options[key];
      }
   },
   inject: function(node) {
      node.appendChild(this.domNode);
   },
   appendChild: function(node) {
      this.domNode.appendChild(node);
   },
   getDomNode: function() {
      return $(this.domNode);
   },
   hide: function() {
      return this.domNode.style.display = 'none';
   },
   show: function() {
      return this.domNode.style.display = 'block'
   },
   dispatchEvent: function() {
      this.options.plugin.fireEvent.apply(this.options.plugin, arguments);
   },
   addEvent: function() {
      this.options.plugin.addEvent.apply(this.options.plugin, arguments);
   },
   addEvents: function() {
      this.options.plugin.addEvents.apply(this.options.plugin, arguments);
   },
   removeEvent: function() {
      this.options.plugin.removeEvent.apply(this.options.plugin, arguments);
   },
   setConstant: function() {
      return this.options.plugin.setConstant.apply(this.options.plugin, arguments);
   },
   getConstant: function() {
      return this.options.plugin.getConstant.apply(this.options.plugin, arguments);
   }
});

Galleries.Plugins.PluginBase = new Class({
   Extends: Events,
   Implements: [Options],
   bounds: {
      events: {}
   },
   domNode: null,
   options: {},
   created: false,
   mainEventName: 'gotoItem',
   List: {},
   Navigators: {},
   constants: {},
   type: null,
   currentIndex: null,
   pages: [],
   playOn: false,
   isAdminMode: false,
   notifyGenerating: true,
   admin: {
      initialized: false,
      activated: false,
      resumePlay: false
   },
   initialize: function(wrapper_id, options) {
      Galleries.Utils.convertDataType(options['visualization']);
      Galleries.Utils.convertDataType(options['viewer']);
      this.wrapperId = wrapper_id;
      Galleries.PerformanceTest.start('setOptions');
      this.setOptions(options);
      Galleries.PerformanceTest.end('setOptions');
      this.domNode = $(wrapper_id);
      if (!this.domNode) {
         alert("failed to initialize Gallery - " + this.type);
         return;
      }
      this.instanceId = options.id ? options.id: wrapper_id;
      Galleries.Utils.saveInstance(this.instanceId, this);
      this.domNode.addClass('ig-gallery').addClass('ig-type-' + this.type);
      this.bounds.events.gotoItem = this.gotoItem.bind(this);
      this.bounds.events.buttonNext = this.next.bind(this);
      this.bounds.events.buttonPrev = this.previous.bind(this);
      this.bounds.events.buttonPlay = this.play.bind(this);
      this.bounds.events.buttonPause = this.pause.bind(this);
      this.bounds.events.view = this.view.bind(this);
      this.bounds.events.loadThumbnail = this.loadThumbnail.bind(this);
      this.bounds.events.generateThumbnail = this.generateThumbnail.bind(this);
      this.addEvents(this.bounds.events);
      this.showLoadingAnimation();
      this.attachLoadHandler();
      this.addEvent('afterPreload',
      function() {
         Galleries.PerformanceTest.end('initialize-' + this.type);
         Galleries.PerformanceTest.dump();
      }.bind(this));
      this.isAdminMode = $try(function() {
         return false;
      },
      $lambda(false));
      this.notifyGenerating = this.isAdminMode;
      if (this.isAdminMode) {
         this.addEvent('buttonPause', this.adminHighlightPause.bind(this));
      }
   },
   attachLoadHandler: function() {
      window.addEvent('domready', this.display.bind(this));
   },
   showLoadingAnimation: function() {
      this.domNode.addClass('ig-loading');
      this.domNode.setStyle('height', this.options.visualization.thumbnail_size.height);
   },
   hideLoadingAnimation: function() {
      this.domNode.setStyle('height', '100%');
      this.domNode.removeClass('ig-loading');
   },
   display: function(index) {
      Galleries.PerformanceTest.start('initialize-' + this.type);
      if (!this.created) {
         Galleries.PerformanceTest.start('createComponents');
         this.createComponents();
         Galleries.PerformanceTest.end('createComponents');
         this.created = true;
         this.hideLoadingAnimation();
      }
      this.fireEvent(this.mainEventName, [$pick(index, 0)]);
   },
   gotoItem: function(index) {
      this.List.gotoItem(index);
   },
   syncNavigation: function(index) {
      for (var key in this.Navigators) {
         this.Navigators[key].syncNavigation(index);
      }
      this.currentIndex = index;
   },
   createComponents: function() {},
   inspectStyleOffsets: function() {
      var elements = {};
      elements.ig_thumbs = this.domNode.getElement('.ig-thumbs');
      elements.ig_thumb = elements.ig_thumbs.getElement('.ig-thumb');
      elements.ig_img = elements.ig_thumb.getElement('.ig-img');
      elements.img = elements.ig_img.getElement('img');
      for (var key in elements) {
         var element = elements[key];
         this.setConstant(key + '_offset_x', Galleries.Utils.Element.getHorizontalOffsets(element));
         this.setConstant(key + '_offset_y', Galleries.Utils.Element.getVerticalOffsets(element));
         if (key == 'ig_thumbs') {
            this.setConstant('ig_thumbs_inner_offset_x', Galleries.Utils.Element.getComputedSizeSum(element, ['padding-left', 'padding-right']));
            this.setConstant('ig_thumbs_inner_offset_y', Galleries.Utils.Element.getComputedSizeSum(element, ['padding-top', 'padding-bottom']));
         }
      }
      var img_width = this.getConstant('img_offset_x') + this.options.visualization.thumbnail_size.width;
      var img_height = this.getConstant('img_offset_y') + this.options.visualization.thumbnail_size.height;
      var reflex_height = 0;
      if (this.options.visualization.thumbnail_effect == 'reflection') {
         var reflex_height = img_height * (Galleries.Constants.effects.reflex.percents_height / 100);
         reflex_height = Math.max(reflex_height, Galleries.Constants.effects.reflex.min_height);
         img_height += reflex_height;
      }
      var image_width = img_width + this.getConstant('ig_img_offset_x');
      var image_height = img_height + this.getConstant('ig_img_offset_y');
      var thumbnail_width = image_width + this.getConstant('ig_thumb_offset_x');
      var thumbnail_height = image_height + this.getConstant('ig_thumb_offset_y');
      this.setConstant('reflex_height', reflex_height);
      this.setConstant('img_width', img_width);
      this.setConstant('img_height', img_height);
      this.setConstant('image_width', image_width);
      this.setConstant('image_height', image_height);
      this.setConstant('thumbnail_width', thumbnail_width);
      this.setConstant('thumbnail_height', thumbnail_height);
   },
   play: function() {
      this.playOn = true;
      this.List.play();
   },
   pause: function() {
      this.playOn = false;
      this.List.pause();
   },
   next: function() {
      this.List.gotoNext.apply(this.List, arguments);
   },
   previous: function() {
      this.List.gotoPrevious.apply(this.List, arguments);
   },
   view: function(index, event) {
      var element = event.target;
      var image_data = this.getImageData(index);
      switch (image_data.link_type) {
      case 'viewer':
         if (!$defined(this.viewer)) {
            new Element('div', {
               id: this.wrapperId + '_viewer'
            }).inject(document.body);
            this.viewer = new Galleries.Plugins.Viewer(this.wrapperId + '_viewer', $merge(this.options, {
               id: this.wrapperId + '_viewer'
            }));
            this.viewer.addEvents({
               onShow: this.viewerShow.bind(this),
               onHide: this.viewerHide.bind(this)
            });
            this.viewer.display(index, element);
         } else {
            this.viewer.show(index, element);
         }
         break;
      case 'url':
         if (image_data.link_target == '_self') {
            window.location.href = image_data.link_url;
         } else {
            window.open(image_data.link_url);
         }
         break;
      }
   },
   setCss: function(selector, properties) {
      var prefix = '#' + this.wrapperId + ' ';
      selector = prefix + selector.clean().split(/\s?,\s?/).join(', ' + prefix);
      Galleries.styleSheets.writecss(selector, properties)
   },
   getImageCaption: function(index) {
      return this.getImageData(index).caption;
   },
   getImageData: function(index) {
      if (!$defined(this.options.images[index])) {
         return {};
      }
      return this.options.images[index];
   },
   setConstant: function(name, value) {
      if (!$defined(this.constants[name])) {
         this.constants[name] = value;
      }
   },
   getConstant: function(name) {
      return this.constants[name];
   },
   loadThumbnail: function(loader, node) {
      loader.apply(this);
   },
   generateThumbnail: function(thumbnail) {
      if (this.notifyGenerating && !this.notificationGenerating) {
         this.notificationGenerating = new Element('div', {
            "class": "ig-notification-generating"
         }).grab(new Element('span', {
            text: Galleries.Constants.notificationGeneratingMessage
         })).inject(this.domNode, 'before');
      }
   },
   viewerShow: function() {
      this.fireEvent('viewerShow');
      this.viewer.resumePlay = this.playOn;
      if (this.playOn) {
         this.pause();
      }
   },
   viewerHide: function() {
      this.fireEvent('viewerHide');
      if (this.viewer.resumePlay) {
         this.play();
      }
      this.viewer.resumePlay = false
   },
   adminInitialize: function() {
      this.admin.initialized = true;
   },
   adminActivate: function() {
      if (this.admin.activated === false) {
         if (this.admin.initialized === false) {
            this.adminInitialize()
         }
         this.admin.resumePlay = this.playOn;
         if (this.playOn) {
            this.pause();
         }
         this.admin.activated = true;
      }
   },
   adminDeactivate: function() {
      if (this.admin.activated === true) {
         if (this.admin.resumePlay) {
            this.play();
         }
         this.admin.resumePlay = false;
         this.admin.activated = false;
      }
   },
   adminHighlightPause: function() {
      this.admin.highlighted = false;
   },
   adminHighlightShow: function() {
      this.admin.highlighted = this.playOn;
      if (this.playOn) {
         this.pause();
      }
   },
   adminHighlightHide: function() {
      if (this.admin.highlighted) {
         this.play();
      }
      this.admin.highlighted = false
   },
   destroy: function() {
      this.removeEvents(this.bounds.events);
      this.pause();
      this.domNode.getElements('*').destroy();
      for (var key in this.options) {
         delete this.options[key];
      }
      this.domNode.erase('class');
      delete this['bounds'];
      delete this['List'];
      delete this['Navigators'];
   }
});

Galleries.Effects.Reflection = function(image, options) {
   options = $extend({
      opacity: 0.5,
      container_height: 'auto',
      reflection_height: 150
   },
   options || {});
   var parent = $(image).getParent();
   var image_width;
   var image_height;
   if (Browser.Engine.trident) {
      image.inject(document.body).set('style', 'visibility:hidden;position:absolute;left:-10000px;top:-10000px');
      image_width = image.width;
      image_height = image.height;
      image.inject(parent).set('style', '-ms-interpolation-mode: bicubic;');
   } else {
      image_width = image.width;
      image_height = image.height;
   }
   if (Browser.Engine.trident) {
      var reflection = new Element('img', {
         src: image.src,
         style: 'filter: flipv progid:DXImageTransform.Microsoft.Alpha(opacity=' + (options.opacity * 100) + ',style=1,finishOpacity=0,startx=0,starty=0,finishx=0,finishy=' + (options.reflection_height / image_height * 100) + '); -ms-interpolation-mode: bicubic;'
      });
   } else {
      var reflection = new Element("canvas");
      if (!reflection.getContext) return;
      try {
         var context = reflection.setProperties({
            width: image_width,
            height: options.reflection_height
         }).getContext("2d");
         context.save();
         context.translate(0, image_height - 1);
         context.scale(1, -1);
         context.drawImage(image, 0, 0, image_width, image_height);
         context.restore();
         context.globalCompositeOperation = "destination-out";
         var gradient = context.createLinearGradient(0, 0, 0, options.reflection_height);
         gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - options.opacity) + ")");
         gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
         context.fillStyle = gradient;
         context.rect(0, 0, image_width, options.reflection_height);
         context.fill();
      } catch(e) {
         return;
      }
   }
   new Element('table', {
      "class": "ig-struct",
      width: "100%"
   }).grab(new Element('tbody').grab(new Element('tr').grab(new Element('td', {
      style: 'height:' + (options.container_height - options.reflection_height) + 'px'
   }).grab(image))).grab(new Element('tr').grab(new Element('td', {
      style: 'vailign:top;height:' + options.reflection_height + 'px'
   }).grab(new Element('div', {
      'style': 'overflow: hidden; height: ' + options.reflection_height + 'px'
   }).grab(reflection))))).inject(parent);
}

Galleries.Components.ComponentBase = new Class({
   domNode: null,
   options: {},
   bounds: {},
   initialize: function(options) {
      for (var key in options) {
         this.options[key] = options[key];
      }
   },
   inject: function(node) {
      node.appendChild(this.domNode);
   },
   appendChild: function(node) {
      this.domNode.appendChild(node);
   },
   getDomNode: function() {
      return $(this.domNode);
   },
   hide: function() {
      return this.domNode.style.display = 'none';
   },
   show: function() {
      return this.domNode.style.display = 'block'
   },
   dispatchEvent: function() {
      this.options.plugin.fireEvent.apply(this.options.plugin, arguments);
   },
   addEvent: function() {
      this.options.plugin.addEvent.apply(this.options.plugin, arguments);
   },
   addEvents: function() {
      this.options.plugin.addEvents.apply(this.options.plugin, arguments);
   },
   removeEvent: function() {
      this.options.plugin.removeEvent.apply(this.options.plugin, arguments);
   },
   setConstant: function() {
      return this.options.plugin.setConstant.apply(this.options.plugin, arguments);
   },
   getConstant: function() {
      return this.options.plugin.getConstant.apply(this.options.plugin, arguments);
   }
});

Galleries.Components.ComponentButton = new Class({
   Extends: Galleries.Components.ComponentBase,
   domNode: null,
   options: {
      type: '',
      className: null,
      disabledClassName: 'ig-disabled',
      events: {
         mousedown: null,
         mouseout: null,
         mouseover: null,
         click: null,
         enable: null,
         disable: null
      },
      events_arguments: {
         mousedown: [],
         mouseout: [],
         mouseover: [],
         click: [],
         enable: [],
         disable: []
      },
      defaultEnabled: false
   },
   className: '',
   bounds: {
      events: {}
   },
   isDisabled: null,
   plugin: {},
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.className = this.options.className ? this.options.className: 'ig-btn ig-' + this.options.type;
      this.domNode = new Element('div', {
         'class': this.className
      });
      this.bounds.events["mousedown"] = this.onMousedown.bind(this);
      this.bounds.events["mouseout"] = this.onMouseout.bind(this);
      this.bounds.events["mouseover"] = this.onMouseover.bind(this);
      this.bounds.events["click"] = this.onClick.bind(this);
      if (this.options.defaultEnabled) {
         this.enable();
      }
   },
   onMousedown: function() {
      this.setState('click');
      this.dispatchEvent('mousedown');
   },
   onMouseout: function() {
      this.setState('normal');
      this.dispatchEvent('mouseout');
   },
   onMouseover: function() {
      this.setState('over');
      this.dispatchEvent('mouseover');
   },
   onClick: function(e) {
      this.dispatchEvent('click');
   },
   setState: function(state) {
      this.state = state;
      this.domNode.set('class', this.className);
      if (state != 'normal') {
         this.domNode.addClass('ig-' + state);
      }
   },
   disable: function() {
      if (this.isDisabled === true) {
         return;
      }
      this.isDisabled = true;
      this.domNode.set('class', this.className + ' ' + this.options.disabledClassName);
      this.domNode.removeEvents(this.bounds.events);
      this.dispatchEvent('disable');
   },
   enable: function() {
      if (this.isDisabled === false) {
         return;
      }
      this.isDisabled = false;
      this.domNode.removeClass(this.options.disabledClassName);
      this.domNode.addEvents(this.bounds.events);
      this.dispatchEvent('enable');
   },
   dispatchEvent: function(type) {
      if ($defined(this.options.events[type])) {
         this.parent(this.options.events[type], this.options.events_arguments[type]);
      }
   }
});

Galleries.Components.ComponentButtonsSet = new Class({
   Extends: Galleries.Components.ComponentBase,
   domNode: null,
   options: {
      first_item: 0,
      last_item: 0,
      events: {
         buttonPrev: {},
         buttonNext: {},
         buttonPlay: {},
         buttonPause: {}
      }
   },
   buttons: {},
   bounds: {
      events: {}
   },
   currentIndex: null,
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.domNode = new Element('div');
      if (this.options.inside_buttons) {
         this.buttons.buttonPrev = new Galleries.Components.ComponentButton({
            plugin: this.options.plugin,
            type: 'prev',
            events: this.options.events.buttonPrev
         });
         this.buttons.buttonNext = new Galleries.Components.ComponentButton({
            plugin: this.options.plugin,
            type: 'next',
            events: this.options.events.buttonNext
         });
      }
      if (this.options.play_button) {
         this.buttons.buttonPlay = new Galleries.Components.ComponentButton({
            plugin: this.options.plugin,
            type: 'play',
            events: this.options.events.buttonPlay,
            defaultEnabled: true
         });
         this.buttons.buttonPause = new Galleries.Components.ComponentButton({
            plugin: this.options.plugin,
            type: 'pause',
            events: this.options.events.buttonPause,
            defaultEnabled: true
         });
         this.buttons.buttonPause.hide();
         this.addEvents({
            listPause: function() {
               this.buttons.buttonPause.hide();
               this.buttons.buttonPlay.show();
            }.bind(this),
            listPlay: function() {
               this.buttons.buttonPlay.hide();
               this.buttons.buttonPause.show();
            }.bind(this)
         });
      }
   },
   getCurrentIndex: function() {
      return this.currentItemIndex;
   },
   syncNavigation: function(index) {
      if (index == this.currentIndex) {
         return;
      }
      if (this.options.inside_buttons) {
         if (this.options.loop) {
            this.buttons.buttonPrev.enable();
            this.buttons.buttonNext.enable();
         } else if (index == this.options.first_item) {
            this.buttons.buttonPrev.disable();
            this.buttons.buttonNext.enable();
         } else if (index == this.options.last_item) {
            this.buttons.buttonPrev.enable();
            this.buttons.buttonNext.disable();
         } else {
            this.buttons.buttonPrev.enable();
            this.buttons.buttonNext.enable();
         }
      }
      this.currentIndex = index;
   },
   inject: function(element) {
      for (var button_name in this.buttons) {
         this.buttons[button_name].inject(element);
      }
   },
   getComponentsButtons: function() {
      if (arguments.length > 0) {
         var buttons = {};
         for (var i = 0; i < arguments.length; i++) {
            var button_name = arguments[i];
            if (this.buttons[button_name]) {
               buttons[button_name] = this.buttons[button_name];
            }
         }
         return buttons;
      } else {
         return this.buttons;
      }
   }
});

Galleries.Components.ComponentCaption = new Class({
   Extends: Galleries.Components.ComponentBase,
   options: {
      duration: 'normal'
   },
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.captionTextHolder = new Element('p');
      this.domNode = new Element('div', {
         "class": "ig-caption"
      }).grab(this.captionTextHolder);
      if (Galleries.Utils.stringToBool(this.options.duration)) {
         this.captionTextHolder.set('tween', {
            duration: this.options.duration / 2,
            link: 'chain',
            transition: 'quart:in:out',
            onComplete: function() {
               if (this.captionTextHolder.getStyle('opacity') == 0) {
                  this.captionTextHolder.set('html', this.captionText);
               }
            }.bindWithEvent(this)
         });
      }
      this.addEvent('listStart', this.setCaption.bind(this));
   },
   setCaption: function(index) {
      this.captionText = this.options.plugin.getImageCaption(index);
      if (Galleries.Utils.stringToBool(this.options.duration)) {
         this.captionTextHolder.tween('opacity', '0');
         this.captionTextHolder.tween('opacity', '1');
      } else {
         this.captionTextHolder.set('opacity', '1');
         this.captionTextHolder.set('html', this.captionText);
      }
   }
});

Galleries.Components.ComponentListBase = new Class({
   Extends: Galleries.Components.ComponentBase,
   currentIndex: null,
   itemsCount: 1,
   itemsPerPage: 0,
   options: {
      pages_to_preload: 5
   },
   pages: [],
   notLoadedPages: [],
   componentsPerPage: {},
   tumbnailInstances: [],
   transitionList: {
      set: $lambda(),
      play: $lambda(),
      stop: $lambda(),
      walk: $lambda()
   },
   play: function() {
      this.transitionList.play(true);
   },
   pause: function() {
      this.transitionList.stop();
   },
   gotoItem: function(index, direction, skip_animation) {
      if (index == this.currentIndex) {
         if (skip_animation) {
            this.transitionList.set(index);
         }
         return;
      }
      var prev_item_index = this.currentIndex;
      this.currentIndex = index;
      if (!$defined(direction)) {
         direction = (index > prev_item_index || index == this.pages.getLast()) ? 'next': 'previous';
      }
      this.preloadPages();
      var transition_list_index = this.pages.indexOf(this.currentIndex);
      if (skip_animation) {
         this.onListStart(transition_list_index, prev_item_index);
         this.transitionList.set(transition_list_index);
         this.onListComplete(transition_list_index);
      } else {
         this.transitionList.walk(transition_list_index, true, direction);
      }
   },
   getCurrentIndex: function() {
      return this.currentIndex;
   },
   getItemsCount: function() {
      return this.itemsCount;
   },
   getPageComponents: function(page) {
      return $splat(this.componentsPerPage['page_' + page]);
   },
   setPageComponent: function(page, component) {
      return this.componentsPerPage['page_' + page] = component;
   },
   preloadPages: function(index) {
      if (this.notLoadedPages.length == 0) {
         return;
      }
      this.dispatchEvent('beforePreload');
      index = this.pages.indexOf($pick(index, this.currentIndex, this.pages[0]));
      var scope = [];
      var slice_arguments = [];
      var pages_length = this.pages.length - 1;
      var start_offset = index - this.options.pages_to_preload;
      var end_offset = index + this.options.pages_to_preload + 1;
      var total_pages = (this.options.pages_to_preload * 2) + 1;
      if (total_pages >= pages_length) {
         slice_arguments.push([0]);
      } else if (start_offset >= 0 && end_offset <= pages_length) {
         slice_arguments.push([start_offset, end_offset]);
      } else if (end_offset >= pages_length) {
         slice_arguments.push([start_offset]);
         slice_arguments.push([0, end_offset - 1 - pages_length]);
      } else if (start_offset < 0) {
         slice_arguments.push([start_offset]);
         slice_arguments.push([0, end_offset]);
      }
      for (var i = 0; i < slice_arguments.length; i++) {
         scope = scope.concat(this.pages.slice.apply(this.pages, slice_arguments[i]))
      }
      var pages = this.notLoadedPages.intersect(scope);
      this.loadedPages(pages);
   },
   loadedPages: function(pages) {
      if (pages.length == 0) {
         return;
      }
      for (var i = 0; i < pages.length; i++) {
         this.getPageComponents(pages[i]).invoke('loadContent');
      }
      this.notLoadedPages = this.notLoadedPages.differentiate(pages);
      this.dispatchEvent('afterPreload');
   },
   getPages: function() {
      return this.pages;
   },
   calulatePrevNextItemIndex: function(direction) {
      var index = this.pages.indexOf($pick(this.currentIndex, this.pages[0]));
      var item_index;
      if (direction == 'previous' && index == 0 && this.options.loop) {
         item_index = this.pages.getLast();
      } else if (direction == 'next' && index == this.pages.getLast() && this.options.loop) {
         item_index = this.pages[0];
      } else {
         item_index = this.pages[index + (direction == 'previous' ? -1 : 1)];
      }
      return item_index;
   },
   gotoNext: function() {
      var index = this.calulatePrevNextItemIndex('next');
      if ($type(index) == 'number') {
         this.gotoItem(index, 'next');
      }
   },
   gotoPrevious: function() {
      var index = this.calulatePrevNextItemIndex('previous');
      if ($type(index) == 'number') {
         this.gotoItem(index, 'previous');
      }
   },
   createTransitionList: function(elements, transition_list_options) {
      var options = $extend({
         type: this.options.thumbnail_transition,
         interval: (this.options.slideshow_timeout || 5) * 1000,
         endlessChain: (this.options.loop && this.options.thumbnail_transition == 'slide' && this.itemsCount > 1),
         onBeforeStart: this.onListBeforeStart.bind(this),
         onStart: this.onListStart.bind(this),
         onComplete: this.onListComplete.bind(this),
         onAfterComplete: this.onListAfterComplete.bind(this),
         onPlayStop: this.dispatchEvent.bind(this, ['listPause']),
         onPlayStart: this.dispatchEvent.bind(this, ['listPlay'])
      },
      transition_list_options);
      this.transitionList = new Galleries.Effects.TransitionList(elements, options);
   },
   onListBeforeStart: function(new_index, old_index) {
      this.dispatchEvent('listBeforeStart', [new_index, old_index]);
   },
   onListStart: function(new_index, old_index) {
      this.dispatchEvent('listStart', [new_index, old_index]);
   },
   onListComplete: function(index) {
      index = this.pages[index];
      if (this.transitionList.playOn) {
         this.currentIndex = index;
      }
      this.options.plugin.syncNavigation(index);
      this.preloadPages(index);
      this.dispatchEvent('listComplete', [index])
   },
   onListAfterComplete: function(index) {
      this.dispatchEvent('listAfterComplete', [index]);
   },
   getArrangementPlaceholder: function(index) {
      if (index == 0) {
         return this.options.first_placeholder;
      } else {
         return this.options.images[index - 1].arrangement_placeholder;
      }
   },
   getTumbnailInstances: function() {
      if (this.tumbnailInstances.length == 0) {
         for (var page_id in this.componentsPerPage) {
            this.tumbnailInstances = this.tumbnailInstances.concat($splat(this.componentsPerPage[page_id]).invoke('getTumbnailInstance'));
         }
      }
      return this.tumbnailInstances;
   }
});

Galleries.Components.ComponentListSlideshow = new Class({
   Extends: Galleries.Components.ComponentListBase,
   options: {
      src_key: 'thumb_url',
      auto_scale: false,
      columns: 1,
      rows: 1,
      autoplay: false,
      loop: false
   },
   isFirstLoad: true,
   createOptionsSet: function(item_index) {
      var options = {
         plugin: this.options.plugin,
         thumbnail_size: this.options.thumbnail_size,
         thumbnail_effect: this.options.thumbnail_effect,
         thumbnail_transition: this.options.thumbnail_transition,
         caption_visibility: this.options.caption_visibility,
         item_index: item_index,
         image_data: this.options.images[item_index],
         src_key: this.options.src_key,
         auto_scale: this.options.auto_scale,
         notify_generating: this.options.notify_generating
      };
      return options;
   },
   gotoItem: function(index, force_direction, skip_animation) {
      this.parent.apply(this, arguments);
      if (this.isFirstLoad && this.itemsCount > 1 && this.options.autoplay) {
         this.options.plugin.play();
      }
      this.isFirstLoad = false;
   },
   onListStart: function(new_index, old_index) {
      this.parent.apply(this, arguments);
      if (!this.options.loop && new_index == this.pages[0] && this.pages.getLast() == old_index) {
         this.options.plugin.pause();
      }
   },
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.itemsCount = this.options.images.length;
      this.domNode = new Element('div', {
         'class': 'ig-thumbs'
      });
      var use_endless_chain = (this.options.loop && this.options.thumbnail_transition == 'slide' && this.itemsCount > 1);
      for (var i = 0; i < this.itemsCount; i++) {
         this.pages.push(i);
         var list_item_options = this.createOptionsSet(i);
         if (this.options.create_placeholders) {
            list_item_options.arrangement_placeholder_left = document.createElement('div');
            list_item_options.arrangement_placeholder_left.className = "ig-placeholder ig-placeholder-left";
            list_item_options.arrangement_placeholder_left.innerHTML = this.getArrangementPlaceholder(i);
            list_item_options.arrangement_placeholder_right = document.createElement('div');
            list_item_options.arrangement_placeholder_right.className = "ig-placeholder ig-placeholder-right";
            if ((i + 1) == this.itemsCount) {
               list_item_options.arrangement_placeholder_right.innerHTML = this.getArrangementPlaceholder(this.itemsCount);
            }
         }
         var list_item = new Galleries.Components.ComponentListItem(list_item_options);
         this.setPageComponent(i, list_item);
         list_item.inject(this.domNode);
      }
      if (use_endless_chain) {
         var first_item_copy = new Galleries.Components.ComponentListItem(this.createOptionsSet(0));
         first_item_copy.inject(this.domNode);
         first_item_copy.loadContent();
      }
      this.notLoadedPages = this.pages;
   },
   inject: function() {
      var image_holders = this.domNode.getElements('.ig-thumb').addClass('ig-slide');
      this.parent.apply(this, arguments);
   }
});

Galleries.Components.ComponentListItem = new Class({
   Extends: Galleries.Components.ComponentBase,
   domNode: null,
   options: {
      auto_scale: false,
      image_data: {
         image_attributes: {},
         caption: ''
      },
      caption_visibility: false,
      force_caption_text: false,
      arrangement_placeholder_left: false,
      arrangement_placeholder_right: false
   },
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.processImageAttributes();
      this.domNode = new Element('div', this.options.image_data.image_attributes);
      this.domNode.className = "ig-thumb";
      if (this.options.arrangement_placeholder_left) {
         this.domNode.appendChild(this.options.arrangement_placeholder_left);
      }
      if (this.options.arrangement_placeholder_right) {
         this.domNode.appendChild(this.options.arrangement_placeholder_right);
      }
      this.thumbnail = new Galleries.Components.ComponentThumbnail(this.options);
      this.thumbnail.inject(this.domNode);
      if (this.options.caption_visibility) {
         var caption = this.options.image_data.caption;
         if ((!$defined(caption) || caption.length == 0) && this.options.force_caption_text) {
            caption = '&nbsp;';
         }
         new Element('div', {
            "class": "ig-caption"
         }).grab(new Element('p', {
            "class": this.options.capption_text_class,
            "html": caption
         })).inject(this.domNode);
      }
   },
   inject: function() {
      this.parent.apply(this, arguments);
   },
   loadContent: function() {
      this.thumbnail.loadContent();
   },
   processImageAttributes: function() {
      if (!$defined(this.options.image_data.image_attributes)) {
         this.options.image_data.image_attributes = {};
      }
      if (!$defined(this.options.image_data.image_attributes.id)) {
         var id = this.options.image_data.id ? this.options.image_data.id: ('e' + (Native.UID++));
         this.options.image_data.image_attributes.id = id;
      }
   },
   getTumbnailInstance: function() {
      return this.thumbnail;
   }
});

Galleries.Components.ComponentNavigation = new Class({
   Extends: Galleries.Components.ComponentBase,
   domNode: null,
   options: {
      first_item: 0,
      last_item: 0
   },
   plugin: {},
   currentIndex: null,
   buttons: {},
   cache: {
      pages: {},
      selectedElement: null
   },
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.domNode = new Element('div', {
         'class': 'ig-nav'
      });
   },
   inject: function(node) {
      this.parent.apply(this, arguments);
      switch (this.options.type) {
      case 'numbers':
         this.createNumbers();
         break;
      case 'buttons':
         this.createButtons();
         break;
      case 'buttons_and_numbers':
         this.createButtonsAndNumbers();
         break;
      case 'dots':
         this.createDots();
         break;
      }
   },
   syncNavigation: function(index) {
      if (index == this.currentIndex) {
         return;
      }
      var element = this.domNode.getElement('*[item_index=' + index + ']');
      if (element && element.get('state') != 'selected') {
         this.setStateSelected(element, true);
      }
      if ($defined(this.buttons.buttonPrev) && $defined(this.buttons.buttonNext)) {
         this.setButtonsState(index);
      }
      this.currentIndex = index;
   },
   getCurrentIndex: function() {
      return this.currentIndex;
   },
   addElementEvents: function(element) {
      var element_events = {
         click: this.setStateSelected.bind(this, [element]),
         mousedown: this.setStateClicked.bind(this, [element]),
         mouseover: this.setStateRollover.bind(this, [element]),
         mouseout: this.setStateNormal.bind(this, [element])
      }
      element.addEvents(element_events);
      element.store('element_events', element_events);
   },
   attachElementEvents: function(element) {
      var element_events = element.retrieve('element_events');
      element.addEvents(element_events);
   },
   detachElementEvents: function(element) {
      var element_events = element.retrieve('element_events');
      element.removeEvents(element_events);
   },
   setElementStateSelected: function(element) {
      element.set('state', 'selected');
      element.addClass('ig-selected');
   },
   setStateSelected: function(element, skipGotoItem) {
      if (this.cache.selectedElement) {
         if (this.cache.selectedElement == element) {
            return;
         }
         this.attachElementEvents(this.cache.selectedElement);
         this.setStateNormal(this.cache.selectedElement);
      }
      this.cache.selectedElement = element;
      this.setElementStateSelected(element);
      this.detachElementEvents(element);
      if (element.get('item_index') && !skipGotoItem) {
         var index = element.get('item_index').toInt();
         this.dispatchEvent('gotoItem', [index]);
      }
   },
   setStateClicked: function(element) {
      element.set('state', 'clicked');
      element.addClass('ig-click');
   },
   setStateRollover: function(element) {
      element.set('state', 'rollover');
      element.addClass('ig-over');
   },
   setStateNormal: function(element) {
      element.set('state', 'normal');
      element.removeClass('ig-over');
      element.removeClass('ig-click');
      element.removeClass('ig-selected');
   },
   setButtonsState: function(index) {
      if (this.options.loop) {
         this.buttons.buttonPrev.enable();
         this.buttons.buttonNext.enable();
      } else if (index == this.options.first_item) {
         this.buttons.buttonPrev.disable();
         this.buttons.buttonNext.enable();
      } else if (index == this.options.last_item) {
         this.buttons.buttonPrev.enable();
         this.buttons.buttonNext.disable();
      } else {
         this.buttons.buttonPrev.enable();
         this.buttons.buttonNext.enable();
      }
   },
   createNumbers: function() {
      this.domNode.addClass('ig-numbers');
      for (var i = 0; i < this.options.pages.length; i++) {
         var node = new Element('div', {
            'class': 'ig-number',
            item_index: this.options.pages[i]
         }).grab(new Element('p', {
            text: i + 1
         })).inject(this.domNode);
         this.addElementEvents(node);
      }
   },
   createButtons: function() {
      this.domNode.addClass('ig-buttons');
      var holder = new Element('tr');
      this.domNode.grab(new Element('table', {
         "align": "center",
         "class": "ig-struct"
      }).grab(new Element('tbody').grab(holder)));
      this.buttons.buttonPrev = new Galleries.Components.ComponentButton({
         plugin: this.options.plugin,
         type: 'prev',
         events: {
            click: 'onButtonPrev'
         },
         className: 'ig-btn ig-prev'
      });
      this.buttons.buttonPrev.inject(new Element('td').inject(holder));
      this.buttons.buttonNext = new Galleries.Components.ComponentButton({
         plugin: this.options.plugin,
         type: 'next',
         events: {
            click: 'onButtonNext'
         },
         className: 'ig-btn ig-next'
      });
      this.buttons.buttonNext.inject(new Element('td').inject(holder));
   },
   createButtonsAndNumbers: function() {
      this.domNode.addClass('ig-buttons-nums');
      var holder = new Element('tr');
      this.domNode.grab(new Element('table', {
         "align": "center",
         "class": "ig-struct"
      }).grab(new Element('tbody').grab(holder)));
      this.buttons.buttonPrev = new Galleries.Components.ComponentButton({
         plugin: this.options.plugin,
         type: 'prev',
         events: {
            click: 'onButtonPrev'
         },
         className: 'ig-btn ig-prev'
      });
      this.buttons.buttonPrev.inject(new Element('td').inject(holder));
      var number = new Element('div', {
         'class': 'ig-num ig-num1',
         'text': '1'
      });
      new Element('td').inject(holder).grab(number);
      new Element('td').inject(holder).grab(new Element('div', {
         'class': 'ig-num ig-delim',
         'text': '/'
      }));
      new Element('td').inject(holder).grab(new Element('div', {
         'class': 'ig-num ig-num2',
         'text': this.options.pages.length
      }));
      this.buttons.buttonNext = new Galleries.Components.ComponentButton({
         plugin: this.options.plugin,
         type: 'next',
         events: {
            click: 'onButtonNext'
         },
         className: 'ig-btn ig-next'
      });
      this.buttons.buttonNext.inject(new Element('td').inject(holder));
      this.addEvent('listComplete',
      function(item_index) {
         this.element.set('text', this.pages.indexOf(item_index) + 1)
      }.bind({
         element: number,
         pages: this.options.pages
      }));
   },
   createDots: function() {
      this.domNode.addClass('ig-dots');
      for (var i = 0; i < this.options.pages.length; i++) {
         var node = new Element('div', {
            'class': 'ig-dot',
            item_index: this.options.pages[i]
         }).inject(this.domNode);
         this.addElementEvents(node);
      }
   }
});

Galleries.Components.ComponentThumbnail = new Class({
   Extends: Galleries.Components.ComponentBase,
   domNode: null,
   options: {
      auto_scale: false,
      image_data: {},
      src_key: 'thumb_url',
      thumbnail_effect: false,
      notify_generating: false
   },
   plugin: {},
   imageSource: null,
   imageSourceStatus: 'loading',
   canvasSize: {
      width: null,
      height: null
   },
   imageSize: {
      width: null,
      height: null
   },
   initialize: function(options) {
      this.parent.apply(this, arguments);
      this.imageSource = String(this.options.image_data[this.options.src_key]);
      if (this.imageSource.test(/\.000$/)) {
         this.imageSourceStatus = 'generating';
         if (this.options.notify_generating) {
            this.dispatchEvent('generateThumbnail', [this]);
         }
      }
      this.domNode = document.createElement('div');
      this.image = new Element('img');
      var table = document.createElement('table');
      var table_body = document.createElement('tbody');
      var table_row = document.createElement('tr');
      this.imageHolder = document.createElement('td');
      this.domNode.className = 'ig-img';
      table.align = 'center';
      table.className = "ig-struct";
      this.imageHolder.className = "ig-" + this.imageSourceStatus;
      table.appendChild(table_body);
      table_body.appendChild(table_row);
      table_row.appendChild(this.imageHolder);
      this.imageHolder.appendChild(this.image);
      this.domNode.appendChild(table);
      this.bounds.events = {
         load: this.onImageLoad.bind(this),
         click: this.onImageClick.bindWithEvent(this),
         error: this.onImageError.bind(this)
      };
      this.image.addEvents(this.bounds.events);
   },
   loadContent: function() {
      this.dispatchEvent('loadThumbnail', [this.setSource.bind(this), this.domNode]);
   },
   setSource: function() {
      this.image.src = this.options.image_data[this.options.src_key];
   },
   onImageLoad: function() {
      if (Browser.Engine.trident) {
         this.image.addClass('ig-loading').inject(document.body);
      }
      this.imageSize = {
         width: this.image.width,
         height: this.image.height
      };
      if (Browser.Engine.trident) {
         this.imageHolder.appendChild(this.image).removeClass('ig-loading');
      }
      this.loaded = true;
      if (this.options.auto_scale) {
         this.scaleImage();
      }
      if (Galleries.Utils.stringToBool(this.options.thumbnail_effect)) {
         this.addEffect(this.options.thumbnail_effect);
      }
      this.image.set('tween', {
         duration: 1000,
         transition: 'sine:out'
      });
      this.image.set("opacity", "0");
      this.imageHolder.className = '';
      this.image.tween("opacity", "1");
   },
   onImageError: function() {},
   onImageClick: function(event) {
      this.dispatchEvent('view', [this.options.item_index, event]);
   },
   addEffect: function(effect) {
      if (effect == 'reflection') {
         var options = {
            opacity: Galleries.Constants.effects.reflex.opacity,
            container_height: this.getConstant('img_height'),
            reflection_height: this.getConstant('reflex_height')
         }
         Galleries.Effects.Reflection(this.image, options)
      } else if (effect == 'polaroid') {}
   },
   setCanvasSize: function(width, height) {
      this.canvasSize = {
         width: width,
         height: height
      };
      if (this.options.auto_scale && this.loaded) {
         this.scaleImage();
      }
   },
   scaleImage: function() {
      var image_size = this.getImageSize();
      this.image.width = image_size.width;
      this.image.height = image_size.height;
   },
   getImageSize: function() {
      var options_size_key = this.options.src_key.replace('_url', '') + '_size';
      var image_size = {
         width: $defined(this.imageSize.width) ? this.imageSize.width: parseInt(this.options.image_data[options_size_key].width),
         height: $defined(this.imageSize.height) ? this.imageSize.height: parseInt(this.options.image_data[options_size_key].height)
      };
      if (this.canvasSize.width < image_size.width || this.canvasSize.height < image_size.height) {
         var x_ratio = this.canvasSize.width / image_size.width;
         var y_ratio = this.canvasSize.height / image_size.height;
         if ((x_ratio * image_size.height) <= this.canvasSize.height) {
            image_size.width = Math.ceil(x_ratio * image_size.width);
            image_size.height = Math.ceil(x_ratio * image_size.height);
         } else {
            image_size.width = Math.ceil(y_ratio * image_size.width);
            image_size.height = Math.ceil(y_ratio * image_size.height);
         }
      }
      return image_size;
   }
});

Galleries.Plugins.Slideshow = new Class({
   Extends: Galleries.Plugins.PluginBase,
   type: 'slideshow',
   mainEventName: 'gotoItem',
   options: {
      visualization: {
         thumbnail_size: {
            width: 600,
            height: 400
         },
         caption_visibility: true,
         navigation: "dots",
         inside_buttons: true,
         thumbnail_transition: "slide",
         thumbnail_effect: false,
         play_button: true,
         autoplay: false,
         slideshow_timeout: 5,
         loop: true
      },
      images: []
   },
   initialize: function(wrapper_id, options) {
      this.parent(wrapper_id, options);
      var effect = this.options.visualization.thumbnail_effect;
      this.domNode.addClass('ig-effect-' + (effect == false ? 'none': effect));
   },
   createComponents: function() {
      this.parent();
      this.listHolder = new Element('td');
      this.galleryBody = new Element('table', {
         "align": "center",
         "class": "ig-struct"
      }).grab(new Element('tbody').grab(new Element('tr').grab(this.listHolder)));
      this.domNode.grab(this.galleryBody);
      if (this.options.visualization.caption_visibility) {
         Galleries.PerformanceTest.start('createComponentCaption');
         this.captionHolder = new Element('div');
         this.domNode.grab(this.captionHolder);
         var transition_type = this.options.visualization.thumbnail_transition;
         var caption = new Galleries.Components.ComponentCaption({
            plugin: this,
            duration: Galleries.Constants[transition_type + '_duration']
         });
         caption.inject(this.captionHolder);
         Galleries.PerformanceTest.end('createComponentCaption');
      }
      Galleries.PerformanceTest.start('createComponentListSlideshow');
      var list_options = {
         plugin: this,
         thumbnail_size: this.options.visualization.thumbnail_size,
         thumbnail_transition: this.options.visualization.thumbnail_transition,
         thumbnail_effect: this.options.visualization.thumbnail_effect,
         autoplay: this.options.visualization.autoplay,
         slideshow_timeout: this.options.visualization.slideshow_timeout,
         loop: this.options.visualization.loop,
         caption_visibility: false,
         images: this.options.images,
         src_key: 'thumb_url',
         auto_scale: false,
         notify_generating: this.notifyGenerating,
         pages_to_preload: Galleries.Constants.slideshow.pages_to_preload,
         first_placeholder: this.options.first_arrangement_placeholder,
         create_placeholders: $defined(this.options.first_arrangement_placeholder)
      };
      this.List = new Galleries.Components.ComponentListSlideshow(list_options);
      this.List.inject(this.listHolder);
      Galleries.PerformanceTest.end('createComponentListSlideshow');
      Galleries.PerformanceTest.start('inspectStyleOffsets');
      this.inspectStyleOffsets();
      Galleries.PerformanceTest.end('inspectStyleOffsets');
      Galleries.PerformanceTest.start('setGalleryDimensions');
      this.setGalleryDimensions();
      Galleries.PerformanceTest.end('setGalleryDimensions');
      Galleries.PerformanceTest.start('createTransitionList');
      this.createTransitionList();
      Galleries.PerformanceTest.end('createTransitionList');
      var items_count = this.List.getItemsCount();
      if (items_count > 1) {
         this.pages = this.List.getPages();
         if (Galleries.Utils.stringToBool(this.options.visualization.thumbnail_effect)) {
            this.setCss('.ig-slide', {
               width: this.getConstant('image_width'),
               height: this.getConstant('image_height')
            });
         } else {
            this.setCss('.ig-slide', {
               width: this.getConstant('image_width')
            });
         }
         if (this.options.visualization.inside_buttons || this.options.visualization.play_button) {
            Galleries.PerformanceTest.start('createComponentButtonsSet');
            var buttons_set_options = {
               plugin: this,
               first_item: this.pages[0],
               last_item: this.pages.getLast(),
               inside_buttons: this.options.visualization.inside_buttons,
               play_button: this.options.visualization.play_button,
               autoplay: this.options.visualization.autoplay,
               loop: this.options.visualization.loop,
               events: {
                  buttonPrev: {
                     click: 'onButtonPrev'
                  },
                  buttonNext: {
                     click: 'onButtonNext'
                  },
                  buttonPlay: {
                     click: 'onButtonPlay'
                  },
                  buttonPause: {
                     click: 'onButtonPause'
                  }
               }
            };
            this.Navigators.ButtonsSet = new Galleries.Components.ComponentButtonsSet(buttons_set_options);
            var list_node = this.List.getDomNode();
            this.Navigators.ButtonsSet.inject(list_node);
            Galleries.PerformanceTest.end('createComponentButtonsSet');
            Galleries.PerformanceTest.start('setButtonPosition');
            var thumbnail_height = this.getConstant('thumbnail_height');
            if (this.options.visualization.thumbnail_effect == 'reflection') {
               thumbnail_height -= this.getConstant('reflex_height');
            }
            var buttons = this.Navigators.ButtonsSet.getComponentsButtons();
            for (var button_name in buttons) {
               var element = buttons[button_name].getDomNode();
               var height = Element.getComputedStyle(element, 'height').toInt();
               if (button_name == 'buttonPlay' || button_name == 'buttonPause') {
                  var style_top = (thumbnail_height - height);
               } else {
                  var style_top = thumbnail_height / 2;
               }
               Element.setStyle(element, 'top', style_top);
            }
            Galleries.PerformanceTest.end('setButtonPosition');
         }
         if (this.options.visualization.navigation) {
            Galleries.PerformanceTest.start('createComponentNavigation');
            this.navigationHolder = new Element('td');
            this.domNode.grab(new Element('table', {
               "align": "center",
               "class": "ig-struct"
            }).grab(new Element('tbody').grab(new Element('tr').grab(this.navigationHolder))));
            var navigation_options = {
               plugin: this,
               first_item: this.pages[0],
               last_item: this.pages.getLast(),
               pages: this.pages,
               type: this.options.visualization.navigation,
               loop: this.options.visualization.loop
            };
            this.Navigators.Navigation = new Galleries.Components.ComponentNavigation(navigation_options);
            this.Navigators.Navigation.inject(this.navigationHolder);
            Galleries.PerformanceTest.end('createComponentNavigation');
         }
      }
      if (this.options.visualization.thumbnail_effect == 'reflection') {
         if (this.options.visualization.caption_visibility) {
            var offsets = ['ig_thumbs_inner_offset_x', 'ig_thumbs_inner_offset_y', 'ig_thumb_offset_x', 'ig_thumb_offset_y', 'ig_img_offset_x', 'ig_img_offset_y', 'img_offset_x', 'img_offset_y'];
            var offsets_sum = {
               left: 0,
               top: 0
            };
            for (var i = 0; i < offsets.length; i++) {
               offsets_sum[(i % 2 == 0) ? 'left': 'top'] += this.getConstant(offsets[i]) / 2;
            }
            this.captionHolder.setStyles({
               position: 'absolute',
               left: offsets_sum.left,
               top: this.options.visualization.thumbnail_size.height + offsets_sum.top,
               width: this.options.visualization.thumbnail_size.width,
               overflow: "hidden",
               height: this.getConstant('reflex_height')
            }).inject(this.List.getDomNode());
         }
         if (Browser.Engine.trident && this.options.visualization.thumbnail_transition == 'slide') {
            this.setCss('.ig-mask', {
               position: 'relative'
            });
         }
      }
   },
   createTransitionList: function() {
      this.List.createTransitionList(this.domNode.getElements('.ig-slide'));
   },
   setGalleryDimensions: function() {
      this.setCss('.ig-img td', {
         width: this.getConstant('img_width'),
         height: this.getConstant('img_height')
      });
      this.setCss('.ig-thumb', {
         width: this.getConstant('image_width')
      });
      this.setCss('.ig-thumbs', {
         width: this.getConstant('thumbnail_width')
      });
      this.setCss('.ig-mask', {
         width: this.getConstant('thumbnail_width')
      });
   },
   adminInitialize: function() {
      this.parent();
      this.setCss('.ig-placeholder', {
         width: Galleries.Constants.placeholder_width,
         height: this.getConstant('img_height')
      });
      this.setCss('.sk-obj-placeholder', {
         width: Galleries.Constants.placeholder_width,
         height: this.getConstant('img_height')
      });
   },
   adminActivate: function() {
      if (this.admin.activated === false) {
         if (this.admin.initialized === false) {
            this.adminInitialize()
         }
         var placeholder_width = Galleries.Constants.placeholder_width;
         var thumb_offset_width = placeholder_width * 2;
         this.setCss('.ig-thumb', {
            width: this.getConstant('image_width') + thumb_offset_width
         });
         this.setCss('.ig-thumbs', {
            width: this.getConstant('thumbnail_width') + (placeholder_width * 2)
         });
         this.setCss('.ig-mask', {
            width: this.getConstant('thumbnail_width') + (placeholder_width * 2)
         });
         this.setCss('.ig-placeholder', {
            display: 'block'
         });
         if (this.options.visualization.thumbnail_transition == 'slide') {
            this.List.gotoItem(this.currentIndex, null, true);
         }
      }
      this.parent();
   },
   adminDeactivate: function() {
      if (this.admin.activated === true) {
         this.setCss('.ig-placeholder', {
            display: 'none'
         });
         this.setGalleryDimensions();
         this.List.gotoItem(this.currentIndex, null, true);
      }
      this.parent();
   }
});

Galleries.Plugins.SlideshowAnimated = new Class({
   Extends: Galleries.Plugins.Slideshow,
   type: 'slideshow-animated',
   storage: {},
   captionOptions: {},
   options: {
      visualization: {
         caption_offset: {
            left: 0,
            top: 0
         }
      }
   },
   durations: {
      none: {
         In: 0,
         Out: 0
      },
      slide: {
         In: 1000,
         Out: 500
      },
      fade: {
         In: 1000,
         Out: 500
      }
   },
   initialize: function(wrapper_id, options) {
      options.visualization.caption_visibility = "0";
      options.visualization.thumbnail_effect = "none";
      this.storage = $H();
      this.bounds.events.listComplete = this.startIn.bind(this);
      this.bounds.events.listBeforeStart = this.startOut.bind(this);
      this.bounds.captionClick = this.captionClick.bindWithEvent(this);
      this.bounds.animationComplete = this.animationComplete.bind(this);
      this.parent(wrapper_id, options);
      if (this.options.images.length > 1 && this.options.visualization.navigation == 'numbers' && Browser.Engine.trident5) {
         this.addEvent('listAfterComplete',
         function() {
            this.navigationWrapper.show('');
         }.bind(this));
      }
      for (var property in this.options.visualization.caption_offset) {
         if (isNaN(this.options.visualization.caption_offset[property])) {
            this.options.visualization.caption_offset[property] = 0;
         }
      }
   },
   createComponents: function() {
      this.parent();
      Galleries.PerformanceTest.start('createCaptions');
      this.createCaptions();
      Galleries.PerformanceTest.end('createCaptions');
      if (this.List.getItemsCount() > 1) {
         this.moveNavigation();
      }
   },
   createCaptions: function() {
      var images_length = this.options.images.length;
      var captions_wrapper = this.List.getDomNode();
      for (var i = 0; i < images_length; i++) {
         var caption = $pick(this.getImageData(i).caption, '');
         var element_p = document.createElement('p');
         var element_div = new Element('div', {
            "class": "ig-caption"
         });
         element_p.innerHTML = caption;
         element_div.addEvent('click', this.bounds.captionClick);
         element_div.appendChild(element_p);
         captions_wrapper.appendChild(element_div);
         this.saveTypeByIndex(i, 'caption', element_div);
      }
   },
   captionClick: function(event) {
      this.view(this.currentIndex, event);
   },
   startIn: function(index) {
      this.buildStyleObject(index);
      var styles = this.getTypeByIndex(index, 'styles');
      var element = this.getTypeByIndex(index, 'caption');
      if (!this.hasTypeByIndex(index, 'prepared')) {
         this.saveTypeByIndex(index, 'prepared', true);
         element.setStyles(styles.Initial);
         element.set('morph', {
            link: 'chain',
            transition: 'quart:in:out'
         });
      }
      element.set('morph', {
         duration: this.durations[styles.animation].In,
         onComplete: $empty
      }).morph(styles.In);
   },
   startOut: function(new_ndex, index) {
      if (this.hasTypeByIndex(index, 'prepared')) {
         var styles = this.getTypeByIndex(index, 'styles');
         var element = this.getTypeByIndex(index, 'caption');
         element.set('morph', {
            duration: this.durations[styles.animation].Out,
            onComplete: this.bounds.animationComplete.pass(index)
         }).morph(styles.Out);
      }
   },
   animationComplete: function(index) {
      var styles = this.getTypeByIndex(index, 'styles');
      var element = this.getTypeByIndex(index, 'caption');
      element.setStyles(styles.Initial);
   },
   buildStyleObject: function(index) {
      if (this.hasTypeByIndex(index, 'styles')) {
         return;
      }
      var image_data = this.getImageData(index);
      var caption_options = $unlink(this.captionOptions);
      if ($defined(image_data.caption_offset)) {
         for (var key in image_data.caption_offset) {
            var value = image_data.caption_offset[key];
            if (Number(value) == parseFloat(value)) {
               caption_options['offset_' + key] = Number(value)
            }
         }
      }
      var styleObject = {
         Initial: {
            display: 'none',
            width: this.getConstant('thumbnail_width') - caption_options.offset_left - 20,
            left: caption_options.offset_left,
            top: caption_options.offset_top
         },
         In: {
            display: 'block',
            left: caption_options.offset_left,
            top: caption_options.offset_top
         },
         Out: {
            left: caption_options.offset_left,
            top: caption_options.offset_top
         },
         animation: 'none'
      };
      if (Galleries.Utils.stringToBool(image_data.caption_animation)) {
         caption_options.animation = image_data.caption_animation.clean();
      }
      var animation = JSON.decode(caption_options.animation);
      if ($defined(animation.transitions)) {
         if (animation.transitions.contains("fade")) {
            styleObject.animation = "fade";
            styleObject.Initial.display = "none";
            styleObject.Initial.opacity = 0;
            styleObject.In.display = "block";
            styleObject.In.opacity = "1";
            styleObject.Out.opacity = "0";
         } else {
            styleObject.animation = "slide";
         }
      }
      if ($defined(animation.direction)) {
         var directions = animation.direction.split("-").associate(["Initial", "Out"]);
         var delta = this.getConstant("transitions_delta")[styleObject.animation];
         for (state_name in directions) {
            var position = directions[state_name];
            switch (position) {
            case "up":
               styleObject[state_name].top = styleObject.In.top - delta.Initial.y;
               styleObject[state_name].left = styleObject.In.left;
               break;
            case "down":
               styleObject[state_name].top = styleObject.In.top + delta[state_name].y;
               styleObject[state_name].left = styleObject.In.left;
               break;
            case "left":
               styleObject[state_name].top = styleObject.In.top;
               styleObject[state_name].left = styleObject.In.left - delta[state_name].x;
               break;
            case "right":
               styleObject[state_name].top = styleObject.In.top;
               styleObject[state_name].left = styleObject.In.left + delta[state_name].x;
               break;
            }
         }
      }
      this.saveTypeByIndex(index, 'styles', styleObject);
   },
   inspectStyleOffsets: function() {
      this.parent();
      this.captionOptions = {
         offset_left: this.options.visualization.caption_offset.left,
         offset_top: this.options.visualization.caption_offset.top,
         animation: this.options.visualization.caption_animation
      }
      var transitions_delta = {
         slide: {
            Initial: {
               x: this.getConstant('thumbnail_width'),
               y: this.getConstant('thumbnail_height')
            },
            Out: {
               x: this.getConstant('thumbnail_width'),
               y: this.getConstant('thumbnail_height')
            }
         },
         fade: {
            Initial: {
               x: 100,
               y: 50
            },
            Out: {
               x: 100,
               y: 50
            }
         },
         none: {
            Initial: {
               x: 0,
               y: 0
            },
            Out: {
               x: 0,
               y: 0
            }
         }
      };
      this.setConstant('transitions_delta', transitions_delta);
   },
   setGalleryDimensions: function() {
      this.parent();
      this.setCss('.ig-thumbs', {
         overflow: 'hidden',
         height: this.getConstant('thumbnail_height')
      });
      this.setCss('.ig-nav-width', {
         width: this.getConstant('thumbnail_width')
      });
   },
   moveNavigation: function() {
      if (this.options.visualization.navigation) {
         var position_parts = this.options.visualization.navigation_position.split('_');
         var position = position_parts.associate(['vertical', 'horizontal']);
         this.navigationWrapper = this.navigationHolder.getParent('table');
         if (this.options.visualization.navigation == 'numbers' && Browser.Engine.trident5) {
            this.navigationWrapper.hide();
         }
         this.navigationWrapper.setProperty('align', position.horizontal);
         new Element('table', {
            "align": "center",
            "class": "ig-struct ig-nav-width ig-nav-" + position.vertical
         }).grab(new Element('tbody').grab(new Element('tr').grab(new Element('td').grab(this.navigationWrapper)))).inject(this.List.getDomNode());
      }
   },
   createTransitionList: function() {
      var transition_list_options = {
         delayStart: 500,
         delayAfterComplete: 1000,
         stopOnManualTransition: false,
         applyCurrentItemHeight: false
      };
      this.List.createTransitionList(this.domNode.getElements('.ig-slide'), transition_list_options);
   },
   saveTypeByIndex: function(index, type, item) {
      this.storage.set(type + '_' + index, item)
   },
   getTypeByIndex: function(index, type) {
      return this.storage.get(type + '_' + index)
   },
   hasTypeByIndex: function(index, type) {
      return this.storage.has(type + '_' + index)
   },
   destroy: function() {
      var images_length = this.options.images.length;
      for (var i = 0; i < images_length; i++) {
         this.getTypeByIndex(i, 'caption').destroy();
      }
      delete this['storage'];
      this.parent();
   }
});

Galleries.Plugins.Viewer = new Class({
   Extends: Galleries.Plugins.PluginBase,
   type: 'viewer',
   mainEventName: 'gotoItem',
   overlayOpacity: 0.33,
   animationOptions: {
      overlay: {
         show: {
            transition: 'sine:out',
            duration: 600,
            onComplete: null
         },
         hide: {
            transition: 'sine:in',
            duration: 0,
            onComplete: null
         }
      },
      clone: {
         show: {
            transition: 'sine:out',
            duration: 200,
            onComplete: null
         },
         hide: {
            transition: 'sine:in',
            duration: 0,
            onComplete: null
         }
      },
      body: {
         show: {
            transition: 'sine:out',
            duration: 400,
            onComplete: null
         },
         hide: {
            transition: 'sine:in',
            duration: 0,
            onComplete: null
         }
      }
   },
   currentIndex: 0,
   storage: {},
   drag: null,
   options: {
      viewer: {
         zoom: false,
         inside_buttons: false,
         caption_visibility: true,
         loop: true,
         play_button: false,
         navigation: "buttons_and_numbers",
         dim_lights: true,
         autoplay: false,
         slideshow_timeout: 5,
         size_behaviour: 'resizable',
         width: 800,
         height: 400
      },
      images: []
   },
   initialize: function() {
      this.bounds.viewerKeydown = this.onKeyDown.bind(this);
      if (this.options.viewer.size_behaviour == 'static') {
         this.bounds.viewerResize = this.onResizeStatic.bind(this);
      } else {
         this.bounds.viewerResize = this.onResize.bind(this);
      }
      this.bounds.events.listStart = this.closeZoomOnStart.bind(this);
      this.bounds.zoomLoad = this.zoomLoad.bind(this);
      this.bounds.zoomMove = this.zoomMove.bind(this);
      this.bounds.zoomClose = this.zoomClose.bind(this);
      this.createAnimationEvents();
      this.parent.apply(this, arguments);
      this.notifyGenerating = false;
      if (!this.options.viewer.dim_lights) {
         this.overlayOpacity = 0.01;
         this.animationOptions.overlay.show.duration = 1;
         this.animationOptions.overlay.hide.duration = 1;
      }
      this.storage.largeImageCache = new Element('img', {
         events: {
            load: this.onLargeImageLoad.bind(this)
         }
      });
   },
   display: function(index, element) {
      Galleries.PerformanceTest.start('initialize-' + this.type);
      if (!this.created) {
         Galleries.PerformanceTest.start('createComponents');
         this.createComponents();
         Galleries.PerformanceTest.end('createComponents');
         this.created = true;
         this.hideLoadingAnimation();
      }
      this.show(index, element);
   },
   attachLoadHandler: function() {},
   showLoadingAnimation: function() {},
   hideLoadingAnimation: function() {},
   closeZoomOnStart: function() {
      if (this.zoomHolder.retrieve('shown')) {
         this.zoomClose();
      }
   },
   createComponents: function() {
      this.parent();
      this.domNode.set('class', 'ig-viewer');
      this.domNode.setStyles({
         'opacity': 0,
         'visibility': 'hidden'
      });
      var tbody = new Element('tbody');
      this.listHolder = new Element('td');
      this.zoomHolder = new Element('div', {
         "class": "ig-viewer-zoom"
      }).hide();
      this.viewerPanel = new Element('div', {
         "class": "ig-viewer-panel"
      });
      this.captionHolder = new Element('div', {
         "class": "ig-viewer-caption"
      });
      this.navigationHolder = new Element('div', {
         "class": "ig-viewer-nav"
      });
      this.closeButton = new Element('div', {
         "class": "ig-viewer-close"
      });
      this.viewerBody = new Element('div', {
         "class": "ig-viewer-waypoint"
      }).inject(this.domNode).grab(new Element('table', {
         "align": "center",
         "class": "ig-struct"
      }).grab(tbody.grab(new Element('tr').grab(this.listHolder)))).grab(this.closeButton);
      if (this.options.viewer.caption_visibility || this.options.viewer.navigation) {
         tbody.grab(new Element('tr').grab(new Element('td', {
            width: "100%"
         }).grab(this.viewerPanel.grab(new Element('table', {
            "class": "ig-struct",
            width: "100%"
         }).grab(new Element('tbody').grab(new Element('tr').grab(new Element('td', {
            width: "100%"
         }).grab(this.captionHolder)).grab(new Element('td').grab(this.navigationHolder))))))))
      }
      this.overlay = new Element('div', {
         'class': 'ig-overlay',
         'styles': {
            'opacity': 0,
            'visibility': 'visible',
            'height': 0,
            'overflow': 'hidden'
         }
      }).inject(document.body);
      this.computeSize();
      this.domNode.setStyles(this.getBoxStyles());
      this.closeButton.addEvent('click', this.hide.bind(this));
      this.overlay.addEvent('click', this.hide.bind(this));
      if (this.options.viewer.caption_visibility) {
         var caption = new Galleries.Components.ComponentCaption({
            plugin: this,
            duration: Galleries.Constants['fade_duration']
         });
         caption.inject(this.captionHolder);
         this.viewerPanel.addClass('ig-viewer-panel-with-caption');
      }
      var list_options = {
         plugin: this,
         thumbnail_size: this.options.visualization.thumbnail_size,
         thumbnail_transition: 'fade',
         slideshow_timeout: this.options.viewer.slideshow_timeout,
         autoplay: this.options.viewer.autoplay,
         thumbnail_effect: false,
         loop: this.options.viewer.loop,
         caption_visibility: false,
         images: this.options.images,
         src_key: 'large_url',
         auto_scale: true,
         notify_generating: this.notifyGenerating,
         full_src: 'full_url',
         pages_to_preload: Galleries.Constants.viewer.pages_to_preload
      };
      this.List = new Galleries.Components.ComponentListSlideshow(list_options);
      this.List.inject(this.listHolder);
      this.tumbnailInstances = this.List.getTumbnailInstances();
      var items_count = this.List.getItemsCount();
      if (items_count > 1) {
         this.pages = this.List.getPages();
         if (this.options.viewer.inside_buttons || this.options.viewer.play_button) {
            var buttons_set_options = {
               plugin: this,
               first_item: this.pages[0],
               last_item: this.pages.getLast(),
               inside_buttons: this.options.viewer.inside_buttons,
               play_button: this.options.viewer.play_button,
               autoplay: this.options.viewer.autoplay,
               loop: this.options.viewer.loop,
               events: {
                  buttonPrev: {
                     click: 'onButtonPrev'
                  },
                  buttonNext: {
                     click: 'onButtonNext'
                  },
                  buttonPlay: {
                     click: 'onButtonPlay'
                  },
                  buttonPause: {
                     click: 'onButtonPause'
                  }
               }
            };
            this.Navigators.ButtonsSet = new Galleries.Components.ComponentButtonsSet(buttons_set_options);
            this.Navigators.ButtonsSet.inject(this.List.getDomNode());
         }
         if (this.options.viewer.navigation) {
            var navigation_options = {
               plugin: this,
               pages: this.pages,
               first_item: this.pages[0],
               last_item: this.pages.getLast(),
               type: this.options.viewer.navigation,
               loop: this.options.viewer.loop
            };
            this.Navigators.Navigation = new Galleries.Components.ComponentNavigation(navigation_options);
            this.Navigators.Navigation.inject(this.navigationHolder);
            this.viewerPanel.addClass('ig-viewer-panel-with-navigation');
         }
      }
      this.inspectStyleOffsets();
      this.setGalleryDimensions();
      this.List.createTransitionList(this.domNode.getElements('.ig-slide'));
      this.zoomHolder.inject(this.listHolder);
      this.bigImage = new Element('img').hide().addEvent('load', this.bounds.zoomLoad).inject(this.zoomHolder);
      if (Browser.Engine.trident) {
         this.bigImage.ondragstart = $lambda(false);
      }
   },
   inspectStyleOffsets: function() {
      this.parent();
      var viewer_panel_height = 0;
      if (this.options.viewer.caption_visibility || this.options.viewer.navigation) {
         var panel_dimensions = this.viewerPanel.getDimensions({
            computeSize: true,
            styles: ['border', 'padding', 'margin']
         });
         viewer_panel_height = panel_dimensions.totalHeight;
      }
      this.setConstant('viewer_panel_height', viewer_panel_height);
   },
   computeSize: function() {
      var computed_styles;
      if (this.options.viewer.size_behaviour == 'static') {
         computed_styles = ['border', 'padding'];
      } else {
         computed_styles = ['border', 'padding', 'margin'];
      }
      var viewer_size = this.domNode.getDimensions({
         computeSize: true,
         styles: computed_styles
      });
      viewer_size.x = viewer_size.computedLeft + viewer_size.computedRight;
      viewer_size.y = viewer_size.computedBottom + viewer_size.computedTop;
      this.setConstant('viewer_size', viewer_size);
   },
   setGalleryDimensions: function() {
      var box_dimensions = this.getBoxStyles();
      var width = box_dimensions.width;
      var height = box_dimensions.height - this.getConstant('viewer_panel_height');
      this.storage.canvasSize = {
         width: width - this.getConstant('ig_thumbs_offset_x'),
         height: height - this.getConstant('ig_thumbs_offset_y')
      };
      this.tumbnailInstances.invoke('setCanvasSize', width, height);
      this.setCss('.ig-viewer-zoom, .ig-img td', {
         width: this.storage.canvasSize.width,
         height: this.storage.canvasSize.height
      });
      this.setCss('.ig-slide', {
         width: this.storage.canvasSize.width
      });
   },
   getBoxSize: function() {
      if (this.options.viewer.size_behaviour == 'static') {
         return {
            x: this.options.viewer.width + this.getConstant('viewer_size').x,
            y: this.options.viewer.height + this.getConstant('viewer_size').y
         }
      } else {
         return window.getSize();
      }
   },
   getBoxStyles: function() {
      var size = this.getBoxSize();
      var styles = {
         width: size.x - this.getConstant('viewer_size').x,
         height: size.y - this.getConstant('viewer_size').y
      };
      if (this.options.viewer.size_behaviour == 'static') {
         styles.marginLeft = -(size.x / 2);
         styles.marginTop = -(size.y / 2);
      } else {
         styles.left = 0;
         styles.top = 0;
      }
      return styles;
   },
   view: function(item_index, e) {
      if (!this.options.viewer.zoom) {
         return;
      }
      e = new Event(e);
      this.pause();
      if (!this.drag) {
         this.drag = new Drag(this.bigImage, {
            modifiers: {
               x: 'left',
               y: 'top'
            },
            grid: 1,
            onComplete: this.bounds.zoomMove,
            onCancel: this.bounds.zoomClose
         });
      }
      var coordinates = $(e.target).getCoordinates();
      var scroll = window.getScroll();
      this.storage.clickOffsetsRatios = {
         x: (e.event.offsetX ? e.event.offsetX: e.event.pageX - coordinates.left) / e.target.getWidth(),
         y: (e.event.offsetY ? e.event.offsetY: e.event.pageY - coordinates.top) / e.target.getHeight()
      };
      var data = this.getImageData(item_index);
      var big_image_src = data.full_url ? data.full_url: data.large_url;
      this.List.hide();
      this.zoomHolder.addClass('ig-loading').show('block');
      this.bigImage.show('block').set('src', '').set('src', big_image_src);
   },
   onResize: function() {
      var size = window.getSize();
      this.overlay.setStyles({
         width: size.x,
         height: size.y
      });
      var target_box = this.getBoxStyles();
      this.domNode.setStyles(target_box);
      this.setGalleryDimensions();
      this.setZoomRectangle();
      this.List.gotoItem(this.currentIndex, null, true);
   },
   onResizeStatic: function() {
      var size = window.getSize();
      this.overlay.setStyles({
         width: size.x,
         height: size.y
      });
   },
   onKeyDown: function(e) {
      switch (e.key) {
      case 'backspace':
      case 'left':
         this.previous();
         break;
      case 'space':
      case 'right':
         this.next();
         break;
      case 'esc':
         this.hide();
         break;
      default:
         return;
      }
      e.preventDefault();
   },
   createAnimationEvents: function() {
      this.animationOptions.overlay.show.onComplete = this.overlayShowComplete.bindWithEvent(this);
      this.animationOptions.overlay.hide.onComplete = this.overlayHideComplete.bindWithEvent(this);
      this.animationOptions.clone.show.onComplete = this.cloneShowComplete.bindWithEvent(this);
      this.animationOptions.clone.hide.onComplete = this.cloneHideComplete.bindWithEvent(this);
      this.animationOptions.body.show.onComplete = this.bodyShowComplete.bindWithEvent(this);
      this.animationOptions.body.hide.onComplete = this.bodyHideComplete.bindWithEvent(this);
   },
   applyAnimationOptions: function(action) {
      this.overlay.set('tween', {
         duration: this.animationOptions.overlay[action].duration,
         transition: this.animationOptions.overlay[action].transition,
         onComplete: this.animationOptions.overlay[action].onComplete,
         link: 'cancel'
      });
      this.cloneElement.set('morph', {
         duration: this.animationOptions.clone[action].duration,
         transition: this.animationOptions.clone[action].transition,
         onComplete: this.animationOptions.clone[action].onComplete
      });
      this.domNode.set('tween', {
         duration: this.animationOptions.body[action].duration,
         transition: this.animationOptions.body[action].transition,
         onComplete: this.animationOptions.body[action].onComplete,
         link: 'chain'
      });
   },
   cloneShowComplete: function() {
      this.storage.cloneShowComplete = true;
      if (this.storage.largeImageCached) {
         this.startViewerBodyAnimation();
      }
   },
   onLargeImageLoad: function() {
      this.storage.largeImageCached = true;
      if (this.storage.cloneShowComplete) {
         this.startViewerBodyAnimation();
      }
   },
   startViewerBodyAnimation: function() {
      this.storage.largeImageCached = false;
      this.storage.cloneShowComplete = false;
      this.domNode.setStyles({
         opacity: "0",
         visibility: 'visible',
         display: ''
      });
      this.List.gotoItem(this.currentIndex, null, true);
      this.domNode.addClass('ig-viewer-animation');
      this.domNode.tween("opacity", "1");
   },
   bodyShowComplete: function() {
      this.domNode.removeClass('ig-viewer-animation');
      this.storage.largeImageCache.src = '';
      this.cloneElement.dispose();
      this.overlay.tween('opacity', this.overlayOpacity);
      this.fireEvent('show');
   },
   overlayShowComplete: function() {},
   overlayHideComplete: function() {
      this.overlay.setStyles({
         'height': 0,
         'top': ''
      });
      this.domNode.setStyles({
         'opacity': 0,
         'visibility': 'hidden',
         'display': 'none'
      });
      this.zoomHolder.hide();
      this.List.show();
      this.fireEvent('hide');
   },
   bodyHideComplete: function() {},
   cloneHideComplete: function() {},
   show: function(index, element) {
      this.addViewerEvents();
      this.currentIndex = index;
      this.closed = false;
      var size = window.getSize();
      this.overlay.setStyles({
         width: size.x,
         height: size.y
      });
      var position = element.getPosition();
      var clone_styles = {
         position: 'absolute',
         left: position.x,
         top: position.y,
         width: element.width,
         height: element.height
      }
      this.cloneElement = element.clone();
      this.cloneElement.setStyles(clone_styles);
      this.cloneElement.addClass('ig-thumbnail-clone');
      var viewer_styles = this.getBoxStyles();
      viewer_styles.opacity = "0";
      viewer_styles.display = 'block';
      this.domNode.setStyles(viewer_styles);
      this.applyAnimationOptions('show');
      this.setGalleryDimensions();
      var large_image_size = this.tumbnailInstances[index].getImageSize();
      if (isNaN(large_image_size.width) == false && isNaN(large_image_size.height) == false) {
         var container_position = this.List.getDomNode().getPosition();
         var morph_properties = {
            width: large_image_size.width,
            height: large_image_size.height,
            left: container_position.x + ((this.storage.canvasSize.width - large_image_size.width) / 2),
            top: container_position.y + ((this.storage.canvasSize.height - large_image_size.height) / 2)
         };
         var cloned_image_ratio = clone_styles.width / clone_styles.height;
         var large_image_ratio = large_image_size.width / large_image_size.height;
         if (cloned_image_ratio > large_image_ratio) {
            var new_height = large_image_size.width / cloned_image_ratio;
            morph_properties.top += (morph_properties.height - new_height) / 2;
            morph_properties.height = new_height;
         } else if (cloned_image_ratio < large_image_ratio) {
            var new_width = large_image_size.height * cloned_image_ratio;
            morph_properties.left += (morph_properties.width - new_width) / 2;
            morph_properties.width = new_width;
         }
         this.cloneElement.inject(document.body);
         this.cloneElement.morph(morph_properties);
      } else {
         this.cloneShowComplete();
      }
      this.storage.largeImageCache.src = this.getImageData(index).large_url;
   },
   hide: function(page, anchor) {
      this.closed = true;
      this.removeViewerEvents();
      this.pause();
      this.applyAnimationOptions('hide');
      this.overlay.tween('opacity', "0");
   },
   addViewerEvents: function() {
      window.addEvent('resize', this.bounds.viewerResize);
      document.addEvent('keydown', this.bounds.viewerKeydown);
   },
   removeViewerEvents: function() {
      window.removeEvent('resize', this.bounds.viewerResize);
      document.removeEvent('keydown', this.bounds.viewerKeydown);
   },
   zoomLoad: function() {
      this.zoomHolder.store('shown', true);
      this.storage.imageSize = {
         width: this.bigImage.width,
         height: this.bigImage.height
      };
      this.setZoomRectangle();
      this.zoomHolder.removeClass('ig-loading');
   },
   zoomClose: function() {
      this.zoomHolder.store('shown', false);
      this.zoomHolder.hide();
      this.bigImage.setStyles({
         left: '',
         top: ''
      });
      this.List.show('block');
   },
   zoomMove: function(img) {
      this.storage.clickOffsetsRatios = {
         x: (Math.abs(img.getStyle('left').toInt() - (this.storage.canvasSize.width / 2)) / this.storage.imageSize.width),
         y: (Math.abs(img.getStyle('top').toInt() - (this.storage.canvasSize.height / 2)) / this.storage.imageSize.height)
      }
   },
   setZoomRectangle: function() {
      if (!this.zoomHolder.retrieve('shown')) {
         return;
      }
      var left = this.storage.canvasSize.width - this.storage.imageSize.width;
      var top = this.storage.canvasSize.height - this.storage.imageSize.height;
      if (left > 0) {
         left = left / 2;
      }
      if (top > 0) {
         top = top / 2;
      }
      var ranges = {
         x: {
            min: left,
            max: Math.max(0, left)
         },
         y: {
            min: top,
            max: Math.max(0, top)
         }
      };
      this.drag.setOptions({
         limit: {
            x: [ranges.x.min, ranges.x.max],
            y: [ranges.y.min, ranges.y.max]
         }
      });
      var click_offset_x = Math.floor(this.storage.clickOffsetsRatios.x * this.storage.imageSize.width);
      var click_offset_y = Math.floor(this.storage.clickOffsetsRatios.y * this.storage.imageSize.height);
      var position = {
         x: (this.storage.canvasSize.width / 2) - click_offset_x,
         y: (this.storage.canvasSize.height / 2) - click_offset_y
      }
      for (var z in position) {
         if (position[z] > ranges[z].max) {
            position[z] = ranges[z].max;
         } else if (position[z] < ranges[z].min) {
            position[z] = ranges[z].min;
         }
      }
      this.bigImage.setStyles({
         left: position.x,
         top: position.y
      });
   }
});

Galleries.Effects.TransitionList = function(items, options) {
   var type = String.toLowerCase(options.type).capitalize();
   if (Galleries.Effects.TransitionList[type] == undefined) {
      return new Galleries.Effects.TransitionList.Base(items, options);
   }
   return new Galleries.Effects.TransitionList[type](items, options);
}
Galleries.Effects.TransitionList.Base = new Class({
   Implements: [Options, Events],
   options: {
      interval: 5000,
      fxOptions: {
         duration: 'long',
         transition: Fx.Transitions.Quart.easeInOut
      },
      index: 0,
      applyCurrentItemHeight: true,
      delayStart: false,
      delayAfterComplete: false,
      stopOnManualTransition: true,
      onBeforeStart: null,
      onStart: null,
      onComplete: null,
      onAfterComplete: null,
      onPlayStop: null,
      onPlayStart: null
   },
   timer: null,
   currentIndex: null,
   previousIndex: null,
   disabled: false,
   queueHolder: [],
   playOn: false,
   initialize: function(items, options) {
      this.setOptions(options);
      if (this.options.delayStart > 0) {
         this.options.interval += parseInt(this.options.delayStart)
      }
      if (this.options.delayAfterComplete > 0) {
         this.options.interval += parseInt(this.options.delayAfterComplete)
      }
      this.options.fxOptions["onComplete"] = this.onComplete.bind(this);
      this.items = items;
      this.prepareItems();
      this.set(this.options.index)
   },
   prepareItems: function() {
      this.container = this.items[0].getParent();
      if (this.options.applyCurrentItemHeight) {
         this.container.set('tween', {
            duration: this.options.fxOptions.duration,
            transition: this.options.fxOptions.transition,
            link: 'chain'
         });
      }
   },
   applyCurrentItemHeight: function() {
      if (this.items[this.currentIndex]) {
         var container_height = this.container.getStyle('height').toInt();
         var current_item_height = this.items[this.currentIndex].getDimensions({
            computeSize: true,
            styles: ['border', 'padding', 'margin']
         }).totalHeight;
         if (current_item_height && container_height != current_item_height) {
            this.container.setStyle('height', container_height);
            this.container.tween('height', current_item_height);
         }
      }
   },
   walk: function(index, is_manual, direction, is_fromqueue) {
      if (this.disabled && !is_fromqueue) {
         this.queueHolder.push(this.walk.bind(this, [index, is_manual, direction, true]));
         return;
      }
      this.disabled = true;
      if (this.playOn === true && is_manual) {
         if (this.options.stopOnManualTransition) {
            this.stop();
         } else {
            $clear(this.timer);
            this.timer = this.next.periodical(this.options.interval, this, [false]);
         }
      }
      this.fireEvent('beforeStart', [index, this.currentIndex]);
      if (this.options.delayStart) {
         this.start.delay(this.options.delayStart, this, [index, direction]);
      } else {
         this.start(index, direction);
      }
   },
   start: function(index, direction) {
      this.fireEvent('start', [index, this.currentIndex]);
      if (this.currentIndex !== index && $defined(this.items[index])) {
         this.previousIndex = this.currentIndex;
         this.currentIndex = index;
         if (this.options.applyCurrentItemHeight) {
            this.applyCurrentItemHeight()
         }
         this.items[this.currentIndex].show('block');
         this.items[this.previousIndex].hide();
      }
      this.onComplete();
   },
   next: function(is_manual) {
      var next = this.currentIndex + 1 < this.items.length ? this.currentIndex + 1 : 0;
      this.walk(next, is_manual, 'next');
   },
   previous: function(is_manual) {
      var previous = this.currentIndex > 0 ? this.currentIndex - 1 : this.items.length - 1;
      this.walk(previous, is_manual, 'previous');
   },
   set: function(index) {
      this.previousIndex = null;
      this.currentIndex = index;
      this.items.hide();
      this.items[index].show('block')
   },
   play: function(wait) {
      if (this.playOn === true) {
         return;
      }
      this.playOn = true;
      this.fireEvent('playStart');
      if (!wait) {
         this.next();
      }
      this.timer = this.next.periodical(this.options.interval, this, [false]);
   },
   stop: function() {
      if (this.playOn === false) {
         return;
      }
      this.playOn = false;
      $clear(this.timer);
      this.fireEvent('playStop');
   },
   onComplete: function() {
      this.fireEvent('complete', [this.currentIndex]);
      if (this.options.delayAfterComplete) {
         this.afterComplete.delay(this.options.delayAfterComplete, this);
      } else {
         this.afterComplete();
      }
   },
   afterComplete: function() {
      this.fireEvent('afterComplete', [this.currentIndex]);
      if (this.queueHolder.length > 0) {
         this.queueHolder.shift().run();
      } else {
         this.disabled = false;
      }
   }
});

Galleries.Effects.TransitionList.Slide = new Class({
   Extends: Galleries.Effects.TransitionList.Base,
   options: {
      fxOptions: {
         wheelStops: false
      },
      endlessChain: false,
      applyCurrentItemHeight: false
   },
   endlessChainHelper: {
      element: null,
      reset: false
   },
   initialize: function(items, options) {
      if (!items || items.length == 0) {
         return;
      }
      this.parent(items, options);
   },
   prepareItems: function() {
      this.container = document.createElement('div');
      var holder = this.items[0].getParent();
      var table = document.createElement('table');
      var table_body = document.createElement('tbody');
      this.stripholder = document.createElement('tr');
      this.container.className = "ig-mask";
      table.align = 'center';
      table.className = "ig-struct";
      holder.appendChild(this.container);
      table.appendChild(table_body);
      table_body.appendChild(this.stripholder);
      this.container.appendChild(table);
      var table_cell;
      for (var i = 0; i < this.items.length; i++) {
         table_cell = document.createElement('td');
         table_cell.className = "ig-slide-td";
         table_cell.appendChild(this.items[i]);
         this.stripholder.appendChild(table_cell);
      }
      if (this.options.endlessChain) {
         this.endlessChainHelper.element = this.items.getLast();
      }
      this.scroll = new Fx.Scroll(this.container, this.options.fxOptions);
   },
   start: function(index, direction) {
      this.fireEvent('start', [index, this.currentIndex]);
      var position = this.getScrollWidth(this.items[index]);
      if (this.options.endlessChain) {
         if (index === this.items.length - 2 && direction == 'previous') {
            this.scroll.set(this.getScrollWidth(this.items.getLast()), 0);
         }
         if (index === 0 && direction == 'next') {
            var position = this.getScrollWidth(this.items.getLast());
            this.endlessChainHelper.reset = true
         }
      }
      this.previousIndex = this.currentIndex;
      this.currentIndex = index;
      this.scroll.start(position, 0);
   },
   getScrollWidth: function(element) {
      if (Browser.Engine.trident) {
         var step_width = Galleries.Utils.Element.getBorderWidth(element) + Galleries.Utils.Element.getMarginWidth(element) + element.clientWidth;
         var position = this.items.indexOf(element);
         return step_width * position;
      } else {
         return element.getPosition(this.container)['x'] - Galleries.Utils.Element.getComputedSizeSum(element, ['margin-left']);
      }
   },
   next: function(manual) {
      var items_length = this.options.endlessChain ? this.items.length - 1 : this.items.length;
      var next = this.currentIndex + 1 < items_length ? this.currentIndex + 1 : 0;
      this.walk(next, manual, 'next');
   },
   set: function(index) {
      this.previousIndex = null;
      this.currentIndex = index;
      var position = this.getScrollWidth(this.items[this.currentIndex]);
      this.scroll.set(position, 0);
   },
   previous: function(manual) {
      var items_length = this.options.endlessChain ? this.items.length - 1 : this.items.length;
      var previous = this.currentIndex > 0 ? this.currentIndex - 1 : items_length - 1;
      this.walk(previous, manual, 'previous');
   },
   onComplete: function() {
      if (this.endlessChainHelper.reset) {
         this.endlessChainHelper.reset = false;
         var position = this.getScrollWidth(this.items[0]);
         this.scroll.set(position, 0);
      }
      this.parent();
   }
});

Galleries.Effects.TransitionList.Fade = new Class({
   Extends: Galleries.Effects.TransitionList.Base,
   initialize: function(items, options) {
      if (!items || items.length == 0) {
         return;
      }
      this.parent(items, options);
   },
   prepareItems: function() {
      this.parent();
      this.container.setStyles({
         position: 'relative'
      });
   },
   start: function(index, direction) {
      this.fireEvent('start', [index, this.currentIndex]);
      if (this.currentIndex !== index) {
         this.previousIndex = this.currentIndex;
         this.currentIndex = index;
         var styles = {
            display: 'block',
            zIndex: 2,
            opacity: 0,
            position: 'absolute',
            top: Galleries.Utils.Element.getComputedSizeSum(this.container, ['padding-top']),
            left: Galleries.Utils.Element.getComputedSizeSum(this.container, ['padding-left'])
         };
         this.items[this.currentIndex].setStyles(styles).set('tween', this.options.fxOptions).tween('opacity', '1');
         if (this.options.applyCurrentItemHeight) {
            this.applyCurrentItemHeight()
         }
         if ($defined(this.items[this.previousIndex])) {
            this.items[this.previousIndex].setStyle('zIndex', '1').set('tween', $merge(this.options.fxOptions, {
               onComplete: null
            })).tween('opacity', '0');
         }
      } else {
         this.onComplete();
      }
   },
   onComplete: function() {
      if (this.items[this.previousIndex]) {
         this.items[this.previousIndex].setStyles({
            display: 'none',
            'zIndex': '0'
         });
      }
      this.items[this.currentIndex].setStyles({
         left: 0,
         top: 0,
         position: 'relative'
      });
      this.parent();
   },
   set: function(index) {
      this.parent(index);
      var styles = {
         zIndex: 2,
         opacity: 1,
         visibility: 'visible'
      }
      this.items[index].setStyles(styles);
   }
});
