'use strict';

/*@
Ox.SplitPanel <f> SpliPanel Object
    options <o> Options object
        elements <[o]|[]> Array of two or three element objects
            collapsible <b|false> If true, can be collapsed (if outer element)
            collapsed <b|false> If true, is collapsed (if collapsible)
            defaultSize <n|s|"auto"> Default size in px (restorable via reset)
            element <o> Any Ox.Element
                If any element is collapsible or resizable, all elements must
                have an id.
            resettable <b|false> If true, can be resetted (if outer element)
                Note that reset fires on doubleclick, and if the element is also
                collapsible, toggle now fires on singleclick, no longer on click.
                Singleclick happens 250 ms later.
            resizable <b|false> If true, can be resized (if outer element)
            resize <[n]|[]> Min size, optional snappy points, and max size
            size <n|s|"auto"> Size in px (one element must be "auto")
            tooltip <b|s|false> If true, show tooltip, if string, append it
        orientation <s|"horizontal"> orientation ("horizontal" or "vertical")
    self <o> Shared private variable
    ([options[, self]]) -> <o:Ox.Element> SpliPanel Object
        resize <!> resize
            Fires on resize, on both elements being resized
        resizeend <!> resizeend
            Fires on resize, on both elements being resized
        resizepause <!> resizepause
            Fires on resize, on both elements being resized
        toggle <!> toggle
            Fires on collapse or expand, on the element being toggled
@*/

Ox.SplitPanel = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
            .defaults({
                elements: [],
                orientation: 'horizontal'
            })
            .options(options || {})
            .addClass('OxSplitPanel');

    self.defaultSize = self.options.elements.map(function(element) {
        return !Ox.isUndefined(element.defaultSize)
            ? element.defaultSize : element.size;
    });
    self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation];
    self.edges = Ox.UI.EDGES[self.options.orientation];
    self.initialized = false;
    self.length = self.options.elements.length;

    self.$elements = [];
    self.$resizebars = [];
    self.options.elements.forEach(function(element, index) {
        var elementIndices = index == 0 ? [0, 1] : index == 1 ? [1, 0] : [2, 1],
            resizebarIndex = self.$resizebars.length;
        self.options.elements[index] = Ox.extend({
            collapsible: false,
            collapsed: false,
            defaultSize: 'auto',
            resettable: false,
            resizable: false,
            resize: [],
            size: 'auto',
            tooltip: false
        }, element);
        // top and bottom (horizontal) or left and right (vertical)
        self.edges.slice(2).forEach(function(edge) {
            element.element.css(
                edge, (parseInt(element.element.css(edge)) || 0) + 'px'
            );
        });
        if (element.collapsed) {
            // left/right (horizontal) or top/bottom (vertical)
            that.css(self.edges[index == 0 ? 0 : 1], -element.size + 'px');
        }
        self.$elements[index] = element.element.appendTo(that);
        if (element.collapsible || element.resizable) {
            self.$resizebars[resizebarIndex] = Ox.Resizebar({
                collapsed: element.collapsed,
                collapsible: element.collapsible,
                edge: self.edges[index == 0 ? 0 : 1],
                orientation: self.options.orientation == 'horizontal'
                    ? 'vertical' : 'horizontal',
                resettable: element.resettable,
                resizable: element.resizable,
                resize: element.resize,
                size: element.size,
                tooltip: element.tooltip === true ? '' : element.tooltip
            })
            .bindEvent({
                reset: function() {
                    that.resetElement(index);
                },
                resize: function(data) {
                    onResize(elementIndices, data.size);
                    triggerEvents(elementIndices, 'resize', data);
                },
                resizepause: function(data) {
                    triggerEvents(elementIndices, 'resizepause', data);
                },
                resizeend: function(data) {
                    triggerEvents(elementIndices, 'resizeend', data);
                },
                toggle: function(data) {
                    that.toggleElement(index);
                }
            })
            [index == 0 ? 'insertAfter' : 'insertBefore'](self.$elements[index]);
        }
    });

    setSizes();

    function getSize(index) {
        var element = self.options.elements[index];
        return element.size + (element.collapsible || element.resizable);
    }

    function getVisibleSize(index) {
        var element = self.options.elements[index];
        return getSize(index) * !element.collapsed;
    }

    function onResize(elementIndices, size) {
        var dimension = self.dimensions[0],
            edge = self.edges[elementIndices[0] == 0 ? 0 : 1];
        self.options.elements[elementIndices[0]].size = size;
        elementIndices.forEach(function(elementIndex, index) {
            self.$elements[elementIndex].css(
                index == 0 ? dimension : edge,
                (index == 0 ? size : size + 1) + 'px'
            );
        });
    }

    function setSizes(animate) {
        // will animate if animate is truthy and call animate if it's a function
        self.options.elements.forEach(function(element, index) {
            var $resizebar,
                css = {},
                edges = self.edges.slice(0, 2).map(function(edge) {
                     // left/right (horizontal) or top/bottom (vertical)
                     var value = parseInt(self.$elements[index].css(edge));
                     return !self.initialized && value || 0;
                });
            if (element.size != 'auto') {
                // width (horizontal) or height (vertical)
                css[self.dimensions[0]] = element.size + 'px';
            }
            if (index == 0) {
                // left (horizontal) or top (vertical)
                css[self.edges[0]] = edges[0] + 'px';
                // right (horizontal) or bottom (vertical)
                if (element.size == 'auto') {
                    css[self.edges[1]] = getSize(1) + (
                        self.length == 3 ? getVisibleSize(2) : 0
                    ) + 'px';
                }
            } else if (index == 1) {
                // left (horizontal) or top (vertical)
                if (self.options.elements[0].size != 'auto') {
                    css[self.edges[0]] = edges[0] + getSize(0) + 'px';
                } else {
                    css[self.edges[0]] = 'auto'; // fixme: why is this needed?
                }
                // right (horizontal) or bottom (vertical)
                css[self.edges[1]] = (self.length == 3 ? getSize(2) : 0) + 'px';
            } else {
                // left (horizontal) or top (vertical)
                if (element.size == 'auto') {
                    css[self.edges[0]] = getVisibleSize(0) + getSize(1) + 'px';
                } else {
                    css[self.edges[0]] = 'auto'; // fixme: why is this needed?
                }
                // right (horizontal) or bottom (vertical)
                css[self.edges[1]] = edges[1] + 'px';
            }
            if (animate) {
                self.$elements[index].animate(css, 250, function() {
                    index == 0 && Ox.isFunction(animate) && animate();
                });
            } else {
                self.$elements[index].css(css);
            }
            if (element.collapsible || element.resizable) {
                $resizebar = self.$resizebars[
                    index < 2 ? 0 : self.$resizebars.length - 1
                ];
                // left or right (horizontal) or top or bottom (vertical)
                css = Ox.extend(
                    {}, self.edges[index == 0 ? 0 : 1], element.size + 'px'
                );
                if (animate) {
                    $resizebar.animate(css, 250);
                } else {
                    $resizebar.css(css);
                }
                $resizebar.options({size: element.size});
            }
        });
        self.initialized = true;
    }

    function triggerEvents(elementIndices, event, data) {
        elementIndices.forEach(function(elementIndex, index) {
            var $element = self.$elements[elementIndex],
                size = index == 0 ? data.size : $element[self.dimensions[0]]();
            $element.triggerEvent(event, {size: size});
        });
    }

    /*@
    isCollapsed <f> Tests if an outer element is collapsed
        (index) -> <b> True if collapsed
        index <i> The element's index
    @*/
    that.isCollapsed = function(index) {
        return self.options.elements[index].collapsed;
    };

    /*@
    replaceElement <f> Replaces an element
        (index, element) -> <f> replace element
        index <n> The element's index
        element <o> New element
    @*/
    that.replaceElement = function(index, element) {
        // top and bottom (horizontal) or left and right (vertical)
        self.edges.slice(2).forEach(function(edge) {
            element.css(edge, (parseInt(element.css(edge)) || 0) + 'px');
        });
        self.$elements[index] = element;
        self.options.elements[index].element.replaceWith(
            self.options.elements[index].element = element
        );
        setSizes();
        return that;
    };

    /*@
    resetElement <f> Resets an outer element to its initial size
    @*/
    that.resetElement = function(index) {
        var element = self.options.elements[index];
        element.size = self.defaultSize[index];
        setSizes(function() {
            element.element.triggerEvent('resize', {
                size: element.size
            });
            element = self.options.elements[index == 0 ? 1 : index - 1];
            element.element.triggerEvent('resize', {
                size: element.element[self.dimensions[0]]()
            });
        });
        return that;
    };

    /*@
    size <f> Get or set size of an element
        (index) -> <i> Returns size
        (index, size) -> <o> Sets size, returns SplitPanel
        (index, size, callback) -> <o> Sets size with animation, returns SplitPanel
        index <i> The element's index
        size <i> New size, in px
        callback <b|f> Callback function (passing true animates w/o callback)
    @*/
    that.resizeElement = that.size = function(index, size, callback) {
        var element = self.options.elements[index];
        if (arguments.length == 1) {
            return element.element[self.dimensions[0]]()
                * !that.isCollapsed(index);
        } else {
            element.size = size;
            setSizes(callback);
            return that;
        }
    };

    /*@
    toggleElement <f> Toggles collapsed state of an outer element
        (index) -> <o> The SplitPanel
        index <s|i> The element's index
    @*/
    that.toggleElement = function(index) {
        if (self.toggling) {
            return that;
        }
        var element = self.options.elements[index],
            value = parseInt(that.css(self.edges[index == 0 ? 0 : 1]))
                + element.element[self.dimensions[0]]()
                * (element.collapsed ? 1 : -1),
            animate = Ox.extend({}, self.edges[index == 0 ? 0 : 1], value);
        self.toggling = true;
        that.animate(animate, 250, function() {
            element.collapsed = !element.collapsed;
            element.element.triggerEvent('toggle', {
                collapsed: element.collapsed
            });
            self.$resizebars[index < 2 ? 0 : self.$resizebars.length - 1].options({
                collapsed: element.collapsed
            });
            element = self.options.elements[index == 0 ? 1 : index - 1];
            element.element.triggerEvent('resize', {
                size: element.element[self.dimensions[0]]()
            });
            self.toggling = false;
        });
        return that;
    };

    return that;

};