Show:
/* Copyright © 2015-2016 David Valdman */

define(function(require, exports, module) {
    var EventHandler = require('../../events/EventHandler');
    var Stream = require('../../streams/Stream');
    var ResizeStream = require('../../streams/ResizeStream');
    var SizeNode = require('../SizeNode');
    var LayoutNode = require('../LayoutNode');
    var layoutAlgebra = require('../algebras/layout');
    var sizeAlgebra = require('../algebras/size');

    var SIZE_KEYS = SizeNode.KEYS;
    var LAYOUT_KEYS = LayoutNode.KEYS;

    /**
     * A node in the render tree. As such, it wraps a layout or size node,
     *  providing them with an `add` method. By adding nodes, the render tree
     *  is constructed, the leaves of which are `Surfaces`.
     *
     *  @constructor
     *  @class RenderTreeNode
     *  @private
     *  @param object {Object|SizeNode|LayoutNode|Surface|View}
     */
    function RenderTreeNode(object) {
        // layout and size inputs
        this._layout = new EventHandler();
        this._size = new EventHandler();

        // layout and size streams
        this.size = null;
        this.layout = null;

        this.root = null;

        if (object) _set.call(this, object);
    }

    /**
     * Extends the render tree with a new node. Similar to how a tree data structure
     *  is created, but instead of a node with an array of children, children subscribe
     *  to notifications from the parent.
     *
     *  Nodes can be instances of `LayoutNode`, `SizeNode`, or Object literals with
     *  size and layout properties, in which case, appropriate nodes will be created.
     *
     *  This method also takes `Views` (subtrees) and `Surfaces` (leaves).
     *
     * @method add
     * @chainable
     * @param node {Object|SizeNode|LayoutNode|Surface|View} Node
     * @return {RenderTreeNode}
     */
    RenderTreeNode.prototype.add = function add(node) {
        var childNode;

        if (node.constructor === Object){
            // Object literal case
            return _createNodeFromObjectLiteral.call(this, node);
        }
        else if (node._isView){
            // View case
            if (this.root)
                node._node.root = this.root;
            else if (this.tempRoot)
                node._node.tempRoot = this.tempRoot;
            childNode = node;
        }
        else {
            // Node case
            childNode = new RenderTreeNode(node);
            if (this.tempRoot)
                childNode.tempRoot = this.tempRoot;
            else childNode.root = _getRootNode.call(this);
        }

        childNode._layout.subscribe(this.layout || this._layout);
        childNode._size.subscribe(this.size || this._size);

        return childNode;
    };

    function _createNodeFromObjectLiteral(object){
        var sizeKeys = {};
        var layoutKeys = {};

        for (var key in object){
            if (SIZE_KEYS[key]) sizeKeys[key] = object[key];
            else if (LAYOUT_KEYS[key]) layoutKeys[key] = object[key];
        }

        var node = this;
        var needsSize = Object.keys(sizeKeys).length > 0;
        var needsLayout = Object.keys(layoutKeys).length > 0;

        // create extra align node if needed
        if (needsSize && layoutKeys.align){
            var alignNode = new LayoutNode({
                align : layoutKeys.align
            });
            delete layoutKeys.align;
            node = node.add(alignNode);
        }

        // create size node first if needed
        if (needsSize)
            node = node.add(new SizeNode(sizeKeys));

        // create layout node if needed
        if (needsLayout)
            node = node.add(new LayoutNode(layoutKeys));

        return node;
    }

    function _getRootNode(){
        if (this.root) return this.root;
        if (this.tempRoot) return _getRootNode.call(this.tempRoot);
        return this;
    }

    function _set(object) {
        if (object instanceof SizeNode){
            this.size = ResizeStream.lift(
                function SGSizeAlgebra (objectSpec, parentSize){
                    if (!parentSize) return false;
                    return (objectSpec)
                        ? sizeAlgebra(objectSpec, parentSize)
                        : parentSize;
                },
                [object, this._size]
            );
            return;
        }
        else if (object instanceof LayoutNode){
            this.layout = Stream.lift(
                function SGLayoutAlgebra (objectSpec, parentSpec, size){
                    if (!parentSpec || !size) return false;
                    return (objectSpec)
                        ? layoutAlgebra(objectSpec, parentSpec, size)
                        : parentSpec;
                },
                [object, this._layout, this._size]
            );
            return;
        }

        // object is a leaf node
        object._size.subscribe(this._size);
        object._layout.subscribe(this._layout);
        object._getRoot = _getRootNode.bind(this);
    }

    module.exports = RenderTreeNode;
});