/**
Copyright (c) 2011 Trophy Toolbox. All rights reserved.
*/
var ImageSwitcher;
new function()
{
    var Browser = new function()
    {
        var index = navigator.userAgent.indexOf("MSIE");
        if(index > 0)
        {
            this.name = 'IE';
            this.version = parseInt(navigator.userAgent.substring(index+5));
        }
    }

    /**
    Format number to pixels
    */
    var px = function(value)
    {
        return Math.round(value)+'px';
    }

    /**
    Format number to percentage
    */
    var pc = function(value)
    {
        return Math.round(value)+'%';
    }

    /**
    Copy members of one object to another
    */
    var mapObj = function(obj, fromObj)
    {
        for(var i in fromObj)
            obj[i] = fromObj[i];
        return obj;
    }

    /**
    Creates a new object copying the members from two objects
    */
    var add = function(obj, fromObj)
    {
        var o = {};
        mapObj(o, obj);
        mapObj(o, fromObj);
        return o;
    }

    var preloadImage = function(p)
    {
        var img, src = p.src;
        var timeout;

        var clear = function()
        {
            img.onerror = null;
            img.onload = null;
            preloadImage.loading[src] = null;
            clearTimeout(timeout);
        }

        var preload = function()
        {
            img = new Image();
            img.onload = function(){
                preloadImage.loaded[src] = true;
                p.onLoad();
                clear();
            }
            img.onerror = function(){
                p.onError();
                clear();
            }
            img.src = src;
            preloadImage.loading[src] = img;
            timeout = setTimeout(function(){
                if(img.onload)
                    img.onload();
            }, 5000);
        }
        if(src in preloadImage.loaded)
            p.onLoad();
        else
            preload();
    }
    preloadImage.loading = {};
    preloadImage.loaded = {};

    var createImage = function(node, url, width, height)
    {
        var img = node.ownerDocument.createElement('img');
        if(width)
            img.style.width = width;
        if(height)
            img.style.height = height;
        img.src = url;
        return img;
    }

    var createText = function(node, text)
    {
        var node = node.ownerDocument.createElement('span');
        node.innerHTML = text;
        return node;
    }

    var setOpacity = function(node, value)
    {
        node.style.opacity = value;
        node.style.filter = "alpha(opacity="+(value*100)+")";
    }

    /**
    Check whether an object contains a member and its value passes the given tests
    */
    var checkMember = function(obj, member, mandatory, defaultValue, checker)
    {
        if(obj[member] === undefined)
        {
            if(mandatory)
                throw "Mandatory parameter ommited: "+ member;
            if(defaultValue !== undefined)
                obj[member] = defaultValue;
        }
        else if(checker && !checker(obj[member]))
            throw "Incorrect parameter value: "+ member;
    }

    /**
    Checks that a set of values passes the given tests
    */
    var checkValues = function(obj, checks, fillDefaults)
    {
        for (var i in checks)
        {
            var c = checks[i];
            checkMember(obj, i, c.mandatory, fillDefaults? c.defaults: undefined, c.check);
        }
    }

    var isColor = function(){ return true};
    var isFont = function(){ return true};
    var isSize = function(){ return true};

    /**
    Creates a table with the given number of cells and columns
    */
    var Table = function(p)
    {
        var node = p.node;
        var doc = node.ownerDocument;
        var table = doc.createElement('table');
        var tbody = doc.createElement('tbody');
        var thead = doc.createElement('thead');

        table.appendChild(thead);
        table.appendChild(tbody);

        thead.style.display = 'none';

        for(var i = 0; i < p.rows; i++)
        {
            var tr = doc.createElement('tr');
            tbody.appendChild(tr);
            for(var j = 0; j < p.columns; j++)
            {
                var td = doc.createElement('td');
                tr.appendChild(td);
                if(p.eachCell)
                    p.eachCell(i, j, td);
            }
        }

        mapObj(table.style, p.style);
        node.appendChild(table);
        return table;
    }

    var Interval = new function()
    {
        var interval;
        var fps = 40;
        var handlers = [];

        var handler = function(){
            var next = [];
            for(var i = 0; i < handlers.length;i++)
            {
                var done = handlers[i]();
                if(!done)
                    next.push(handlers[i]);
            }
            handlers = next;
            if(handlers.length == 0)
            {
                clearInterval(interval);
                interval = null;
            }
        }

        this.add = function(callback){
	    if(!interval)
            	interval = setInterval(handler, Math.round(1000/fps));
            handlers.push(callback);
        }
    }

    /**
    Configures a callback to be called periodically for a given amount of time
    */
    var setFor = function(time, fps, callback)
    {
        var startTime = (new Date).getTime();
        Interval.add(function()
        {
            var currentTime = (new Date).getTime();
            var elapsed = currentTime - startTime;
            if(elapsed > time)
            {
                callback(1, true);
                return true;
            }
            else
            {
                callback(elapsed/time);
                return false;
            }
        });
    }

    /**
    Creates an absolute positioned div and ataches it to the parent.
    */
    var createMask = function(p)
    {
        var node = p.node;
        var mask = node.ownerDocument.createElement('div');
        mapObj(mask.style, p.style);
        //mask.style.width = '100%';
        //mask.style.height = '100%';
        mask.style.position = 'absolute';

        if(node.firstChild)
            node.insertBefore(mask, node.firstChild);
        else
            node.appendChild(mask);

        return mask;
    }

    /**
    @class Handles mouseover interaction over an element, creating a transparent clickable.
    */
    var MouseHandler = function(p)
    {
        var node = p.node;
        var link;
        var target;
        var mask = createMask({
            node: node,
            style: p.style
        });
        mask.style.borderWidth = '0px';
        mask.style.backgroundColor = p.mouseoverColor;
        setOpacity(mask, 0);

        //setup events

        mask.onmouseover = function()
        {
            setOpacity(mask, 0.5);
        }

        mask.onmouseout = function()
        {
            setOpacity(mask, 0);
        }

        mask.onclick = function()
        {
            if(link)
                open(link, target);
        }

        /**
        Configure the current link and target for click events
        */
        this.setLink = function(url, _target)
        {
            mask.style.cursor = 'pointer';
            link = url;
            target = _target;
        }

        this.enable = function()
        {
            mask.style.display = 'block';
        }

        this.disable = function()
        {
            mask.style.display = 'none';
        }

        this.getNode = function()
        {
            return mask;
        }
    }

    /**
    @class Creates an element that fades and transitions an other element
    */
    var Fader = function(p)
    {
        var $this = this;
        var bgColor = p.style.backgroundColor;
        var mask = createMask({
            node: p.node,
            style: p.style
        });
        if(p.transparent)
        {
            setOpacity(mask, 0);
            mask.style.borderWidth = '0px';
            mask.style.display = 'none';
        }

        /**
        Fades in from a given color during a given period of time
        */
        this.fadeIn = function(p)
        {
            mask.style.display = 'block';
            mask.style.backgroundColor = p.color || bgColor;
            setOpacity(mask, 1);
            setFor(p.time, 50, function(value, end)
            {
                setOpacity(mask, 1-value);
                if(end)
                {
                    mask.style.display = 'none';
                    p.onEnd();
                }
            });
        }

        /**
        Creates a transition effect, fades out, generates an onChange event, then fades in again and generates an onEnd event.
        */
        this.transition = function(p)
        {
            mask.style.display = 'block';
            mask.style.backgroundColor = p.color || bgColor;
            setFor(p.time/2, 50, function(value, end)
            {
                setOpacity(mask, value);
                if(end)
                {
                    p.onChange();
                    $this.fadeIn({
                        time: p.time/2,
                        onEnd: p.onEnd
                    });
                }
            });
        }

        this.getNode = function()
        {
            return mask;
        }
        this.remove = function()
        {
            mask.parentNode.removeChild(mask);
        }
    }

    /**
    @class Creates a slide over an other element with a given color.
    @augments Fader
    */
    var Slider = function(p)
    {
        var slideTime = p.slideTime;
        var slideType = p.slideType;

        var fader = Fader.call(this, {
            node: p.node,
            style: add(p.style,{
                borderWidth: '0px'
            })
        });

        var mask = this.getNode();
        var bw = parseInt(p.style.borderWidth);

        var ieFix = 100 + parseInt(p.style.borderWidth);
        if(Browser.name == 'IE')
        {
            mapObj(mask.style, {
                left: px(-bw),
                right: px(-bw),
                top: px(-bw),
                height: pc(ieFix)
            });
        }
        else
        {
            mapObj(mask.style, {
                left: px(-bw),
                right: px(-bw),
                top: px(-bw),
                bottom: px(-bw)
            });
        }

        /**
        Creates an inner element for the slide, it's necessary to correct a rendering bug in IE.
        */
        var fill = function(node)
        {
            var div = node.ownerDocument.createElement('div');
            mapObj(div.style, {
                width: '100%',
                height: '100%',
                lineHeight: '0px',
                fontSize: '1px',
                margin: '0px',
                overflow: 'hidden'
            });
            node.appendChild(div);
            return div;
        }

        /**
        Sets the size for the slider in a given instant.
        */
        var setSize = function(value)
        {
            //This is another way of handling the size of the slide
/*
            mapObj(mask.style, {
                width: pc(slideType == 'left-right' || slideType == 'right-left'? (value*100): 100),
                height: pc(slideType == 'top-bottom' || slideType == 'bottom-top'? (value*100): 100),
                //height: px(slideType == 'top-bottom' || slideType == 'bottom-top'? value*p.height: p.height),
                marginBottom: pc(slideType == 'top-bottom'? (1-value)*100: 0),
                //marginBottom: px(slideType == 'top-bottom'? (1-value)*p.height: 0),
                marginTop: pc(slideType == 'bottom-top'? (1-value)*100: 0),
                //marginTop: px(slideType == 'bottom-top'? (1-value)*p.height: 0),
                marginLeft: pc(slideType == 'right-left'? (1-value)*100: 0),
                marginRight: pc(slideType == 'left-right'? (1-value)*100: 0)
            });
            */

            if(Browser.name == 'IE')
            {
                //This is the way of handling the size for IE.
                //It is slightly different from the other browsers because of a problem with the vertical size.
                //Like IE ignores the bottom property, height must be used, this makes it harder to match the
                //parent size when it has borders.
                switch(slideType)
                {
                case 'left-right':
                    mask.style.right = value == 1? px(-bw): pc((1-value)*100);
                    break;
                case 'right-left':
                    mask.style.left = value == 1? px(-bw): pc((1-value)*100);
                    break;
                case 'bottom-top':
                    mask.style.top = value == 1? px(-bw): pc((1-value)*100);
                    mask.style.height = value == 1? pc(ieFix): pc((value)*100);
                    break;
                case 'top-bottom':
                    mask.style.height = value == 1? pc(ieFix): pc((value)*100);
                    break;
                default:
                    mask.style.height = value == 1? pc(ieFix): pc((value)*100);
                    break;
                }
            }
            else
            {
                //This is the general form to match the size of the parent(including borders).
                switch(slideType)
                {
                case 'left-right':
                    mask.style.right = value == 1? px(-bw): pc((1-value)*100);
                    break;
                case 'right-left':
                    mask.style.left = value == 1? px(-bw): pc((1-value)*100);
                    break;
                case 'bottom-top':
                    mask.style.top = value == 1? px(-bw): pc((1-value)*100);
                    break;
                case 'top-bottom':
                    mask.style.bottom = value == 1? px(-bw): pc((1-value)*100);
                    break;
                default:
                    mask.style.bottom = value == 1? px(-bw): pc((1-value)*100);
                    break;
                }
            }
        }

        /**
        Slides in, then calls the onEnd event.
        */
        this.slideIn = function(p)
        {
            mask.style.display = 'inline-block';
            //if(slideType == 'top-bottom')
            setFor(slideTime, 40, function(value, end){
                setSize(value);
                if(end)
                    p.onEnd();
            });
        }

        //setup initial values for the slide.
        fill(mask);
        mask.style.display = 'none';
        setSize(0);
    }

    /**
    @class Creates a panel that contains elements to display. It handles horizontal and vertical centering.
    */
    var Panel = function(p)
    {
        var mask = createMask({
            width: '100%',
            height: '100%',
            node: p.node,
            style: p.style
        });
        //mask.style.overflow = 'hidden';
        //mask.style.display = 'none';
        mask.style.backgroundColor = 'transparent'
        mask.style.borderColor = 'transparent'

        //Element that clips the content that overflows
        var clipper = p.node.ownerDocument.createElement('div');
        mapObj(clipper.style,{
            width: '100%',
            height: '100%',
            overflow: 'hidden'
        });

        //element to handle vertical centering in non IE browsers.
        var table = p.node.ownerDocument.createElement('div');
        mapObj(table.style,{
            width: '100%',
            height: '100%'
        });

        if(Browser.name != "IE")
        {
            mapObj(table.style,{
                display: 'table'
            });
        }

        //element to be centered vertically.
        var vcenter = p.node.ownerDocument.createElement('div');
        if(Browser.name == 'IE')
        {
            mapObj(vcenter.style,{
                position: 'absolute',
                top: '50%',
                width: '100%'
            });
        }
        else
        {
            mapObj(vcenter.style,{
                display: 'table-cell',
                verticalAlign: 'middle',
                width: '100%'
            });
        }

        //element that actually contains the display content.
        var container = p.node.ownerDocument.createElement('div');
        if(Browser.name == 'IE')
        {
            mapObj(container.style, {
                position: 'relative',
                top: '-50%'
            });
        }

        //configure elements
        mapObj(container.style, {
            textAlign: 'center',
            padding: p.cellPadding,
            color: p.style.color,
            fontFamily: p.style.fontFamily,
            fontSize: p.style.fontSize,
            fontStyle: p.style.fontStyle,
            fontWeight: p.style.fontWeight
        });

        mask.appendChild(clipper);
        clipper.appendChild(table);
        table.appendChild(vcenter);
        vcenter.appendChild(container);

        this.getNode = function()
        {
            return mask;
        }

        this.getContainer = function()
        {
            return container;
        }

        /**
        Show panel and display corners for IE if present
        */
        this.show = function()
        {
            mask.style.display = 'inline-block';
            mask.style.backgroundColor = p.style.backgroundColor;
            mask.style.borderColor = p.style.borderColor;
            if(this.showCorners)
               this.showCorners();
        }
    }

    /**
    @class Generates corners for Internet Explorer.
    It uses two absolute positioned divs (one at the top and one at the bottom)
    which contain the divs that actually draw the corners.
    */
    var Corners = function(p)
    {
        var borderRadius = p.borderRadius;
        var borderWidth = p.borderWidth;
        var backgroundColor = p.backgroundColor;
        var outOffset = p.outOffset || 0;
        var offset = p.offset || 0;
        var zIndex = p.zIndex || 1;
        var divCount = parseInt(borderRadius) - outOffset;
        var innerRadius = divCount - offset;

        if(!borderWidth)
            p.node.style.borderWidth = '0px';

        //div that contains the top corners.
        var upperMask = createMask({
            node: p.node,
            style:{
                top: '0px',
                lineHeight: '0px'
            }
        });
        mapObj(upperMask.style, {
            height: px(divCount),
            width: 'auto',
            left: px(outOffset),
            right: px(outOffset),
            top: px(outOffset),
            zIndex: zIndex
        });

        //div that contains the bottom corners.
        var lowerMask = createMask({
            node: p.node,
            style:{
                bottom: '0px',
                lineHeight: '0px'
            }
        });
        mapObj(lowerMask.style, {
            height: px(divCount),
            width: 'auto',
            left: px(outOffset),
            right: px(outOffset),
            bottom: px(outOffset),
            zIndex: zIndex
        });

        /**
        Creates a div with 1px height with equal borders at both sides and transparent background.
        */
        var createLine = function(node, border)
        {
            var div = node.ownerDocument.createElement('div');
            mapObj(div.style, {
                width: 'auto',
                height: '1px',
                lineHeight: '0px',
                fontSize: '1px',
                margin: '0px',
                overflow: 'hidden',
                borderTop: '0px',
                borderBottom: '0px',
                borderLeft: px(border),
                borderRight: px(border),
                borderColor: backgroundColor,
                borderStyle: 'solid',
                backgroundColor: 'transparent'
            });
            node.appendChild(div);
            return div;
        }

        /**
        Creates a set of lines with different borders in order to clip the actual corners of the element behind.
        */
        var setBorders = function(mask, func)
        {
            for(var i = 0; i < divCount; i++)
            {
                var res = func(i) + offset;
                if (res < 0) res = 0;
                if (res > divCount) res = divCount;
                var line = createLine(mask, res);
                //if(bordes, createLine(line, res);
            }
        }

        /**
        Given the hypotenuse and one side of a triangle, it returns the length of the other side.
        */
        var getY = function(r, x)
        {
            return Math.sqrt((r*r)-(x*x));
        }

        /**
        Given the Nth line of one of the divs that clip the upper corners, it returns border width for that line
        */
        var calc = function(i)
        {
            if(i < offset)
                return divCount;
            var v = i - offset + 1;
            return innerRadius - getY(innerRadius, innerRadius -v);
        }

        //Builds the upper and lower corners.
        setBorders(upperMask, calc);
        setBorders(lowerMask, function(i)
        {
            return calc(divCount - 1 - i);
        });

        this.showCorners = function()
        {
            upperMask.style.display = 'block';
            lowerMask.style.display = 'block';
        }
        this.hideCorners = function()
        {
            upperMask.style.display = 'none';
            lowerMask.style.display = 'none';
        }
    }

    /**
    @class A sequence panel is a panel that displays multiple images and text, with transitions and slide in effects.
    */
    var SequencePanel = function(p)
    {
        var node = p.node;
        var doc = node.ownerDocument;

        var container = doc.createElement('div');
        var children = p.children || [];
        var currentIndex;
        var currentNode;
        var height = p.cellHeight - (parseInt(p.style.borderWidth)*2);

        //Create all the instances of the different components to be used.

        var panel = new Panel({
            node: container,
            style: add(p.style,{
                zIndex: 1,
                width: '100%',
                height: px(height)
            }),
            cellPadding: p.cellPadding
        });
        //panel.getNode().style.height= px(height);

        var slide1 = new Slider({
            node: panel.getNode(),
            style: add(p.style,{
                zIndex: 12,
                backgroundColor: p.slideFirstColor
            }),
            slideTime: p.slideTime,
            slideType: p.slideType
        });

        var slide2 = new Slider({
            node: panel.getNode(),
            style: add(p.style,{
                zIndex: 13,
                backgroundColor: p.slideSecondColor
            }),
            slideTime: p.slideTime,
            slideType: p.slideType
        });

        var mh = new MouseHandler({
            node: panel.getNode(),
            style: add(p.style,{
                zIndex: 5,
                width: '100%',
                height: '100%'
            }),
            mouseoverColor: p.mouseoverColor
        });

        var fader = new Fader({
            node: panel.getNode(),
            transparent: true,
            style: add(p.style,{
                zIndex: 5,
                width: '100%',
                height: '100%'
            })
        });

        /**
        Creates a new child to display, can be an image or text
        */
        var createChild = function(d)
        {
            var c = d.type == 'text'? createText(node, d.src): createImage(node, d.src);
            c.style.display = 'none';
            //c.style.position = 'relative';
            //c.style.top = '-50%';
            mh.setLink(d.link, d.target || p.target);
            panel.getContainer().appendChild(c);
            return c;
        }

        /**
        Show the next element to display, including preloading and transitions.
        */
        var showNext = function()
        {
            var nextIndex = currentNode? currentIndex + 1: 0;
            nextIndex = nextIndex >= children.length? 0: nextIndex;
            var nextChild = children[nextIndex];

            /**
            Transition and switch elements
            */
            var show = function(){
                var nextNode = createChild(nextChild);
                mh.disable();
                fader.transition({
                    time: p.childrenTransitionTime,
                    onChange: function(){
                        if(currentNode)
                            currentNode.parentNode.removeChild(currentNode);
                        nextNode.style.display = 'inline-block';
                    },
                    onEnd: function(){
                        currentNode = nextNode;
                        currentIndex = nextIndex;
                        mh.enable();
                        if(children.length > 1)
                        {
                            setTimeout(function(){
                                showNext();
                            }, p.childrenPauseTime);
                        }
                    }
                });
            }

            //Depending whether it's image or text, the content is preloaded or not.
            if(nextChild.type == 'text')
                show();
            else
            {
                preloadImage({
                    src: nextChild.src,
                    onLoad: show,
                    onError: show
                });
            }
        }

        /**
        Prepare layout and do initial slides and fade in.
        */
        var init = function()
        {
            mapObj(container.style, {
                //overflow: 'hidden',
                position: 'relative',
                width: '100%',
                height: '100%'
            });
            node.appendChild(container);

            mh.disable();

            //Only for IE, setup corner management.
            if(Browser.name == "IE" && Browser.version < 9)
            {
                var br = p.style.borderRadius;
                var bw = p.style.borderWidth;
                var bgc = p.parentBackgroundColor;
                var offset = parseInt(p.style.borderWidth);
                //var q = $(sn); if(q.corner) q.corner('cc:'+p.parentBackgroundColor);
                new Corners({ node: slide1.getNode(), borderRadius: br, backgroundColor:bgc });
                new Corners({ node: slide2.getNode(), borderRadius: br, backgroundColor:bgc });
                new Corners({ node: fader.getNode(), borderRadius: br, backgroundColor:bgc });
                new Corners({ node: mh.getNode(), borderRadius: br, backgroundColor:bgc });

                //the panel uses two sets of corners, in order to create the borders (if they are present)
                //this is the first set
                var c1 = new Corners({
                    node: panel.getNode(),
                    borderRadius: br,
                    backgroundColor: bgc,
                    borderWidth: bw,
                    offset: 0,
                    outOffset: -offset,
                    zIndex: 11
                });
                c1.hideCorners();

                //this is the second set, only used if there are borders.
                var c2;
                if(parseInt(bw) > 0)
                {
                    c2 = new Corners({
                    node: panel.getNode(),
                        borderRadius: br,
                        backgroundColor: p.style.borderColor,
                        borderWidth: bw,
                        offset: offset,
                        outOffset: -offset,
                        zIndex: 10
                    });
                    c2.hideCorners();
                }
                panel.showCorners = function()
                {
                   if(c1) c1.showCorners();
                   if(c2) c2.showCorners();
                }
            }

            /**
            Fade in slides and show initial content.
            */
            var fadeIn = function()
            {
                slide2.fadeIn({
                    color: p.slideSecondColor,
                    time: p.childrenFadeInTime,
                    onEnd: function(){
                        //slide2.remove();
                        slide2 = null;
                        panel.show();
                        slide1.fadeIn({
                            color: p.slideFirstColor,
                            time: p.childrenFadeInTime,
                            onEnd: function(){
                                //slide1.remove();
                                slide1 = null;
                                if(children.length > 0)
                                    showNext();
                            }
                        });
                    }
                });
            };

            //Wait initial delay, then start slides.
            setTimeout(function(){
                slide1.slideIn({
                    onEnd: function(){}
                });
                setTimeout(function(){
                    slide2.slideIn({
                        onEnd: function(){
                            fadeIn();
                        }
                    });
                }, 200);
            }, p.slideInitialDelay);
        }

        init();
    }


    /**
    @class Represents a dashboard of content, with transitions and slide in effects.
    */
    ImageSwitcher = function()
    {
        var globalConf;
        var cellConf = {};

        /**
        Gets a parameter value for a particular cell configuration,
        if the cell has no parameter then returns the global default.
        */
        var getParameter = function(conf, member)
        {
            return (member in conf)? conf[member]: globalConf[member];
        }

        /**
        Gets a set of parameters from a cell's configuration.
        */
        var mapConf = function(obj, conf, members)
        {
            for(var m in members)
            {
                var value = getParameter(conf, members[m]);
                if(value !== undefined)
                    obj[m] = value;
            }
            return obj;
        }

        /**
        Checks values, and setup defaults for the common parameters,
        the ones that can be setup for individual cells or globally.
        */
        var commonCheck = function(values, fillDefaults)
        {
            checkValues(values, {
                fontFamily: {check: isFont, defaults:'arial'},
                fontSize: {check: isSize, defaults: '11px'},
                fontStyle: {},
                fontWeight: {},
                color: {check:isColor},
                backgroundColor: {check:isColor, defaults:'#ffffff'},
                borderColor: {check:isColor, defaults:'gray'},
                borderWidth: {check:isSize},
                borderStyle: {defaults:'solid'},
                borderRadius: {check: isSize, defaults: "5px"},
                mouseoverColor: {check:isColor, defaults:'silver'},
                slideTime: {check: isFinite, defaults: 500},
                slideInitialDelay: {check: isFinite, defaults: 500},
                slideType: {defaults: 'left-right'}, //right-left, top-bottom, bottom-top
                slideFirstColor: {check: isColor, defaults: '#c0e0e0'},
                slideSecondColor: {check: isColor, defaults: '#60e0e0'},
                childrenTransitionTime: {check: isFinite, defaults: 1000},
                childrenFadeInTime: {check: isFinite, defaults: 500},
                childrenPauseTime: {check: isFinite, defaults: 2000 },
                target: {defaults: '_blank'}
            }, fillDefaults);
        }

        /**
        ImageSwitcher-wide configuration, sets up global parameters and default values for individual cells.
        */
        this.config = function(values)
        {
            checkValues(values, {
                node: {mandatory: true},
                rows: {check: isFinite, mandatory: true},
                columns: {check: isFinite, mandatory: true},
                cellPadding: {check: isSize, defaults: '5px'},
                cellSpacing: {check: isSize, defaults: '5px'},
                switcherWidth: {check: isSize},
                switcherHeight: {check: isSize},
                switcherBackgroundColor: {check: isColor, defaults:'transparent'}
            }, true);

            commonCheck(values, true);
            globalConf = values;
        }

        /**
        Configures the contents of a cell
        */
        this.addCell = function(values)
        {
            checkValues(values, {
                row: {check: isFinite, mandatory: true},
                column: {check: isFinite, mandatory: true}
            });

            commonCheck(values);
            cellConf[values.row+'_'+values.column] = values;
        }

        /**
        Starts the ImageSwitcher, nothing is created until this method is called.
        */
        this.start = function()
        {
            var cellHeight = Math.round(parseInt(globalConf.switcherHeight)/globalConf.rows);
            var t = new Table({
                node: globalConf.node,
                rows: globalConf.rows,
                columns: globalConf.columns,
                style: {
                    width:globalConf.switcherWidth,
                    height:globalConf.switcherHeight,
                    backgroundColor: globalConf.switcherBackgroundColor,
                    tableLayout:'fixed',
                    textAlign:'left'
                },
                eachCell: function(i, j, c){
                    var conf = cellConf[i+'_'+j] || {};
                    var s = new SequencePanel({
                        node: c,
                        children: conf.children || [],
                        mouseoverColor: getParameter(conf, 'mouseoverColor'),
                        cellHeight: cellHeight,
                        childrenPauseTime: getParameter(conf, 'childrenPauseTime'),
                        childrenTransitionTime: getParameter(conf, 'childrenTransitionTime'),
                        childrenFadeInTime: getParameter(conf, 'childrenFadeInTime'),
                        cellPadding: getParameter(conf, 'cellPadding'),
                        slideTime: getParameter(conf, 'slideTime'),
                        slideType: getParameter(conf, 'slideType'),
                        slideInitialDelay: getParameter(conf, 'slideInitialDelay'),
                        target: getParameter(conf, 'target'),
                        parentBackgroundColor: globalConf.switcherBackgroundColor,
                        slideFirstColor: getParameter(conf, 'slideFirstColor'),
                        slideSecondColor: getParameter(conf, 'slideSecondColor'),
                        style: mapConf({}, conf, {
                            fontFamily: "fontFamily",
                            fontSize: "fontSize",
                            fontStyle: "fontStyle",
                            fontWeight: "fontWeight",
                            color: "color",
                            backgroundColor: "backgroundColor",
                            borderRadius: "borderRadius",
                            MozBorderRadius: "borderRadius",
                            WebkitBorderRadius: "borderRadius",
                            MsBorderRadius: "borderRadius",
                            borderColor: "borderColor",
                            borderWidth: "borderWidth",
                            borderStyle: "borderStyle"
                        })
                    });
                    c.style.height = cellHeight +'px';
                }
            });
            t.cellSpacing = globalConf.cellSpacing;
        }
    }
};
