/*  ContentFlow, version 0.6 
 *  (c) 2007, 2008 Sebastian Kutsch
 *  <http://www.jacksasylum.eu/ContentFlow/>
 *
 *  ContentFlow is distributed under the terms of the MIT license.
 *  (see http://www.jacksasylum.eu/ContentFlow/LICENSE)
 *
 *--------------------------------------------------------------------------*/

/* Global configutaion and initilization object */
var ContentFlowGlobal = {
    Flows: new Array,
    AddOns: {}, 
    scriptName: 'contentflow.js',
    scriptElement:  null,
    Browser: { 
        IE: document.all && !window.opera ? true : false,
        IE6: document.all && !window.opera && typeof(window.XMLHttpRequest) == "undefined" ? true : false,
        IE7: document.all && !window.opera && typeof(window.XMLHttpRequest) != "undefined" && typeof(document.querySelectorAll) == "undefined"? true : false,
        IE8: document.all && !window.opera && typeof(document.querySelectorAll) != "undefined" ? true : false,
        WebKit: /WebKit/i.test(navigator.userAgent) ? true : false, 
        iPhone: /iPhone|iPod/i.test(navigator.userAgent)? true : false,
        Safari: /Safari/i.test(navigator.userAgent) && !/Chrome/i.test(navigator.userAgent) ? true : false,
        Chrome: /Chrome/i.test(navigator.userAgent) ? true : false,
        Opera: window.opera ? true : false,
        Konqueror: navigator.appName.indexOf('Konqueror') != -1 ? true : false
    },

    getScriptElement:function (scriptName) {
        var regex = new RegExp(scriptName);
        var scripts = document.getElementsByTagName('script');
        for (var i=0; i<scripts.length; i++) {
            if (scripts[i].src && regex.test(scripts[i].src))
                return scripts[i];
        }
        return '';
    },

    getScriptPath: function (scriptElement, scriptName) {
        var regex = new RegExp(scriptName);
        return scriptElement.src.replace(regex, '');
    },

    addScript: function  (path) {
        if (this.Browser.IE || this.Browser.WebKit) {
            document.write('<script type="text/javascript" src="'+path+'"><\/script>');
        }
        else {
            var script = document.createElement('script');
            script.src = path;
            script.setAttribute('type', 'text/javascript');
            document.getElementsByTagName('head')[0].appendChild(script);
        }
    },

    addScripts: function  (basePath, filenames) {
        for (var i=0; i<filename.length; i++)
            this.addScript(basepath+filenames[i]);
    },

    addStylesheet: function (path) {
        //if (this.Browser.IE || this.Browser.WebKit) {
            document.write('<link rel="stylesheet" title="Standard" href="'+path+'" type="text/css" media="screen" />');
        //}
        //else {
            //var link = document.createElement('link');
            //link.setAttribute('rel', 'stylesheet');
            //link.setAttribute('titel', 'Standard');
            //link.setAttribute('href', path);
            //link.setAttribute('type', 'text/css');
            //link.setAttribute('media', 'screen');
            //document.getElementsByTagName('head')[0].appendChild(link);
        //}

    },

    addStylesheets: function  (basePath, filenames) {
        for (var i=0; i<filename.length; i++)
            this.addStylesheet(basepath+filenames[i]);
    },

    initPath: function () {
        /* get / set basic values */
        this.scriptElement = this.getScriptElement(this.scriptName);
        if (!this.scriptElement) {
            this.scriptName = 'contentflow_src.js';
            this.scriptElement = this.getScriptElement(this.scriptName);
        }

        this.BaseDir = this.getScriptPath(this.scriptElement, this.scriptName) ;
        if (!this.AddOnBaseDir) this.AddOnBaseDir = this.BaseDir;
        if (!this.CSSBaseDir) this.CSSBaseDir = this.BaseDir;
    },

    init: function () {
        /* add default stylesheets */
        this.addStylesheet(this.CSSBaseDir+'contentflow.css');
        this.addStylesheet(this.CSSBaseDir+'mycontentflow.css');    // FF2: without adding a css-file FF2 hangs on a reload.
                                                                    //      I don't have the slidest idea why
                                                                    //      Could be timing problem
        /* add AddOns scripts */
        if (this.scriptElement.getAttribute('load')) {
            var AddOns = this.scriptElement.getAttribute('load').replace(/\ +/g,' ').split(' ');
            for (var i=0; i<AddOns.length; i++) {
                if (AddOns[i] == '') continue;
                //if (AddOns[i] == 'myStyle') {
                    //this.addStylesheet(this.BaseDir+'mycontentflow.css');
                    //continue;
                //}
                this.addScript(this.AddOnBaseDir+'ContentFlowAddOn_'+AddOns[i]+'.js');
            }
        }

        /* ========== ContentFlow auto initialization on document load ==========
         * thanks to Dean Edwards
         * http://dean.edwards.name/weblog/2005/02/order-of-events/
         */
        var CFG = this;

        /* for Mozilla, Opera 9, Safari */
        if (document.addEventListener) {
            /* for Safari */
            if (/WebKit/i.test(navigator.userAgent)) { // sniff
                var _timer = setInterval(function() {
                    if (/loaded|complete/.test(document.readyState)) {
                        clearInterval(_timer);
                        CFG.onloadInit(); // call the onload handler
                    }
                }, 10);
            }
            else {
              document.addEventListener("DOMContentLoaded", CFG.onloadInit, false);
            }
        }

        /* for Internet Explorer */
        /*@cc_on @*/
        /*@if (@_win32)
        document.write("<script id=__ie_cf_onload defer src=javascript:void(0)><\/script>");
        var script = document.getElementById("__ie_cf_onload");
        script.onreadystatechange = function() {
            if (this.readyState == "complete") {
                CFG.onloadInit(); // call the onload handler
            }
        };
        /*@end @*/

        /* for all other browsers */
        window.addEvent('load', CFG.onloadInit, false);
        /* ================================================================== */

    },

    onloadInit: function () {
        // quit if this function has already been called
        if (arguments.callee.done) return;
        // flag this function so we don't do the same thing twice
        arguments.callee.done = true;
        
        /* fix for mootools */
        if (window.Element && document.all && !window.opera) {
            for (var prop in window.CFElement.prototype) {
                if(!window.Element.prototype[prop]) {
                    window.Element.prototype[prop] = window.CFElement.prototype[prop];
                }
            }
        }
        /* fix for Prototype */
        if (window.$ && typeof Prototype != "undefined" && document.all && !window.opera) {
            var o$ = window.$
            window.$ = function (el) { return o$(window._$(el)) } ;
        }
        else if (!window.$) {
            window.$ = window._$;
        }


        /* init all manualy created flows */
        for (var i=0; i< ContentFlowGlobal.Flows.length; i++) {
            ContentFlowGlobal.Flows[i].init(); 
        }

        /* init the rest */
        var divs = document.getElementsByTagName('div');
        DIVS: for (var i = 0; i < divs.length; i++) {
            if (divs[i].className.match(/\bContentFlow\b/)) {
                for (var j=0; j<ContentFlowGlobal.Flows.length; j++) {
                    if (divs[i] == ContentFlowGlobal.Flows[j].container) continue DIVS;
                }
                var CF = new ContentFlow(divs[i],{}, false);
                CF.init();
            }
        }
    }
};

ContentFlowGlobal.initPath();



/* ========== ContentFlowAddOn ========== */
var ContentFlowAddOn = function (name, methods, register) {
    if (typeof register == "undefined" || register != false)
        ContentFlowGlobal.AddOns[name] = this;

    this.name = name;
    if (!methods) methods = {};
    this.methods = methods;

    this.scriptpath = ContentFlowGlobal.AddOnBaseDir;;
    if (methods.init) {
        var init = methods.init.bind(this);
        init(this);
    }
};

ContentFlowAddOn.prototype = {
    Browser: ContentFlowGlobal.Browser,

    addScript: ContentFlowGlobal.addScript,
    addScripts: ContentFlowGlobal.addScripts,

    addStylesheet: function (path) {
        if (!path)
            path = this.scriptpath+'ContentFlowAddOn_'+this.name+'.css';
        ContentFlowGlobal.addStylesheet(path);
    },
    addStylesheets: ContentFlowGlobal.addStylesheets,

    _init: function (flow) {
        this._setConf(flow);
        this._initMethods(flow);
    },

    _setConf: function(flow) {
        if (this.methods.ContentFlowConf) {
            flow.setConfig(this.methods.ContentFlowConf);
        }
    },

    _initMethods: function (flow) {
        var methods = [
            'onclickActiveItem',
            'onMakeActive',
            'onclickPreButton',
            'onclickNextButton',
            'calcSize',
            'calcCoordinates',
            'calcRelativeItemPosition',
            'calcStepWidth',
            'calcZIndex',
            'calcFontSize'
        ];

        for (i=0; i<methods.length; i++) {
            if (this.methods[methods[i]] ) {
                flow['_'+methods[i]] = this.methods[methods[i]];
            }
        }
    }
};


/*
 * DEFAULT AddOn
 */
var ContentFlowDefaultAddOn = new ContentFlowAddOn('DEFAULT', {

    onclickActiveItem: function (item) {
        var url, target;

        if (url = item.content.getAttribute('href')) {
            target = item.content.getAttribute('target');
        }
        else if (url = item.element.getAttribute('href')) {
            target = item.element.getAttribute('target');
        }
        else if (url = item.content.getAttribute('src')) {
            target = item.content.getAttribute('target');
        }

        if (url) {
            if (target)
                window.open(url, target).focus();
            else
                window.location.href = url;
        }
    },

    onMakeActive: function (activeItem) {},
    
    onclickPreButton: function (event) {
        this.moveToIndex('pre');
        Event.stop(event);
    },
    
    onclickNextButton: function (event) {
        this.moveToIndex('next');
        Event.stop(event);
    },

    calcStepWidth: function(diff, absDiff) {
        if (absDiff > this._conf.visibleItems) {
            if (diff > 0) {
                var stepwidth = diff - this._conf.visibleItems;
            } else {
                var stepwidth = diff + this._conf.visibleItems;
            }
        } else if (this._conf.visibleItems >= this.items.length) {
            var stepwidth = diff / this.items.length;
        } else {
            var stepwidth = diff * ( this._conf.visibleItems / this.items.length);
            //var stepwidth = diff/absDiff * Math.max(diff * diff,Math.min(absDiff,0.3)) * ( this._conf.visibleItems / this.items.length);
            //var stepwidth = this.flowSpeedFactor * diff / this.visibleItems;
            //var stepwidth = this.flowSpeedFactor * diff * ( this.visibleItems / this.items.length)
            //var stepwidth = this.flowSpeedFactor * diff / this._millisecondsPerStep * 2; // const. speed
        }
        return stepwidth;
    },
    
    calcSize: function (relativePosition, side) {
        var rP = relativePosition;
        var vI = this._conf.visibleItems;
        var maxHeight = this.maxHeight;

        var h = maxHeight/(Math.abs(rP)+1);
        var w = h;
        return {width: w, height: h};
    },

    calcCoordinates: function (relativePosition, side) {
        var rP = relativePosition;
        var vI = this._conf.visibleItems;
        var maxHeight = this.maxHeight;

        var f = 1 - 1/Math.exp( Math.abs(rP)*0.75);
        var x =  this.flowCenter.x * (1 + side *vI/(vI+1)* f); 
        var y = this.maxHeight;

        return {x: x, y: y};
    },
    calcRelativeItemPosition: function(relativePosition, side, size) {
        var rP = relativePosition;
        var vI = this._conf.visibleItems;
        var maxHeight = this.maxHeight;

        var x = -size.width/2;
        var y = -size.height;
        return {x: x, y: y};
    },

    calcZIndex: function (x, f, I) {
        return -Math.abs(I);
    },

    calcFontSize: function (x, f, size) {
        return size.height / this.maxHeight;
    }

});




/* ========== ContentFlow ========== */
var ContentFlow = function (container, config, initOnLoad) {

    ContentFlowGlobal.Flows.push(this);

    if (container) {
        this.container = container;
        this._conf = {};
        for (var option in this._defaultConf) {
            this._conf[option] = this._defaultConf[option];
        }
        //this.setConfig(config?config:{});
        this._userConf = config?config:{};
        this.initAfterAddOn = new ContentFlowAddOn('INITAFTER', { }, false);
    } else {
        throw ('ContentFlow ERROR: No flow container node or id given');
    }

};

ContentFlow.prototype = {
    _imagesToLoad: 0,
    _activeItem: 0,
    _currentPosition: 0,
    _targetPosition: 0,
    _stepLock: false,
    _millisecondsPerStep: 50, 
    _fileRegEx: /(([^\/?=&]+)\.(\w+)){1}$/,
    Browser: ContentFlowGlobal.Browser,
    
    _conf: {},

    _defaultConf: { 
        /* pre conf */
        useAddOns: 'all', // all, none, [AddOn1, ... , AddOnN]

        biggestItemPos: 0,
        loadingTimeout: 30000, //milliseconds
        activeElement: 'content', // item or content

        maxItemHeight: 0,
        scaleFactor: 1.0,
        scaleFactorLandscape: 1.0,

        circularFlow: true,
        visibleItems: -1,
        startItem:  "center",
        scrollInFrom: "pre",

        flowSpeedFactor: 1.0,
        flowDragFriction: 1.0,
        scrollWheelSpeed: 1.0,

        reflectionType: "clientside",   // client-side, server-side, none
        reflectionWithinImage: /iPhone|iPod/i.test(navigator.userAgent)? false : true,
        reflectionColor: "transparent", // none, transparent, overlay or hex RGB CSS style #RRGGBB
        reflectionHeight: 0.5,          // float (relative to original image height)
        negativeMarginOnFloat: "auto",  // auto, none or float (relative to reflectionHeight)
        reflectionOverlaySrc: "img/reflectionOverlay.png",
        reflectionServerSrc: "{URLTO}{FILENAME}_reflection.{EXT}"  // {URLTO}, {FILE}, {FILENAME}, {EXT}

    },


    /*
     * ==================== public methods ==================== 
     */


    /*
     * calls _init() if ContentFlow has not been initialized before
     * needed if ContentFlow is not automatically initialized in window.load
     */
    init: function () {
        if(this.isInit) return;
        this._init();
    },

    /*
     * returns a data object, containing all configuration parameters and their values
     */
    getConfig: function () {
        var conf = {};
        for (var option in this._defaultConf) {
            conf[option] = this._conf[option];
        }
        return conf;
    },

    /*
     * parses configuration object and initializes configuration values
     */
    setConfig: function(config) {
        if (!config) return;
        var dC = this._defaultConf;
        for (var option in config) {
            if (dC[option] == "undefined" ) continue;
            switch (option) {
                case "scrollInFrom":
                case "startItem":
                case "negativeMarginOnFloat":
                    if (typeof(config[option]) == "number"  || typeof(config[option]) == "string") {
                        this._conf[option] = config[option];
                    }
                    break;
                default:
                    if (typeof(dC[option] == config[option])) {
                        this._conf[option] = config[option];
                    }
            }
        }
        switch (this._conf.reflectionColor) {
            case "overlay":
                break;
            case this._conf.reflectionColor.search(/#[0-9a-fA-F]{6}/)>= 0?this._conf.reflectionColor:this._conf.reflectionColor+"x":
                this._conf.reflectionColorRGB = {
                    hR: this._conf.reflectionColor.slice(1,3),
                    hG: this._conf.reflectionColor.slice(3,5),
                    hB: this._conf.reflectionColor.slice(5,7),
                    iR: parseInt(this._conf.reflectionColor.slice(1,3), 16),
                    iG: parseInt(this._conf.reflectionColor.slice(3,5), 16),
                    iB: parseInt(this._conf.reflectionColor.slice(5,7), 16)
                };
                break;
            case "none":
            case "transparent":
            default:
                this._conf.reflectionColor = "transparent"; 
                this._conf.reflectionColorRGB = {
                    hR: 0, hG: 0, hB:0,
                    iR: 0, iG: 0, iB:0
                };
                break;
        }
        if (this._conf.negativeMarginOnFloat == "none") this._conf.negativeMarginOnFloat = 0;
        if (this.items) {
            if (this._conf.visibleItems <  0)
                this._conf.visibleItems = Math.round(Math.sqrt(this.items.length));
            this._conf.visibleItems = Math.min(this._conf.visibleItems, this.items.length - 1);
        }

    },

    getItem: function (index) {
        return this.items[this._checkIndex(Math.round(index))]; 
    },

    /*
     * returns the index number of the active item
     */
    getActiveItem: function() {
        return this._activeItem;
    },

    /*
     * returns the number of items the flow contains
     */
    getNumberOfItems: function () {
        return this.items.length;
    },

    /*
     * reinitializes sizes.
     * called on window.resize
     */
    resize: function () {
        this._initSizes();
        this._initStep();
    }, 

    /*
     * scrolls flow to item i
     */

    moveToPosition: function (p) {
        if (!this._conf.circularFlow) p = this._checkIndex(p);
        this._targetPosition = p;
        this._initStep();
    },
    moveToIndex: function (index) {
        this._targetPosition = Math.round(this._getPositionByIndex(this._getIndexByKeyWord(index, this._activeItem.index, !this._conf.circularFlow)));
        this._initStep();
    },
    moveToItem: function (item) {
        var i;
        if (item.itemIndex) i = item.itemIndex;
        else i = item.index;
        this.moveToIndex(i);
    },
    moveTo: function (i) {
        if (typeof i == "object") this.moveToItem(i);
        if (isNaN(i) || i == Math.floor(i)) this.moveToIndex();
        else this.moveToPosition(i);
    }, 

    /*
     * initializes item and adds it at index position
     */
    addItem: function(el, index) {
        if (typeof index == "string") {
            switch (index) {
                case "first":
                case "start":
                    index = 0;
                    break;
                case "last":
                case "end":
                    index = this.itemsLastIndex + 1;
                    break;
                default:
                    index = this._activeItem.index;
            }
        }

        index = Math.max(index, 0);
        index = Math.min(index, this.itemsLastIndex + 1);
        
        /* insert item at index position into flow */ 
        if (index > this.itemsLastIndex || !this.items[index])
            this.flow.appendChild(el);
        else
            this.flow.insertBefore(el, this.items[index].element);

        if (this._activeItem.index < 0 ) // what for?
            $(el).addClassName('active');

        this.items.splice(index,0, {element: el, index:index});
        /* init item after insertion. that way it's part of the document and all styles are applied */
        this._initItem(index);

        /* adjust item indices */
        for (var i = index ; i < this.items.length; i++) {
            this.items[i].element.itemIndex = i;
            this.items[i].index = i;
        }
        this._setLastIndex();

        /* adjust targetItem, currentPos so that current view does not change*/
        if (this._getPositionByIndex(index) <= this._targetPosition) {
            this._targetPosition++;
            if (!this._conf.circularFlow)
                this._targetPosition = Math.min(this._targetPosition, this.itemsLastIndex);
        } 
        if (this._getPositionByIndex(index) <= this._currentPosition) {
            this._currentPosition++;
            if (!this._conf.circularFlow)
                this._currentPosition = Math.min(this._currentPosition, this.itemsLastIndex);
        }
        
        this._initStep();
        return index;
        
    },
        
    /*
     * removes item at index position, cleans it up and returns it
     */
    rmItem: function(index) {
        if  (index == "undefined" || index == "undifined") index = this._activeItem.index;
        index = this._checkIndex(index);
        if (!this.items[index]) return null;

        var Item = this.items[index];
        var item = Item.element;
        var content = content;

        /* remove event listeners */
        var ciItem = this._ciItem
        var caItem = this._caItem;
        Item[this._conf.activeElement].removeEvent('click', ciItem, false);
        if (window.removeEventListener)
            Item[this._conf.activeElement].removeEvent('click', caItem, false);
        else
            Item[this._conf.activeElement].onclick = function () {};

        item.style.height = "";
        item.style.width = "";
        item.style.margin = "";
        item.style.top = "";
        item.style.left = "";
        item.style.fontSize = "";
        item.style.zIndex = "";
        item.style.display = "";

        /* remove classes */
        item.removeClassName('active');
        item.removeClassName('withReflection');
        if (Item.image) {
            content.removeClassName('portray');
            content.removeClassName('landscape');
        }
            
        /* cleanup arrays and remove generated content */
        if (Item.image) {
            //this.itemsContent[index].width = this.itemsContent[index].origWidth;
            //this.itemsContent[index].height = this.itemsContent[index].origHeight;
            content.removeAttribute('width');
            content.removeAttribute('height');
            content.style.width = "";
            content.style.height = "";
            content.style.margin = "";

            if (Item.reflection) {
                item.removeChild(Item.reflection);
                if (Item.reflectionOverlay) item.removeChild(Item.reflectionOverlay);
            }
            if (Item.overlay) item.removeChild(Item.overlay);
        }

        this.items.splice(index,1);

        /* adjust item indices */
        for (var i = index ; i < this.items.length; i++) {
            this.items[i].itemIndex = i;
        }

        this._setLastIndex();
        
        /* adjust targetItem, currentPos and activeItem so that current view does not change*/
        this._targetPosition = this._checkIndex(this._targetPosition);
        this._currentPosition = this._checkIndex(this._currentPosition);
        this._activeItem = this.getItem(this._activeItem.index);
        if (index < this._targetPosition) {
            this._targetPosition = this._checkIndex(--this._targetPosition);
        }
        if (index < this._currentPosition) {
            this._currentPosition = this._checkIndex(--this._currentPosition);
        }
        if (index < this._activeItem.index) {
            this._activeItem = this.getItem(this._activeItem.index - 1);
        }

        /* remove item from DOM tree, take the next step and return removed item  */
        var removedItem = item.parentNode.removeChild(item);
        this._initStep();
        return removedItem;

    },

    /*
     * unsets/deactivates the method called when a (on)click event is fired on the active item
     */
    unsetOnclickActiveItem: function () {
        this.setOnclickActiveItem(function () {});
    },
    
    /*
     * sets the function 'method' to be the (on)click method called
     * when a click event happens on an active item. It will be bound to the
     * contentflow object (so 'this' refers tho the CF object).
     */
    setOnclickActiveItem: function (method) {
        if (this.isInit)
            this._onclickActiveItem = method;
        else
            this.initAfterAddOn.methods.onclickActiveItem = method;
    },
    setClickActiveItem: function (method) {
        this.setOnclickActiveItem(method);
    }, 
    
    /*
     * sets the function 'method' to be the (on)click method called
     * when a click event happens on an 'pre' button. It will be bound to the
     * contentflow object (so 'this' refers tho the CF object).
     */
    setOnclickPreButton: function (method) {
        if (this.isInit)
            this._onclickPreButton = method;
        else
            this.initAfterAddOn.methods.onclickPreButton = method;
    },

    /*
     * sets the function 'method' to be the (on)click method called
     * when a click event happens on an 'next' button. It will be bound to the
     * contentflow object (so 'this' refers tho the CF object).
     */
    setOnclickNextButton: function (method) {
        if (this.isInit)
            this._onclickNextButton = method;
        else
            this.initAfterAddOn.methods.onclickNextButton = method;
    },

    /*
     * these methods set the coresponding private method and
     * bind the given function to the CF object
     */
    setOnMakeActive: function (method) {
        if (this.isInit)
            this._onMakeActive = method.bind(this);
        else
            this.initAfterAddOn.methods.onMakeActive = method;
    },
    setCalcStepWidth: function (method) {
        if (this.isInit)
            this._calcStepWidth = method.bind(this);
        else
        this.initAfterAddOn.methods.calcStepWidth = method;
    },
    setCalcSize: function (method) {
        if (this.isInit)
            this._calcSize = method.bind(this);
        else
            this.initAfterAddOn.methods.calcSize = method;
    },
    setCalcCoordinates: function (method) {
        if (this.isInit)
            this._calcCoordinates = method.bind(this);
        else
            this.initAfterAddOn.methods.calcCoordinates = method;
    },
    setCalcRelativeItemPosition: function (method) {
        if (this.isInit)
            this._calcRelativeItemPosition = method.bind(this);
        else
            this.initAfterAddOn.methods.calcRelativeItemPosition = method;
    },
    setCalcZIndex: function (method) {
        if (this.isInit)
            this._calcZIndex = method.bind(this);
        else
            this.initAfterAddOn.methods.calcZIndex = method;
    },
    setCalcFontSize: function (method) {
        if (this.isInit)
            this._calcFontSize = method.bind(this);
        else
            this.initAfterAddOn.methods.calcFontSize = method;
    },

    /*
     * ==================== helper methods ====================
     */

    /*
     * checks if index is within the index range of the this.items array
     * returns a value that is within this range
     */
    _checkIndex: function (index) {
        index = Math.max(index, 0);
        index = Math.min(index, this.itemsLastIndex);
        return index;
    },

    /*
     * sets the object property itemsLastIndex
     */
    _setLastIndex: function () {
        this.itemsLastIndex = this.items.length - 1;
    },

    _getItemByIndex: function (index) {
        return this.items[this._checkIndex(index)];
    },

    _getItemByPosition: function (position) {
        return this._getItemByIndex(this._getIndexByPosition(position));
    },

    /* returns the position of an item-index relative to current position */
    _getPositionByIndex: function(index) {
        if (!this._conf.circularFlow) return this._checkIndex(index);
        var cI = this._getIndexByPosition(this._currentPosition);
        var dI = index - cI;
        if (Math.abs(dI) > dI+this.items.length)
            dI+= this.items.length;
        else if (Math.abs(dI) > (Math.abs(dI-this.items.length)))
            dI -= this.items.length;

        return this._currentPosition + dI;

    },

    /* returns the index an item at position p would have */
    _getIndexByPosition: function (position) {
        if (position < 0) var mod = 0;
        else var mod = 1;

        var I = (Math.round(position) + mod) % this.items.length;
        if (I>0) I -= mod;
        else if(I<0) I += this.items.length - mod;
        else if(position<0) I = 0;
        else I = this.items.length - 1;

        return I;
    },

    _getIndexByKeyWord: function (keyword, relativeTo, check) {
        if (relativeTo)
            var index = relativeTo;
        else
            var index = this._activeItem.index;

        if (isNaN(keyword)) {
            switch (keyword) {
                case "first":
                case "start":
                    index = 0;
                    break;
                case "last":
                case "end":
                    index = this.itemsLastIndex;
                    break;
                case "middle":
                case "center":
                    index = Math.round(this.itemsLastIndex/2);
                    break;
                case "next":
                    index += 1;
                    break;
                case "pre":
                case "previous":
                    index -= 1;
                    break;
                case 'visible':
                case 'visiblePre':
                case 'visibleLeft':
                    index -= this._conf.visibleItems;
                    break;
                case 'visibleNext':
                case 'visibleRight':
                    index += this._conf.visibleItems;
                    break;
                default:
                    index = index;
            }
        }
        else {
            index = keyword;
        }
        if (check != false)
            index = this._checkIndex(index);
        
        return index;
    },

    _setImageFormat: function (img) {
        img.origProportion = img.height / img.width;
        img.setAttribute('origProportion', img.height / img.width);
        //img.origWidth = img.width;
        //img.origHeight = img.height;
        if (img.origProportion >= 1)
            img.addClassName('portray');
        else
            img.addClassName('landscape');
    },

    /*
     * add reflection to item i
     */
    _addReflection: function(item) {
        var reflection;
        var image = item.content;

        if (this._conf.reflectionType == "serverside") {
            var mFILE = this._fileRegEx.exec(image.src);
            var sURLTO = image.src.replace(new RegExp(mFILE[1]+'$'), '');

            var src = this._conf.reflectionServerSrc;
            src = src.replace(/\{URLTO\}/, sURLTO);
            src = src.replace(/\{FILE\}/, mFILE[1]);
            src = src.replace(/\{FILENAME\}/, mFILE[2]);
            src = src.replace(/\{EXT\}/, mFILE[3]);

            reflection = item.reflection = document.createElement('img');
            reflection.src = src;

        } else {

            if (this.Browser.IE) {
                var filterString = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
                if (this._conf.reflectionColorRGB) {
                    // transparent gradient
                    if (this._conf.reflectionColor == "transparent") {
                        var RefImg = reflection = item.reflection = document.createElement('img');
                        reflection.src = image.src;
                    }
                    // color gradient
                    else {
                        reflection = item.reflection = document.createElement('div');
                        var RefImg = document.createElement('img');
                        RefImg.src = image.src;
                        reflection.width = RefImg.width;
                        reflection.height = RefImg.height;
                        RefImg.style.width = '100%';
                        RefImg.style.height = '100%';
                        var color = this._conf.reflectionColorRGB;
                        reflection.style.backgroundColor = '#'+color.hR+color.hG+color.hB;
                        reflection.appendChild(RefImg);
                    }
                    filterString += ' progid:DXImageTransform.Microsoft.Alpha(opacity=0, finishOpacity=50, style=1, finishX=0, startY='+this._conf.reflectionHeight*100+' finishY=0)';
                } else {
                    var RefImg = reflection = item.reflection = document.createElement('img');
                    reflection.src = image.src;
                }
                // crop image (streches and crops (clip on default dimensions), original proportions will be restored through CSS)
                filterString += ' progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22='+1/this._conf.reflectionHeight+')';

                RefImg.style.filter = filterString;

            } else {
                if (this._conf.reflectionWithinImage)
                    var canvas = item.canvas = $(document.createElement('canvas'));
                else 
                    var canvas = reflection = item.reflection = document.createElement('canvas');

                if (canvas.getContext) {
                    if (this._conf.reflectionWithinImage) {
                        for (var i=0; i <image.attributes.length; i++) {
                            canvas.setAttributeNode(image.attributes[i].cloneNode(true));
                        }
                    }

                    var context = canvas.getContext("2d");
                        
                    // overwrite default height and width
                    if (this._conf.reflectionWithinImage) {
                        canvas.width = image.width;
                        canvas.height = image.height; 
                        this._setImageFormat(canvas);
                        canvas.height = image.height * (1 + this._conf.reflectionHeight);
                        
                        //alert(this.maxHeight+"::"+image.origProportion);
                        //canvas.height = this.maxHeight;
                        //canvas.width = this.maxHeight / image.origProportion;
                        //this._setImageFormat(canvas);
                        //canvas.height = canvas.height * (1 + this._conf.reflectionHeight);
                    }
                    else {
                        canvas.width = image.width;
                        canvas.height = image.height * this._conf.reflectionHeight;
                    }
                        
                    context.save(); /* save default context */

                    /* draw image into canvas */
                    if (this._conf.reflectionWithinImage) {
                        context.drawImage(image, 0, 0);
                        //context.drawImage(image, 0, 0, canvas.width, this.maxHeight);
                    }

                    /* mirror image by transformation of context and image drawing */
                    if (this._conf.reflectionWithinImage) { // -1 for FF 1.5
                        var contextHeight = image.height * 2 - 1;
                        //var contextHeight = this.maxHeight * 2 - 1;
                    }
                    else {
                        var contextHeight = image.height - 1;
                    }
                    
                    context.translate(0, contextHeight);
                    context.scale(1, -1);
                    /* draw reflection image into canvas */
                    context.drawImage(image, 0, 0);
                    //context.drawImage(image, 0, 0, canvas.width, this.maxHeight);

                    /* restore default context for simpler further canvas manupulation */
                    context.restore();
                        
                    if (this._conf.reflectionColorRGB) {
                        var gradient = context.createLinearGradient(0, 0, 0, canvas.height);

                        var alpha = [0, 0.5, 1];
                        if (this._conf.reflectionColor == "transparent") {
                            context.globalCompositeOperation = "destination-in";
                            alpha = [1, 0.5, 0];
                        }

                        var red = this._conf.reflectionColorRGB.iR;
                        var green = this._conf.reflectionColorRGB.iG;
                        var blue = this._conf.reflectionColorRGB.iB;
                        if (this._conf.reflectionWithinImage) {
                            gradient.addColorStop(0, 'rgba('+red+','+green+','+blue+','+alpha[0]+')');
                            gradient.addColorStop(image.height/canvas.height, 'rgba('+red+','+green+','+blue+','+alpha[0]+')');
                            gradient.addColorStop(image.height/canvas.height, 'rgba('+red+','+green+','+blue+','+alpha[1]+')');
                            //gradient.addColorStop(this.maxHeight/canvas.height, 'rgba('+red+','+green+','+blue+','+alpha[0]+')');
                            //gradient.addColorStop(this.maxHeight/canvas.height, 'rgba('+red+','+green+','+blue+','+alpha[1]+')');
                        }
                        else {
                            gradient.addColorStop(0, 'rgba('+red+','+green+','+blue+','+alpha[1]+')');
                        }
                        gradient.addColorStop(1, 'rgba('+red+','+green+','+blue+','+alpha[2]+')');

                        context.fillStyle = gradient;
                        context.fillRect(0, 0, canvas.width, canvas.height);
                        
                    }

                    if (this._conf.reflectionWithinImage) {
                        image.parentNode.replaceChild(canvas, image);
                        item.content = canvas;
                        delete item.image;// = true;
                    }
                    
                } else {
                    this._conf.reflectionWithinImage = false;
                    delete item.reflection;
                }

            }
        }
        if (reflection) {
            reflection.className = "reflection";
            item.element.appendChild(reflection);

            if (this._conf.reflectionColor == "overlay" && item.reflection ) {
                if (this.Browser.IE6) {
                    var refOverlay = item.reflectionOverlay = document.createElement('span');
                    refOverlay.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+this._conf.reflectionOverlaySrc+'", sizingMethod="scale")';
                } else {
                    var refOverlay = item.reflectionOverlay = document.createElement('img');
                    refOverlay.src = this._conf.reflectionOverlaySrc;
                }
                refOverlay.width = this.reflectionOverlay.width;
                refOverlay.height = this.reflectionOverlay.height;
                refOverlay.className = 'refoverlay';
                item.element.appendChild(refOverlay);
            }
            
            /* be shure that caption is last child */
            if (item.caption) item.element.appendChild(item.caption);
        } 

    },


    /* just for debugging proposes */
    info: function(string) {
        var info = document.getElementById('info');
        info.innerHTML = string;
    },


    /*
     * ==================== item click events ====================
     * handles the click event on an active and none active item
     */

    _onclickInactiveItem: function () {},
    _clickInactiveItem: function (event) {
        if(!event) var event = window.event;
        var el = event.target ? event.target : event.srcElement;
        var index = el.itemIndex ? el.itemIndex : el.parentNode.itemIndex;
        this._onclickInactiveItem(this.items[index]);
        this.moveToIndex(index);
    },


    _onclickActiveItem: function () {},
    _clickActiveItem: function (event) {
        if(!event) var event = window.event;
        var el = event.target ? event.target : event.srcElement;
        var index = el.itemIndex ? el.itemIndex : el.parentNode.itemIndex;
        this._onclickActiveItem(this.items[index]);
    },



    /*
     * ==================== initialization ====================
     */


    /* -------------------- main init -------------------- */
    _init: function () {

        if (typeof(this.container) == 'string') { // no node
            var container = document.getElementById(this.container);
            if (container) {
                this.container = container;
            } else {
                throw ('ContentFlow ERROR: No element with id \''+this.container+'\' found!');
                return;
            }
        }
        
        /* reserve CSS namespace */

        $(this.container).addClassName('ContentFlow');

        this.flow = $(this.container).getChildrenByClassName('flow')[0];
        if (!this.flow) {
            throw ('ContentFlow ERROR: No element with class\'flow\' found!');
            return;
        }

        /* init AddOns */
        this._initAddOns();
        this.setConfig(this._userConf);
        
        /* init click functions */
        this._ciItem =  this._clickInactiveItem.bind(this);
        this._caItem =  this._clickActiveItem.bind(this);

        if (this._conf.activeElement != "content")
            this._conf.activeElement = "element";

        //this._initSizes(); // ......

        this._initItemList();

        var now = new Date();
        var cf = this;
        var timer = window.setInterval (
            function() {
                if ( cf._imagesToLoad == 0 || new Date() - now > cf._conf.loadingTimeout ) {
                    clearInterval(timer);
                    cf._initStep2();
                }
            }, 10
        );
        
        this.isInit = true;

    },

    _initStep2: function () {
        
        this._initScrollbar();
        this.globalCaption = this.container.getChildrenByClassName('globalCaption')[0];
        this.loadIndicator = this.container.getChildrenByClassName('loadIndicator')[0];
        
        this.flow.style.visibility = "visible"; // show flow after images are loaded
        this._initSizes();

        /* init startparameters */
        this._initStartParameters();

        /* setup observers */
        this._initObservers();

        /* take first step */
        this._initStep();
        
        /* show the flow if it was hidden */
        if (this.loadIndicator) this.loadIndicator.style.display = "none";
        if (this.scrollbar) this.scrollbar.style.visibility = "visible";
    },


    /* ---------- init Scrollbar ---------- */ 
    _initScrollbar: function() {
        this.scrollbar = this.container.getChildrenByClassName('scrollbar')[0];
        if (this.scrollbar) {
           this.slider = $(this.scrollbar).getChildrenByClassName('slider')[0];
            if (this.slider) {
                this.position = this.slider.getChildrenByClassName('position')[0];
            }

        }
    },

    /* ---------- init AddOns ---------- */ 
    _initAddOns: function () {
        ContentFlowGlobal.AddOns['DEFAULT']._init(this);
        if (!this._userConf.useAddOns || typeof this._userConf.useAddOns == "string") { // use all AddOns for this Flow
            this._conf.useAddOns = new Array();
            if (this._conf.useAddOns != "none") {
                for (var AddOn in ContentFlowGlobal.AddOns) this._conf.useAddOns.push(AddOn);
            }
        }
        else {
            this._conf.useAddOns = this._userConf.useAddOns;
        }

        for (var i=0; i< this._conf.useAddOns.length; i++) { // init all AddOns that should be used
            var AddOn = ContentFlowGlobal.AddOns[this._conf.useAddOns[i]];
            if (AddOn) {
                    AddOn._init(this);
                    this.container.addClassName('ContentFlowAddOn_'+AddOn.name);
                    if (AddOn.methods.onloadInit)
                        AddOn.methods.onloadInit(this);
            }
        }
        this.initAfterAddOn._init(this);

    },

    /* ---------- init elemet sizes ---------- */ 
    _initSizes: function () {
        if (this.containerOldHeight) this.container.style.height = this.containerOldHeight;
        if (this.flowOldHeight) this.flow.style.height = this.flowOldHeight;
        this.containerOldHeight = "auto";
        this.flowOldHeight = "auto";
        
        if (this._conf.maxItemHeight <= 0) {

            this.maxHeight = this.flow.clientWidth / 3 * screen.height/screen.width * this._conf.scaleFactor;  // divided by 3 because of left/center/right, yes it's a magic number

            if (this.container.style.height && this.container.style.height != "auto") {
                this.maxHeight = this.container.clientHeight / (this._conf.scaleFactor* (this._conf.reflectionType != "none" ? 1 + this._conf.reflectionHeight : 1)); 
                this.containerOldHeight = this.container.style.height;
            }
            else if (this.flow.style.height && this.flow.style.height != "auto") {
                this.maxHeight = this.flow.clientHeight / (this._conf.scaleFactor* (this._conf.reflectionType != "none" ? 1 + this._conf.reflectionHeight : 1));
                this.flowOldHeight = this.flow.style.height;
            }
        }
        else {
            this.maxHeight = this._conf.maxItemHeight;
        }

        if (this.scrollbar) {
            this.scrollbarDim = this.scrollbar.getDimensions();
            this.scrollbarCenter = {x: this.scrollbarDim.width/2, y:this.scrollbarDim.height/2};
            this.scrollbarPos = this.scrollbar.findPos();
            var maxHeightCorrection = this.scrollbarDim.height;

            if (this.slider) {
                this.sliderDim = this.slider.getDimensions();
                this.sliderCenter = {x: this.sliderDim.width/2, y:this.sliderDim.height/2};
                this.sliderPos = this.slider.findPos();
                maxHeightCorrection += this.sliderDim.height;

                if (this.position) {
                    this.position.innerHTML="&nbsp;";
                    this.positionDim = this.position.getDimensions();
                    this.positionPos = this.position.findPos();
                    var extraSpace = this.positionPos.top - this.sliderPos.top;
                    if (extraSpace > 0) {
                        extraSpace += -this.scrollbarDim.height + this.positionDim.height;
                        this.scrollbar.style.marginBottom = extraSpace + "px";
                    }
                    else {
                        extraSpace *= -1;
                        this.scrollbar.style.marginTop = extraSpace + "px";
                    }
                    maxHeightCorrection += extraSpace;
                }
            }
            if (this.container.style.height && this.container.style.height != "auto")
                this.maxHeight -= maxHeightCorrection; 
        }
        var maxItemSize = this._calcSize(this._conf.biggestItemPos, 1);


        if (this._conf.reflectionType != "none") {
            this.flow.style.height = maxItemSize.height * (1 + this._conf.reflectionHeight) + "px";
            if (typeof(this._conf.negativeMarginOnFloat) == "number") {
                this.flow.style.marginBottom = -maxItemSize.height * (this._conf.reflectionHeight * this._conf.negativeMarginOnFloat)+ "px";
            } else {
                this.flow.style.marginBottom = -maxItemSize.height * this._conf.reflectionHeight+ "px";
            }
            this.flowDim = this.flow.getDimensions();
            if (this.container.clientHeight < this.flowDim.height) {
                this.container.style.height = this.flowDim.height+"px";
            }
        } else {
            this.flow.style.height = maxItemSize.height + "px";
            this.flow.style.marginBottom = "0";
        }
        this.flowDim = this.flow.getDimensions();
        this.flowCenter = {x: this.flowDim.width/2, y:maxItemSize.height/2};
    },

    /*
     * ---------- init item lists ----------
     */
    _initItemList: function () {
        this.shownItems = {};
        var items = this.flow.getChildrenByClassName('item');
        this.items = new Array();
        for (var i=0; i<items.length; i++) {
            this.items[i] = {};
            this.items[i].element = $(items[i]);
            this.items[i].item = this.items[i].element;
            this._initItem(i);
            this.shownItems[i] = i;
        }
        this.items[0].element.addClassName('active');
        this._activeItem = this.items[0];
        this._setLastIndex();
    },

    /*
     *  init item
     */
    _initItem: function(index) {
        var item = this.items[index];
        item.index = index;
        item.element.itemIndex = index;

        item.content = item.element.getChildrenByClassName('content')[0];
        item.caption = item.element.getChildrenByClassName('caption')[0];
        item.label = item.element.getChildrenByClassName('label')[0];
            
        /* if content is image set properties */
        if (item.content.nodeName == "IMG") {
            this._imagesToLoad++;

            var foo = function () { 
                this._imagesToLoad--;
                item.image = item.content;
                this._setImageFormat(item.image);
                if (this._conf.reflectionType != "none") this._addReflection(item);
                
                var ciItem = this._ciItem;
                if (window.addEventListener) {
                    item[this._conf.activeElement].addEvent('click', ciItem, false);
                } else {
                    item[this._conf.activeElement].onclick = ciItem;
                }
            };

            var foobar = foo.bind(this);
            if (item.content.complete && item.content.width > 0)
                foobar();
            else
                item.content.onload = foobar;

            /* add overlay if Gecko because of DnD of images */
            /*
            if (!document.all) {
                this.itemsOverlay[index] = document.createElement('div');
                this.itemsOverlay[index].className = 'overlay';
                item.appendChild(this.itemsOverlay[index]);
                if (this._conf.reflectionType != "none") {
                    item.addClassName('withReflection');
                    this.itemsOverlay[index].style.height = (1 + this._conf.reflectionHeight) * 100 +"%";
                }
            }
            */

        } 

    },

    /*
     * initializes the startparameter visibleItems, startItem and scrollinFrom
     */
    _initStartParameters: function () {

        if (this._conf.visibleItems < 0)
            this._conf.visibleItems = Math.round(Math.sqrt(this.items.length));
        this._conf.visibleItems = Math.min(this._conf.visibleItems, this.items.length - 1);

        this._targetPosition = this._getIndexByKeyWord(this._conf.startItem);

        var index = this._getIndexByKeyWord(this._conf.scrollInFrom, this._targetPosition);
        switch (this._conf.scrollInFrom) {
            case "next":
                index -= 0.5;
                break;
            case "pre":
                index += 0.5;
                break;
            case "none":
                this._activeItem = this.getItem(index);
        } 
        this._currentPosition = index;
        
    },

    /*
     * adds event listeners to elements
     */
    _initObservers: function() {

        var divs = this.container.getElementsByTagName('div');
        for (var i = 0; i < divs.length; i++) {
            if ($(divs[i]).hasClassName('preButton')) {
                var pre = divs[i];
                var mt = this._onclickPreButton.bind(this);
                pre.addEvent('click', mt, false);
            }
            else if (divs[i].hasClassName('nextButton')) {
                var next = divs[i];
                var mt = this._onclickNextButton.bind(this);
                next.addEvent('click', mt, false);
            }
        }

        if (this.scrollbar) {
            var sb = this._clickedScrollbar.bind(this);
            this.scrollbar.addEvent('click', sb, false);

            if (this.slider) {
                var actS = this._activateDragSlider.bind(this);
                this.slider.addEvent('mousedown', actS, false);
            }
        }

        if (this._conf.flowDragFriction > 0) {
            var actF = this._activateDragFlow.bind(this);
            this.flow.addEvent('mousedown', actF, false);
        }

        if (this._conf.scrollWheelSpeed > 0) {
            var wheel = this._wheel.bind(this);
            if(window.addEventListener) this.container.addEventListener('DOMMouseScroll', wheel, false);
            this.container.onmousewheel = wheel;
        }

        var resize = this.resize.bind(this);
        window.addEvent('resize', resize, false);
        
        var key = this._keyStroke.bind(this);
        this.container.onkeydown = key;
    },


    /*
     * ==================== Key strok ====================
     */

    /*
     * handles keystroke events
     */
    _keyStroke: function(event) {
        if(!event) var event = window.event;
        if (event.which) {
            var keyCode = event.which;
        } else if (event.keyCode) {
            var keyCode = event.keyCode;
        }

        switch (keyCode) {
            case 37:
                this.moveToIndex('pre');
                break;
            case 38:
                this.moveToIndex('visibleNext');
                break;
            case 39:
                this.moveToIndex('next');
                break;
            case 40:
                this.moveToIndex('visiblePre');
                break;
        }
    },
    
    /*
     * ==================== mouse wheel ====================
     * Event handler for mouse wheel event
     * http://adomas.org/javascript-mouse-wheel/
     */

    _wheel: function (event) {
        if (!event) var event = window.event; // MS
        
        var delta = 0;
        if (event.wheelDelta) {
            delta = event.wheelDelta/120; 
        } else if (event.detail) {
            delta = -event.detail/3;
        }

        if (delta) {
            var target = this._targetPosition ;
            if (delta < 0 ) {
                target += (1 * this._conf.scrollWheelSpeed);
            } else {
                target -= (1 * this._conf.scrollWheelSpeed);
            } 
            this.moveToPosition(Math.round(target));
        }

        Event.stop(event);
    },

    /*
     * ==================== drag and throw flow ====================
     */

    /*
     * init mousemove and mouseup events on flow 
     */
    _activateDragFlow: function(event) {
        if (!event) var event = window.event;

        this.mouseX = event.clientX; 
        this.ms = new Date();

        var dragF = this.dragF = this._dragFlow.bind(this);
        window.addEvent('mousemove',dragF, false);

        var deaDF = this._deactivateDragFlow.bind(this);
        window.addEvent('mouseup', deaDF, false);
    },

    /*
     * remove mousemove event from flow
     */
    _deactivateDragFlow: function(event) {
        if (!event) var event = window.event;
        Event.stop(event);

        var dragF = this.dragF;
        window.removeEvent('mousemove', dragF, false);

        this.moveToPosition(Math.round(this._targetPosition));
        
    },

    /*
     * handles the mouse movemend and inits next step while mousedown
     */
    _dragFlow: function(event) {
        if (!event) var event = window.event;

        //var ms = new Date();
        var mouseX = event.clientX;
        
        //var dur = (this.ms - ms); // ms
        var dist = mouseX - this.mouseX; // px / or px per sec because _dragFlow wil be called in shorter intervalls if draged fast
        var itemDist = (dist / this.flowDim.width)* (2*this._conf.visibleItems +1); // items
        var target = this._currentPosition - itemDist * 2*this._conf.visibleItems / this._conf.flowDragFriction ;

        //this.ms = ms;
        this.mouseX = mouseX; 

        this.moveToPosition(target);
    },

    /*
     * ==================== scrollbar/slider ====================
     */

    /*
     * handles the positioning of the slider
     */
    _setSliderPosition: function (relPos) {
        relPos = relPos - Math.floor(relPos) + this._getIndexByPosition(Math.floor(relPos));
        if (Math.round(relPos) < 0)
            relPos = this.itemsLastIndex;
        else if (relPos <= 0)
            relPos = 0;
        else if (Math.round(relPos) > this.itemsLastIndex)
            relPos = 0;
        else if (relPos >= this.itemsLastIndex)
            relPos = this.itemsLastIndex;


        if (this.items.length > 1) {
            var sPos = (relPos / this.itemsLastIndex)* this.scrollbarDim.width;
        } else {
            var sPos = 0.5 * this.scrollbarDim.width;
        }
        this.slider.style.left = sPos - this.sliderCenter.x+ "px";
        this.slider.style.top = this.scrollbarCenter.y - this.sliderCenter.y +"px";

    },

    _setSliderLabel: function (index) {
        if (this.position) {
            index = this._checkIndex(Math.round(index))
            if (this.items[index].label)
                this.position.innerHTML = this.items[index].label.innerHTML;
            else
                this.position.innerHTML = index + 1;
            this.position.style.left = (this.sliderDim.width - this.position.clientWidth)/2 + "px";
        }
    },

    /*
     * handles the click event on the scrollbar
     */
    _clickedScrollbar: function(event) {
        if (!event) var event = window.event;

        if (!this.lockScrollbarClick) {
            var mouseX = event.clientX; 
            var positionOnScrollbar = mouseX - this.scrollbarPos.left;
            var targetIndex = Math.round(positionOnScrollbar/this.scrollbarDim.width * this.itemsLastIndex);
            this.moveToIndex(targetIndex);
        }
        else
            this.lockScrollbarClick = false;
    },

    /* -------------------- drag slider -------------------- */

    /*
     * init mousemove event on slider
     */
    _activateDragSlider: function(event) {
        if (!event) var event = window.event;

        //this.sliderPos = parseInt(this.slider.style.left);
        this.sliderStartPosition = parseInt(this.slider.style.left) + this.sliderCenter.x;
        this.dragSliderStartPosition = this._currentPosition;
        this.sliderStartIndex = this._getIndexByPosition(this.dragSliderStartPosition);
        this.mouseX = event.clientX; 
        this.lockScrollbarClick = true;

        var dragS = this.dragS = this._dragSlider.bind(this);
        window.addEvent('mousemove', dragS, false);
    },

    /*
     * remove mousemove event from slider
     */
    _deactivateDragSlider: function(event) {
        var dragS = this.dragS;
        window.removeEvent('mousemove', dragS, false);
        this._targetPosition = Math.round(this._targetPosition);
        this._initStep(true);
    },
    
    /*
     * handles the slider movemend and inits next step on mouse move
     */
    _dragSlider: function(event) {
        if (!event) var event = window.event;

        var deaDS = this._deactivateDragSlider.bind(this);
        window.addEvent('mouseup', deaDS, false);

        var newSliderPosition = this.sliderStartPosition + event.clientX - this.mouseX;
        var selectedIndex = this._checkIndex(newSliderPosition / this.scrollbarDim.width * this.itemsLastIndex);
        this._targetPosition =  this.dragSliderStartPosition + selectedIndex - this.sliderStartIndex;

        this.sliderLock = true;
        this._setSliderPosition(selectedIndex);
        this._setSliderLabel(selectedIndex);
        this._initStep(true);
    },

    /*
     * ==================== set global Caption ====================
     */
    _setGlobalCaption: function () {
        if (this.globalCaption) {
            this.globalCaption.innerHTML = '';
            if(this._activeItem.caption)
                this.globalCaption.appendChild(this._activeItem.caption.cloneNode(true));
        }
    },

    /*
     * ==================== move items ====================
     */

    /*
     * intend to make a step 
     */
    _initStep: function (holdSlider) {
        if(holdSlider) {
            this.sliderLock = true;
        } else {
            this.sliderLock = false;
        }
        if (!this._stepLock) {
            this._stepLock = true;
            this._step();
        }
    },

    /*
     * make a step
     */
    _step: function () {
        var diff = this._targetPosition - this._currentPosition; 
        var absDiff = Math.abs(diff);
        if ( absDiff > 0.001) { // till activeItem is nearly at position 0

            this._currentPosition += this._conf.flowSpeedFactor * this._calcStepWidth(diff, absDiff);

            var AI = this.items[(this._getIndexByPosition(this._currentPosition))];

            if (AI != this._activeItem) {
                var ciItem = this._ciItem;
                var caItem = this._caItem;

                /* remove click events from old active item */
                this._activeItem.element.removeClassName('active');
                if (window.addEventListener) {
                    this._activeItem[this._conf.activeElement].removeEvent('click', caItem, false);
                } else {
                    this._activeItem[this._conf.activeElement].onclick = ciItem;
                }

                this._activeItem = AI;
            
                /* add click events to new active Item*/
                this._activeItem.element.addClassName('active');
                if (window.addEventListener) {
                    this._activeItem[this._conf.activeElement].addEvent('click', caItem, false);
                } else {
                    this._activeItem[this._conf.activeElement].onclick = caItem;
                }

                if (this.slider && !this.sliderLock)
                    this._setSliderLabel(this._activeItem.index);
                this._setGlobalCaption();
                this._onMakeActive(this._activeItem);
            }
            
            this._positionItems();

            var st = this._step.bind(this);
            setTimeout(st,this._millisecondsPerStep);

        } else {
            this.sliderLock = false;
            //this._currentPosition = this._activeItem.index;
            this._currentPosition = Math.round(this._currentPosition);
            this._setGlobalCaption();
            this._positionItems();
            this._stepLock = false;
        }

        if (this.slider && !this.sliderLock) {
            this._setSliderPosition(this._currentPosition);
        }
    },
    
    /*
     * position items
     */
    _positionItems: function () {
        //var cP = this._currentPosition;
        
        var start = this._currentPosition - this._conf.visibleItems;
        var end = this._currentPosition + this._conf.visibleItems;
        if (!this._conf.circularFlow) {
            start = this._checkIndex(start);
            end = this._checkIndex(end);
        }
        
        this.oldShownItems = this.shownItems;
        this.shownItems = {};

        /* get indeces of all visible items*/
        for (var p = start ; p <= end; p++) {
            var i = this._getIndexByPosition(p);
            this.shownItems[i] = p;
        }

        /* hide all items, that where show last time but not now*/
        for (var e in this.oldShownItems) {
            if (!this.shownItems[e]) this.items[e].element.style.display = "none";
        }
        
        for (var i in this.shownItems) {
            var p = this.shownItems[i];
            var item = this.items[i];
            var itemEl = this.items[i].element;
            itemEl.style.display = "none"; // don't show item, till all manipulations are done

            /* Index and position relative to activeItem */
            var relativeIndex = Math.round(p - this._currentPosition);
            var relativePosition = Math.round(p) - this._currentPosition;
            var side = relativePosition < 0 ? -1 : 1;
            side *= relativePosition == 0 ? 0 : 1;

            var size = this._calcSize ( relativePosition, side );
            var coords = this._calcCoordinates ( relativePosition, side );
            var relItemPos = this._calcRelativeItemPosition( relativePosition, side, size );
            var zIndex = this._calcZIndex ( relativePosition, side, relativeIndex );
            var fontSize = this._calcFontSize ( relativePosition, side, size );

            /* set position */
            itemEl.style.left = coords.x + relItemPos.x + "px";
            itemEl.style.top = coords.y + relItemPos.y + "px";
            
            /* set size */
            itemEl.style.width = size.width +"px";
            itemEl.style.height = size.height +"px";

            if (this.items[i].image || this._conf.reflectionWithinImage) {
                this.positionContent(i, size);
            }

            itemEl.style.zIndex = 32768 + zIndex; // just for FF
            itemEl.style.display = "block";
            itemEl.style.visibility = "visible";

        }
    },

    positionContent: function (i, size, position) {
        var Item = this.items[i];
        var content = this.items[i].content;
        var proportion = content.origProportion;

        /* calc size and position of content */
        if (proportion >= 1) {
            var contentDim = {
                height: size.height,
                width: size.height / proportion 
            };
        } else {
            if (this._conf.scaleFactorLandscape == "max") {
                var height = size.height;
            }
            else {
                var height = size.width * proportion * this._conf.scaleFactorLandscape;
            }
            height = height > this.maxHeight ? this.maxHeight : height;
            var contentDim = {
                height: height,
                width: height / proportion 
            };
        }
        var contentPos = {
            left: this.Browser.IE ? 0 : (size.width - contentDim.width )/2,
            top: size.height - contentDim.height
        };

        /* set dimensions and position of content */
        if (this._conf.reflectionType != "none" && this._conf.reflectionWithinImage && !this.Browser.IE) {
            content.style.height = contentDim.height * (1 + this._conf.reflectionHeight) + "px";
        } else {
            content.style.height = contentDim.height + "px";
        }
        content.style.width = contentDim.width + "px";
        content.style.marginLeft = contentPos.left + "px";
        content.style.marginTop = contentPos.top + "px";



        if (this.items[i].reflection) {
            /* set dimensions and position of reflection */
            var reflection = this.items[i].reflection
            reflection.style.height = contentDim.height*this._conf.reflectionHeight+"px";
            reflection.style.width = content.style.width;
            reflection.style.marginLeft = content.style.marginLeft;

            if (this.items[i].reflectionOverlay) {
                /* set dimensions and position of reflection-overlay */
                var refover = this.items[i].reflectionOverlay;
                refover.style.height = reflection.style.height;
                refover.style.width = reflection.style.width;
                refover.style.marginLeft = reflection.style.marginLeft;
                refover.style.marginTop = '-'+reflection.style.height;
            }
        }
    }
};


/* ==================== extendig javascript/DOM objects ==================== */

/*
 *  adds bind method to Function class
 *  http://www.digital-web.com/articles/scope_in_javascript/
 */

if (!Function.bind) {
    Function.prototype.bind = function(obj) {
        var method = this;
        return function () {
            return method.apply(obj, arguments);
        };
    };
}


/*
 * extending Math object
 */
if (!Math.erf2) {
    // error function (http://en.wikipedia.org/wiki/Error_function), implemented as erf(x)^2
    Math.erf2 = function (x) {
        var a = - (8*(Math.PI -3)/(3*Math.PI*(Math.PI -4)));
        var x2 = x*x;
        var f = 1 - Math.pow(Math.E, -x2 * (4/Math.PI + a*x2)/(1+a*x2));
        return f;
    };
}

if (!Math._2PI05) {
    Math._2PI05 = Math.sqrt(2*Math.PI);
}

if (!Math.normDist) {
    // normal distribution
    Math.normDist = function (x, sig, mu) {
        if (!sig) var sig = 1;
        if (!mu) var mu = 0;
        if (!x) var x = - mu;
        return 1/(sig * Math._2PI05) * Math.pow(Math.E, - (x-mu)*(x-mu)/(2*sig*sig) );
    };
}

if (!Math.normedNormDist) {
    Math.normedNormDist = function (x, sig, mu) {
        return this.normDist(x, sig, mu)/this.normDist(mu, sig, mu);
    };
}

if (!Math.exp) {
    Math.exp = function(x) {
        return Math.pow(Math.E, x);
    };
}

if (!Math.ln) {
    Math.ln = Math.log;
}

if (!Math.log2) {
    Math.log2 = function (x) {
        return Math.log(x)/Math.LN2;
    };
}

if (!Math.log10) {
    Math.log10 = function (x) {
        return Math.log(x)/Math.LN10;
    };
}

if (!Math.logerithm) {
    Math.logerithm = function (x, b) {
        if (!b || b == Math.E)
            return Math.log(x);
        else if (b == 2)
            return Math.log2(x);
        else if (b == 10)
            return Math.log10(x);
        else
            return Math.log(x)/Math.log(b);
    };
}


/*
 * extending Event object
 */
if (!Event) var Event = {};

if (!Event.stop) {
    Event.stop = function (event) {
        event.cancelBubble = true;
        if (event.preventDefault) event.preventDefault();
        if (event.stopPropagation) event.stopPropagation();
        return false;
    };
}

/*
 * extending Element object
 */
if (document.all && !window.opera) {
    window._$ = function (el) {
        if (CFElement.prototype.extend && !el.extend) CFElement.prototype.extend(el);
        return el;
    };
} else {
    window._$ = function (el) {
        return el;
    };
}

if (!window.HTMLElement) {
    CFElement = {};
    CFElement.prototype = {};
    CFElement.prototype.extend = function (el) {
        for (var method in this) {
            if (!el[method]) el[method] = this[method];
        }
    };
}
else {
    CFElement = window.HTMLElement;
}


/*
 * Thanks to Peter-Paul Koch
 * http://www.quirksmode.org/js/findpos.html
 */
if (!CFElement.findPos) {
    CFElement.prototype.findPos = function() {
        var obj = this;
        var curleft = curtop = 0;
        if (obj.offsetParent) {
            curleft = obj.offsetLeft;
            curtop = obj.offsetTop;
            while (obj = obj.offsetParent) {
                curleft += obj.offsetLeft;
                curtop += obj.offsetTop;
            }
        }
        return {left:curleft, top:curtop};
    };
}

if (!CFElement.getDimensions) {
    CFElement.prototype.getDimensions = function() {
        return {width: this.clientWidth, height: this.clientHeight};
    };
}

/*
 * checks if an element has the class className
 */
if (!CFElement.hasClassName) {
    CFElement.prototype.hasClassName = function(className) {
        return (new RegExp('\\b'+className+'\\b').test(this.className));
    };
}

/*
 * adds the class className to the element
 */ 
if (!CFElement.addClassName) {
    CFElement.prototype.addClassName = function(className) {
        if(!this.hasClassName(className)) {
           this.className += (this.className ? ' ':'') + className ;
        }
    };
}

/*
 * removes the class className from the element el
 */ 
if (!CFElement.removeClassName) {
    CFElement.prototype.removeClassName = function(className) {
        this.className = this.className.replace(new RegExp('\\b'+className+'\\b'), '').replace(/\s\s/g,' ');
    };
}

/*
 * removes or adds the class className from/to the element el
 * depending if the element has the class className or not.
 */
if (!CFElement.toggleClassName) {
    CFElement.prototype.toggleClassName = function(className) {
        if(this.hasClassName(className)) {
            this.removeClassName(className);
        } else {
            this.addClassName(className);
        }
    };
}

/*
 * returns all children of element el, which have the class className
 */
if (!CFElement.getChildrenByClassName) {
    CFElement.prototype.getChildrenByClassName = function(className) {
        var children = new Array();
        for (var i=0; i < this.childNodes.length; i++) {
            var c = this.childNodes[i];
            if (c.nodeType == 1 && $(c).hasClassName(className)) {
                children.push(c);
            }
        }
        return children;
    };
}

/*
 * Browser independent event handling method.
 * adds the eventListener  eventName to element el and attaches the function method to it.
 */
if (!CFElement.addEvent) {
    CFElement.prototype.addEvent = function(eventName, method, capture) {
        if (this.addEventListener)
            this.addEventListener(eventName, method, capture);
        else
            this.attachEvent('on'+eventName, method);
    };
}
   
/*
 * Browser independent event handling method.
 * removes the eventListener  eventName with the attached function method from element el.
 */
if (!CFElement.removeEvent) {
    CFElement.prototype.removeEvent = function(eventName, method, capture) {
        if (this.removeEventListener)
            this.removeEventListener(eventName, method, capture);
        else
            this.detachEvent('on'+eventName, method);
    };
}

/*
 * Browser independent event handling method.
 * adds the eventListener  eventName to element el and attaches the function method to it.
 */
if (!window.addEvent) {
    window.addEvent = function(eventName, method, capture) {
        if (this.addEventListener) {
            this.addEventListener(eventName, method, capture);
        } else {
            if (eventName != 'load' && eventName != 'resize')
                document.attachEvent('on'+eventName, method);
            else
                this.attachEvent('on'+eventName, method);
        }
    };
}
   
/*
 * Browser independent event handling method.
 * removes the eventListener  eventName with the attached function method from element el.
 */
if (!window.removeEvent) {
    window.removeEvent = function(eventName, method, capture) {
        if (this.removeEventListener) {
            this.removeEventListener(eventName, method, capture);
        } else {
            if (eventName != 'load' && eventName != 'resize')
                document.detachEvent('on'+eventName, method);
            else
                this.detachEvent('on'+eventName, method);
        }
    };
}

/* ==================== start it all up ==================== */
ContentFlowGlobal.init();

