function prep_images(settings, el) {
    var $ul = $(el);
    var $firstimg = $ul.find('img:first-child');
    var width = $firstimg.width(); // this gets fine-tuned later
    var height = $firstimg.height(); // this gets fine-tuned later

    var $lis = $ul.children();

    var num_images = $lis.length;

    var titles = [];
    var descriptions = [];

    $lis.each(function(number) {
        /*
        We normalise the images here so that it is just a list of
        image tags.

        Possible contents are:

        1) <div class="frame"></div> with an image (possibly wrapped in a
           link to the original) and a .info div with .title and possibly
           .description divs inside.

        2) an a.image linking to the original with the image inside it

        3) just an image
        */

        var $li = $(this);

        var img_src = '';
        var img_alt = '';

        var img_link = '';
        var img_title = '';
        var img_description = '';

        var $div_frame = $li.children('div.frame');
        var $a_img = $li.children('a.img');
        var $img = $li.children('img');

        if ($div_frame.length) {
            // div.frame

            var $a_img = $div_frame.find('a.img')
            if ($a_img.length) {
                img_link = $a_img.attr('href');
            }

            var $img = $div_frame.find('img');
            img_src = $img.attr('src');
            img_alt = $img.attr('alt');

            img_title = $div_frame.find('.title').html();
            if (!img_title) {
                img_title = '';
            }
            titles[titles.length] = img_title;

            img_description = $div_frame.find('.description').html();
            if (!img_description) {
                img_description = '';
            }
            descriptions[descriptions.length] = img_description;
        } else if ($a_img.length) {
            // a.img

            img_link = $a_img.attr('href');

            var $img = $a_img.find('img');
            img_src = $img.attr('src');
            img_alt = $img.attr('alt');

            titles[titles.length] = '';
            descriptions[descriptions.length] = '';

        } else if ($img.length) {
            //img

            img_src = $img.attr('src');
            img_alt = $img.attr('alt');

            titles[titles.length] = '';
            descriptions[descriptions.length] = '';

        } else {
            titles[titles.length] = '';
            return; // rather just skip unknown ones
        }

        var img_width = $img.width();
        var img_height = $img.height();
        // set width so that it is at least as wide as the widest.
        if (img_width && (!width || img_width > width)) {
            width = img_width;
        }
        // set height so that it is at least as high as the shortest.
        if (img_height && (!height || img_height > height)) {
            height = img_height;
        }

        var html = '<img src="'+img_src+'" title="'+img_alt+'" />';
        $li.html(html);

    });

    // set the list element's dimensions so that absolute positioning works
    $lis.each(function() {
        $(this).css('width', width+'px').css('height', height+'px');
    });

    // the list gets given the width of the number of images * width
    var total_width = num_images * width;

    $ul
        .css('width', total_width+'px')
        .css('height', height+'px')
        .wrap('<div class="'+settings.wrapclass+'"></div>');

    var $wrapper = $ul.parent();

    // make the wrapper the size of one of the images,
    // because that's the "window" through which the images are seen
    $wrapper
        .css('width', width+'px')
        /*.css('height', height+'px')*/;

    return {
        'width': width,
        'height': height,
        'num_images': num_images,
        'titles': titles,
        'descriptions': descriptions,
        'wrapper': $wrapper,
        'ul': $ul
    }
}

jQuery.fn.igallery = function(options) {
    var settings = jQuery.extend({
        wrapclass: "igallerywrap"
    }, options);

    return this.each(function() {
        var info = prep_images(settings, this);

        var $ul = info['ul'];
        var $wrapper = info['wrapper'];
        var num_images = info['num_images'];
        var titles = info['titles'];
        var descriptions = info['descriptions'];
        var width = info['width'];
        var height = info['height'];

        var display_descriptions = $ul.hasClass('descriptions');

        // ======

        var speed = 100;
        var classes = $ul.attr('class').split(' ');

        for (var i = 0; i < classes.length; i++) {
            var classname = classes[i];

            if (classname.indexOf('speed-') == 0) {
                var parts = classname.split('-');
                if (parts.length == 2) {
                    var s = parts[1]-0;
                    if (s && s > 0) {
                        speed = s;
                    }
                }
                break;
            }
        }

        var next = '<a href="#next" class="next" title="next">Next</a>';
        var prev = '<a href="#prev" class="prev" title="previous">Previous</a>';
        var t_div = '<span class="title">'+titles[0]+'</span>';
        $wrapper.append('<div class="nav">'+t_div+' '+next+' '+prev+'</div>');
        $wrapper.append('<div class="total">1 / '+num_images+'</div>');

        if (display_descriptions && descriptions[0]) {
            $wrapper.append('<div class="description">'+descriptions[0]+'</div>');
        }

        if ($ul.hasClass('middle')) {
            $wrapper.addClass('middle');
        }

        var index = 0;
        $wrapper.find('.next').click(function() {
            if (index+1 < num_images) {
                index++;
                $ul.animate({
                    marginLeft: -index*width+'px'
                }, speed);
                if (index+1 >= num_images) {
                    $(this).css('display', 'none');
                }
                $wrapper.find('.prev').css('display', 'block');
                $wrapper.find('.total').text(index+1+' / '+num_images);
                $wrapper.find('.title').text(titles[index]);

                if (display_descriptions && descriptions[index]) {
                    $wrapper.find('.description').html(descriptions[index]);
                } else {
                    $wrapper.find('.description').html('');
                }
            }

            return false;
        });

        $wrapper.find('.prev').click(function() {
            if (index > 0) {
                index--;
                $ul.animate({
                    marginLeft: -index*width+'px'
                }, speed);
                if (index <= 0) {
                    $(this).css('display', 'none');
                }
                $wrapper.find('.next').css('display', 'block');
                $wrapper.find('.total').text(index+1+' / '+num_images);
                $wrapper.find('.title').text(titles[index]);

                if (display_descriptions && descriptions[index]) {
                    $wrapper.find('.description').html(descriptions[index]);
                } else {
                    $wrapper.find('.description').html('');
                }
            }

            return false;
        });

        $wrapper.mouseenter(function() {
            //$wrapper.find('.nav').css('display', 'block');
            $wrapper.addClass('mouseover');
            $wrapper.removeClass('mouseout');
        });

        $wrapper.mouseleave(function() {
            //$wrapper.find('.nav').css('display', 'none');
            $wrapper.addClass('mouseout');
            $wrapper.removeClass('mouseover');
        });

        $wrapper.addClass('mouseout');
    });
}

jQuery.fn.islideshow = function(options) {
    var settings = jQuery.extend({
        wrapclass: "islideshowwrap"
    }, options);

    return this.each(function() {
        var $this = $(this);

        if ($this.find('li').length < 2) {
            return;
        }

        var Q = $({});
        var index = 0;
        var items = $this.find('> *');
        var images = $this.find('img');

        var info = prep_images(settings, this);

        var $ul = info['ul'];
        var $wrapper = info['wrapper'];
        var num_images = info['num_images'];
        var titles = info['titles'];
        var descriptions = info['descriptions'];
        var width = info['width'];
        var height = info['height'];

        var display_descriptions = $ul.hasClass('descriptions');

        // ===

        var timeout = 4000;
        var speed = 1000;
        var classes = $ul.attr('class').split(' ');

        for (var i = 0; i < classes.length; i++) {
            var classname = classes[i];

            if (classname.indexOf('timeout-') == 0) {
                var parts = classname.split('-');
                if (parts.length == 2) {
                    var t = parts[1]-0;
                    if (t && t > 0) {
                        timeout = t;
                    }
                }
            }
            if (classname.indexOf('speed-') == 0) {
                var parts = classname.split('-');
                if (parts.length == 2) {
                    var s = parts[1]-0;
                    if (s && s > 0) {
                        speed = s;
                    }
                }
            }
        }

        if (display_descriptions && descriptions[0]) {
            $wrapper.append('<div class="description">'+descriptions[0]+'</div>');
        } else {
            // insert an empty description div so we can reference it later
            $wrapper.append('<div class="description"></div>');
        }

        if ($ul.hasClass('middle')) {
            $wrapper.addClass('middle');
        }

        var playpause = '<a href="#" class="playpause pause">pause</a>';
        $wrapper.append(playpause);

        $playpause = $wrapper.find('.playpause');
        var pp_width = $playpause.width();
        var pp_height = $playpause.height();

        var pp_top = height/2-pp_height/2;
        var pp_left = width/2-pp_width/2;
        $playpause.css('top', pp_top+'px');
        $playpause.css('left', pp_left+'px');

        $playpause.click(pause);

        $wrapper.mouseenter(function() {
            $wrapper.addClass('mouseover');
            $wrapper.removeClass('mouseout');
        });

        $wrapper.mouseleave(function() {
            $wrapper.addClass('mouseout');
            $wrapper.removeClass('mouseover');
        });

        $wrapper.find('li:first').css('display', 'block');
        $wrapper.addClass('mouseout');

        items.css({
            'display': 'none',
            'position': 'absolute',
            'top': 0,
            'left': 0
        });

        // show the first item
        $(items[0]).css({
            'display': 'block',
            'opacity': 1,
            'z-index': 2
        });

        function animate() {
            if (display_descriptions && descriptions[index]) {
                $wrapper.find('.description').html(descriptions[index]);
            } else {
                $wrapper.find('.description').html('');
            }

            // fade out old item
            $(items[old_index])
                .animate({'opacity': 0}, settings.speed, 'linear', function() {
                    $(this).css({
                        'display': 'none',
                        'opacity': 0,
                        'z-index': 0
                    });
                });

            var $item = $(items[index]);

            // fade in new item
            $item
                .css({
                    'display': 'block',
                    'z-index': 1
                })
                .animate({'opacity': 1}, settings.speed, 'linear', function() {
                    $(this).css({
                        'display': 'block',
                        'opacity': 1,
                        'z-index': 2
                    });
                });
        }

        function next() {
            old_index = index++;

            if (index > images.length-1) {
                index = 0;
            }

            $image = $(images[index]);
            $image.attr('src', $image.attr('src'));
            if ($image.attr('complete')) {
                animate(index, old_index);
            } else {
                // wait for the image to load
                $image.load(function() {
                    animate(index, old_index);
                });
            }
        }

        function start() {
            $image = $(images[index]);
            $image.attr('src', $image.attr('src'));
            if ($image.attr('complete')) {
                // image is already loaded
                Q.delay(timeout).queue(function() {
                    next();
                    start();

                    Q.dequeue();
                });
            } else {
                // wait for the current image to load
                stop(); // pause while the image is loading
                $image.load(function() {
                    Q.delay(timeout).queue(function() {
                        next();
                        start();

                        Q.dequeue();
                    });
                });
            }
        }

        function stop() {
            Q.stop(true);
        }

        function play() {
            $wrapper.find('.play').unbind('click')
                .removeClass('play').addClass('pause').click(pause)
                .text('pause');

            start();

            return false;
        }

        function pause() {
            $(this).text('play');
            $wrapper.find('.pause').unbind('click')
                .removeClass('pause').addClass('play').click(play)
                .text('play');

            stop();

            return false;
        }

        // wait for the first image to load
        if ($.browser.msie) {
            play();
        } else {
            $image = $(images[0]);
            if ($image.attr('complete')) {
                play();
            } else {
                $image.bind('load', play);
            }
        }
    });
}

