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

define(function(require, exports, module) {
    var Transform = require('../core/Transform');

    var usePrefix = !('transform' in window.document.documentElement.style);
    var devicePixelRatio = 2 * (window.devicePixelRatio || 1);
    var MIN_OPACITY = 0.0001;
    var MAX_OPACITY = 0.9999;
    var EPSILON = 1e-5;
    var _zeroZero = [0, 0];

    /**
     * Responsible for committing CSS3 properties to the DOM and providing DOM event hooks
     *  from a provided DOM element. Where Surface's API handles inputs from the developer
     *  from within Samsara, ElementOutput handles the DOM interaction layer.
     *
     *
     * @class DOMOutput
     * @constructor
     * @namespace Core
     * @uses Core.LayoutNode
     * @uses Core.SizeNode
     * @private
     * @param {Node} element document parent of this container
     */
    function DOMOutput() {
        this._cachedSpec = {};
        this._opacityDirty = true;
        this._originDirty = true;
        this._transformDirty = true;
        this._isVisible = true;
    }

    function _round(value, unit){
        return (unit === 1)
            ? Math.round(value)
            : Math.round(value * unit) / unit
    }

    function _formatCSSTransform(transform, unit) {
        var result = 'matrix3d(';
        for (var i = 0; i < 15; i++) {
            if (Math.abs(transform[i]) < EPSILON) transform[i] = 0;
            result += (i === 12 || i === 13)
                ? _round(transform[i], unit) + ','
                : transform[i] + ',';
        }
        return result + transform[15] + ')';
    }

    function _formatCSSOrigin(origin) {
        return (100 * origin[0]) + '% ' + (100 * origin[1]) + '%';
    }

    function _xyNotEquals(a, b) {
        return (a && b) ? (a[0] !== b[0] || a[1] !== b[1]) : a !== b;
    }

    var _setOrigin = usePrefix
        ? function _setOrigin(element, origin) {
            element.style.webkitTransformOrigin = _formatCSSOrigin(origin);
        }
        : function _setOrigin(element, origin) {
            element.style.transformOrigin = _formatCSSOrigin(origin);
        };

    var _setTransform = (usePrefix)
        ? function _setTransform(element, transform, unit) {
            element.style.webkitTransform = _formatCSSTransform(transform, unit);
        }
        : function _setTransform(element, transform, unit) {
            element.style.transform = _formatCSSTransform(transform, unit);
        };

    function _setSize(target, size){
        if (size[0] === true) size[0] = target.offsetWidth;
        else if (size[0] >= 0) target.style.width = size[0] + 'px';

        if (size[1] === true) size[1] = target.offsetHeight;
        else if (size[1] >= 0) target.style.height = size[1] + 'px';
    }

    // pointerEvents logic allows for DOM events to pass through the element when invisible
    function _setOpacity(element, opacity) {
        if (!this._isVisible && opacity > MIN_OPACITY) {
            element.style.pointerEvents = 'auto';
            this._isVisible = true;
        }

        if (opacity > MAX_OPACITY) opacity = MAX_OPACITY;
        else if (opacity < MIN_OPACITY) {
            opacity = MIN_OPACITY;
            if (this._isVisible) {
                element.style.pointerEvents = 'none';
                this._isVisible = false;
            }
        }

        element.style.opacity = opacity;
    }

    DOMOutput.prototype.querySelector = function querySelector(target, selector){
        return target.querySelector(selector);
    };

    DOMOutput.prototype.querySelectorAll = function querySelectorAll(target, selector){
        return target.querySelectorAll(selector);
    };

    DOMOutput.prototype.applyClasses = function applyClasses(target, classList) {
        for (var i = 0; i < classList.length; i++)
            target.classList.add(classList[i]);
    };

    DOMOutput.prototype.applyProperties = function applyProperties(target, properties) {
        for (var key in properties)
            target.style[key] = properties[key];
    };

    DOMOutput.prototype.applyAttributes = function applyAttributes(target, attributes) {
        for (var key in attributes)
            target.setAttribute(key, attributes[key]);
    };

    DOMOutput.prototype.removeClasses = function removeClasses(target, classList) {
        for (var i = 0; i < classList.length; i++)
            target.classList.remove(classList[i]);
    };

    DOMOutput.prototype.removeProperties = function removeProperties(target, properties) {
        for (var key in properties)
            target.style[key] = '';
    };

    DOMOutput.prototype.removeAttributes = function removeAttributes(target, attributes) {
        for (var key in attributes)
            target.removeAttribute(key);
    };

    DOMOutput.prototype.on = function on(target, type, handler) {
        target.addEventListener(type, handler);
    };

    DOMOutput.prototype.off = function off(target, type, handler) {
        target.removeEventListener(type, handler);
    };

    DOMOutput.prototype.applyContent = function applyContent(target, content) {
        if (content instanceof Node) {
            while (target.hasChildNodes()) target.removeChild(target.firstChild);
            target.appendChild(content);
        }
        else target.innerHTML = content;
    };

    DOMOutput.prototype.recallContent = function recallContent(target) {
        var df = document.createDocumentFragment();
        while (target.hasChildNodes()) df.appendChild(target.firstChild);
        return df;
    };

    DOMOutput.prototype.makeVisible = function makeVisible(target){
        target.style.display = '';

        // for true-sized elements, reset height and width
        if (this._cachedSize) {
            if (this._cachedSize[0] === true) target.style.width = 'auto';
            if (this._cachedSize[1] === true) target.style.height = 'auto';
        }
    };

    DOMOutput.prototype.makeInvisible = function makeInvisible(target){
        target.style.display = 'none';
        target.style.opacity = '';
        target.style.width = '';
        target.style.height = '';

        if (usePrefix) {
            target.style.webkitTransform = '';
            target.style.webkitTransformOrigin = '';
        }
        else {
            target.style.transform = '';
            target.style.transformOrigin = '';
        }

        this._cachedSpec = {};
    };

    DOMOutput.prototype.commitLayout = function commitLayout(target, layout) {
        var cache = this._cachedSpec;

        var transform = layout.transform || Transform.identity;
        var opacity = (layout.opacity === undefined) ? 1 : layout.opacity;
        var origin = layout.origin || _zeroZero;

        this._transformDirty = Transform.notEquals(cache.transform, transform);
        this._opacityDirty = this._opacityDirty || (cache.opacity !== opacity);
        this._originDirty = this._originDirty || (origin && _xyNotEquals(cache.origin, origin));

        if (this._opacityDirty) {
            cache.opacity = opacity;
            _setOpacity.call(this, target, opacity);
        }

        if (this._originDirty){
            cache.origin = origin;
            _setOrigin(target, origin);
        }

        if (this._transformDirty) {
            cache.transform = transform;
            _setTransform(target, transform, this.roundToPixel ? 1 : devicePixelRatio);
        }

        this._originDirty = false;
        this._transformDirty = false;
        this._opacityDirty = false;
    };

    DOMOutput.prototype.commitSize = function commitSize(target, size){
        if (size[0] !== true) size[0] = _round(size[0], devicePixelRatio);
        if (size[1] !== true) size[1] = _round(size[1], devicePixelRatio);

        if (_xyNotEquals(this._cachedSpec.size, size)){
            this._cachedSpec.size = size;
            _setSize(target, size);
            return true;
        }
        else return false;
    };

    DOMOutput.prototype.promoteLayer = function (target){
        target.style.willChange = 'transform, opacity';
    };

    DOMOutput.prototype.demoteLayer = function(target) {
        target.style.willChange = 'auto';
    };

    module.exports = DOMOutput;
});