
// this is only for setting up the namespaces
ImageFlow = {};
ImageFlow.Mode1 = {};
ImageFlow.Mode2 = {};
/**
 * the app is responsible for toggling between the gallery's two modes,
 * and dispatching events to the currently active mode (represented by the
 * ImageFlow.Mode1.App and ImageFlow.Mode2.App (see below)
 *   
 * @param {Object} config     the galleries configuration. 
 * it defined in the html-file which actually starts the gallery
 * @see gui.html  
 */
ImageFlow.App = function(config) {
    
    /**
     * shows the gallery in carusell-mode
     */
    this.mode1 = function() {
        nav.setActiveModeButton(1);
        switchMode(app1);
    };
    
    /**
     * shows the gallery in 2d-mode
     */
    this.mode2 = function() {
        nav.setActiveModeButton(2);
        switchMode(app2);
    };
    
    /**
     * displays an overlay (to shade the page's originally content)
     * and brings up the gallery
     */
    this.show = function() {
        // Hides the scrollbar in IE6 to prevent scrolling
        if (typeof document.body.style.maxHeight === "undefined") {//if IE 6
            jQuery("body","html").css({height: "100%", width: "100%"});
            jQuery("html").css("overflow","hidden");
            jQuery('#imageFlow-overlay').css({
                top:jQuery(document).scrollTop()
            });
            jQuery('#imageFlow-container').css({
                top:jQuery(document).scrollTop()
            });
        }
        // show overlay
        var pageInfo = ImageFlow.util.getWindowSize();
        jQuery('#imageFlow-overlay').css({
            width: '100%',
            height: pageInfo.height,
            opacity: 0
        }).fadeTo(350, 0.8).show();
        
        jQuery('#imageFlow-container').css({
            zIndex: 10001,
            width: '100%'
        }).show();
        // show App
        currentApp.show();
        nav.enable();
    };
    
    /**
     * hides the gallery
     */
    this.hide = function() {
        // hide overlay
        
        //jQuery('#imageFlow-overlay').fadeOut(function() {
        //});
        // Shows the scrollbar in IE6 to allow scrolling again
        if (typeof document.body.style.maxHeight == "undefined") {//if IE 6
            jQuery("body","html").css({height: "auto", width: "auto"});
            jQuery("html").css("overflow","");
        }
        jQuery('#imageFlow-overlay').css({
            display: 'none'
        });
        
        jQuery('#imageFlow-container').css({
        }).hide();
        nav.disable();
    };
    
    /**
     * sets the galleries mode
     * @param {Object} app
     */
    function switchMode(app) {
        if (currentApp) {
            currentApp.hide();
        }
        currentApp = app;
        currentApp.show();
    }
    /**
     * this method sets up the galleries userinterface by injecting the the following HTML into 
     * the current document. 
     * 
     * it might be doubious not to use javascript to set up the nodes, but I find the plain html
     * much more readable and easier to maintain.
     */
    function injectHtml() {
        var html = '';
        html +='<div id="imageFlow-overlay"></div>\n';
        html +='<div id="imageFlow-container" style="display:none;">\n';
        html +='    <div id="imageFlow">\n';
        html +='        <div id="imageFlow-mode1">\n';
        html +='            <div id="imageFlow-canvasContainer">\n';
//        html +='                <a href="#" id="imageFlow-button-close"></a>\n';
        html +='                <div id="imageFlow-frame"><img id="imageFlow-canvas" /></div>\n';
        html +='                <div id="imageFlow-meta">\n';
        html +='                    <div id="imageFlow-subtitle">Titel</div>\n';
        html +='                    <div id="imageFlow-imageinfo">Bild <span id="imageFlow-imageIndex">?</span> von <span id="imageFlow-imageCount">?</span></div>\n';
        html +='                </div>\n';        
        html +='            </div>\n';        
                    
        html +='            <div id="imageFlow-thumbs-container">\n';
        html +='                <div class="imageFlow-thumbs">\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                    <img class="imageFlow-thumb active" />\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                    <img class="imageFlow-thumb" />\n';
        html +='                </div>\n';
        html +='                <a href="#" id="imageFlow-previous"></a>\n';
        html +='                <a href="#" id="imageFlow-next"></a>\n';
        html +='            </div>\n';
        html +='        </div>\n';
    
        html +='        <div id="imageFlow-mode2">\n';
        html +='            <div class="imageFlow-mode2-images">\n';
        html +='                <img src="" />\n';
        html +='                <img src="" />\n';
        html +='                <img src="" />\n';
        html +='                <img src="" />\n';
        html +='                <img src="" />\n';
        html +='            </div>\n';
        html +='        </div>\n';
                
        html +='        <div id="imageFlow-nav">\n';
        html +='            <div id="imageFlow-button-mode1">\n';
        html +='                <a href="#" id="imageflow-link-mode1"><span>Einzelbilder</span></a>\n';
        html +='            </div>\n';
        html +='            <div id="imageFlow-button-mode2">\n';
        html +='                <a href="#" id="imageflow-link-mode2"><span>Bl&auml;ttern</span></a>\n';
        html +='            </div>\n';
        html +='            <div class="imageFlow-seperator"></div>\n';
        html +='            <div id="imageFlow-download"><a id="imageFlow-download-link" href="#"><span>Download...</span></a></div>\n';
        html +='            <div id="imageFlow-close"><a href="#" id="imageFlow-button-close">Schlie&szlig;en</a></div>\n';
        html +='        </div>\n';            
        html +='    </div>\n';
        html +='</div>\n';
        
        // TODO: swap elements
        //jQuery('body').children().eq(0).before(html);
        if (jQuery("#imageFlow-overlay").length == 0)
            jQuery('body').append(html);
        ImageFlow.util.log('html injected into dom');
    }
    /**
    * wires up the controller-events, sets up the gui and loads the images 
    */
    function init() {
        injectHtml();
        jQuery(window).bind("resize", function() {
            jQuery('#imageFlow-overlay').css({
                height: jQuery(window).height()
            })
        });
        nav = new ImageFlow.Nav();
    
        if (config.loader === undefined) {
            throw 'no loaded configured';
        }
        
        loader = config.loader;
        loader.load( onImagesLoaded );
        
        /**
         * listens for the click on the mode1-button
         */
        nav.onMode1Click = function() {
            that.mode1();
        };
        /**
         * listens for the click on the mode1-button
         */
        nav.onMode2Click = function() {
            that.mode2();
        };
        /**
         * listens for the navigation-events
         */
        nav.onPrevious = function(){
            //app1.previous();
            //app2.previous();
            currentApp.previous();
        }
        /**
         * listens for the navigation-events
         */
        nav.onNext = function() {
            //app1.next();
            //app2.next();
            currentApp.next();
        };
        
        /**
         * listens for the navigation-events
         */
        nav.onImageClick = function(slot) {
            //app1.gotoSlot( slot );
            //app2.gotoSlot( slot );
            currentApp.gotoSlot( slot );
        };
        
        nav.onEscape = function() {
            that.hide(1);
        };
        /*nav.onDownload = function() {
            return true;
        }*/
        
    }
    
    /**
     * fired when all images have been loaded
     * @param {DOMElement} images
     */
    function onImagesLoaded(images) {
        ImageFlow.util.log('images loaded', images);
        app1 = new ImageFlow.Mode1.App(config, images);
        app2 = new ImageFlow.Mode2.App(config, images);
        
        // sets the default app
        currentApp = config.mode == 2 ? app2 : app1;
        nav.setActiveModeButton(config.mode);
    }
    
    var app1, app2, currentApp = null, nav;
    var that = this;
    init();
};
/**
 * 
 * @param {Map} config
 * @param {List<ImageFlow.Image>} images
 */
ImageFlow.Mode1.App = function(config, images) {
    this.show = function() {
        jQuery('#imageFlow').removeClass('mode2');
        jQuery('#imageFlow').addClass('mode1');
        that.refresh();
    }
    
    this.refresh = function() {
        gui.setThumbs( controller.getVisibleThumbs() );
        gui.setCurrent( controller.getCurrentImage() );
        
        // refresh a.href
        //var a = document.getElementById("imageFlow-download-link");
        //a.href = window.imgFlow.downloadScript + "?file=" + controller.getCurrentImage().imageUrl;
    }
    
    this.hide = function() {
        jQuery('#imageFlow').removeClass('mode1');
        jQuery('#imageFlow').removeClass('mode2');
    }
    
    this.next = function() {
        controller.next();
        that.refresh();
    };
    
    this.previous = function() {
        controller.previous();
        that.refresh();
    };
    
    /**
     * scrolls the thumbnails to the given slot 
     * @param {DOM-Element} slot
     */
    this.gotoSlot = function( slot ) {
        controller.gotoSlot( slot );
        that.refresh();
    };
    
    /**
     * returns the currently displayed image
     */
    this.getCurrentImage = function() {
        return controller.getCurrentImage();
    };
    function init()
    {
        ImageFlow.util.log('config:', config);
        controller = new ImageFlow.Mode1.Controller( { thumbSlots: config.thumbSlots, images: images } );
        
        gui = new ImageFlow.Mode1.Gui({ 
            imageCount: images.length
        });
    }
    
    var that = this;
    var loader = config.loader;
    var controller = null;
    var gui = null;
    
    init();    
};
/**
 * the carusell-mode main program
 * @param {Object} config
 * @param {List<ImageFlow.Image>} images
 */
ImageFlow.Mode2.App = function(config, images ) {
    /**
     * brings up the mode's gui
     */
    this.show = function(){
        // set up the timer to refreshes the gui frequently
        interval = window.setInterval( run, 15);
        jQuery('#imageFlow').removeClass('mode1');
        jQuery('#imageFlow').addClass('mode2');
        jQuery('#imageFlow').show();
        controller.setAnimationSync( config.sync );
        update();
    };
    
    /**
     * hides the mode's gui
     */
    this.hide = function(){
        if( interval ) {
            window.clearInterval( interval );
            interval = null;
        }
        controller.setAnimationSync( true );
        jQuery('#imageFlow').removeClass('mode2');
        jQuery('#imageFlow').removeClass('mode1');
    };
    
    /**
     * returns <code>true</code> if the mode is currently running  
     */
    this.isRunnning = function(){
        return interval == true;
    };
    
    /**
     * navigates to the next image (rotates the carusell in clockwise direction) 
     */
    this.next = function() {
        controller.next();
    };
    /**
     * navigates to the previous image (rotates the carusell in counterclockwise direction) 
     */
    this.previous = function() {
        controller.previous();
    };
    /**
     * rotates to the given slot
     * @param {Object} slot
     */
    this.gotoSlot = function( slot ) {
        controller.gotoSlot( slot );
        update();
    };
    
    /**
     * get's the current active image (which shon "in front")
     */
    this.getCurrentImage = function() {
        return controller.getCurrentImage();
    };
    
    function init() {
        var radius = ImageFlow.util.getWindowSize().width / 2;
        controller = new ImageFlow.Mode2.Controller( { slots: jQuery('#imageFlow .imageFlow-mode2-images img'), images: images, radius: radius });
        gui = new ImageFlow.Mode2.Gui( { images: images, pseudo3d: config.pseudo3d } );
        //gui.setupImages();
    }
    
    /**
     * triggered by the timer-interval
     * @see ImageFlow.Mode2.App.init() 
     */
    function run() {
        if (controller.needsRefresh()) {
            update();
        }
    }
    
    /**
     * updates the animation
     */
    function update()
    {
        //controller.refresh();
        
        //gui.update_start();
        var img = null, data = null;
        
        var slots = controller.getSlots();
        
        for (var i = 0; i < slots.length; i++) {
            var slot = slots[i];
            
            // get the appropriate (precalculated) data for the slot, taking into
            // account the carusell's current rotation 
            data = controller.getPositionFor(slot);
            
            if (data !== undefined) {
                gui.renderSlot(slot, data);
            }
            else {
                ImageFlow.util.log("no data ", slot);
                //throw 'no data';
            }
        }
        //gui.update_end();        
        
        // update a.href
        //var a = document.getElementById("imageFlow-download-link");
        //a.href = window.imgFlow.downloadScript + "?file=" + controller.getCurrentImage().imageUrl;
    }
    
    var that = this;
    var controller, gui, interval = null;
    init();
};ImageFlow.Mode1.Controller = function(config) {
    /**
     * return an int that indicates the index of the current item in the image-iterator
     * needes (according to Steven...) to maintain the current active image 
     * when toggling between both modes
     */
    this.getPosition = function(){
        return position;
    };
    
    this.getNumberOfImages = function() {
        return images.length;
    };
    
    /**
     * @return ImageFlow.Image
     */
    this.getCurrentImage = function() {
        return imageIterator.current();
    };
    
    /**
     * returns a span of the imageIterator containing those images
     * currently visible in the thumbnail area
     */
    this.getVisibleThumbs = function(){
        var visibleThumbs = [];
        var it = new ImageFlow.util.ArrayIterator(images);
        
        var firstIndex = imageIterator.getIndex() - Math.ceil(thumbSlots / 2) + 1; 
        it.setIndex(firstIndex);
        
        ImageFlow.util.log('Controller.getVisibleThumbs(): getIndex:' + it.getIndex());
        ImageFlow.util.log('Controller.getVisibleThumbs(): range is ' + firstIndex + ' to ' + (firstIndex + thumbSlots));
        for (var i = 0; i < thumbSlots; i++) {
            visibleThumbs.push(it.current());
            it.next();
        }
    
        return visibleThumbs;
    };
    
    /**
     * navigates to the next image 
     */
    this.next = function() {
        ImageFlow.util.log('Controller: next()');
        ++position;
        return imageIterator.next();
    };
    /**
     * navigates to the previous image 
     */
    this.previous = function() {
        ImageFlow.util.log('Controller: previous()');
        --position;
        return imageIterator.previous();
    };
    
    /**
     * navigates to the given slot, by rotating the thumbnails as well
     * as updating the currently active image
     * @param {ImageFlow.Image} slot
     */
    this.gotoSlot =  function( slot ) {
        ImageFlow.util.log('Controller: going to ', slot.image);
        var image = imageIterator.seek( slot.image);
        return image;
    };
    
    function init() {
        thumbSlots = config.thumbSlots;
        images = config.images;
        imageIterator = new ImageFlow.util.ArrayIterator(images);
        ImageFlow.util.log('Controller: number of thumbnail-slots: ', thumbSlots);
    };
    var that = this;
    
    var images = null,
        loader = null,
        imageIterator = null,
        thumbSlots,
        position = 0;
    
    init();
}
ImageFlow.Mode2.Controller = function(config) {
    
    /**
     * Steven!
     * @param {Object} value
     */
    this.setAnimationSync = function( value ){
        animationSync = value == true;
    };
    
    this.getRotationFor = function(slot) {
        return slot.rotation;
    };
    this.getPosition = function(){
        return position;
    };
    this.gotoPosition = function( newPosition ){
        var positionDiff = newPosition-position;
        startRotating( positionDiff * gap );
        position += positionDiff;
    }
    
    this.next = function( animationOverride ) {
        startRotating( gap );
        ++position;
    };
    
    this.previous = function() {
        startRotating( -gap );
        --position;
    };
    this.gotoSlot =  function( slot ) {
        ImageFlow.util.log('Controller: going to ', slot.image);
        changed = true;
        var toRotate = 90 - slot.rotation;
        position += Math.round( toRotate / gap );
        startRotating( toRotate );
    };
    
    this.needsRefresh = function() {
        if (!changed) {
            return false;
        }
        changed = false;
        return true;
    };
    
    this.getPositionFor = function(slot) {
        var r = Math.round(slot.rotation);
        //ImageFlow.util.log("getting data for rotation= ", r);
        return data[r];
    };
    
    this.getSlots = function() {
        return slots;
    };
    
    this.getCurrentImage = function() {
        // fixme
        imageIterator.setIndex(iteratorIndex + 2);
        return imageIterator.current();
    };
    
    
    function init() {
        slots = config.slots;
        radius = config.radius;
        images = config.images;
        that.setAnimationSync( config.sync );
        
        gap = 180 / slots.length;
        var middle = Math.floor((slots.length - 1) / 2);
        
        for (var i = 0; i < slots.length / 2; i++) {
            slots[middle - i].rotation = 90 - i * gap;
            slots[middle + i].rotation = 90 + i * gap;
        }
        
        
//        for (var i = 0; i < slots.length; i++) {
//            ImageFlow.util.log("slot "+i + ": ", slots[i].rotation);
//        }
        
        precalculatePositions();
        
        imageIterator = new ImageFlow.util.ArrayIterator(images);
        iteratorIndex = -Math.floor( (slots.length -1) / 2);
        assignImages();
    }
    
    function startRotating( rotation )
    {
        //ImageFlow.util.log( 'rotation: ' + rotation );
        if( animationSync )
            updateRotation( rotation );
        else
        {
            if( interval )
                return;
            rotating = rotation;
            lastUpdate = ImageFlow.util.getMilliseconds();
            interval =  window.setInterval( updateRotationCallback, 1000/30 );
        }
    }
    
    function updateRotationCallback(){
        if( rotating ){
            var currTime = ImageFlow.util.getMilliseconds();
            var timediff = currTime-lastUpdate;
            lastUpdate = currTime;
            
            var toRotate = (gap/1000)*timediff;
            toRotate = Math.min( Math.abs( rotating ), toRotate );
            toRotate *= rotating > 0 ? 1 : -1;
            
            updateRotation( toRotate );
            rotating += -toRotate;
        }
        if( !rotating ){
            window.clearInterval( interval );
            interval = null;
        }
    }
    
    function updateRotation(amount) {
        for (var i = 0; i < slots.length; i++) {
            var slot = slots[i];
            var image = null;
            slot.rotation += amount;
            
            if( slot.rotation > 180 ) {
                slot.rotation = (slot.rotation + 180) % 360;
                
                image = imageIterator.setIndex( ++iteratorIndex + slots.length - 1 );
                //ImageFlow.util.log("reassigning image of slot " + i, " (was: ", slot.src, " new: ", image.imageUrl);
                
                slot.src = image.imageUrl;
                slot.image = image;
            }
            else if( slot.rotation < 0 ){
                slot.rotation += 180;
                
                image = imageIterator.setIndex( --iteratorIndex );
                //ImageFlow.util.log("reassigning image of slot " + i, " (was: ", slot.src, " new: ", image.imageUrl);
                
                slot.src = image.imageUrl;
                slot.image = image;
            }
            
            
            slot.title = 'Slot: ' + i + ' iterator index:' +imageIterator.getIndex();
            
            jQuery(slot).css('opacity', that.getPositionFor( slot ).opacity );
        }
        changed = true;
    }
    
    function assignImages() {
        ImageFlow.util.log("initially assigning images");
        
        imageIterator.setIndex( iteratorIndex );
        
        for (var i = slots.length-1; i >= 0; --i) {
            var slot = slots[i];
            slot.src = imageIterator.current().imageUrl;
            slot.image = imageIterator.current();
            ImageFlow.util.log("assigning image image ", slot.src, "to slot ", i, slot);
            slot.title = 'Slot: ' + i + ' iterator index:' +imageIterator.getIndex();
            jQuery(slot).css('opacity', that.getPositionFor( slot ).opacity );
            imageIterator.next();
        }
        imageIterator.setIndex( iteratorIndex );
    }
    
    function precalculatePositions() {
        var x, height;
        
        for (var i = 0; i <= 180; i++) {
            
            var radiant = ImageFlow.util.Degree2Rad( i+0 );
            
            size = Math.max(1, Math.ceil(Math.sin( radiant ) * imageWidth) + 1); /* * (Math.sin ( radiant ))*/;
            
            x = Math.cos( radiant ) * radius;
            var opacity = Math.sin( radiant );
            
            x += radius;
            data[i] = ({
                size: size,
                x: Math.ceil(x),
                opacity: opacity
            });
        }
    }        
    
    var that = this;
    var imageIterator;
    var slots, gap;
    var radius;
//    var imageWidth = 320;
    var imageWidth = 240;
    var data = [];
    var changed = true;
    var iteratorIndex;
    var rotating =0;
    var lastUpdate = 0;
    var interval = null;
    var animationSync;
    var position =0;
    init();
};/**
 * the ordinary 2d-mode of the gallery
 * @param {Object} config
 */
ImageFlow.Mode1.Gui = function(config) {
    
    /**
     * displays the given images as thumnbails
     * @param {List<Image>}        the <code>ImageFlow.Image</code>s to display
     * @see Image.js 
     */
    this.setThumbs = function(images) {
        ImageFlow.util.log('thumbs to load: ', images);
        ImageFlow.util.log('config', config);
        
        if (images.length != thumbSlots.length) {
            throw 'invalid number of thumbs (was: ' + images.length + ', expected: ' + thumbSlots.length + ')';
        }
        
        for (var i = 0; i < images.length; i++) {
            //ImageFlow.util.log('assigning image with index ' + i + ' to slot ', thumbSlots[i]);
            thumbSlots[i].src = images[i].thumbUrl;
            thumbSlots[i].image = images[i];
        }
        
        jQuery('#imageFlow-imageCount').html(config.imageCount);
    };
    
    
    /**
     * sets the large image 
     * @param {Image} the image to display. this is NOT a node but an instance of <code>ImageFlow.Image</code>
     * @see Image.js
     */
    this.setCurrent = function(image) {
        canvas.src = image.imageUrl;
        jQuery('#imageFlow-subtitle').text(image.title);
        
        // FIXME: very dirty hack to extract id
        var id = image.id.match(/-id([0-9]*)$/)[1];
        jQuery('#imageFlow-imageIndex').text((parseInt(id) + 1).toString());
        
        
        function resize() {
           // var dimension = image.scale(450, 340);
            var dimension = image.scale(320, 240);
            canvas.width = dimension.width;
            canvas.height = dimension.height;
            ImageFlow.util.log(image.width, image.height, dimension.width, dimension.height);
        }
        
        if (image.isLoaded()) {
            resize();
        }
        else {
            image.onReady = resize;
        }
        
    };
    
    function init() {
        thumbSlots = jQuery('.imageFlow-thumb');
        canvas = jQuery('#imageFlow-canvas')[0];
        container = jQuery('#imageFlow');
    }
    
    var that = this;
    var thumbSlots = null;
    var canvas = null;
    var container = null;
    init();
}
/**
 * the "Karusell" - mode of te gallery
 * @param {Object} config
 */
ImageFlow.Mode2.Gui = function(config) {
    
    this.setupReflection = function() {
        for (var i = 0; i < images.length; i++) {
            var domImg = imageSlots[i];
                domImg.onload = function() {
                    this.reflection = Reflection.add ( this, {} );
                    jQuery(this.reflection).css( { position: 'absolute', top: (parseInt(this.style.top)+parseInt(this.height))+'px', left: this.style.left, width: this.style.width});
                    ImageFlow.util.log( this.reflection );
                }
        }
    }
    
    /**
     * updates the position and size of a slot
     * @param {DomNode} slot     the DOM img-node to update
     * @param {int} x             the new position on the x-axis
     * @param {int} size        a factor used to calculate the image's width and height
     * @param {float} opacity    the slots opacity (0: invisible, 1: maximum)
     */
    this.renderSlot = function(slot, data) {
        
        var x = data.x;
        
        var image = slot.image;
        
        var height = data.size;
        var width = image.width / image.height * height;
        
        if (pseudo3d) {
            var top = height / 2;
        }
        else {
            var top = windowHeight / 3 - slot.height / slot.width * width / 2;
        }
        
        jQuery(slot).css({
            left: x - width / 2, 
            height: height,
            width: width, 
            zIndex: height,
            top: top,
            opacity: data.opacity
        });
        
        //if(slot.reflection )
        //    jQuery(slot.reflection).css( {left: x, width: width, zIndex: width+100, top: (parseInt(slot.style.top)+parseInt(slot.height))+'px', height: width / 3 } );
/*
        jQuery(slot.reflection).css({
            position: 'absolute',
            left: x, 
            width: width + 3, 
            zIndex: width + 100,
            height: width / 3,
            top: top + Math.ceil(image.height / image.width * width) - 1
        });*/
    };
    
    /**
     * hides the canvas (which contains the whole gui) when the gui is updated (to speed up the animation) 
     */
    this.update_start = function()
    {
        node_canvas.hide();
    }
    
    /**
     * unhides the canvas at the end of an update-process 
     */
    this.update_end = function()
    {
        node_canvas.show();
    }
    
    function init() {
        node_canvas = jQuery('#imageFlow-mode2');
        images = config.images;
        pseudo3d = config.pseudo3d ? config.pseudo3d : false;
        imageSlots = jQuery('#imageFlow .imageFlow-mode2-images img');
        ImageFlow.util.log('number of mode2-image slots:', imageSlots.length);
        if (imageSlots.length == 0) {
            throw 'no image slots found';
        }
        //that.setupReflection();
    }
    
    var that = this;
    var imageSlots, images, pseudo3d = false;
    var node_canvas = null;
    var windowHeight = ImageFlow.util.getWindowSize().height;
    init();
    
};/**
 * represents an image
 * @param {Object} properties    map containing 
 *         (String)imageUrl, 
 *         (String)thumbUrl 
 *         (String) title of the image 
 */
ImageFlow.Image = function(properties) {
    this.imageUrl = null;
    this.thumbUrl = null;
    this.title = null;
    this.width = -1;
    this.height = -1;
    
    this.scale = function(maxWidth, maxHeight) {
        
        var newWidth = maxWidth;
        var newHeight = maxHeight;
        var imgWidth = that.width;
        var imgHeight = that.height;
        if (imgWidth && imgHeight) {
            
            if ( imgWidth > imgHeight) {
                newWidth = maxWidth;
                newHeight = imgHeight / imgWidth *  maxWidth;
            }
            else {
                newHeight = maxHeight;
                newWidth = imgWidth / imgHeight *  maxHeight;
            }
        }
        return { width: newWidth, height: newHeight };
        
    }
    
    this.onReady = function() {};
    this.isLoaded = function() {
        return this.width > 0&& this.height > 0;
    };
    
    
    function init() {
        var img = new Image();
        
        img.onload = function() {
            that.height = this.height;
            that.width = this.width;
            that.onReady();
        }
        img.src = that.imageUrl;
    }
    var that = this;
    ImageFlow.util.apply(properties, this);
    init();
};
/**
 * a loader-implementation that allows the images to be provided directly via javascript
 * @see gui.html for sample usage 
 * @param {Object} imgs
 */
ImageFlow.InlineLoader = function(imgs) {
    this.load = function(callback) {
        makeIds(images);
        
        
        callback(images);
    }
    
    this.addImage = function(img) {
        images.push(img);
    }
    
    function makeIds() {
        for (var i = 0; i < images.length; i++) {
            images[i].id = "imageflow-id"+ i;
        }
    }
    
    function init() {
        if (imgs && imgs.length) {
            for (var i = 0; i < imgs.length; i++) {
                if (imgs[i] && imgs[i].imageUrl) {
                    that.addImage(imgs[i]);
                    log("image added: ", imgs[i]);
                }
            }
        }
    }
    
    var that = this;
    var images = [];
    var index = 0;
    var log = ImageFlow.util.log;
    init();
    
}; /**
 * a loader-implementation that loads the images from a url via an ajax-request
 * it expects the images as a json string:
 * 
 *     [
 *         { 'imageUrl': 'img1.jpg', 'thumUrl': '1-thumb.jpg'},
 *        { 'imageUrl': 'img2.jpg', 'thumUrl': '2-thumb.jpg'}
 *     ]
 * 
 * @see LoaderTest.html for example usage
 */
ImageFlow.JsonLoader = function(config) {
    this.jsonUrl = null;
    this.synchron = false;
    var that = this;
    
    ImageFlow.util.apply(config, this);
    
    this.load = function(callback) {
        jQuery.ajax({ 
            url: that.jsonUrl,
            dataType: 'json', 
            success: function(result) {
                var images = that.parse(result);
                callback(images); 
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                throw errorThrown;
            },
            async: !that.synchron
        });
    };
    
    this.parse = function(json) {
        var images = [];
        for (var i = 0; i < json.length; i++) {
            
            images.push(new ImageFlow.Image({
                imageUrl: json[i].imageUrl, 
                thumbUrl: json[i].thumbUrl,
                id: 'imageflow-img' + i  
            }));
        }
        
        return images;
    }
};/**
 * this basically hooks up the keyboard- and mouse-events for
 * the interaction with the galleries gui
 * it is used by the <code>ImageFlow.App</code>, which dispatches the events
 * to the currently active mode's main program
 */
ImageFlow.Nav = function() {
    this.setActiveModeButton = function(mode) {
        if (mode == 1) {
            jQuery('#imageFlow-button-mode2').removeClass('active');
            jQuery('#imageFlow-button-mode1').addClass('active');
        }
        else {
            jQuery('#imageFlow-button-mode1').removeClass('active');
            jQuery('#imageFlow-button-mode2').addClass('active');
        }
    };
    
    this.enable = function() { enabled = true; };
    this.disable = function() { enabled = false; };
    this.onMode1Click = function() {};
    this.onMode2Click = function() {};
    this.onNext = function() {};
    this.onPrevious = function() {};
    this.onDownload = function() {};
    this.onEscape = function() {};
    function init() {
        jQuery('#imageflow-link-mode1').click(function() {
            that.onMode1Click();
            return false;
        });
        
        jQuery('#imageflow-link-mode2').click(function() {
            that.onMode2Click();
            return false;
        });
        
        jQuery('a#imageFlow-next, #imageFlow-next a ').click(  function() { if (enabled) that.onNext(); return false;} );
        jQuery('a#imageFlow-previous, #imageFlow-previous a').click( function() { if (enabled) that.onPrevious(); return false;} );
        //jQuery('a#imageFlow-download, #imageFlow-download a, #imageFlow-download-link').click( function() { if (enabled) return that.onDownload(); } );
        jQuery('#imageFlow-button-close').click( function() { if (enabled) that.onEscape(); return false;}  );
        
        jQuery('#imageFlow .imageFlow-mode2-images img, #imageFlow .imageFlow-thumbs img').click( function(event) {
            if (enabled) 
                that.onImageClick(event.target);
            return false;
        });
        
        // register global key-handler
        jQuery(document).keydown(  function(e) {
            if (!enabled) {
                return;
            }
            
            var keyCode = e.keyCode ? e.keyCode : e.which;
            switch (keyCode) {
                case 27: // escape key
                    that.onEscape();
                    break;
                case 39: // cursor-key >
                case 43: // +
                    that.onNext();
                    break;
                case 37: // cursor-key  <
                case 45: // key -
                    that.onPrevious();
                    break;
                case 49: // key 1
                    that.onMode1Click();
                    break;
                case 50: // key 2
                    that.onMode2Click();
                    break;
                default:
                    return;
            }
            
            return false;
        });        
    };
    
    var enabled = false;
    var that = this;
    init();
};ImageFlow.util = {};
ImageFlow.util.apply = function(src, dest) {
    for (var i in src) {
        dest[i] = src[i];
    }
};
ImageFlow.util.getMilliseconds = function(){
    return (new Date()).getTime();    
};
ImageFlow.util.Degree2Rad = function( degree ){
    return Math.PI * degree / 180;
};
ImageFlow.util.ArrayIterator = function( array ) {
    
    this.next = function() {
        return that.setIndex(index + 1);
    };
    
    this.previous = function() {
        return that.setIndex(index - 1);
    };
    
    this.current = function() {
        return array.length == 0 ? null : array[index];
    };
    
    this.setIndex = function( newIndex ){
        if( newIndex >= array.length )
        {
            index = newIndex % array.length;
        }
//        else if ( array.length == 1) {
//            index = 0;
//        }
        else if( newIndex <  0 )
        {
            that.setIndex(array.length + ( newIndex % array.length));
        }
        else
        {
            index = newIndex;
        }
        return that.current();
    };
    
    this.get = function(idx) {
        return array.length == 0 ? null : array[idx];
    };
    
    this.getIndex = function() {
        return index;
    };
    
    this.seek = function(value) {
        for (var i = 0; i < array.length; i++) {
            if (array[i] === value) {
                return that.setIndex(i);
            }
        }
    };
    
    var that = this;
    var index = 0;
    
};
ImageFlow.util.log = function() {
    if (typeof console !== 'undefined') {
        console.debug(arguments);
    }
};
ImageFlow.util.getWindowSize = function() {
      var myWidth = 0, myHeight = 0;
      if( typeof( window.innerWidth ) == 'number' ) {
        //Non-IE
        myWidth = window.innerWidth;
        myHeight = window.innerHeight;
      } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
        //IE 6+ in 'standards compliant mode'
        myWidth = document.documentElement.clientWidth;
        myHeight = document.documentElement.clientHeight;
      } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
        //IE 4 compatible
        myWidth = document.body.clientWidth;
        myHeight = document.body.clientHeight;
      }
      return { width: myWidth, height: myHeight };
};
    function showImageFlow(pageGuid) {
        try {
            // No GalleryExpert so far? Create Array
            if (!window.GalleryExpert)
                window.GalleryExpert = [];
            // Does our GalleryExpert with this pageGuid already exisst?
            if (!window.GalleryExpert[pageGuid]) { 
                if (!window.flowMap)
                    throw "Fehler beim Laden der Grafiken";
                    
                var fapp = window.flowMap[pageGuid];
                if (!fapp || fapp.length <= 0) 
                    throw "Fehler beim Laden der Grafiken oder keine Grafiken vorhanden";
                    // Create new GelelryExpert
                window.GalleryExpert[pageGuid] = new ImageFlow.App({
                    loader: new ImageFlow.InlineLoader(fapp),
                    thumbSlots: 7,
                    mode: 1,
                    pseudo3d: false,
                    downloadScript: ''
                });
                //window.GalleryExpert[pageGuid].downloadScript = '';
            }
            GalleryExpert[pageGuid].show();
        }
        catch(e) {
            (typeof e == "string" ) ? alert(e) : alert(e.message + ", " + e.number + ", " + e.description);
                
        }
        return false;
    };
