if (!mysource) {
    var mysource = {};
}

// Provides namespacing for Image Gallery widget (ImageGallery).
mysource.MysourceImageGallery = {};


// Alias for mysource.ImageGallery.
var mig = mysource.MysourceImageGallery;

/**
 * References holder for each image loaders since each gallery creates its own.
 * This is being used in setTimeout() callback function in ImageLoader itself
 * to get to its own local variables.
 */
mig.imageLoaders = {};

mig.displayCount = 0;

/**
 * Create a new Mysource Image Gallery.
 *
 * @param {Object} ops List of options passed to Gallery() constructor.
 */
mig.createMysourceImageGallery = function(ops)
{
    new mig.Gallery(ops);

};


/**
 * Gallery class.
 *
 * Gallery class is responsible for setting gallery options and displaying
 * the selected image in the main image window. It also creates GalleySlider
 * object and initialize it.
 *
 * @param {Object} ops List of options passed to Gallery contructor.
 */
mig.Gallery = function(ops)
{
    var self = this;

    this.galleryType = 'normal';
    if (ops.height === 'small' || ops.height === 'big') {
        // Lightbox View.
        this.galleryType = 'light';
    }

    // Set gallery options.
    this.galleryid    = ops.galleryid + '-' + mig.displayCount;
    this.galleryWidth = ops.width || 580;
    mig.displayCount++;

    // Gallery theme.
    this.galleryTheme = ops.theme || 'black';

    // This is the main object includes all image information.
    this.images   = ops.images || {};
    this.imageids = [];

    // Generate flat list of image ids.
    dfx.foreach(this.images, function(iid) {
        self.imageids.push(iid);
        return true;
    });

    // Current image's index in this.imageids array.
    this.currentIndex = 0;
    this.currentImage = null;

    if (this.galleryType === 'normal') {
        // If the gallery height is smaller than 400px, use smaller size slider.
        // Also, adjust gallery size together.
        this.galleryHeight      = ops.height || 523;
        this.smallThumb         = (this.galleryHeight <= 400);
        this.imageDisplayWidth  = (this.galleryWidth - 20);
        this.imageDisplayHeight = (this.galleryHeight - 135);
        if (this.smallThumb === true) {
            this.imageDisplayHeight += 30;
        }

        // Let's get some DOM elements to use in the class.
        this.container      = ops.container;
        this.galleryImage   = dfx.getClass('galleryImage', this.container)[0];
        this.imageContainer = dfx.getClass('imageContainer', this.container)[0];
        this.prevNavButton  = dfx.getClass('galleryImagePrev', this.container)[0];
        this.nextNavButton  = dfx.getClass('galleryImageNext', this.container)[0];
        this.imageCaption   = dfx.getClass('imgCaption', this.container)[0];

        // Adjust gallery size!
        dfx.setStyle(this.galleryImage, 'height', this.imageDisplayHeight + 'px');
    } else if (this.galleryType === 'light') {
        // Initiate LightBox version of image gallery.
        // All these values are used to caculate the image and container size.
        var setDimensions = function() {
            var winSize = dfx.getWindowDimensions();
            // This is the maximum size that the overlay box can expand.
            self.maxContainerW      = (winSize.width - 180);
            self.maxContainerH      = (winSize.height - 80);
            self.captionHeight      = 29;
            self.imageDisplayWidth  = self.maxContainerW;
            self.imageDisplayHeight = ((self.maxContainerH - self.captionHeight) - 2);
        };

        setDimensions();

        window.onresize = function(e) {
            setDimensions();
        };

        this.popOverlay = document.createElement('div');

        var c = '<div class="galleryPopContainer">';
        c    += '   <div class="galleryPopBorder">';
        c    += '       <div class="galleryPopImage">';
        c    += '           <img class="imagePopContainer" src="" alt="img2" height="48" width="48" style="visibility: hidden;">';
        c    += '           <a href="#" class="galleryPopImageNext"><span>Show next gallery image</span></a>';
        c    += '           <a href="#" class="galleryPopImagePrev"><span>Show previous gallery image</span></a>';
        c    += '       </div>';
        c    += '       <div class="galleryPopFooter">';
        c    += '           <p class="galleryPopCaption"> </p>';
        c    += '       </div>';
        c    += '   </div><!-- .galleryPopBorder -->';
        c    += '   <a href="#" class="galleryPopClose"> </a>';
        c    += '</div><!-- .galleryPopContainer -->';

        dfx.setHtml(this.popOverlay, c);
        dfx.addClass(this.popOverlay, 'galleryPopOverlay');

        var editingAsset = dfx.getId('EditingAsset');
        if (editingAsset) {
            editingAsset.appendChild(this.popOverlay);
        } else {
            document.body.appendChild(this.popOverlay);
        }

        this.container           = ops.container;
        this.galleryPopContainer = dfx.getClass('galleryPopContainer', this.popOverlay)[0];
        this.imageContainer      = dfx.getClass('imagePopContainer', this.popOverlay)[0];
        this.prevNavButton       = dfx.getClass('galleryPopImagePrev', this.popOverlay)[0];
        this.nextNavButton       = dfx.getClass('galleryPopImageNext', this.popOverlay)[0];
        this.imageCaption        = dfx.getClass('galleryPopCaption', this.popOverlay)[0];
        this.galleryPopFooter    = dfx.getClass('galleryPopFooter', this.popOverlay)[0];
        this.galleryPopClose     = dfx.getClass('galleryPopClose', this.popOverlay)[0];
        this.galleryHeight       = ops.height;
        this.smallThumb          = (this.galleryHeight === 'small');

        dfx.hideElement(this.popOverlay);
    }//end if

    // This flag is used to indicate the time when this gallery is loaded very
    // first time. When the full gallery displays the first image, PHP already
    // paints first image src. Do not re-create <img> tag for screenshot's sake.
    this.firstTimeLoading = true;

    // Image loader object.
    this.imageLoader = new mig.ImageLoader(this.galleryid);

    // Create a reference to imageLoader object in mig namespace so that
    // its setInterval() function call can correctly refer to itself.
    mig.imageLoaders[this.galleryid] = this.imageLoader;

    // Let's add pre loader gif image first in the queue.
    this.imageLoader.addImage('gallery_loader', mig.Gallery.preLoaderPath);

    // Add all thumb images to image loader.
    dfx.foreach(this.imageids, function(idx) {
        var iid = self.imageids[idx];
        if (self.images[iid].url.search(/\?/) === -1) {
            // Not found.
            self.imageLoader.addImage(iid + '_tb', self.images[iid].url + '?tb=1');
        } else {
            self.imageLoader.addImage(iid + '_tb', self.images[iid].url + '&tb=1');
        }

        return true;
    });

    // If the loader is ready, then start initialisation!
    this.isLoaderReady(function() {
        // So now, let's create a bottom slider.
        self.imageSlider = new mig.Gallery.Slider(
            self,
            self.galleryid,
            self.images,
            self.imageids,
            self.imageLoader,
            self.galleryWidth,
            self.galleryHeight,
            self.smallThumb,
            self.container
        );

        // Slider thumb onclick callback!
        // After slider does its own job like update selected class names,
        // it's time to update main window with the selected image.
        self.imageSlider.addOnSelectCallback(function(id) {
            self.currentImage = id;
            dfx.foreach(self.imageids, function(idx) {
                if (self.imageids[idx] === id) {
                    self.currentIndex = idx;
                    return false;
                }

                return true;
            });

            self.displayImage();
        });

        if (this.galleryType === 'normal') {
            self.init(function() {
                // Set the first image as the currently selected one and display it!
                if (self.imageids.length > 1) {
                    dfx.showElement(this.nextNavButton);
                }

                self.currentIndex = 0;
                self.currentImage = self.imageids[self.currentIndex];
                self.displayImage();

                // Note that we initiate gallery slider after gallery finishes
                // it's initialization. Currently callback function does nothing.
                self.imageSlider.init(function() {});
            });
        } else if (this.galleryType === 'light') {
            self.init(function() {
                self.imageSlider.init(function() {});
            });
        }
    });

};

// Preloader (gif) web path.
mig.Gallery.preLoaderPath = '__web/Systems/ImageGallery/images/pre-loader.gif';

mig.Gallery.prototype = {

    /**
     * Initialize the gallery.
     *
     * @param {Function} cb Callback function.
     */
    init: function(cb)
    {
        // Remove prev and next button click event to stop the link tag.
        dfx.removeEvent(this.prevNavButton, 'click');
        dfx.removeEvent(this.nextNavButton, 'click');

        var self = this;
        dfx.addEvent(this.prevNavButton, 'click', function(e) {
            self.previous();
            return false;
        });

        dfx.addEvent(this.nextNavButton, 'click', function(e) {
            self.next();
            return false;
        });

        if (this.galleryType === 'light') {
            dfx.removeEvent(this.galleryPopClose, 'click');
            dfx.addEvent(this.galleryPopClose, 'click', function(e) {
                dfx.hideElement(self.popOverlay);
                return false;
            });

            // When the user clicks overlay area, it also closes pop-up.
            dfx.addEvent(this.popOverlay, 'click', function(e) {
                var target = dfx.getMouseEventTarget(e);
                if (dfx.hasClass(target, 'galleryPopOverlay') === true) {
                    dfx.hideElement(self.popOverlay);
                    return false;
                }
            });
        }

        if (cb) {
            cb.call(this);
        }

    },

    /**
     * Wait until preloader has been loaded to the browser, then call callback.
     *
     * @param {Function} cb Callback function.
     */
    isLoaderReady: function(cb)
    {
        var self = this;
        if (this.imageLoader.ready('gallery_loader') === true) {
            cb.call(self);
        } else {
            setTimeout(function() {
                self.isLoaderReady(cb);
            }, 100);
        }

    },

    /**
     * Navigate to the previous image.
     */
    previous: function()
    {
        if (this.currentIndex > 0) {
            this.currentIndex = (this.currentIndex - 1);
            this.currentImage = this.imageids[this.currentIndex];
            if (this.galleryType === 'light') {
                dfx.hideElement(this.prevNavButton);
                dfx.hideElement(this.nextNavButton);
                dfx.setStyle(this.imageContainer, 'visibility', 'hidden');
            }

            this.imageSlider.locateImageToMiddle(this.currentImage, true);
            this.displayImage();
        }

    },

    /**
     * Navigate to the next image.
     */
    next: function()
    {
        var imgLen = this.imageids.length;
        if (this.currentIndex < (imgLen - 1)) {
            this.currentIndex = this.currentIndex + 1;
            this.currentImage = this.imageids[this.currentIndex];
            if (this.galleryType === 'light') {
                dfx.hideElement(this.prevNavButton);
                dfx.hideElement(this.nextNavButton);
                dfx.setStyle(this.imageContainer, 'visibility', 'hidden');
            }

            this.imageSlider.locateImageToMiddle(this.currentImage, true);
            this.displayImage();
        }

    },

    /**
     * Display the selected image in the main window.
     */
    displayImage: function()
    {
        var self = this;

        if (this.galleryType === 'light') {
            dfx.setStyle(this.galleryPopFooter, 'visibility', 'hidden');
            dfx.setStyle(this.imageContainer, 'visibility', 'hidden');
            dfx.showElement(this.popOverlay);
            dfx.hideElement(this.galleryPopClose);
        }

        // Let's add an image to image loader. If the image was already added
        // once, it will be in a ready status straight.
        this.imageLoader.addImage(
            this.currentImage,
            this.images[this.currentImage].url
        );

        // Set the title of the image.
        this.imageSlider.selectImage(this.currentImage);
        var titleHtml = this.images[this.currentImage].title;
        titleHtml    += '&nbsp;(' + (this.currentIndex + 1);
        titleHtml    += ' of ' + this.imageids.length + ' images)';
        dfx.setHtml(this.imageCaption, titleHtml);

        var nW = this.images[this.currentImage].width;
        var nH = this.images[this.currentImage].height;

        // We need to adjust each image's size to fit into the gallery frame.
        if (this.images[this.currentImage].width > this.imageDisplayWidth
            || this.images[this.currentImage].height > this.imageDisplayHeight
        ) {
            var size = this._scaleDownImage(
                this.imageDisplayWidth,
                this.imageDisplayHeight,
                this.images[this.currentImage].width,
                this.images[this.currentImage].height
            );

            nW = size.nW;
            nH = size.nH;
        }

        var setNavButtons = function() {
            // Control next/prev buttons.
            if (self.currentIndex === 0) {
                dfx.hideElement(self.prevNavButton);
                dfx.showElement(self.nextNavButton);
            } else if (self.currentIndex === (self.imageids.length - 1)) {
                dfx.hideElement(self.nextNavButton);
                dfx.showElement(self.prevNavButton);
            } else {
                dfx.showElement(self.nextNavButton);
                dfx.showElement(self.prevNavButton);
            }

            dfx.setStyle(self.imageContainer, 'visibility', 'visible');
        };

        // Wait until the image loading has finished, then display it.
        var _displayImage = function() {
            if (self.imageLoader.ready(self.currentImage) === true) {
                var parent = self.imageContainer.parentNode;

                if (self.galleryType === 'normal') {
                    // We can not re-use the existing <img> tag since WebKit based
                    // browsers (e.g safari, chrome) takes some delay until the (evel)
                    // loaded image to be rendered on the screen, resulting in the
                    // preloader enlarged so big. So let's remove the existing <img> and
                    // create a new one!
                    if (self.firstTimeLoading === false) {
                        dfx.remove(self.imageContainer);
                        var img = document.createElement('img');
                        dfx.setStyle(img, 'visibility', 'hidden');
                        dfx.addClass(img, 'imageContainer');
                        img.src = self.images[self.currentImage].url;
                        img.alt = self.images[self.currentImage].alt;
                        img.setAttribute('width', nW);
                        img.setAttribute('height', nH);

                        self.imageContainer = img;
                        parent.appendChild(self.imageContainer);
                    } else {
                        dfx.setStyle(self.imageContainer, 'visibility', 'hidden');
                        self.imageContainer.setAttribute('width', nW);
                        self.imageContainer.setAttribute('height', nH);
                        self.firstTimeLoading = false;
                    }

                    // Adjust to the vertical centre.
                    if (nH < self.imageDisplayHeight) {
                        dfx.setStyle(self.imageContainer, 'margin-top', Math.ceil((self.imageDisplayHeight - nH) / 2) + 'px');
                    } else {
                        dfx.setStyle(self.imageContainer, 'margin-top', '0px');
                    }

                    setNavButtons();
                } else if (self.galleryType === 'light') {
                    var targetContainerW = nW;
                    var targetContainerH = nH + self.captionHeight + 2;
                    var params = {
                        width: targetContainerW,
                        height: targetContainerH,
                        marginTop: ((targetContainerH / 2) * (-1)),
                        marginLeft: ((targetContainerW / 2) * (-1))
                    };

                    dfx.animate(self.galleryPopContainer, params, 600, function() {
                        // We can not re-use the existing <img> tag since WebKit based
                        // browsers (e.g safari, chrome) takes some delay until the (evel)
                        // loaded image to be rendered on the screen, resulting in the
                        // preloader enlarged so big. So let's remove the existing <img> and
                        // create a new one!
                        var img = document.createElement('img');
                        dfx.setStyle(img, 'visibility', 'hidden');
                        dfx.addClass(img, 'imageContainer');
                        img.src = self.images[self.currentImage].url;
                        img.alt = self.images[self.currentImage].alt;
                        img.setAttribute('width', (nW - 4));
                        img.setAttribute('height', nH);

                        dfx.insertBefore(self.imageContainer, img);
                        dfx.remove(self.imageContainer);
                        self.imageContainer = img;

                        setNavButtons();
                        dfx.setStyle(self.galleryPopFooter, 'visibility', 'visible');
                        dfx.showElement(self.galleryPopClose);
                    });
                }//end if
            } else {
                setTimeout(function() {
                    _displayImage();
                }, 300);
            }//end if
        };

        // If the image has been loaded yet, display the loader instead
        // and wait.
        if (self.firstTimeLoading === false) {
            self.displayLoaderImage();
        }

        _displayImage();

    },

    displayLoaderImage: function()
    {
        if (this.galleryType === 'normal') {
            this.imageContainer.setAttribute('width', 48);
            this.imageContainer.setAttribute('height', 48);
            dfx.setStyle(this.imageContainer, 'margin-top', ((this.imageDisplayHeight / 2) - 24) + 'px');
            this.imageContainer.src = mig.Gallery.preLoaderPath;
        }

    },

    /**
     * Calculate scaled width/height of the image to display in the passed
     * display region.
     *
     * @param {Integer} gW Width of display region.
     * @param {Integer} gH Height of image.
     * @param {Integer} iW Width of display region.
     * @param {Integer} iH Height of image.
     */
    _scaleDownImage: function(gW, gH, iW, iH)
    {
        var size = {};
        if (iW > gW && iH <= gH) {
            // Fit to gallery width.
            // iW : iH = gW : x .
            // (iH * gW) / iW = x .
            // newHeight = (iH * gW) / iW; .
            // newWidth  = gW; .
            nW = gW;
            nH = Math.ceil((iH * gW) / iW);
        } else if (iH > gH && iW <= gW) {
            // Fit to gallery height.
            // iW : iH = x : gH
            // (iW * gH) / iH = x .
            nW = Math.ceil((iW * gH) / iH);
            nH = gH;
        } else if (iW > gW && iH > gH) {
            if (iW >= iH) {
                // Fit to gallery width.
                nW = gW;
                nH = Math.ceil((iH * gW) / iW);

                if (nH > gH) {
                    // Fit to gallery height again.
                    iH = nH; iW = nW;
                    nW = Math.ceil((iW * gH) / iH);
                    nH = gH;
                }
            } else if (iW < iH) {
                // Fit to gallery height.
                nW = Math.ceil((iW * gH) / iH);
                nH = gH;

                if (nW > gW) {
                    // Fit to gallery width again.
                    iH = nH; iW = nW;
                    nW = gW;
                    nH = Math.ceil((iH * gW) / iW);
                }
            }//end if
        } else if (iW < gW && iH < gH) {
            nW = iW;
            nH = iH;
        }//end if

        size.nW = nW;
        size.nH = nH;

        return size;

    }

};


/**
 * Gallery Slider class.
 *
 * Slider class controls image slider in the bottom of the gallery.
 *
 * @param {String}  galleryid     Id of this gallery.
 * @param {Object}  images        Image information passed from Gallery class.
 * @param {Array}   imageids      List of image ids.
 * @param {Object}  imageLoader   ImageLoader object shared with Galley class.
 * @param {Integer} galleryWidth  Width of the gallery.
 * @param {Integer} galleryHeight Height of the gallery.
 * @param {Boolean} smallThumb    True if gallery height is smaller than 400px.
 */
mig.Gallery.Slider = function(
    gallery,
    galleryid,
    images,
    imageids,
    imageLoader,
    galleryWidth,
    galleryHeight,
    smallThumb,
    container
)
{
    this.gallery          = gallery;
    this.galleryid        = galleryid;
    this.id               = this.galleryid + '_slider';
    this.images           = images;
    this.imageids         = imageids;
    this.imageLoader      = imageLoader;
    this.smallThumb       = smallThumb;
    this.thumbImageHeight = 75;
    if (this.smallThumb === true) {
        this.thumbImageHeight = 45;
    }

    // Current image displayed.
    this.currentImageid = this.imageids[0];

    // Image onselect event callbacks.
    this.onSelectCallbacks = [];

    // Loading thumb images.
    this.ready        = false;
    this.waitingQueue = [];

    // Various width values in pixel for navigation and animation.
    // this.stripWidth and this.endAnimationWidth will be set after
    // all thumbnails are loaded.
    this.sliderAnimationSpeed = 600;
    this.galleryWidth         = galleryWidth;
    this.leftMargin           = 10;
    this.rightMargin          = 10;
    this.visibleRegionWidth   = ((this.galleryWidth - this.leftMargin) - this.rightMargin);
    this.stripWidth           = null;
    this.startAnimationLeft   = this.leftMargin;
    this.endAnimationLeft     = null;
    this.stripLeftOffset      = this.leftMargin;
    this.currentSliderIndex   = null;
    this.imageOffsets         = [];
    this.imgClickedOffsets    = {};
    this.container            = container;
    this.thumbListUl          = dfx.getClass('thumbList', this.container)[0];
    this.thumbs               = dfx.getClass('galleryThumbs', this.container)[0];
    this.thumbsInner          = dfx.getClass('galleryThumbsInner', this.container)[0];
    this.thumbStripDiv        = dfx.getClass('thumbStrip', this.container)[0];

    // During the animation, block it.
    this.inAnimation = false;

    // Becomes true when the number of thumbs are not small so that it does not
    // need any sliding animation.
    this.noSliderAnimation = false;

    // Initialize thubm strip 'left' style here. Dont do it with CSS,
    // it can be overriden by user definition.
    dfx.setStyle(this.thumbStripDiv, 'left', this.leftMargin + 'px');

    var sliderClassName = '';
    if (this.smallThumb === true) {
        sliderClassName = 'Small';
    }

    this.sliderLeftButtonDiv  = dfx.getClass('thumbSlider' + sliderClassName + 'Left', this.container)[0];
    this.sliderRightButtonDiv = dfx.getClass('thumbSlider' + sliderClassName + 'Right', this.container)[0];
    this.sliderLeftButton     = dfx.getClass('thumbSliderLeftAnchor', this.container)[0];
    this.sliderRightButton    = dfx.getClass('thumbSliderRightAnchor', this.container)[0];

    // Show Thumbs slider. It is hidden by default for non-javascript mode.
    dfx.showElement(this.thumbs);

    // Adjust slider size!
    dfx.setStyle(this.thumbsInner, 'width', this.galleryWidth + 'px');

};

mig.Gallery.Slider.prototype = {

    /**
     * Initialize Gallery Slider.
     *
     * @param {Function} cb Callback funtion.
     */
    init: function(cb)
    {
        var self = this;

        dfx.removeEvent(this.sliderLeftButton, 'click');
        dfx.removeEvent(this.sliderRightButton, 'click');

        // Click left slider arrow.
        dfx.addEvent(this.sliderLeftButton, 'click', function(e) {
            if (self.inAnimation === true
                || self.startAnimationLeft === self.stripLeftOffset
                || self.noSliderAnimation === true
            ) {
                return false;
            }

            var target = self.stripLeftOffset + self.visibleRegionWidth;
            if (target >= self.startAnimationLeft) {
                target = self.startAnimationLeft;
            } else {
                var found = false;
                var num   = self.imageOffsets.length;
                for (var i = (num - 1); i > 0; i--) {
                    var lo = self.imageOffsets[i].leftOffset;
                    var ro = self.imageOffsets[i].rightOffset;
                    if (found === false && lo >= target) {
                        target = lo;
                        found  = true;
                    }
                }
            }

            self.inAnimation = true;
            dfx.animate(self.thumbStripDiv, {left: target}, self.sliderAnimationSpeed, function() {
                self.stripLeftOffset = target;
                self.inAnimation     = false;
                self.updateNavButtons();
            });

            return false;
        });

        // Click right slider arrow.
        dfx.addEvent(this.sliderRightButton, 'click', function(e) {
            if (self.inAnimation === true
                || self.endAnimationLeft === self.stripLeftOffset
                || self.noSliderAnimation === true
            ) {
                return false;
            }

            var target = (self.stripLeftOffset - self.visibleRegionWidth);
            if (target <= self.endAnimationLeft) {
                target = self.endAnimationLeft;
            } else {
                var found = false;
                dfx.foreach(self.imageOffsets, function(idx) {
                    var lo = self.imageOffsets[idx].leftOffset;
                    var ro = self.imageOffsets[idx].rightOffset;
                    if (found === false && ro <= target) {
                        target = lo;
                        found  = true;
                    }

                    return true;
                });
            }

            self.inAnimation = true;
            dfx.animate(self.thumbStripDiv, {left: target}, self.sliderAnimationSpeed, function() {
                self.stripLeftOffset = target;
                self.inAnimation     = false;
                self.updateNavButtons();
            });

            return false;
        });

        // Okay. Let'b build HTML with pre-loader gif image and put thumb images
        // as they are loaded by image loader.
        // Once it's done, call the passed callback function. The callback
        // function to init() is finally called in _processQueueLoader().
        var content = '';
        dfx.foreach(this.imageids, function(idx) {
            var iid     = self.imageids[idx];
            var thumbid = iid + '_tb';
            content    += '<li style="padding: 0px 0px 0px 0px; ';
            content    += 'margin: 0px 5px 0px 0px;" id="' + self.id + '_';
            content    += iid + '_list" class="thumbItem';

            // Calculate each thumb image's width here.
            var thumbW = Math.ceil((self.images[iid].width * self.thumbImageHeight) / self.images[iid].height);
            self.images[iid].thumbW = thumbW;

            // First item selected by default.
            if (idx === 0) {
                content += ' thumbItemSelected';
            }

            content += ' ">';
            if (self.imageLoader.ready(thumbid) === true) {
                content += '<img id="' + self.id + '_' + iid + '" height="';
                content += self.thumbImageHeight + '" ';
                content += 'width="' + thumbW + '" src="';
                if (self.images[iid].url.search(/\?/) === -1) {
                    // Not found.
                    content += self.images[iid].url + '?tb=1" alt="' + self.images[iid].alt + '" />';
                } else {
                    content += self.images[iid].url + '&tb=1" alt="' + self.images[iid].alt + '" />';
                }
            } else {
                self.waitingQueue.push(iid);
                content += '<img id="' + self.id + '_' + iid + '" height="';
                content += self.thumbImageHeight + '" width="45" src="';
                content += mig.Gallery.preLoaderPath + '" alt="' + self.images[iid].alt + '" />';
            }

            content += '</li>';

            // Caculate each image's offsets.
            var imagePadding = 4;
            var imageBorder  = 1;
            var imageMargin  = 5;
            var totalThumbW  = (self.images[iid].thumbW + (imagePadding + imageBorder) * 2);

            var minMiddleOffset = (self.leftMargin - Math.ceil(self.visibleRegionWidth / 2));

            if (idx === 0) {
                var rightOffset  = (self.leftMargin - totalThumbW);
                var imgMidOffset = (self.leftMargin - Math.ceil(totalThumbW / 2));
                self.imageOffsets.push({
                    leftOffset: self.leftMargin,
                    width: self.images[iid].thumbW,
                    rightOffset: rightOffset,
                    midOffset: imgMidOffset,
                    thumbDisplayWidth: totalThumbW
                });

                self.imgClickedOffsets[iid] = self.leftMargin;
                prevOffset = (self.leftMargin - (totalThumbW + imageMargin));
            } else {
                var rightOffset  = (prevOffset - totalThumbW);
                var leftOffset   = prevOffset;
                var imgMidOffset = (leftOffset - Math.ceil(totalThumbW / 2));

                var imgClickedOffset = null;
                if (imgMidOffset < minMiddleOffset) {
                    imgClickedOffset = (self.leftMargin - Math.abs(minMiddleOffset - imgMidOffset));
                } else if (imgMidOffset >= minMiddleOffset) {
                    imgClickedOffset = self.leftMargin;
                }

                self.imageOffsets.push({
                    leftOffset: prevOffset,
                    width: self.images[iid].thumbW,
                    rightOffset: rightOffset,
                    midOffset: imgMidOffset,
                    thumbDisplayWidth: totalThumbW
                });

                self.imgClickedOffsets[iid] = imgClickedOffset;
                prevOffset = (prevOffset - totalThumbW - imageMargin);
            }//end if

            return true;
        });

        // Calculate the total width of thumb strip here!
        var stripW = 0;
        var imgGap = 5;
        dfx.foreach(this.imageOffsets, function(idx) {
            stripW += self.imageOffsets[idx].thumbDisplayWidth + imgGap;
            return true;
        });

        // Deduct the last imgGap here.
        stripW = (stripW - imgGap);
        dfx.setStyle(this.thumbStripDiv, 'width', stripW + 'px');
        if (stripW <= self.visibleRegionWidth) {
            this.noSliderAnimation = true;
        }

        this.stripWidth       = stripW;
        this.endAnimationLeft = ((this.galleryWidth - this.stripWidth) - this.rightMargin);
        dfx.setStyle(this.thumbListUl, 'padding-left', '0px');

        dfx.foreach(this.imgClickedOffsets, function(iid) {
            if (self.imgClickedOffsets[iid] <= self.endAnimationLeft) {
                self.imgClickedOffsets[iid] = self.endAnimationLeft;
            }

            return true;
        });

        // Set the list HTML to <ul> tag.
        dfx.setHtml(this.thumbListUl, content);

        // Now let's attach onclick event to each image thumbs.
        dfx.foreach(this.imageids, function(idx) {
            var iid   = self.imageids[idx];
            var imgid = self.id + '_' + iid;

            dfx.addEvent(dfx.getId(imgid), 'click', function(e) {
                if (self.inAnimation === true) {
                    return false;
                }

                self.gallery.displayLoaderImage();
                self.selectImage(iid);
                self.locateImageToMiddle(iid, false);
            });

            return true;
        });

        // Init slider nav buttons.
        dfx.hideElement(this.sliderLeftButtonDiv);
        if (this.noSliderAnimation === true) {
            dfx.hideElement(this.sliderRightButtonDiv);
        } else {
            dfx.showElement(this.sliderRightButtonDiv);
        }

        // Now things are ready. Let's start put thumb images on the place!
        this.onReadyCb = cb;
        if (this.waitingQueue.length > 0) {
            self._processQueueLoader();
        }

    },


    updateNavButtons: function()
    {
        if (this.noSliderAnimation === true) {
            dfx.hideElement(this.sliderLeftButtonDiv);
            dfx.hideElement(this.sliderRightButtonDiv);
        } else if (this.stripLeftOffset === this.startAnimationLeft) {
            dfx.hideElement(this.sliderLeftButtonDiv);
            dfx.showElement(this.sliderRightButtonDiv);
        } else if (this.stripLeftOffset === this.endAnimationLeft) {
            dfx.hideElement(this.sliderRightButtonDiv);
            dfx.showElement(this.sliderLeftButtonDiv);
        } else {
            dfx.showElement(this.sliderLeftButtonDiv);
            dfx.showElement(this.sliderRightButtonDiv);
        }

    },


    /**
     * When the thumb image is clicked, call selectImage() to update class names
     * then go through each callback functions.
     *
     * @param {String} id Selected imageid.
     */
    locateImageToMiddle: function(id, noCallback)
    {
        var self = this;
        if (this.noSliderAnimation === true) {
            if (noCallback === false) {
                var cbLen = self.onSelectCallbacks.length;
                for (var i = 0; i < cbLen; i++) {
                    self.onSelectCallbacks[i].call(self, id);
                }
            }

            this.updateNavButtons();
        } else if (this.imgClickedOffsets[id] !== null) {
            this.inAnimation = true;
            dfx.animate(this.thumbStripDiv, {left: this.imgClickedOffsets[id]}, 300, function() {
                if (noCallback === false) {
                    // Call onselect callbacks.
                    var cbLen = self.onSelectCallbacks.length;
                    for (var i = 0; i < cbLen; i++) {
                        self.onSelectCallbacks[i].call(self, id);
                    }
                }

                self.stripLeftOffset = self.imgClickedOffsets[id];
                self.inAnimation     = false;
                self.updateNavButtons();
            });
        } else {
            if (noCallback === false) {
                // Call onselect callbacks.
                var cbLen = self.onSelectCallbacks.length;
                for (var i = 0; i < cbLen; i++) {
                    self.onSelectCallbacks[i].call(self, id);
                }
            }
        }//end if

    },

    /**
     * Update the selected thumb's class name as selected. Also removes the
     * previsouly selected thumb.
     *
     * @param {String} id Selected imageid.
     */
    selectImage: function(id)
    {
        if (this.currentImageid === id) {
            return;
        }

        var oldList = dfx.getId(this.id + '_' + this.currentImageid + '_list');
        var newList = dfx.getId(this.id + '_' + id + '_list');

        dfx.removeClass(oldList, 'thumbItemSelected');
        dfx.addClass(newList, 'thumbItemSelected');

        this.currentImageid = id;

    },

    /**
     * Loading all thumb images are also managed as a queue.
     */
    _processQueueLoader: function()
    {
        var self = this;
        if (this.waitingQueue.length > 0) {
            var tmp  = [];
            var stop = false;
            dfx.foreach(this.waitingQueue, function(idx) {
                var iid     = self.waitingQueue[idx];
                var thumbid = self.waitingQueue[idx] + '_tb';
                if (self.imageLoader.ready(thumbid) === true) {
                    var img = dfx.getId(self.id + '_' + iid);
                    if (dfx.isset(img) === false) {
                        // This block is intended to handle the case when the
                        // loader is still loading the image in Preview, but
                        // the user clicked Edit mode. We need to stop the
                        // scheduled image loading.
                        stop = true;
                        return false;
                    } else {
                        // Image has been loaded, set the correct width attribute
                        // and src url.
                        var tW = Math.ceil((self.images[iid].width * self.thumbImageHeight) / self.images[iid].height);
                        img.setAttribute('width', tW);

                        if (self.images[iid].url.search(/\?/) === -1) {
                            // Not found.
                            img.setAttribute('src', self.images[iid].url + '?tb=1');
                        } else {
                            img.setAttribute('src', self.images[iid].url + '&tb=1');
                        }
                    }
                } else {
                    tmp.push(iid);
                }//end if

                return true;
            });

            if (stop === false) {
                this.waitingQueue = tmp;
                setTimeout(function() {
                    self._processQueueLoader();
                }, 500);
            }
        } else {
            // Fix for Opera browser to display thumb strip correctly.
            self.onReadyCb.call(self);
        }//end if

    },

    /**
     * Add image onSelect callback function.
     */
    addOnSelectCallback: function(cb)
    {
        this.onSelectCallbacks.push(cb);

    }

};


/**
 * ImageLoader class.
 *
 * Every image including thumb/actual image are loaded to the browser via
 * this class. It maintains the queue of images to load and can coltrol the
 * maximum number of images to load at once.
 */
mig.ImageLoader = function(galleryid)
{
    this.galleryid   = galleryid;
    this.images      = {};
    this.total       = 0;
    this.downloading = 0;
    this.maxDownload = 5;

    this.waiting = [];
    this.downloadingIntervalId = null;

};

mig.ImageLoader.prototype = {

    addImage: function(id, url)
    {
        if (dfx.isset(this.images[id]) === true) {
            return;
        }

        if (this.downloading > this.maxDownload) {
            this.waiting.push({
                id: id,
                url: url
            });

            if (this.downloadingIntervalId === null) {
                var self = this;
                this.downloadingIntervalId = setInterval(function() {
                    self._process();
                }, 500);
            }
        } else {
            this._startLoading(id, url, this.galleryid);
        }

        this.total = this.total + 1;

    },

    ready: function(id)
    {
        if (dfx.isset(this.images[id]) === false) {
            return false;
        }

        return this.images[id].ready;

    },

    get: function(id)
    {
        if (this.ready(id) === false) {
            return null;
        }

        return this.images[id];

    },

    _startLoading: function(id, url, gid)
    {
        var image = new Image();
        dfx.addEvent(image, 'load', function() {
            // This block is intended to handle the case when the
            // loader is still loading the image in Preview, but
            // the user clicked Edit mode. We need to stop the
            // scheduled image loading.
            if (dfx.isset(mig.imageLoaders[gid]) === true) {
                mig.imageLoaders[gid].images[id].ready = true;
                mig.imageLoaders[gid].downloading      = (mig.imageLoaders[gid].downloading - 1);
            }
        });

        // Note: It's important that we set these values first before setting
        // src attribute. When the browser caches the previously loaded image,
        // then image.src = url; statement can fire 'onload' event instantly
        // before id is added to this.images object. It will introduce id
        // undefined error in onload callback function!
        this.downloading = (this.downloading + 1);
        this.images[id]  = {
            ready: false,
            imgElem: image,
            url: url
        };

        image.src = url;

    },

    _process: function()
    {
        var queueLen = this.waiting.length;
        if (queueLen > 0 && this.downloading < this.maxDownload) {
            var nextImg = this.waiting[0];
            this._startLoading(nextImg.id, nextImg.url, this.galleryid);

            this.waiting = this.waiting.slice(1);
        } else if (queueLen === 0) {
            clearInterval(this.downloadingIntervalId);
            this.downloadingIntervalId = null;
        }

    }

};
