/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp = {
    classname: "NrdpBridge",
    _init: false,
    _syncComplete: false,
    _isError: false,
    _classes: { },
    _backchannels: [],
    _MAX_STACK_SIZE: 16384, // When we log the stack trace of an exception

    get isReady() { return this._init && this._syncComplete && !this._isError; },
    get debug() { return this._syncData.debug; },
    get options() { return this._syncData.options; },
    get uiQueryString() { return this.system.uiQueryString; },
    set uiQueryString(query) { nrdp.system.uiQueryString = query; },
    get bootURL() { return this.system.bootURL; },
    get started() { return this._syncData.started; },
    get trustStoreHash() { return this._syncData.trustStoreHash; },

    get READY() { return "READY"; },

    get COMPLETE() { return "COMPLETE"; },
    get NETWORK_ERROR() { return "NETWORK_ERROR"; },
    get ACTION_ID() { return "ACTION_ID"; },
    get ERROR() { return "ERROR"; },

    get hasSuspendHandler() { return this._syncData.hasSuspendHandler; },
    set hasSuspendHandler(v) { this._syncData.hasSuspendHandler=v; nrdp._setProperty(null, "hasSuspendHandler", v, true); },

    _mapProperty: function _mapProperty(object, property) {
        var classname = object;
        if (typeof classname !== "string") {
            classname = classname.classname;
            if(!classname)
                return undefined;
        }
        var classinfo = this._classes[classname];
        if(!classinfo)
            return undefined;

        if (typeof property === "string")
            return classinfo.properties.byName[property];
        return classinfo.properties.byIndex[property];
    },
    _mapMethod: function _mapMethod(object, method) {
        var classname = object;
        if (typeof classname !== "string") {
            classname = classname.classname;
            if(!classname) {
                nrdp.log.error("Could not find object classname " + object, "NRDP_SCRIPT");
                return undefined;
            }
        }
        var classinfo = this._classes[classname];
        if(!classinfo)
            return undefined;

        if (typeof method === "string")
            return classinfo.methods.byName[method];
        return classinfo.methods.byIndex[method];
    },


    _tryBackchannel: function _tryBackchannel(chan) {
        if (chan.init()) {
            nrdp.log.trace(" using backchannel " + chan.name);
            nrdp._backchannel = chan;
            return true;
        } else {
            return false;
        }
    },

    setupBackchannel: function setupBackchannel() {
        var i;
        if (nrdp._backchannel) {
            return;
        }
        nrdp.log.trace("nrdp.setupBackchannel called" + (typeof window !== "undefined" ? (", window.location = " + window.location) : ""));
        for (i = 0; i < nrdp._backchannels.length; i++) {
            if (typeof nrdp._backchannels[i] !== "object")
                continue;
            if (nrdp._tryBackchannel(nrdp._backchannels[i]))
                return;
        }
    },

    shutdownBackchannel: function shutdownBackchannel() {
        if (!nrdp._backchannel) {
            return;
        }
        nrdp.log.trace("nrdp.shutdownBackchannel called", "NRDP_SCRIPT");
        this._sendSyncdEvent(function() { this._callEventListeners(this, { type: "shutdown" } ); }, this);
        nrdp.storage.flush();
        if(nrdp._backchannel.shutdown)
            nrdp._backchannel.shutdown();
    },

    _sendSyncdEvent: function _sendSyncdEvent(fn, that, event) { //for gibbon to hook
        fn.call(that, event);
    },

    hookInit: function hookInit(fn) {
        nrdp._hookInitFunction = fn;
    },

    _sendInitEvent: function _sendInitEvent() {
        var sendInit = this._sendSyncdEvent.bind(this, function() { this._callEventListeners(nrdp, {type: "init", status: this.READY}, true); }, this);
        if (this._hookInitFunction) {
            this._hookInitFunction(sendInit);
            delete this._hookInitFunction;
        } else {
            sendInit();
        }
    },

    init: function init() {
        if (nrdp._init) {
            if (nrdp._syncComplete)
                nrdp._sendInitEvent();
            return;
        }
        nrdp._init = true;
        nrdp.log.trace("nrdp.init called", "NRDP_SCRIPT");
        nrdp.setupBackchannel();
        if (nrdp._syncComplete) {
            nrdp._sendInitEvent();
        }
    },

    now: function now() {
        if (nrdp._backchannel && nrdp._backchannel.now)
            return nrdp._backchannel.now();
        return Date.now();
    },

    exit: function exit() {
        nrdp._invoke(null, "quit");
    },

    ping: function ping(cb) {
        var sentTime = nrdp.mono();
        nrdp._fn("ping", {}, function(receivedTime) {
            var eventTime = nrdp.mono();
            nrdp.log.warn('Bridge ping : JS -> bridge = ' + (receivedTime - sentTime) +
                ' : bridge -> JS = ' + (eventTime - receivedTime) +
                ' : roundtrip = ' + (eventTime - sentTime));
            if (cb)
                cb(sentTime, receivedTime, eventTime);
        });
    },

    assert: function assert(a, message) {
        var s;
        if (!a) {
            message = 'JS ASSERTION FAILED' + (message ? ' : ' + message : '');
            try { throw new Error(); }
            catch(e) { s = e.stack; }
            nrdp.log.fatal(message + (s ? '\n' + s : '' ), "NRDP_SCRIPT");
            nrdp._invoke(null, "assert");
        }
    },

    mono: function mono() {
        if (nrdp._backchannel && nrdp._backchannel.mono)
            return nrdp._backchannel.mono();
        return undefined;
    },

    drmTime: function drmTime() {
        if (nrdp._backchannel && nrdp._backchannel.drmTime)
            return nrdp._backchannel.drmTime();
        return Math.floor(Date.now() / 1000.0);
    },

    atob: function atob(s, returnTypedArray) {
        if (nrdp._backchannel && nrdp._backchannel.atob)
            return nrdp._backchannel.atob(s, returnTypedArray);
        return undefined;
    },

    btoa: function btoa(s, urlSafe, returnTypedArray) {
        if (nrdp._backchannel && nrdp._backchannel.btoa)
            return nrdp._backchannel.btoa(s, urlSafe, returnTypedArray);
        return undefined;
    },

    atoutf8: function atoutf8(s) {
        if (nrdp._backchannel && nrdp._backchannel.atoutf8)
            return nrdp._backchannel.atoutf8(s);
        return undefined;
    },

    utf8toa: function utf8toa(s) {
        if (nrdp._backchannel && nrdp._backchannel.utf8toa)
            return nrdp._backchannel.utf8toa(s);
        return undefined;
    },

    compress: function compress(data, type, binary) {
        if (nrdp._backchannel && nrdp._backchannel.compress)
            return nrdp._backchannel.compress(data, type, binary);
        return undefined;
    },

    uncompress: function compress(data, type, returnTypedArray) {
        if (nrdp._backchannel && nrdp._backchannel.compress)
            return nrdp._backchannel.uncompress(data, type, returnTypedArray);
        return undefined;
    },

    random: function random(data) {
        if (nrdp._backchannel && nrdp._backchannel.random)
            return nrdp._backchannel.random(data);
    },

    gctag: function gctag(name) {
        if (nrdp._backchannel && nrdp._backchannel.gctag)
            return nrdp._backchannel.gctag(name);
        return {};
    },

    getConfigList: function getConfigList() {
        this._callEventListeners(this, {type:'config', list: this._syncData.configList});
    },

    config: {},

    setConfigData: function setConfigData(file, data) {
        nrdp.config[file] = data;
        nrdp._invoke(null, "setConfigData", {name: file, data: data});
    },

    _path: "nrdp",
    addEventListener: function addEventListener(evt, listener) {
        if(evt == "suspendChanged")
            this.hasSuspendHandler = true;
        nrdp._addEventListener(this, evt, listener);
    },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    _findObject: function _findObject(name) {
        var obj = nrdp;
        if (name != "nrdp") {
            var bits = name.split('.');
            for(var i = 1; obj && i < bits.length; ++i) {  // skip beginning "nrdp"
                obj = obj[bits[i]];
            }
        }
        return obj;
    },

    _setProperty: function _setProperty(subobj, prop, val, isSync) {
        if (!nrdp._backchannel) {
            nrdp.log.info("unhandled _setProperty " + subobj + " " + prop, "NRDP_SCRIPT");
        }
        var objName = subobj ? "nrdp." + subobj : "nrdp";
        if (!isSync) {
            var obj = nrdp._findObject(objName);
            if (!obj) {
                nrdp.log.error("could not find object " + objName + " to set property " + prop + " on", "NRDP_SCRIPT");
                return;
            }
            if (!obj._setProperty_current)
                obj._setProperty_current = {};
            if (obj._setProperty_current[prop]) {
                if (!obj._setProperty_pending)
                    obj._setProperty_pending = {};
                obj._setProperty_pending[prop] = {
                    object: subobj,
                    property: prop,
                    value: val
                };
                return;
            } else {
                obj._setProperty_current[prop] = true;
            }
        }
        nrdp._backchannel.setProperty(subobj, prop, val);
    },

    _construct: function _construct(subobj, method, args) {
        if (!nrdp._backchannel) {
            nrdp.log.info("unhandled _construct " + subobj + " " + method, "NRDP_SCRIPT");
        }
        var obj = nrdp._backchannel.construct(subobj, method, args);
        if (obj)
            nrdp._sendSyncdEvent(nrdp._gotEvent, nrdp, obj);
        return obj;
    },

    _invoke: function _invoke(subobj, method, args) {
        if (!nrdp._backchannel) {
            nrdp.log.info("unhandled _invoke " + subobj + " " + method, "NRDP_SCRIPT");
        }
        nrdp._backchannel.invoke(subobj, method, args);
    },

    _addEventListener: function _addEventListener(object, eventType, listener) {
        if (!listener)
            return false;
        var listeners, path;
        if (typeof object === "string") {
            if (!nrdp._oldStyleListeners)
                nrdp._oldStyleListeners = {};
            listeners = nrdp._oldStyleListeners;
            eventType = object + "." + eventType;
            path = eventType;
        } else {
            if (!object._eventListeners)
                object._eventListeners = {};
            listeners = object._eventListeners;
            path = object._path + "." + eventType;
        }
        nrdp.log.trace("add listener for " + path, "NRDP_SCRIPT");
        if (!listeners[eventType])
            listeners[eventType] = [];
        listeners[eventType].push(listener);
        return true;
    },

    _removeEventListener: function _removeEventListener(object, eventType, listener) {
        if (!listener)
            return false;

        var myListeners, parent, path;
        if (typeof object === "string") {
            eventType = object + "." + eventType;
            parent = nrdp._oldStyleListeners;
            path = eventType;
        } else if (typeof object === "object") {
            parent = object._eventListeners;
            path = object._path + "." + eventType;
        }

        if (!parent)
            return false;
        myListeners = parent[eventType];
        if (!myListeners)
            return false;
        var index = myListeners.indexOf(listener);

        if (index >= 0) {
            if (myListeners.length == 1)
                delete parent[eventType];
            else
                myListeners.splice(index, 1);
        }

        var len = parent[eventType] ? parent[eventType].length : 0;
        nrdp.log.trace("remove listener for " + path + ", length is " + len, "NRDP_SCRIPT");
        return true;
    },

    _hasEventListener: function _hasEventListener(object, eventType) {
        if (typeof object === "string") {
            eventType = object + "." + eventType;
            return (nrdp._oldStyleListeners && nrdp._oldStyleListeners[eventType] && nrdp._oldStyleListeners[eventType].length);
        } else {
            return (object._eventListeners &&
                    object._eventListeners[eventType] &&
                    object._eventListeners[eventType].length);
        }
    },

    _callEventListeners: function _callEventListeners(object, event, remove) {
        if (!this._syncComplete && event.type != "fatalerror") {
            return;
        }
        var myListeners;
        var path;
        if (typeof object === "string") {
            var eventType = object + "." + event.type;
            if (!nrdp._oldStyleListeners || !nrdp._oldStyleListeners[eventType])
                return;
            if (remove) {
                myListeners = nrdp._oldStyleListeners[eventType];
                delete nrdp._oldStyleListeners[eventType];
            } else {
                myListeners = nrdp._oldStyleListeners[eventType].slice(0);
            }
            path = object;
        } else {
            if (!object._eventListeners || !object._eventListeners[event.type])
                return;
            if (remove) {
                myListeners = object._eventListeners[event.type];
                delete object._eventListeners[event.type];
            } else {
                myListeners = object._eventListeners[event.type].slice(0);
            }
            path = object._path;
        }
        for (var i = 0; i < myListeners.length; i++) {
            var listener = myListeners[i];
            if (listener) {
                nrdp.log.trace("call listener for " + path + "." + event.type, "NRDP_SCRIPT");
                listener(event);
            }
        }
    },

    _urlEncode: function _urlEncode(obj) {
        var str = "";
        for (var p in obj) {
            if (str) str += "&";
            str += p + "=" + encodeURIComponent(obj[p]);
        }
        return str;
    },

    _gotEvent: function _gotEvent(event) {
        var start, evt;
        if (nrdp._syncData && nrdp.debug && nrdp._syncComplete) {
            start = nrdp.mono();
            if (start - event.time > 50) {
                nrdp.log.debug("JS event waited " + (start - event.time) + "ms: " + JSON.stringify(event, function(k, v) {
                    return (k === 'data' || k === 'value') ? '[redacted]' : v;
                }), "NRDP_SCRIPT");
            }
        }
        try {
            if (event.type == "Event") {
                this._gotEventEvent(event);
            } else if (event.type == "ClassSync") {
                this._gotClassSyncEvent(event);
            } else if (event.type == "PropertyUpdate" || event.type == "Construct") {
                this._gotPropertyUpdateEvent(event);
            } else if (event.type == "SetProperty") {
                this._gotSetPropertyEvent(event);
            } else if (event.type == "Method") {
                this._gotMethodEvent(event);
            } else if (event.type == "EventSourceError") {
                nrdp._isError = true;
                nrdp.log.info("EventSource went away, sending fatalerror", "NRDP_SCRIPT");
                evt = {
                    type: "fatalerror"
                };
                this._callEventListeners(this, evt);
            } else {
                nrdp.log.error("unhandled eventsource type " + event.type, "NRDP_SCRIPT");
            }
        } catch (e) {
            var tags = {},
                length;
            if (typeof e !== "string") {
                for (var n in e) {
                    if (e.hasOwnProperty(n)) {
                        tags[n] = e[n];
                    }
                }
            }
            // chrome defines this
            if (!tags.stack && e.stack) {
                tags.stack = e.stack;
            }
            if (typeof tags.stack == 'string') {
                length = tags.stack.length;
                if (length && length > nrdp._MAX_STACK_SIZE) {
                    tags.stack = tags.stack.substr(0, nrdp._MAX_STACK_SIZE/2) +
                    '\n...[stack truncated, it was ' + length + ' characters]...\n' +
                    tags.stack.substr(-nrdp._MAX_STACK_SIZE/2);
                }
            }
            nrdp.log.error("JAVASCRIPT EXCEPTION: " + e.toString(), "NRDP_SCRIPT", undefined, tags);
            evt = {
                type: "exception",
                exception: e
            };
            this._callEventListeners(this, evt);
            if (nrdp.options.enable_js_assert) nrdp.assert();
        }
        if (start !== undefined) {
            var end = nrdp.mono();
            if (nrdp._syncData && (end - start > nrdp.options.js_delay)) {
                nrdp.log.warn("handling JS event took " + (end - start) + "ms: " + JSON.stringify(event, function(k, v) {
                    return k === 'data' ? '[redacted]' : v;
                }), "NRDP_SCRIPT");
            }
        }
    },

    _gotEventEvent: function _gotEventEvent(event) {
        if (!event.object) {
            if (event.name == "ObjectSyncComplete") {
                this._syncComplete = true;
                nrdp.log.trace("object sync is complete", "NRDP_SCRIPT");
                if (this._init) {
                    this._sendInitEvent();
                }
            } else if (event.name == "factoryReset") {
                var evt = {
                    type: event.name
                };
                this._callEventListeners(this, evt);
            }
        } else {
            var obj = this._findObject(event.object);
            if (!obj || typeof obj["_handleEvent"] !== "function" || !obj["_handleEvent"](event))
                nrdp.log.warn("unhandled event " + event.object + " " + event.name, "NRDP_SCRIPT");
        }
    },

    _gotClassSyncEvent: function _gotClassSyncEvent(event) {
        var classinfo = { methods: { byIndex: [], byName: {} }, properties: { byIndex: [], byName: {} } };
        for(var method in event.value.methods) {
            classinfo.methods.byIndex.push(event.value.methods[method]);
            classinfo.methods.byName[event.value.methods[method]] = method;
        }
        for(var property in event.value.properties) {
            classinfo.properties.byIndex.push(event.value.properties[property]);
            classinfo.properties.byName[event.value.properties[property]] = property;
        }
        this._classes[event.name] = classinfo;
    },

    _gotPropertyUpdateEvent: function _gotPropertyUpdateEvent(event) {
        var obj = this._findObject(event.object);
        if (!obj) {
            nrdp.log.error("Could not find object " + event.object + " for sync data", "NRDP_SCRIPT");
            return;
        }
        var fn;
        if (typeof obj["_updateProperty"] === "function")
            fn = obj["_updateProperty"];
        else if (!obj._syncData)
            obj._syncData = {};
        for (var property in event.properties) {
            if (typeof property !== "string") {
                var propertyName = this._mapProperty(obj, property);
                if(!propertyName) {
                    nrdp.log.error("Could not map PropertyUpdate: " + event.object + " " +  property, "NRDP_SCRIPT");
                    continue;
                }
                property = propertyName;
            }
            if (typeof event.properties[property] !== "function") {
                if (fn)
                    fn.call(obj, property, event.properties[property]);
                else
                    obj._syncData[property] = event.properties[property];
            }
        }
    },

    _gotSetPropertyEvent: function _gotSetPropertyEvent(event) {
        var obj = this._findObject(event.object);
        if (obj) {
            var property = event.property;
            if (typeof property !== "string") {
                var propertyName = this._mapProperty(obj, property);
                if(!propertyName) {
                    nrdp.log.error("Could not map SetProperty: " + event.object + " " +  property, "NRDP_SCRIPT");
                    return;
                }
                property = propertyName;
            }
            if(obj._setProperty_current)
                delete obj._setProperty_current[property];
            if (obj._setProperty_pending && obj._setProperty_pending[property]) {
                var newset = obj._setProperty_pending[property];
                delete obj._setProperty_pending[property];
                nrdp._setProperty(newset.object, newset.property, newset.value);
            }
        }
    },

    _gotMethodEvent: function _gotMethodEvent(event) {
        // this only happens for an invalid argument error
        var method = event.method;
        if (typeof method !== "string") {
            var methodName = this._mapMethod(event.object, method);
            if(!methodName) {
                nrdp.log.error("Could not map Method: " + event.object + " " +  method, "NRDP_SCRIPT");
                return;
            }
            method = methodName;
        }
        var evt = {
            type: "invalidargument",
            object: event.object,
            method: method,
            argument: event.returnValue
        };
        this._callEventListeners(this, evt);
    },

    _nextIdx: 1,
    _cbs: {},
    _fn: function _fn(name, args, cb) {
        if (!args) args = {};
        args.id = this._nextIdx++;
        this._cbs[args.id] = cb;
        nrdp._invoke(null, name, args);
    },

    parseXml: function parseXml(xml, cb) {
        if (cb) {
            nrdp._fn("parseXML", {xml: xml}, cb);
        } else if (nrdp._backchannel && nrdp._backchannel.parseXML) {
            return nrdp._backchannel.parseXML(xml);
        }
    },

    parseJSON: function parseJSON(json, cb) {
        if (cb) {
            nrdp._fn("parseJSON", {json: json}, cb);
        } else if (nrdp._backchannel && nrdp._backchannel.parseJSON) {
            return nrdp._backchannel.parseJSON(json);
        }
    },

    setTrustStore: function setTrustStore(trustStore) {
        nrdp._invoke(null, "setTrustStore", {trustStore: trustStore});
    },

    setServerTime: function setServerTime(time) {
        nrdp._invoke(null, "setServerTime", {time: time});
    },

    setTestDriverIpAddress: function setTestDriverIpAddress(address) {
        nrdp._invoke(null, "setTestDriverIpAddress", {address: address});
    },

    get suspended() { return this._syncData.suspended; },
    requestSuspend: function requestSuspend(reason) {
        nrdp._invoke(null, "requestSuspend", {reason: reason});
    },
    suspendComplete: function suspendComplete() {
        nrdp._fn("suspendComplete");
    },

    _fixXml: function _fixXml(obj) {
        var children = obj["$children"];
        var len, child;
        if (!children || !children.length)
            return;
        len = children.length;
        for (var i = 0; i < len; i++) {
            child = children[i];
            child["$parent"] = obj;
            child["$sibling"] = children[i + 1];
            var name = child["$name"];
            if (name !== undefined && obj[name] === undefined)
                obj[name] = child;
            this._fixXml(child);
        }
    },

    // If these return something that is not undefined, that value is passed to the callback, if any.

    _eventBrokers:
    {
        'commandReceived':
            function(event) {
                this._callEventListeners(this, {type:'command', parameters: event.data});
            },
        'parsedXML':
            function(event) {
                if (event.data.success)
                    this._fixXml(event.data.object);
                return {data: event.data};
            },
        'suspendChanged':
            function(event) {
                this._callEventListeners(this, {type:'suspendChanged', data: event.data});
            },
        'requestedResume':
            function(event) {
                return {data: event.data};
            },
        'parsedJSON':
            function(event) {
                return {data: event.data.object};
            },
        'pong':
            function(event) {
                return {data: event.data.received};
            }
    },

    _handleEvent: function _handleEvent(event) {
        var broker = this._eventBrokers[event.name],
            result,
            id,
            cb;
        if (!broker)
            return false;
        result = broker.call(this, event);
        if (result) {
            id = event.data.id;
            cb = this._cbs[id];
            try {
                if (typeof cb === 'function')
                    cb(result.data);
            } finally {
                delete this._cbs[id];
            }
        }
        return true;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.audio = {
    classname: "AudioBridge",
    get codecs() { return this._syncData.codecs; },
    get urls() { return this._syncData.urls; },
    get capability() { return this._syncData.capability; },

    load: function load(URL, cb) {
        nrdp.audio._fn("load", { URL: URL }, cb);
    },

    unload: function unload(URL, cb) {
        nrdp.audio._fn("unload", { URL: URL }, cb);
    },

    unloadAll: function unloadAll(cb) {
        nrdp.audio._fn("unloadAll", undefined, cb);
    },

    play: function play(URL, volume, cb, fadeInMs, fadeOutMs) {
        if( typeof fadeInMs !== 'undefined' && typeof fadeOutMs !== 'undefined' ){
            nrdp.audio._fn("play", { URL: URL, volume: volume, fadeInMs: fadeInMs, fadeOutMs: fadeOutMs }, cb);
        }else {
            nrdp.audio._fn("play", { URL: URL, volume: volume, fadeInMs: 0, fadeOutMs: 0 }, cb);
        }
    },

    stop: function stop(URL, cb, fadeOutMs) {
        if( typeof fadeDurationInMs !== 'undefined' ){
            nrdp.audio._fn("stop", { URL: URL, fadeOutMs: fadeOutMs }, cb);
        } else {
            nrdp.audio._fn("stop", { URL: URL, fadeOutMs: 0 }, cb);
        } 
    },

    _nextIdx: 1,
    _cbs: {},
    _fn: function _fn(name, args, cb) {
        if (!args) args = {};
        args.id = this._nextIdx++;
        if (cb)
            this._cbs[args.id] = cb;
        nrdp._invoke("audio", name, args);
    },

    _handleEvent: function _handleEvent(event) {
        if (event.data && event.data.id) {
            if (typeof this._cbs[event.data.id] == "function") {
                var args = [event.data.success, event.data.size];
                if (event.data.hasOwnProperty("URL"))
                    args.unshift(event.data.URL);
                this._cbs[event.data.id].apply(undefined, args);
                delete this._cbs[event.data.id];
            }
        } else {
            return false;
        }
        return true;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.device = {
    classname: "DeviceBridge",
    _path: "device",
    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get VOLUME_NONE() { return 0; },
    get VOLUME_SPEAKER() { return 1; },
    get VOLUME_STREAM() { return 2; },

    // Drm types
    get PLAY_READY() { return 0; },
    get SESSION_DH1() { return 1; },
    get WIDEVINE_CEF() { return 2; },

    get drmType() { return this._syncData.capability.drmType; },

    get currentViewMode() { return this._syncData.currentViewMode; },
    get availableViewModes() { return this._syncData.availableViewModes; },
    get softwareVersion() { return this._syncData.softwareVersion; },
    get certificationVersion() { return this._syncData.certificationVersion; },
    get deviceModel() { return this._syncData.deviceModel; },
    get ESNPrefix() { return this._syncData.ESNPrefix; },
    get SDKVersion() { return this._syncData.SDKVersion; },
    get ESN() { return this._syncData.ESN; },
    get language() { return this._syncData.language; },
    get friendlyName() { return this._syncData.friendlyName; },
    get startupTags() { return this._syncData.startupTags; },
    get volume() { return this._syncData.volume; },
    set volume(v) { nrdp._setProperty("device", "volume", v); },
    get volumeControlType() { return this._syncData.volumeControlType; },
    get volumeStep() { return this._syncData.volumeStep; },
    get mute() { return this._syncData.mute; },
    set mute(m) { nrdp._setProperty("device", "mute", m); },

    get capability() { return this._syncData.capability; },
    get videoOutput() { return this._syncData.videoOutput; },
    get supportedVideoOutput() { return this._syncData.supportedVideoOutput; },
    get activeVideoOutput() { return this._syncData.activeVideoOutput; },
    get dnslist() { return this._syncData.dnslist; },
    get iflist() { return this._syncData.iflist; },
    get ipversions() { return this._syncData.ipversions; },

    get UIVersion() { return this._syncData.UIVersion; },
    set UIVersion(version) { nrdp._setProperty("device", "UIVersion", version); },
    addLibrary: function addLibrary(name, version, component) {
        nrdp._invoke("device", "addLibrary", { name: name, version: version, component: component });
    },
    setUIVersion: function setUIVersion(version) { nrdp._setProperty("device", "UIVersion", version); },

    get UILanguages() {
        return this._syncData.UILanguages;
    },
    set UILanguages(langs) {
        langs = Array.isArray(langs) ? langs : [];
        var val = langs.join(",");
        nrdp._setProperty("device", "UILanguages", val);
    },

    isScreensaverOn: function isScreensaverOn() { return this._syncData.screensaverOn; },

    factoryReset: function factoryReset(cb) {
        nrdp.storage._clearAll();
        nrdp.device._fn("factoryReset", null, cb);
    },
    setViewMode: function setViewMode(viewMode) {
        nrdp._invoke("device", "setViewMode", {viewMode : viewMode});
    },

    setRegistered: function setRegistered(value) {
        nrdp._setProperty("device", "registered", !!value);
    },
    get registered() { return this._syncData.registered; },

    getSignatures: function getSignatures(cb) {
        nrdp.device._fn("getSignatures", null, cb);
    },

    getSystemValue: function getSystemValue(key, cb) {
        nrdp.device._fn("getSystemValue", {key : key}, cb);
    },

    getDisplaySize: function getDisplaySize(cb) {
        nrdp.device._fn("getDisplaySize", null, cb);
    },

    _nextIdx: 1,
    _cbs: {},
    _fn: function _fn(name, args, cb) {
        if (!args) args = {};
        args.idx = this._nextIdx++;
        if (cb)
            this._cbs[args.idx] = cb;
        nrdp._invoke("device", name, args);
    },
    _handleEvent: function _handleEvent(event) {
        if (event.data && event.data.idx) {
            if (typeof this._cbs[event.data.idx] == "function") {
                this._cbs[event.data.idx](event.data.data);
                delete this._cbs[event.data.idx];
            }
        } else {
            return false;
        }
        return true;
    },

    _syncData: {},
    _updateProperty: function _updateProperty(property, value) {
        var evt;
        if (nrdp.isReady) {
            if (property == "screensaverOn") {
                evt = {
                    type: "screensaverchange"
                };
            } else if (property == "capability") {
                evt = {
                    type: "capabilitychange",
                    old: this.capability
                };
            } else if (property == "videoOutput") {
                evt = {
                    type: "videooutputchange",
                    old: this.videoOutput
                };
            } else if (property == "language") {
                evt = {
                    type: "languagechange",
                    old: this.language
                };
            } else if (property == "currentViewMode") {
                evt = {
                    type: "viewmodechange"
                };
            } else if (property == "iflist" ) {
                evt = {
                        type: "networkchange"
                    };
            } else if (property.match(/^volume/) || property == "mute") {
                evt = {
                    type: "volumechange",
                    oldvolume: this.volume,
                    oldmute: this.mute
                };
            }
        }

        this._syncData[property] = value;

        if (evt) {
            nrdp._callEventListeners(this, evt);
        }
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.instrumentation =
{
    classname:          "InstrumentationBridge",
    _path:              "instrumentation",
    addEventListener:   function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener:function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    setParams:          function setParams( enabled, events ){nrdp._invoke("instrumentation","setParams",{"enabled":enabled,"events":events});},

    get ON              (){ return 0; },
    get SWITCHED        (){ return 1; },
    get TEST            (){ return 2; },
    get DEBUG           (){ return 3; },

    get verbose         (){ return this._syncData.verbose; },

    generateEvent:      function generateEvent( category , name , value ){nrdp._invoke("instrumentation","event",{"category":category,"name":name,"value":value});},
    startInterval:      function startInterval( category , name , value , reset ){nrdp._invoke("instrumentation","intervalStart",{"category":category,"name":name,"value":value,"reset":reset});},
    incIntervalCounter: function incIntervalCounter( category , name , counter , increment ){nrdp._invoke("instrumentation","intervalCount",{"category":category,"name":name,"counter":counter,"increment":increment});},
    tagInterval:        function tagInterval( category , name , value ){nrdp._invoke("instrumentation","intervalTag",{"category":category,"name":name,"value":value});},
    endInterval:        function endInterval( category , name , value ){nrdp._invoke("instrumentation","intervalEnd",{"category":category,"name":name,"value":value});},
    cancelInterval:     function cancelInterval( category , name ){nrdp._invoke("instrumentation","intervalCancel",{"category":category,"name":name});},
    stashOn:            function stashOn(){nrdp._invoke("instrumentation","stash",{"on":true});},
    stashOff:           function stashOff(){nrdp._invoke("instrumentation","stash",{"on":false});},
    popStash:           function popStash(cb)
                        {
                            var idx = this._nextIdx++;
                            this._cbs[idx] = cb;
                            nrdp._invoke("instrumentation","popStash",{"idx":idx});
                        },

    _nextIdx:           1,
    _cbs:               {},
    _handleEvent:       function _handleEvent(event)
                        {
                            if (event.data && event.data.idx)
                            {
                                if (typeof this._cbs[event.data.idx] == "function")
                                {
                                    this._cbs[event.data.idx](event.data.events);
                                }
                                delete this._cbs[event.data.idx];
                            }
                            else if ( event.name == "verboseChanged" )
                            {
                                nrdp._callEventListeners(this,{type:event.name});
                            }
                            else
                            {
                                return false;
                            }
                            return true;
                        }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp, nrdp_platform */

nrdp.log = {
    classname: "LogBridge",
    _path: "log",
    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get UIContext() { return this._syncData.UIContext; },
    set UIContext(s) { nrdp._setProperty("log","UIContext",s); },

    get appid() { return this._syncData.appid; },

    get areas() { return this._syncData.areas; },

    get aseCodes() { return this._syncData.aseCodes; },

    get errorCodes() { return this._syncData.errorCodes; },

    get haveTracing() { return this._syncData.haveTracing; },

    get level() { return this._syncData.level; },
    set level(e) { nrdp._setProperty("log", "level", e); },

    get levels() { return this._syncData.levels; },

    get sessionid() { return this._syncData.sessionid; },

    get traceAreas() { return this._syncData.traceAreas; },
    set traceAreas(e) { nrdp._setProperty("log", "traceAreas", e); },

    //get xid() { return this._syncData.xid; },

    flush: function flush() {
        nrdp._invoke("log", "flush");
    },

    _traceAreaMap: null,

    _log: function _log(level, args) {
        var msg = args[0],
            area = args[1],
            type = args[2],
            tags = args[3],
            critical = args[4],
            sendtoAppboot = args[5]?args[5]:false,
            tam,
            logLevel;

        if (nrdp._syncComplete && !nrdp._isError) {
            if (level == "trace" && !nrdp.debug && !this.haveTracing)
                return;

            logLevel = this.levels[level];

            tam = this._traceAreaMap;
            if (tam === null) {
                this._traceAreaMap = tam = {};
                this.areas.forEach(function(x) {
                    tam[x] = x;
                });
            }
            area = tam[area || 'UI_SCRIPT'];

            // compat with cadmium, could get an exception object passed in here
            if (typeof area === "undefined" && (typeof args[1] === "object" && args[1] !== null) && /^\w*Error$/.test(args[1].name)) {
                if (!tags)
                    tags = {};
                tags["exception"] = args[1].message || ('' + args[1]);
                tags["stack"] = args[1].stack;
            }

            nrdp._invoke("log", "log", {
                logLevel: logLevel,
                msg: msg,
                traceArea: area,
                type: type,
                tags: tags,
                critical: critical,
                sendtoappboot: sendtoAppboot
            });

            if (!critical && ( type || (nrdp.log.level > nrdp.log.levels.trace && logLevel >= nrdp.log.level ))) {
                nrdp._callEventListeners(this, {
                    'type': 'logMsgsReady',
                    'data': [{
                        'monotime': nrdp.mono(),
                        'area': area,
                        'level': logLevel,
                        'msg': msg,
                        'tags': tags,
                        'logtype': type,
                        'threadname': "UI_THREAD",
                        'threadid': "1",
                        'critical': false
                    }]
                });
            }

        } else {
            if (args.length != 1)
                msg = args[1] + ": " + args[0];
            this.console(msg);
        }
    },

    console: function console(msg) {
        if (nrdp._backchannel && nrdp._backchannel.console)
            nrdp._backchannel.console(msg);
        else if (typeof nrdp_platform !== "undefined" && nrdp_platform.console) //nrdp
            nrdp_platform.console(msg);
        else if (typeof window !== "undefined" && window.console.log) //browser
            window.console.log(msg);
    },
    debug: function debug() { nrdp.log._log("debug", arguments); },
    info: function info() { nrdp.log._log("info", arguments); },
    warn: function warn() { nrdp.log._log("warn", arguments); },
    error: function error() { nrdp.log._log("error", arguments); },
    fatal: function fatal() { nrdp.log._log("fatal", arguments); },
    trace: function trace() { nrdp.log._log("trace", arguments); },

    resetAppID: function resetAppID(newId) { 
        nrdp.log.error("Resetting Appid:" + nrdp.log.appid );
        nrdp._invoke("log", "resetAppID", {appid: newId}); 
    },
    resetSessionID: function resetSessionID() { nrdp._invoke("log", "resetSessionID"); },

    getLogMessages: function getLogMessages() { nrdp._invoke("log", "getLogMessages"); },

    getCriticalMessages: function getCriticalMessages() { nrdp._invoke("log", "getCriticalMessages"); },

    deleteCriticalMessages: function deleteCriticalMessages(criticalMessages) { nrdp._invoke("log", "deleteCriticalMessages", {criticalMessages: criticalMessages}); },

    _syncData: {},
    _updateProperty: function _updateProperty(property, value) {
        var evt;
        if (nrdp.isReady) {
            if (property == "sessionid") {
                evt = {
                    type: "sessionIDChanged",
                    data: value
                };
            }
        }

        this._syncData[property] = value;

        if (evt) {
            nrdp._callEventListeners(this, evt);
        }
    },

    _handleEvent: function _handleEvent(event) {
        var evt;
        if ( event.name == "logMsgsReady") {
            evt = { data : event.data };
            evt.type = event.name;
            nrdp._callEventListeners(this, evt);
        } else if ( event.name == "criticalMsgsReady") {
            evt = { data : event.data };
            evt.type = event.name;
            nrdp._callEventListeners(this, evt);
        } else if (event.name == "logflush") {
            evt = { name: "logflush", type: "logflush"};
            nrdp._callEventListeners(this, evt);
        } else if (event.name == "appIdResetComplete") {
            evt = { type: "appIdResetComplete", data : event.data};
            nrdp._callEventListeners(this, evt);
        } else {
            return false;
        }

        return true;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.player = {
    classname: "PlayerBridge",
    _path: "player",
    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get bufferPoolSize() { return this._syncData.bufferPoolSize; },

    get currentAudioTrack() { return this._syncData.currentAudioTrack; },
    set currentAudioTrack(trackid) { nrdp._setProperty("player", "currentAudioTrack", trackid); },

    get displayAspectRatio() { return this._syncData.displayAspectRatio; },

    get duration() { return this._syncData.duration; },

    get gopSize() { return this._syncData.gopSize; },

    get maxStreamingBuffer() { return this._syncData.maxStreamingBuffer; },

    get networkProfile() { return this._syncData.networkProfile; },
    set networkProfile(t) { nrdp._setProperty("player", "networkProfile", t); },

    get state() { return this._syncData.state; },

    // IAdaptiveStreamingPlayer state
    get OPENING() { return 0; },
    get PLAYING() { return 1; },
    get PAUSED() { return 2; },
    get STOPPED() { return 3; },
    get CLOSED() { return 4; },

    // NetworkProfile
    get WIRED() { return 0; },
    get WIFI() { return 1; },
    get MOBILE() { return 2; },

    // MediaType
    get MEDIA_UNKNOWN() { return -1; },
    get MEDIA_AUDIO() { return 0; },
    get MEDIA_VIDEO() { return 1; },
    get MEDIA_TEXT() { return 2; },

    // AudioTrackType
    get UNKNOWN_AUDIO() { return -1; },
    get PRIMARY_AUDIO() { return 0; },
    get COMMENTARY_AUDIO() { return 1; },
    get ASSISTIVE_AUDIO() { return 2; },

    addManifest: function addManifest(movieId, manifest) {
        nrdp._invoke("player", "addManifest", {movieId: movieId, manifest: manifest});
    },

    bringVideoToFront: function bringVideoToFront() { nrdp._invoke("player", "bringVideoToFront"); },

    cacheFlush: function cacheFlush(args) {
        nrdp._invoke("player", "cacheFlush", args);
    },

    cacheList: function cacheList(args) {
        nrdp._invoke("player", "cacheList", args);
    },

    cachePrepare: function cachePrepare(drmOnly, manifest, videoType, audioTrack) {
        var args = {
            'drmOnly': drmOnly,
            'manifest': manifest,
            'videoType': videoType,
            'audioTrack': audioTrack
        };
        nrdp._invoke("player", "cachePrepare", args);
    },

    cacheSetSize: function cacheSetSize(maxItems) {
        var args = {'maxItems' : maxItems};
        nrdp._invoke("player", "cacheSetSize", args);
    },

    close: function close() { nrdp._invoke("player", "close"); },

    externalIpAddressChange: function externalIpAddressChange(ipaddr) {
        nrdp._invoke("player", "externalIpAddressChange", {ipaddr: ipaddr});
    },

    getBufferRange: function getBufferRange() { nrdp._invoke("player", "getBufferRange"); },

    obtainPlaybackStat: function obtainPlaybackStat() { nrdp._invoke("player", "obtainPlaybackStat"); },
    obtainStreamingStat: function obtainStreamingStat() { nrdp._invoke("player", "obtainStreamingStat"); },

    open: function open(args) {
        nrdp._invoke("player", "open", args);
    },

    pause: function pause() { nrdp._invoke("player", "pause"); },

    play: function play(pts) {
        var args = {};
        if (pts != undefined && pts != null)
            args.pts = pts;
        nrdp._invoke("player", "play", args);
    },

    provideLicense: function provideLicense(license) { nrdp._invoke("player", "provideLicense", {license: license}); },

    sendVideoToBack: function sendVideoToBack() { nrdp._invoke("player", "sendVideoToBack"); },

    setStreamingBuffer: function setStreamingBuffer(powerSaving, maxBufferLen, minBufferLen) {
        nrdp._invoke("player", "setStreamingBuffer",
                     {powerSaving: powerSaving,
                      maxBufferLen: maxBufferLen,
                      minBufferLen: minBufferLen});
    },

    setVideoBitrateRanges: function setVideoBitrateRanges(ranges) {
        nrdp._invoke("player", "setVideoBitrateRanges", {ranges: ranges});
    },

    setVideoWindow: function setVideoWindow(x, y, width, height, transitionDuration) {
        var args = {
            x: x,
            y: y,
            width: width,
            height: height,
            transitionDuration: transitionDuration
        };
        nrdp._invoke("player", "setVideoWindow", args);
    },

    skip: function skip(ms) { nrdp._invoke("player", "skip", {pts:ms}); },

    stop: function stop() { nrdp._invoke("player", "stop"); },

    swim: function swim(pts, absolute, fuzz, allowRebuffer) {
        var args = { pts: pts };
        if (typeof absolute == "boolean")
            args.absolute = absolute;
        if (typeof fuzz == "number" && !isNaN(fuzz))
            args.fuzz = fuzz;
        if (typeof allowRebuffer == "boolean")
            args.allowRebuffer = allowRebuffer;
        nrdp._invoke("player", "swim", args);
    },

    unpause: function unpause() { nrdp._invoke("player", "unpause"); },

    // player volume
    // getVolume is returned in an event type "volume"
    getVolume : function(cb) {
        nrdp._invoke("player", "getVolume");
    },

    setVolume : function(volume, transition) {
        nrdp._invoke("player", "setVolume",
                     {targetVolume: volume, transitionDuration: transition});
    },

    _handleEvent: function _handleEvent(event) {
        for (var prop in event.data._propups) {
            this._syncData[prop] = event.data._propups[prop];
        }
        if (event.name == "IASPlayer" || event.name == "PlaybackReporter") {
            event.data.time = event.time;
            nrdp._callEventListeners(this, event.data);
            return true;
        } else {
            return false;
        }
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.drmsystem = {
    // properties
    get supportSecureStop() { return this._syncData.supportSecureStop; },
    get supportStorageDeletion() { return this._syncData.supportStorageDeletion; },

   /*
    * secure stop JS APIs
    */

    getSecureStopIds: function(cb)
    {
        nrdp.drmsystem._fn("getSecureStopIds", {}, cb);
    },

    getSecureStop: function(sessionid, cb)
    {
        var args = {'sessionid': sessionid};
        nrdp.drmsystem._fn("getSecureStop", args, cb);
    },

    commitSecureStop: function(sessionid)
    {
        var args = {'sessionid': sessionid};
        nrdp.drmsystem._fn("commitSecureStop", args);
    },

    resetSecureStops: function()
    {
        nrdp.drmsystem._fn("resetSecureStops");
    },

    useSecureStop: function(enable)
    {
        var args = {'enable': enable};
        nrdp.drmsystem._fn("useSecureStop", args);
    },

    /*
     * delete drm license store / key store JS APIs
     */

    deleteDrmStore: function(cb)
    {
      nrdp.drmsystem._fn("deleteDrmStore", {}, cb);
    },

    deleteKeyStore: function(cb)
    {
      nrdp.drmsystem._fn("deleteKeyStore", {}, cb);
    },

    getDrmStoreHash: function(cb)
    {
        nrdp.drmsystem._fn("getDrmStoreHash", {}, cb);
    },

    getKeyStoreHash: function(cb)
    {
        nrdp.drmsystem._fn("getKeyStoreHash", {}, cb);
    },

    _fn: function(name, params, cb)
    {
        if (!params) params = {};
        params.idx = this._nextIdx++;
        if (cb)
            this._cbs[params.idx] = cb;
        nrdp._invoke("drmsystem", name, params);
        return params.idx;
    },

    _handleEvent: function(event)
    {
        var cb, idx;

        if (event.name != "result")
            return false;

        idx = event.data.idx;
        if (typeof this._cbs[idx] == "function")
        {
            cb = this._cbs[idx];
            delete event.data.idx;
            cb(event.data);
        }
        delete this._cbs[idx];

        return true;
    },

    _nextIdx: 1,
    _cbs: {}
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.storage = {
    classname: "StorageBridge",
    _path: "storage",
    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get NO_DEVICE_ACCOUNT() { return "NDAKADN"; },

    get size() { return this._syncData.data ? JSON.stringify(this._syncData.data).length : 0; },

    get flushCount() { return this._syncData.flushCount; },

    get secureStoreSize() { return this._syncData.secureStoreSize; },

    get transientData() { return this._syncData.transientData; },
    set transientData(d) {
        this._syncData.transientData = d;
        // no need to _setProperty here, it will be done as part of flush() on page exit
    },

    length: function length(dak) {
        var data = nrdp.storage._getData(dak);
        var count = 0;
        for (var key in data) {
            if (data[key] != undefined)
                count++;
        }
        return count;
    },

    key: function key(dak, n) {
        var data = nrdp.storage._getData(dak);
        for (var key in data) {
            if (data[key] == undefined)
                continue;
            if (n == 0)
                return key;
            n--;
        }
        return undefined;
    },

    _normalizeKey: function _normalizeKey(key) {
        if (key === undefined)
            return "undefined";
        else if (key === null)
            return "null";
        else
            return key.toString();
    },

    getItem: function getItem(dak, key) {
        var stor = nrdp.storage;
        var data = stor._getData(dak);
        return data[stor._normalizeKey(key)];
    },

    setItem: function setItem(dak, key, value) {
        var stor = nrdp.storage;
        var data = stor._getData(dak);
        key = stor._normalizeKey(key);
        data[key] = value;
        nrdp._invoke("storage", "setItem", {dak: dak, key: key, value: value});
    },

    removeItem: function removeItem(dak, key) {
        var stor = nrdp.storage;
        var data = stor._getData(dak);
        key = stor._normalizeKey(key);
        if (data.hasOwnProperty(key))
            delete data[key];
        nrdp._invoke("storage", "removeItem", {dak: dak, key: key});
    },

    clear: function clear(dak) {
        if (nrdp.storage._syncData.data)
            delete nrdp.storage._syncData.data[dak];
        nrdp._invoke("storage", "clear", {dak: dak});
    },

    _clearAll: function _clearAll() {
        nrdp.storage._syncData = {
            data: {}
        };
        nrdp._invoke("storage", "clearAll");
    },

    disk: {
        get size() { return nrdp.storage._syncData.diskStoreSize; },
        getSize: function getSize(cb) {
            nrdp.storage._fn("diskStoreGetSize", {}, cb);
        },
        create: function create(dak, key, value, cb) {
            nrdp.storage._fn("diskStoreCreate", {dak:dak, key:key, value:value}, cb);
        },
        append: function append(dak, key, value, cb) {
            nrdp.storage._fn("diskStoreAppend", {dak:dak, key:key, value:value}, cb);
        },
        remove: function remove(dak, key, cb) {
            nrdp.storage._fn("diskStoreRemove", {dak:dak, key:key}, cb);
        },
        read: function read(dak, key, begin, end, cb) {
            nrdp.storage._fn("diskStoreRead", {dak:dak, key:key, begin:begin, end:end}, cb);
        }
    },

    _getData: function _getData(dak) {
        if (!this._syncData)
            this._syncData = { data: {} };
        else if (!this._syncData.data)
            this._syncData.data = {};
        if (!this._syncData.data[dak])
            this._syncData.data[dak] = {};
        return this._syncData.data[dak];
    },

    setPersistentData: function setPersistentData(dak, key, data, deflate, cb) {
        nrdp.storage._fn("setPersistentData",
                         {dak:dak, key:key, data:data, deflate:deflate}, cb);
    },
    unsetPersistentData: function unsetPersistentData(dak, key, cb) {
        nrdp.storage._fn("unsetPersistentData",
                         {dak:dak, key:key}, cb);
    },
    getPersistentData: function getPersistentData(dak, key, inflate, cb) {
        nrdp.storage._fn("getPersistentData",
                         {dak:dak, key:key, inflate:inflate}, cb);
    },

    getFlushCount: function getFlushCount() {
        nrdp._invoke("storage", "getFlushCount");
    },

    flush: function flush() {
        nrdp._setProperty("storage", "transientData", nrdp.storage._syncData.transientData, true);
        nrdp._invoke("storage", "flush");
    },

    getUsedSecureStoreSize: function getUsedSecureStoreSize() {
        return nrdp._invoke("storage", "getUsedSecureStoreSize");
    },

    _nextIdx: 1,
    _cbs: {},
    _fn: function _fn(name, args, cb) {
        if (!args) args = {};
        args.idx = this._nextIdx++;
        this._cbs[args.idx] = cb;
        nrdp._invoke("storage", name, args);
    },

    _handleEvent: function _handleEvent(event) {
        if (event.data && event.data.idx) {
            if (typeof this._cbs[event.data.idx] == "function") {
                this._cbs[event.data.idx](event.data);
            }
            delete this._cbs[event.data.idx];
        } else if (event.name == "overbudget") {
            event.data.type = event.name;
            nrdp._callEventListeners(this, event.data);
        } else if (event.name == "flushCount") {
            event.data.type = event.name;
            nrdp._callEventListeners(this, event.data);
        } else {
            return false;
        }
        return true;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.webcrypto = {
    _nextIdx: 1,
    _cbs: {},

    // properties
    get authenticationType() { return this._syncData.authenticationType; },

    _fn: function _fn(name, params, cb)
    {
        if (!params) params = {};
        params.idx = this._nextIdx++;
        this._cbs[params.idx] = cb;
        params.logNoArgs = true;
        nrdp._invoke("webcrypto", name, params);
        return params.idx;
    },

    _handleEvent: function _handleEvent(event)
    {
        var cb, idx;

        if (event.name != "result")
        {
            return false;
        }

        idx = event.data.idx;

        if (typeof this._cbs[idx] == "function")
        {
            cb = this._cbs[idx];
            delete event.data.idx;
            cb(event.data);
        }
        delete this._cbs[idx];

        return true;
    },

    // keyformat constants: matches IWebCrypto::KeyFormat
    get RAW() { return 0; },
    get PKCS8() { return 1; },
    get SPKI() { return 2; },
    get JWK() { return 3; },

    // keyusage contants: matches IWebCrypto::KeyUsage
    get ENCRYPT() { return 0x01; },
    get DECRYPT() { return 0x02; },
    get SIGN() { return 0x04; },
    get VERIFY() { return 0x08; },
    get DERIVE() { return 0x10; },
    get WRAP() { return 0x20; },
    get UNWRAP() { return 0x40; },

    // keytype constants: matches IWebCrypto::KeyType
    get SECRET() { return 0; },
    get PUBLIC() { return 1; },
    get PRIVATE() { return 2; },

    // keywrap method: matches IWebCrypto::JweEncMethod
    get A128GCM() { return 0; },
    get A256GCM() { return 1; },

    setBinary: function setBinary(binary, cb)
    {
        return nrdp.webcrypto._fn("setBinary", {binary: binary}, cb);
    },

    // Algorithm objects
    // Valid algorithm names are: HMAC, AES-CBC, AES-GCM, AES-CTR, RSAES-PKCS1-v1_5,
    // RSASSA-PKCS1-v1_5, RSA-OAEP
    // Valid hash names are:
    // SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
    // var hashparams = {
    //       hash: {
    //          name: string // hashname
    //       }
    // };
    // var rsakeyparams = {
    //       modulusLength: integer,
    //       publicExponent: string
    // };
    // var symkeyparams = {
    //       length: integer // key length in bits
    // };
    // var algorithm = {
    //       name: string, // algorithm name
    //       params: hashparams || rsakeyparams || symkeyparams
    // };

    // Key information
    // When a key is returned in a result, it will have the following properties:
    //  handle (integer)
    //  type (KeyType constants above)
    //  extractable (bool)
    //  usage (array of 0 or more KeyUsage constants above)
    //  algorithm (an algorithm object as described above)

    // import a key
    // @param data: The raw data of the key. This can either be Base64 encoded or a
    //              a DataBuffer.
    // @param keyformat: A keyformat constant (RAW, PKCS8, SPKI, JWK)
    // @param algorithm: An object describing the key algorithm
    // @param extractable: Whether or not the raw keying material may be extracted by app
    // @param keyusage: An array of constants indicating how the key may be used
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property with key information
    importKey: function importKey(data, keyformat, algorithm, extractable, keyusage,
                                  cb)
    {
        return nrdp.webcrypto._fn(
            "importKey", {data: data, keyformat: keyformat, algorithm: algorithm,
                          extractable: extractable, usage: keyusage}, cb);
    },

    // export a key
    // @param key: The keyhandle to export
    // @param keyformat: The keyformat to export the key to
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a property "data" with the raw key data
    exportKey: function exportKey(key, keyformat, cb)
    {
        return nrdp.webcrypto._fn("exportKey", {key: key, keyformat: keyformat}, cb);
    },

    // get key info
    // @param key: The keyhandle to get info on
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property with key information.
    getKeyInfo: function getKeyInfo(key, cb)
    {
        return nrdp.webcrypto._fn("getKeyInfo", {key: key}, cb);
    },

    // Compute message digest
    // @param data: The raw data to hash. Either Base64 encoded or a DataBuffer
    // @param hashalgorithm: The algorithm for hashing
    //                       (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512)
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the hash
    digest: function digest(data, hashalgorithm, cb)
    {
        return nrdp.webcrypto._fn(
            "digest", {data: data, algorithm: hashalgorithm}, cb);
    },

    // AES-128-CBC encrypt data
    // @param key: The keyhandle (returned from importKey) for key to encrypt with
    // @param init: Initialization vector for encryption. Either Base64 or a DataBuffer
    // @param data: The raw data to encrypt. Either Base64 encoded or a DataBuffer
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will haven a "data" property containing the encrypted
    //   buffer (Base64 encoded)
    aesEncrypt: function aesEncrypt(key, init, data, cb)
    {
        return nrdp.webcrypto._fn(
            "aesCrypt", {key: key, init: init, data: data, operation: 0}, cb);
    },

    // AES-128-CBC decrypt data
    // @param key: The keyhandle (returned from importKey) for key to decrypt with
    // @param init: Initialization vector for encryption. Either Base64 or a DataBuffer
    // @param data: The raw data to decrypt. Either Base64 encoded or a DataBuffer
    // @param cb: Continuation function(event):
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the decrypted
    //   buffer (Base64 encoded)
    aesDecrypt: function aesDecrypt(key, init, data, cb)
    {
        return nrdp.webcrypto._fn(
            "aesCrypt", {key: key, init: init, data: data, operation: 1}, cb);
    },

    // AES unwrap - RFC 3394
    // @param data: The wrapped data
    // @param wrappedAlgorithm: The algorithm for the wrapped key
    // @param wrappingKey: The handle to the key used to unwrap
    // @param usage: The usage for the unwrapped key
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property with key information
    aesUnwrap: function aesUnwrap(data, wrappedAlgorithm, wrappingKey, usage, cb)
    {
        return nrdp.webcrypto._fn(
            "aesUnwrap", {data: data, algorithm: wrappedAlgorithm, key: wrappingKey,
                          usage: usage}, cb);
    },

    // Generate a symmetric key
    // This method generates a single random key and places it in the key store.
    // @param algorithm In. The algorithm used to generate the key.
    // @param extractable In. Whether the key should be marked as extractable
    // @param keyusage: An array of constants indicating how the key may be used
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property with key information.
    symKeyGen: function symKeyGen(algorithm, extractable, keyusage, cb)
    {
        return nrdp.webcrypto._fn(
            "symKeyGen", {algorithm: algorithm, extractable: extractable,
                          usage: keyusage}, cb);
    },

    // Generate a RSA public/private key pair
    // @param algorithm: The algorithm object describing how the key will be generated
    // @param extractable: Whether or not the raw keying material may be extracted by app
    // @param keyusage: An array of constants indicating how the key may be used
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have "publickey" and "privatekey" properties each
    //   containing key information.
    rsaKeyGen: function rsaKeyGen(algorithm, extractable, keyusage, cb)
    {
        return nrdp.webcrypto._fn(
            "rsaKeyGen", {algorithm: algorithm, extractable: extractable,
                          usage: keyusage}, cb);
    },

    // RSAES-PKCS1-v1_5 encrypt data
    // @param key: The keyhandle (returned from rsaKeyGen) for key to encrypt with
    // @param data: The raw data to encrypt. Either Base64 encoded or a DataBuffer
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the encrypted
    //   buffer (Base64 encoded)
    rsaEncrypt: function rsaEncrypt(key, data, cb)
    {
        return nrdp.webcrypto._fn(
            "rsaCrypt", {key: key, data: data, operation: 0}, cb);
    },

    // RSAES-PKCS1-v1_5 decrypt data
    // @param key: The keyhandle (returned from rsaKeyGen) for key to decrypt with
    // @param data: The raw data to decrypt. Either Base64 encoded or a DataBuffer
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the decrypted
    //   buffer (Base64 encoded)
    rsaDecrypt: function rsaDecrypt(key, data, cb)
    {
        return nrdp.webcrypto._fn(
            "rsaCrypt", {key: key, data: data, operation: 1}, cb);
    },

    // RSA sign a block of data
    // @param key: The keyhandle (returned from rsaKeyGen) for key to sign with
    // @param data: The raw data to sign. Either Base64 encoded or a DataBuffer
    // @param hashalgorithm: The algorithm for hashing
    //                       (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512)
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the rsa
    //   signature (Base64 encoded)
    rsaSign: function rsaSign(key, data, hashalgorithm, cb)
    {
        return nrdp.webcrypto._fn(
            "rsaSign", {key: key, data: data, algorithm: hashalgorithm}, cb);
    },

    // RSA verify a block of data
    // @param key: The keyhandle (returned from rsaKeyGen) for key to verify with
    // @param data: The raw data to compute the signature of. Either Base64 encoded or a
    //              DataBuffer
    // @param hashalgorithm: The algorithm for hashing
    //                       (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512)
    // @param signature: The signature to verify against
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "verified" property which will be true if the
    //   computed signature matches the passed in signature
    rsaVerify: function rsaVerify(key, data, hashalgorithm, signature, cb)
    {
        return nrdp.webcrypto._fn(
            "rsaVerify", {key: key, data: data, algorithm: hashalgorithm,
                          signature: signature}, cb);
    },

    // Generate a Diffie-Hellman public/private key pair
    // This method computes DH public/private key pair
    // @param algorithm In. The full details about the key gen algorithm, including
    //     the prime and generator values
    // @param extractable In. Whether or not the raw key material may be exported
    // @param keyUsage In. The allowed usages of the keys
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have "publickey" and "privatekey" properties each
    //   containing key information.
    dhKeyGen: function dhKeyGen(algorithm, extractable, keyusage, cb)
    {
        return nrdp.webcrypto._fn(
            "dhKeyGen", {algorithm: algorithm, extractable: extractable,
                         usage: keyusage}, cb);
    },

    // Derive a shared private key
    // This method computes a shared private key using a baseKey produced by
    // dhKeyGen() plus the public key from the remote peer who has previously
    // obtained the public baseKey.
    // @param basekey In. The handle of the key that started the DH exchange,
    //   produced by a call to dhKeyGen
    // @param peerkey In. The raw public key received from the remote peer, base64-encoded
    // @param algorithm In. The full details about the algorithm to be associated with
    //   the derived key
    // @param extractable In. Whether or not the raw key material of the derived
    //   key may be exported
    // @param keyUsage In. The allowed usages of the derived key
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property with key information.
    dhDerive: function dhDerive(basekey, peerkey, algorithm, extractable, keyusage, cb)
    {
        return nrdp.webcrypto._fn(
            "dhDerive", {basekey: basekey, peerkey: peerkey, algorithm: algorithm,
                         extractable: extractable, usage: keyusage}, cb);
    },

    nflxDhDerive: function nflxDhDerive(basekey, peerKeyData, derivationKey, cb)
    {
        return nrdp.webcrypto._fn(
            "nflxDhDerive", {basekey: basekey, peerkey: peerKeyData, derivationKey: derivationKey}, cb);
    },

    // Compute the HMAC signature of the data
    // @param key: The keyhandle for key to HMAC with
    // @param data: The raw data to HMAC. Either Base64 encoded or a DataBuffer
    // @param hashalgorithm: The algorithm for hashing
    //                       (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512)
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the
    //   HMAC (Base64 encoded)
    hmac: function hmac(key, data, hashalgorithm, cb)
    {
        return nrdp.webcrypto._fn(
            "hmac", {key: key, data: data, algorithm: hashalgorithm}, cb);
    },

    // Verify an HMAC signature
    // @param key: The keyhandle for key to HMAC with
    // @param data: The raw data to HMAC. Either Base64 encoded or a DataBuffer
    // @param hashalgorithm: The algorithm for hashing
    //                       (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512)
    // @param signature: The signature to verify against
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "verified" property which will be true if the
    //   computed signature matches the passed in signature
    hmacVerify: function hmacVerify(key, data, hashalgorithm, signature, cb)
    {
        return nrdp.webcrypto._fn(
            "hmacVerify", {key: key, data: data, algorithm: hashalgorithm,
                           signature: signature}, cb);
    },

    // Unwrap a JWE-wrapped key
    // @param data: The base64-encoded wrapped key formatted to the JWE spec
    // @param key: The keyhandle for key to decrypt with
    // @param algorithm In. In case the unwrapped JDK does not have the 'alg'
    //                      field inside it, use this value, otherwise ignore
    // @param extractable: Whether or not the raw keying material may be extracted by app
    // @param keyusage: An array of constants indicating how the key may be used
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "key" property containing key information
    unwrapJwe: function unwrapJwe(data, key, algorithm, extractable, keyusage, cb)
    {
        return nrdp.webcrypto._fn(
            "unwrapJwe", {data: data, key: key, algorithm: algorithm,
                          extractable: extractable, usage: keyusage}, cb);
    },

    //* JWE-wrap an existing key
    // This method wraps an existing key in the keystore according to the rules
    // in draft-ietf-jose-json-web-encryption-08, using an existing wrapping key
    // also in the keystore. The result is a base-64 encoded JWE Compact
    // Serialization described above.
    // @param key In. The handle of the key to use in during the key wrap process.
    // @param keytowrap In. The handle of the key to be wrapped
    // @param algorithm In. The encryption algorithm to be applied to the
    //    Content Master Key (CMK) during the wrapping process. This must be
    //    consistent with the algorithm associated with wrappingKeyHandle.
    //    Only RSA-OAEP and AES-KW algorithms are supported.
    // @param method In. The keywrap method to wrap with.
    //    For AES-KW, only A128GCM is supported,
    //    while RSA-OAEP supports A128GCM and A256GCM.
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, result will have a "data" property containing the wrapped key
    wrapJwe: function wrapJwe(key, keytowrap, algorithm, method, cb)
    {
        return nrdp.webcrypto._fn(
            "wrapJwe", {key: key, keytowrap: keytowrap, algorithm: algorithm,
                        meth: method}, cb);
    },


    // Get a key by name
    // @param name. In. string
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.
    //   On success, and if the key was found, result will have a "key" property
    //   containing the keyInfo (with the name) for the key. If the key was not
    //   found, success will still be true, but key will be undefined.

    getKeyByName: function getKeysByName(name, cb)
    {
        return nrdp.webcrypto._fn(
            "getKeyByName", {name: name}, cb);
    },

    // Given an array of keys, they are persisted so that they will be
    // restored later with the same handles.
    // @param keys: In. array of key objects
    // @param cb: Continuation function(event)
    //   Event named "result" will have a property "success" indicating success.

    persistKeys: function persistKeys(keys, cb)
    {
        return nrdp.webcrypto._fn(
            "persistKeys", {keys: keys}, cb);
    },

    deleteKey: function deleteKey(key, cb)
    {
        return nrdp.webcrypto._fn(
            "deleteKey", {key: key}, cb);
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.websocket = {
    _nextWebSocket: 1,
    _websockets: {},

    _nextIdx: 1,
    _cbs: [],
    _fn: function _fn(name, params, cb) {
        if (!params) params = {};
        params.idx = this._nextIdx++;
        this._cbs[params.idx] = cb;
        nrdp._invoke("websocket", name, params);
        return params.idx;
    },

    _handleEvent: function _handleEvent(event) {
        var that;
        if (event.name === "result") {
            if(!event.data || !event.data.idx) {
                return false;
            }

            if (typeof this._cbs[event.data.idx] === "function") {
                this._cbs[event.data.idx](event.data);
            }
            delete this._cbs[event.data.idx];
        } else if (event.name == "open") {
            that = this._websockets[event.data.websocket];
            if (!that) return false;
            that._protocol = event.data.protocol;
            that._url = event.data.url;
            that._readyState = that.OPEN;
            if(typeof that.onopen === "function") {
                nrdp.log.trace("ws: calling " + that._websocket + " onopen()", "NRDP_SCRIPT");
                that.onopen();
            }
        } else if (event.name == "message") {
            that = this._websockets[event.data.websocket];
            if (!that) return false;
            if(that.readyState === that.OPEN) {
                if(typeof that.onmessage === "function") {
                    nrdp.log.trace("ws: calling " + that._websocket + " onmessage()", "NRDP_SCRIPT");
                    that.onmessage(event.data);
                }
            }
        } else if (event.name == "error") {
            that = this._websockets[event.data.websocket];
            if (!that) return false;
            that._readyState = that.CLOSING;
            if(typeof that.onerror === "function") {
                nrdp.log.trace("ws: calling " + that._websocket + " onerror()", "NRDP_SCRIPT");
                that.onerror();
            }
        } else if (event.name == "close") {
            that = this._websockets[event.data.websocket];
            if (!that) return false;
            if(typeof that.onclose === "function") {
                nrdp.log.trace("ws: calling " + that._websocket + " onclose()", "NRDP_SCRIPT");
                that.onclose();
            }
            that._readyState = that.CLOSED;
            delete this._websockets[that._websocket];
            that._websocket = null;
        } else {
            return false;
        }
        return true;
    },

    create: function create(url, protocols, cb) {
        var websocket = this._nextWebSocket++;
        this._fn("create",
                 { websocket: websocket,
                   url: url,
                   protocols: protocols },
                 cb);
        return websocket;
    },

    send: function send(websocket, data, cb) {
        this._fn("send",
                 { websocket: websocket,
                   data: data },
                 cb);
    },

    close: function close(websocket, cb) {
        this._fn("close",
                 { websocket: websocket },
                 cb);
    },

    test: function test(url, cb) {
        this._fn("test",
                 { url: url },
                 cb);
    }
};

nrdp.WebSocket = function(url, protocols) {
    var that = this;

    that._websocket = nrdp.websocket.create(url, protocols ? protocols : "", function(result) {
        if(result.success) {
            that._readyState = that.CONNECTING;
        }
    });

    nrdp.addEventListener("shutdown", function() { nrdp.websocket.close(that._websocket); });

    nrdp.websocket._websockets[that._websocket] = that;
};

nrdp.WebSocket.prototype = (function() {
    var proto = {
        _websocket: null,
        _readyState: 3,
        _protocol: "",
        _url: "",

        get protocol()      { return this._protocol; },
        get url()           { return this._url; },
        get URL()           { return this._url; },
        get readyState()    { return this._readyState; },
        get CONNECTING()    { return 0; },
        get OPEN()          { return 1; },
        get CLOSING()       { return 2; },
        get CLOSED()        { return 3; },

        onclose: null,
        onerror: null,
        onmessage: null,
        onopen: null,

        send: function send(message) {
            if(this.readyState === this.OPEN) {
                nrdp.websocket.send(this._websocket, message);
            } else {
                nrdp.log.trace("WebSocket " + this._websocket + " not OPEN", "NRDP_SCRIPT");
            }
        },
        close: function close() {
            if(this._websocket) {
                nrdp.websocket.close(this._websocket);
            }
        }
    };
    return proto;
})();
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

nrdp.system = {
    get bootURL() { return this._syncData.bootUrl; },
    get bootURLTime() { return this._syncData.bootUrlTime; },

    get uiQueryString() { return this._syncData.uiQueryString; },
    set uiQueryString(query) { nrdp._setProperty("system", "uiQueryString", query); },

    setLocation: function(url) { nrdp._invoke("system", "setLocation", {location: url}); },

    _handleEvent: function(event) {
        if (event.name == "keypress") {
            var keyCode = -1;
            for (key in nrdpPartner.Keys) {
                if (nrdpPartner.Keys[key] == event.data) {
                    keyCode = key;
                    break;
                }
            }

            if (keyCode == -1) {
                nrdp.log.warn("Unknown key: " + event.data);
                return false;
            } else {
                nrdp.log.trace("Dispatching key " + event.data + ", keycode " + keyCode);
                if (window.nrdp_platform != null && window.nrdp_platform.sendKey != null) {
                    nrdp_platform.sendKey(keyCode);
                } else {
                    var downEvent = document.createEvent("Event");
                    downEvent.initEvent("keydown", true, true);
                    downEvent.which = keyCode;
                    document.dispatchEvent(downEvent);
                    var upEvent = document.createEvent("Event");
                    upEvent.initEvent("keyup", true, true);
                    upEvent.which = keyCode;
                    document.dispatchEvent(upEvent);
                }
            }
            return true;
        } else {
            return false;
        }
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

nrdp.network = {
    _path: "network",
    addEventListener: function(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    _urlIndex:0,
    _dnsIndex:0,
    _urlList: [],
    _datas: {},
    _errorStack: [],
    _googleDNS: "8.8.8.8",
    _netflixDomain : "netflix.com",
    _isDiagnoseRunning: false,
    _abortDiagnosis: false,
    _abortDiagnosisCb:0,

    CONNTIMEOUT: 6000,
    DATATIMEOUT: 4000,

    CONNECTIONSTATE : {
        CONNECTED : 0,
        DISCONNECTED : 1,
        UNKNOWN : 2
    },

    get ERRORGROUP() { return this._syncData.ERRORGROUP; },
    get ERRORCODE() { return this._syncData.ERRORCODE; },

    RECOMMENDEDACTION : {
        RETRY : 0,
        RESTART  : 1,
        REACTIVATE  : 2,
        MODIFY_DNS : 3,
        REDIRECT_SETTINGS : 4,
        REINSTALL : 5,
        RUNDIAGTOOL : 6,
        LOG_ERROR  : 7
    },
    DNSERRORS : {
        SUCCESS : 0,
        NODATA  : 1,
        EFORMERR  : 2,
        SERVFAIL  : 3,
        NOTFOUND : 4,
        NOTIMP : 5,
        REFUSED : 6,
        BADQUERY : 7,
        BADNAME  : 8,
        BADFAMILY : 9,
        BADRESP : 10,
        CONNREFUSED : 11,
        TIMEOUT : 12,
        EOF : 13,
        EFILE : 14,
        NOMEM : 15,
        NODNSSERVER : 16,
        OTHER : 17
    },

    _sendCompletedEvent: function(ra) {
        this._urlIndex = 0;
        this._dnsIndex = 0;
        this._urlList = [];
        var endEvent = {
            type: "diagnosisResult",
            resultArray: this._errorStack,
            recommendedAction: ra
        };
        this._errorStack = [];
        this._isDiagnoseRunning = false;
        nrdp._callEventListeners(this, endEvent);
    },

    _sendAbortCb: function() {
        this._urlIndex = 0;
        this._dnsIndex = 0;
        this._urlList = [];
        this._errorStack = [];
        this._isDiagnoseRunning = false;
        this._abortDiagnosis = false;
        if (nrdp.network._abortDiagnosisCb) {
            nrdp.network._abortDiagnosisCb(true);
        }
    },
    _getDomain: function(url) {
        var domain = url.match(/:\/\/(www[0-9]?\.)?(.[^/:]+)/);
        if (domain) {
            return domain[2];
        }
        return "";
    },
    _dnsCallback: function (data, dns) {
        if (this._abortDiagnosis) {
            this._sendAbortCb();
            return;
       }
        var event = {
            URL: data.url,
            errorgroup: this.ERRORGROUP.DNS_CHECK,
            errorcode: (data.result > this.DNSERRORS.NOMEM) ?
                 this.DNSERRORS.OTHER :
                data.result,
            // recommendedAction: data.result,
            dnsip: dns,
            type: "diagnosisStatus",
            success: !data.result
        };
        nrdp._callEventListeners(this, event);
        this._errorStack.push(event);

        if ((dns == this._googleDNS) && (nrdp.device.dnslist.length < this._dnsIndex)) {
            var myurl = nrdp.options.appboot_url;
            this._dnsIndex = 0;
            this._continueDiag(myurl);
            return;
        }
        if (nrdp.device.dnslist.length > this._dnsIndex) {
            var nextdns = nrdp.device.dnslist[this._dnsIndex++];
            this.checkDNS(nextdns, event.URL, function(evt) {
                nrdp.network._dnsCallback(evt, nextdns);
            });
        } else {
            // now check google DNS
            this._dnsIndex++;
            this.checkDNS(this._googleDNS, event.URL, function(evt) {
                nrdp.network._dnsCallback(evt, nrdp.network._googleDNS);
            });
        }
    },
    _continueDiag: function(url) {
       if (this._abortDiagnosis) {
            this._sendAbortCb();
            return;
       }

        if (this._urlList.length > this._urlIndex) {
            this.getOperation(this._urlList[this._urlIndex++],
                              this.CONNTIMEOUT,
                              this.DATATIMEOUT,
                              function(evt) {
                                  nrdp.network._getCallback(evt);
                              });
        } else {
            // completed. send complete event
            var reco = this.RECOMMENDEDACTION.RETRY;
            for (var index in this._errorStack) {
                if ( (this._errorStack[index].errorcode == this.ERRORCODE.DNS_ERROR) &&  (reco == this.RECOMMENDEDACTION.RETRY) ){
                    reco = this.RECOMMENDEDACTION.REDIRECT_SETTINGS;
                }else if ( (this._errorStack[index].errorcode == this.ERRORCODE.CERT_STATUS_PEW_REVOKED) &&  (reco == this.RECOMMENDEDACTION.RETRY) ){
                    reco = this.RECOMMENDEDACTION.REACTIVATE;
                }
            }
            this._sendCompletedEvent(reco);
        }
    },
    _nflxUrlCallback: function(data, url) {
        if (!this._isDiagnoseRunning) {
            return;
        }
        if (this._abortDiagnosis) {
            this._sendAbortCb();
            return;
        }
        var event = {
            URL: data.url,
            errorgroup: data.errorgroup,
            errorcode: data.errorcode,
            nativeerrorcode: data.nativeerrorcode,
            // recommendedAction: data.result,
            type: "diagnosisStatus",
            success: !data.result
        };

        if (event.errorcode == this.ERRORCODE.DNS_ERROR) {
            //do dnscheck;
            if (nrdp.device.dnslist.length <= 0) {
                event.errorcode = this.ERRORCODE.NO_DNS_SERVER;
                nrdp._callEventListeners(this, event);
                this._errorStack.push(event);
                this._sendCompletedEvent(this.RECOMMENDEDACTION.REDIRECT_SETTINGS);
            } else {
                nrdp._callEventListeners(this, event);
                this._errorStack.push(event);
                var dns = nrdp.device.dnslist[this._dnsIndex++];
                // XXX any reason not to check using all DNS servers at same time?
                if (nrdp.network._getDomain(event.URL) != "") {
                    this.checkDNS(dns,
                                  nrdp.network._getDomain(event.URL),
                                  function(evt) {
                                      nrdp.network._dnsCallback(evt, dns);
                                  });
                } else {
                    this._continueDiag(url);
                }
            }
            return;
        }

        nrdp._callEventListeners(this, event);
        this._errorStack.push(event);
        this._continueDiag(url);

    },

    _getCallback: function(data) {
        if (!this._isDiagnoseRunning) {
            return;
        }
        if (this._abortDiagnosis) {
            this._sendAbortCb();
            return;
        }
        var event = {
            URL: data.url,
            errorgroup: data.errorgroup,
            errorcode: data.errorcode,
            nativeerrorcode: data.nativeerrorcode,
            // recommendedAction: data.result;
            type: "diagnosisStatus",
            success: !data.result
        };

        // XXX take same action if !event.success as _nflxUrlCallback?
        // recommendation to REACTIVATE if PEW revoked and recommendation to REDIRECT  for DNS error
        nrdp._callEventListeners(this, event);
        this._errorStack.push(event);
        if (this._urlList.length > this._urlIndex) {
            this.getOperation(this._urlList[this._urlIndex++],
                              this.CONNTIMEOUT,
                              this.DATATIMEOUT,
                              function(evt) {
                                  nrdp.network._getCallback(evt);
                              });
        } else {
            var reco = this.RECOMMENDEDACTION.RETRY;
            for (var index in this._errorStack) {
                if ( (this._errorStack[index].errorcode == this.ERRORCODE.DNS_ERROR) &&  (reco == this.RECOMMENDEDACTION.RETRY) ){
                    reco = this.RECOMMENDEDACTION.REDIRECT_SETTINGS;
                }else if ( (this._errorStack[index].errorcode == this.ERRORCODE.CERT_STATUS_PEW_REVOKED) &&  (reco == this.RECOMMENDEDACTION.RETRY) ){
                    reco = this.RECOMMENDEDACTION.REACTIVATE;
                }
            }
            this._sendCompletedEvent(reco);
        }
    },

    _isValidIpAvailable: function() {
        var ifList = nrdp.device.iflist;
        var found = false;
        for (var index in ifList) {
            if ( (ifList[index].ipAddress) &&
                 // XXX this list won't work for ipv6
                 // XXX localdomain subnet is class A, should check that it starts with 127
                 (ifList[index].ipAddress.indexOf("127", 0) != 0) &&
                 (ifList[index].ipAddress != "0.0.0.0") &&
                 (ifList[index].ipAddress != "") &&
                 // XXX broken if the IP is 192.168.0.169
                 (ifList[index].ipAddress.indexOf("169", 0) != 0) ) {
                found = true;
                nrdp.log.trace("IP address is " + ifList[index].ipAddress);
                break;
            }
        }
        return found;
    },

    diagnose: function(urls) {
        nrdp.network._datas = {};
        if (!nrdp.network._isValidIpAvailable()) {
            var event = {
                URL: "",
                errorgroup: nrdp.network.ERRORGROUP.NO_IP_ADDRESS,
                errorcode: nrdp.network.ERRORCODE.UNKNOWN_ERROR,
                nativeerrorcode: -1, // duplicated from NativeerrorCodes.h
                type: "diagnosisStatus",
                success: false
            };
            nrdp.log.debug("diagnosis status event " + JSON.stringify(event) );
            nrdp._callEventListeners(nrdp.network, event);
            nrdp.network._errorStack.push(event);
            nrdp.network._sendCompletedEvent(nrdp.network.RECOMMENDEDACTION.REDIRECT_SETTINGS);
            return;
        }
        if (!nrdp.network._abortDiagnosis) {
            nrdp.network._isDiagnoseRunning = true;
            // XXX need to handle multiple calls to diagnose in parallel?
            nrdp.network._urlList = urls;
            // XXX any reason not to check all urls at the same time?
            nrdp.network.getOperation(nrdp.options.appboot_url,
                                      nrdp.network.CONNTIMEOUT,
                                      nrdp.network.DATATIMEOUT,
                                      function(evt) {
                                          nrdp.network._nflxUrlCallback(evt, nrdp.options.appboot_url);
                                      });
        } else {
            nrdp.network._abortDiagnosis = false;
            nrdp.network._abortDiagnosisCb(true);
        }
    },

    abortDiagnosis: function(abortCb) {
        if (nrdp.network._isDiagnoseRunning) {
            nrdp.network._abortDiagnosis = true;
            nrdp.network._abortDiagnosisCb = abortCb;
        }
    },

    getOperation: function(url, connectiontimeout, datatimeout, callback) {
        // XXX what if there's two get operations for the same url at the same time?
        nrdp.network._datas[url] = {cb: callback, results: {}};
        nrdp._invoke("network", "get",
                     {url: url, connectiontimeout: connectiontimeout, datatimeout: datatimeout});
    },

    checkDNS: function(dnsip, url, callback) {
        // XXX what if there's two checks for the same url at the same time?
        nrdp.network._datas[url] = {cb: callback, results: {}};
        nrdp._invoke("network", "checkdns", {dnsip: dnsip, url: url});
    },

    _handleEvent: function(event) {
        if (event.name != "INetwork") {
            return false;
        }
        if ( (event.data.type !== "dnsresult") && (event.data.type !== "getresult") ){
            return false;
        }

        var url = event.data.url;
        var data = this._datas[url];
        if (data) {
            data.cb(event.data);
        }

        return true;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/*
 * all nrdp communication helpers are required to do four things:
 *
 * 1. add object to nrdp._backchannels for initialization
 * 2. provide nrdp._setProperty()
 * 3. provide nrdp._invoke()
 * 4. call nrdp._gotEvent() when an event comes in.
 */

if(typeof window !== "undefined") {
(function() {

var evtsrc;
var iframe;
var nbpdUrl;

nrdp._nbpdAsync = false;

function _gotJSON(json) {
    var event;
    try {
        event = JSON.parse(json);
    } catch (err) {
        console.log("unhandled exception in eventsource " + json);
        console.log(err.toString());
        return;
    }
    if (!event.object && event.name == "aboutToQuit") {
        if (evtsrc) {
            evtsrc.close();
        } else {
            iframe.contentWindow.postMessage("quit", '*');
        }
    }
    nrdp._sendSyncdEvent(nrdp._gotEvent, nrdp, [event]);
};

function _POST(params, async) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", nbpdUrl + "ipc", async);
    xhr.setRequestHeader("Content-type", "text/plain");
    xhr.onerror = function() { console.log("lost IPC connection for POST"); };
    xhr.onreadystatechange = function() {
        if(this.readyState == 4) {
            this.onreadystatechange = undefined;
            this.onerror = undefined;
        }
    };
    xhr.send(JSON.stringify(params));
    return async || xhr.responseText;
};

function _setProperty(obj, prop, val) {
    var args = {
        object: obj ? "nrdp." + obj : "nrdp",
        property: prop,
        value: (typeof val === "undefined" ? null : val)
    };
    _POST(args, nrdp._nbpdAsync);
};

function _invoke(obj, method, args) {
    if (!args) args = {};
    args.object = obj ? "nrdp." + obj : "nrdp";
    args.method = method;
    _POST(args, nrdp._nbpdAsync);
};

function _construct(obj, construct, args) {
    if (!args) args = {};
    args.object = obj ? "nrdp." + obj : "nrdp";
    args.construct = construct;
    var result = _POST(args, false);
    if (result)
        return JSON.parse(result);
    return undefined;
};

function _setupBackchannel() {
    /*
    var urlParams = location.search;
    var getParam = function(key) {
        var sIdx = urlParams.indexOf(key), sep, eIdx;
        if (sIdx !== -1) {
            sep = urlParams.charAt(sIdx - 1);
            if (!sep || sep === "?" || sep === "&") {
                eIdx = urlParams.indexOf("&", sIdx);
                return urlParams.substring(sIdx + (key.length + 1), eIdx !== -1 && eIdx || urlParams.length);
            }
        }
        return undefined;
    };
    var url = getParam("nbpdHost");
    if (url)
    {
        nbpdUrl = url;
    }
    else
    */
    {
        for (var i in document.scripts) {
            if (/^http.*js\/NetflixBridge.js$/.test(document.scripts[i].src)) {
                nbpdUrl = document.scripts[i].src.replace("js/NetflixBridge.js", "");
                break;
            }
        }
    }

    try {
        var evurl = nbpdUrl + "eventsource?objectFilter=-nrdp.gibbon.debugger&name=NBPDJS";
        evtsrc = new EventSource(evurl);
        evtsrc.onmessage = function(evt) { _gotJSON(evt.data); };
        evtsrc.onerror = function(evt) {
            evtsrc.close();
            nrdp._gotEvent({type: "EventSourceError"});
        };
        mongooseBackchannel.evtsrc = evtsrc;
    } catch (x) {
        nrdp.log.trace("Direct EventSource connection failed, using iframe");
        window.addEventListener("message",
                                function(e) {
                                    if (e.origin + "/" === nbpdUrl)
                                        _gotJSON(e.data);
                                },
                                false);

        iframe = document.createElement('iframe');
        iframe.style.setProperty("opacity", 0);
        iframe.width = iframe.height = 0;
        iframe.src = nbpdUrl + "html/iframe.html";
        document.body.appendChild(iframe);
    }

    return true;
};

var mongooseBackchannel = {
    name: "http",
    isNative: false,
    init: _setupBackchannel,
    setProperty: _setProperty,
    construct: _construct,
    invoke: _invoke
};

nrdp._backchannels.push(mongooseBackchannel);

})();
}
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp, nrdp_platform, nrdpPartner */

nrdp.gibbon = {
    _callback: 1,
    _callbacks: {},

    _nextSyncID: 1,
    _nextWidgetId: 1,
    _nextEffectId: 1,
    _nextImageId: 1,
    _debugEvents: 0,

    INT_MAX: 0x7fffffff,

    init: function(cb, url) {
        function makeArray(elt) {
            return Array.isArray(elt) ? elt : [elt];
        }

        var urlList = url ? makeArray(url) : [];

        function load_urls() {
            if (urlList.length == 0) {
                if (cb) cb();
            } else {
                var u = urlList.shift();
                nrdp.gibbon.loadScript({ url: u }, load_urls);
            }
        }

        if (nrdp.isReady) {
            load_urls();
        } else {
            nrdp.addEventListener("init", load_urls);
            nrdp.init();
        }
    },

    // *** DEPRECATED
    get CURL_INIT_ERROR() { return -1; },

    // Debugger
    get DEBUGGER_ATTRIBUTE_MODIFIED() { return 1; },
    get DEBUGGER_SUBTREE_MODIFIED() { return 2; },
    get DEBUGGER_NODE_REMOVED() { return 3; },

    fonts: {
        get entries() { var fonts = JSON.parse(JSON.stringify(nrdp.gibbon._syncData.fonts)); delete fonts["scripts"]; return fonts; },
        set entries(f) { nrdp._setProperty("gibbon", "fonts", f); },
        get scripts() { return nrdp.gibbon._syncData.fonts.scripts; },

        attributes: {
            get NONE() { return 0x00000000; },
            get EMBOLDEN() { return 0x00000001; },
            get SLANT() { return 0x00000002; },
            get MONOSPACE() { return 0x00000004; },
            get SYNTHESIZE() { return 0x00000008; },
            get HINTING() { return 0x00000100; },
            get AUTOHINTER() { return 0x00000200; },
            get HINTNORMAL() { return 0x00000400; },
            get HINTLIGHT() { return 0x00000800; },
            get HINTMONO() { return 0x00001000; },
            get HINTLCD() { return 0x00002000; },
            get HINTLCDV() { return 0x00004000; }
        },
        weight: {
            get NORMAL() { return 0; },
            get BOLD() { return 1; }
        },
        style: {
            get NORMAL() { return 0; },
            get ITALIC() { return 1; }
        }
    },

    get email() {
        return this._syncData.email;
    },

    diskCache: {
        get capacity() {
            if(nrdp.gibbon._syncData.diskCacheConfiguration)
                return nrdp.gibbon._syncData.diskCacheConfiguration.capacity;
            return 0;
        },
        get maxPending() {
            if (nrdp.gibbon._syncData.diskCacheConfiguration)
                return nrdp.gibbon._syncData.diskCacheConfiguration.maxPending;
            return 0;
        },
        get maxWrites() {
            if(nrdp.gibbon._syncData.diskCacheConfiguration)
                return nrdp.gibbon._syncData.diskCacheConfiguration.maxWrites;
            return 0;
        },
        get catalogTimer() {
            if(nrdp.gibbon._syncData.diskCacheConfiguration)
                return nrdp.gibbon._syncData.diskCacheConfiguration.catalogTimer;
            return 0;
        },
        clear: function clear(cb) {
            var id = nrdp.gibbon._setValue(cb);
            nrdp._invoke("gibbon", "clearDiskCache", {id: id});
        },
        _dump: function _dump(cb) {
            var id = nrdp.gibbon._setValue(cb);
            nrdp._invoke("gibbon", "dumpDiskCache", {id: id});
        }
    },

    surfaceCache: {
        get capacity() {
            if (nrdp.gibbon._syncData.surfaceCacheCapacity)
                return nrdp.gibbon._syncData.surfaceCacheCapacity.browse;
            return undefined;
        },
        get playbackCapacity() {
            if (nrdp.gibbon._syncData.surfaceCacheCapacity)
                return nrdp.gibbon._syncData.surfaceCacheCapacity.playback;
            return undefined;
        }
    },

    resourceManager: {
        get capacity() {
            return nrdp.gibbon._syncData.resourceManagerCapacity;
        }
    },

    get password() {
        return this._syncData.password;
    },

    get imageFormats() {
        return this._syncData.imageFormats;
    },

    get defaultLocale() {
        return this._syncData.defaultLocale;
    },

    set defaultLocale(l) {
        nrdp._setProperty("gibbon", "defaultLocale", l);
    },

    get garbageCollectTimeout() {
        return this._syncData.garbageCollectTimeout;
    },

    set garbageCollectTimeout(t) {
        nrdp._setProperty("gibbon", "garbageCollectTimeout", t);
    },

    _path: "gibbon",
    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get effects() { return this.capabilities.effects; },
    get capabilities() { return this._syncData.capabilities; },

    get cookie() { return (this._syncData) ? this._syncData.cookie: ""; },
    set cookie(c) { nrdp._invoke("gibbon", "setCookie", {cookie: c}); },

    setCookie: function setCookie(cookie, callback) {
        var id = nrdp.gibbon._setValue(callback);
        nrdp._invoke("gibbon", "setCookie", {cookie: cookie, id: id});
    },

    _loadSplash: function _loadSplash(request) {
        if(!(/^https?:/).test(nrdp.options.splash_screen) || nrdp.gibbon._splashUrl)
            return;

        var defaultSplash = nrdp.options.splash_screen;
        if(request && request.url && request.url.length) {
            var transientData = nrdp.storage.transientData || {};
            transientData.splash = request;
            nrdp.storage.transientData = transientData;
        } else {
            if(nrdp.storage.transientData)
                request = nrdp.storage.transientData.splash;
            if(!request || !request.url || !request.url.length)
                request = { url: defaultSplash };
        }
        nrdp.gibbon._splashUrl = request.url;
        nrdp.gibbon.loadScript(request, function(response) {
            if (response.exception || response.statusCode != 200) {
                nrdp.log.info("falling back to splash screen " + defaultSplash);
                nrdp.gibbon._splashUrl = defaultSplash;
                nrdp.gibbon.loadScript({ 'url': defaultSplash });
            } else {
                nrdp.gibbon.sync();
            }
        });
    },

    get location() {
        var result = this._baseUrl;
        if(!result) {
            // there is a race condition in c++ when the location is the
            // result of a redirect; the updated location may not come
            // until after nrdp isReady. but nrdp_platform can always be
            // right.
            if(typeof nrdp_platform !== "undefined" && typeof nrdp_platform.location === "function")
                result = nrdp_platform.location();
            else
                result = this._syncData.location;
            if(typeof result == 'object')
                result = result.url;
        }
        return result;
    },
    set location(l) {
        var args = l;
        this._baseUrl = undefined;
        if(typeof args == 'string')
            args = {url: args};
        args.url = this._resolveUrl(args.url);
        nrdp._setProperty("gibbon", "location", args);
    },
    get queryParams() {
        return this._syncData['queryParams'];
    },

    loaded: function loaded() {
        nrdp._invoke("gibbon", "uiLoaded");
    },

    setTimeout: function setTimeout(cb, interval, singleShot) {
        var id;

        if(singleShot === undefined) {
            // default singleShot to true
            singleShot = true;
        }

        id = nrdp.gibbon._setValue({ callback: cb, singleShot: singleShot, interval: interval });
        nrdp._invoke("gibbon", "startTimer", { id: id, singleShot: singleShot, interval: interval });
        if(nrdp.gibbon._debugEvents)
            nrdp.log.error("StartTimer " + id + "@" + interval + ":" + singleShot);
        return id;
    },
    clearTimeout: function clearTimeout(id) {
        if(nrdp.gibbon._getValue(id) !== undefined) {
            if(nrdp.gibbon._debugEvents)
                nrdp.log.error("StoptTimer " + id);
            nrdp.gibbon._deleteValue(id);
            nrdp._invoke("gibbon", "stopTimer", { id: id });
        }
    },

    garbageCollect: function garbageCollect(callback, type) {
        var id = callback ? nrdp.gibbon._setValue(callback) : 0;
        nrdp._invoke("gibbon", "garbageCollect", { id: id, type: type });
    },
    eval: function eval(script, file) {
        if(typeof nrdp_platform !== "undefined" && nrdp_platform.eval)
            nrdp_platform.eval(script, file);
        else
            nrdp._invoke("gibbon", "eval", { script: script, file: file });
    },

    _load: function _load(request, callback) {
        request.id = nrdp.gibbon._setValue(callback);
        request.url = this._resolveUrl(request.url);
        nrdp._invoke("gibbon", "startRequest", request);
        if(nrdp.gibbon._debugEvents)
            nrdp.log.error("StartRequest " + request.id + "@" + request.url);
        return request.id;
    },
    load: function load(request, callback) {
        if(typeof request !== "object") {
            if(typeof callback === "function")
                callback({});
            return -1;
        }
        request.eval = false;
        if(request.async === undefined)
            request.async = true;
        return nrdp.gibbon._load(request, callback);
    },
    loadScript: function loadScript(request, callback) {
        if(typeof request !== "object") {
            if(typeof callback === "function")
                callback({});
            return -1;
        }
        request.eval = true;
        if(request.async === undefined)
            request.async = false;
        return nrdp.gibbon._load(request, callback);
    },
    stopLoad: function stopLoad(id) {
        nrdp.gibbon._deleteValue(id);
        if(nrdp.gibbon._debugEvents)
            nrdp.log.error("StopRequest " + id);
        nrdp._invoke("gibbon", "stopRequest", {id: id });
        return id;
    },

    setAtlasSize: function setAtlasSize(group, width, height) {
        nrdp._invoke("gibbon", "setAtlasSize", { group: group, width: width, height: height });
    },

    setGlyphAtlasSize: function setGlyphAtlasSize(width, height) {
        nrdp._invoke("gibbon", "setGlyphAtlasSize", { width: width, height: height });
    },

    sync: function sync() {
        if(nrdp.gibbon._pendingSync) {
            var syncData;
            for(var id in nrdp.gibbon._pendingSync.widgets) {
                var widget = nrdp.gibbon._pendingSync.widgets[id];
                if(!widget._created && widget.parent && !widget.parent._created)
                    continue;

                var data = widget._pushData();
                if(data) {
                    if(syncData)
                        syncData.push(data);
                    else
                        syncData = [data];
                }
            }
            nrdp.gibbon._pendingSync.widgets = {};
            if(syncData) {
                var sync = { data: syncData };
                if(nrdp.gibbon._debugEvents) {
                    nrdp.log.error("Sync! " + nrdp.gibbon._nextSyncID);
                    sync.debugID = nrdp.gibbon._nextSyncID++;
                }
                nrdp._invoke("gibbon", "updateWidget", sync);
            }
        }
    },
    beginPendingSync: function beginPendingSync() {
        if(!nrdp.gibbon._pendingSync)
            nrdp.gibbon._pendingSync = { widgets: {}, count: 1 };
        else
            ++nrdp.gibbon._pendingSync.count;
    },
    endPendingSync: function endPendingSync() {
        if(nrdp.gibbon._pendingSync) {
            --nrdp.gibbon._pendingSync.count;
            if(!nrdp.gibbon._pendingSync.count) {
                this.sync();
                nrdp.gibbon._pendingSync = undefined;
            }
        }
    },
    _addPendingSync: function _addPendingSync(widget) {
        if(nrdp.gibbon._pendingSync) {
            nrdp.gibbon._pendingSync.widgets[widget._id] = widget;
            return true;
        }
        return false;
    },
    _removePendingSync: function _removePendingSync(widget) {
        if(nrdp.gibbon._pendingSync) {
            delete nrdp.gibbon._pendingSync.widgets[widget._id];
            return true;
        }
        return false;
    },

    _parseQueryParams: function _parseQueryParams(url)
    {
        var result = {};
        if(url) {
            var question = url.indexOf('?');
            if (question > 0) {
                url.substr(question+1).split('&').forEach(
                    function(e) {
                        if (e) {
                            var opt = e.split('=');
                            result[opt[0]] = (opt.length > 1 ? decodeURIComponent(opt[1]) : true);
                        }
                    });
            }
        }
        return result;
    },

    _needPendingSync: function _needPendingSync() {
        return nrdp.gibbon._pendingSync;
    },

    _addDOMBreakpoint: function _addDOMBreakpoint(widgetId, type) {
        if (nrdp.gibbon._breaks === undefined)
            nrdp.gibbon._breaks = {};
        if (nrdp.gibbon._breaks[widgetId] == undefined)
            nrdp.gibbon._breaks[widgetId] = {};
        nrdp.gibbon._breaks[widgetId][type] = function() {
            nrdp.log.error("calling debugger!");
            debugger;
        };
    },
    _removeDOMBreakpoint: function _removeDOMBreakpoint(widgetId, type) {
        var hasAnyProperties = function(obj) {
            for (var p in obj) {
                if (obj[p] !== undefined)
                    return true;
            }
            return false;
        };
        delete nrdp.gibbon._breaks[widgetId][type];
        if (!hasAnyProperties(nrdp.gibbon._breaks[widgetId]))
            delete nrdp.gibbon._breaks[widgetId];
        if (!hasAnyProperties(nrdp.gibbon._breaks))
            nrdp.gibbon._breaks = undefined;
    },

    _debugObject: function _debugObject(obj, objname, num) {
        if(typeof obj === "object") {
            var arr = false;
            if(obj instanceof Array) {
                arr = true;
            }
            var list = [];
            var tp, stp, isobj, name;
            for (var i in obj) {
                tp = typeof obj[i];
                stp = undefined;
                if(tp === "object") {
                    if(obj[i] instanceof Array) {
                        stp = "array";
                    } else if(obj[i] instanceof Date)
                        stp = "date";
                    else if(obj[i] instanceof RegExp)
                        stp = "regexp";
                    isobj = true;
                } else {
                    isobj = false;
                }
                if(arr)
                    name = objname + "[" + i + "]";
                else
                    name = objname + "." + i;
                list.push({ name: i, value: { objectId: (isobj ? (name + ":" + num) : undefined),
                                              type: tp, subtype: stp,
                                              description: (function(o) {
                                                                if(typeof o === "object") {
                                                                    if(o instanceof Array)
                                                                        return "Array[" + o.length + "]";
                                                                    else if(o instanceof Date)
                                                                        return "Date";
                                                                    else if(o instanceof RegExp)
                                                                        return "RegExp";
                                                                    else
                                                                        return "Object";
                                                                } else {
                                                                    return o + "";
                                                                }
                                                            })(obj[i])
                                            }
                          });
            }
            return list;
        } else {
            //return [{ value: { type: typeof obj, description: obj + "" }}];
        }
        return undefined;
    },
    findWidget: function findWidget(id) {
        if(typeof id === "number")
            return nrdp.gibbon.widgets ? nrdp.gibbon.widgets[nrdp.gibbon.Widget._createName(id)] : undefined;
        for(var widget in nrdp.gibbon.widgets) {
            if(nrdp.gibbon.widgets[widget].name == id)
                return nrdp.gibbon.widgets[widget];
        }
        return undefined;
    },
    makeWidget: function makeWidget(obj) {
        var widgetid = nrdp.gibbon._nextWidgetId++;
        if(widgetid > nrdp.gibbon.INT_MAX)
            widgetid = 1;
        var widget = new nrdp.gibbon.Widget(widgetid);
        if(obj) {
            for(var property in obj) {
                if(property == "children") {
                    for(var childobj in obj.children) {
                        var childwidget = nrdp.gibbon.makeWidget(obj.children[childobj]);
                        childwidget.parent = widget;
                    }
                } else if(property == "animations") {
                    for(var aproperty in obj.animations)
                        widget.animate(obj.animations[aproperty].property, obj.animations[aproperty].duration,
                                       obj.animations[aproperty].ease);
                } else if(property == "effects") {
                    for(var eproperty in obj.effects)
                        widget.addEffect(obj.effects[eproperty].type, obj.effects[eproperty].params);
                } else if(property == "images") {
                    for(var iproperty in obj.images)
                        widget.addImage(obj.images[iproperty].background, obj.images[iproperty]);
                } else if(property == "parent" || property == "clone") {
                    widget[property] = nrdp.gibbon.findWidget(obj[property]);
                } else if(property == "image" || property == "backgroundImage") {
                    widget.addImage(property == "backgroundImage", obj[property]);
                } else {
                    widget[property] = obj[property];
                }
            }
        }
        return widget;
     },
    dump: function dump(cb) { nrdp.gibbon.scene.dump(cb); },
    _hookProperty : function _hookProperty(obj, property, hook) {
        var _hook_old = Object.getOwnPropertyDescriptor(obj, property);
        var _hook_new = Object.create(_hook_old);
        _hook_new.set = function(_value) {
            hook.call(this, _value, _hook_old.set);
        };
        Object.defineProperty(obj, property, _hook_new);
    },
    _describeEvent : function _describeEvent(event) {
        var result;
        if(event) {
            if(event.type == "Event") {
                result = event.name;
                if(event.type == "Event" && event.name == "timer" && event.object == "nrdp.gibbon")
                    result += ":" + event.data;
                else if(event.type == "Event" && event.name == "requestFinished" && event.object == "nrdp.gibbon")
                    result += ":" + event.data.id;
                else if(event.type == "Event" && event.name == "inputEvent" && event.object == "nrdp.gibbon")
                    result += ":" + JSON.stringify(event.data);
            } else {
                result = event.type;
            }
        }
        return result;
    },

    startFpsTimer: function startFpsTimer() { nrdp._invoke("gibbon", "startFpsTimer"); },
    stopFpsTimer: function stopFpsTimer() { nrdp._invoke("gibbon", "stopFpsTimer"); },
    getRenderedFps: function getRenderedFps(callback) {
        var id = nrdp.gibbon._setValue(callback);
        nrdp._invoke("gibbon", "getRenderedFps", {id: id});
    },
    getHeapSize: function getHeapSize(callback) {
        var id = nrdp.gibbon._setValue(callback);
        nrdp._invoke("gibbon", "getHeapSize", {id: id});
    },
    moveMouse: function moveMouse(x, y) {
        nrdp._invoke("gibbon", "moveMouse", {x: x, y: y});
    },

    reloadFonts: function reloadFonts() {
        nrdp._invoke("gibbon", "reloadFonts");
    },

    _runConsole: function _runConsole(cmd) {
        nrdp._invoke("gibbon", "runConsole", {command: cmd});
    },

    get _baseUrl() {
        if(this._baseUrlVar)
            return this._baseUrlVar.url;
        return undefined;
    },
    set _baseUrl(u) {
        if(u) {
            var dir = u;
            var q = dir.indexOf("?");
            if(q != -1)
                dir = dir.substr(0, q);
            var s = dir.lastIndexOf("/");
            if(s != -1)
                dir = dir.substr(0, s + 1);
            this._baseUrlVar = { dir: dir, url: u };
        } else {
            this._baseUrlVar = undefined;
        }
    },
    _resolveUrl: function _resolveUrl(url) {
        if(this._baseUrlVar && url && url.indexOf("://") == -1 && url.indexOf("data:") != 0) {
            var baseUrl = this._baseUrlVar.dir;
            if(url[0] == "/") {
                var s = baseUrl.indexOf("/", 8);
                if(s != -1)
                    baseUrl = baseUrl.substr(0, s);
            }
            return baseUrl + url;
        }
        return url;
    },

    _syncData: {},
    _updateProperty: function _updateProperty(property, value) {
        if(property == "networkErrorCodes" || property == "networkErrorGroups") {
            for(var prop in value)
                nrdp.gibbon[prop] = value[prop];
        } else {

            var evt;
            if(property == "location") {
                this._syncData['queryParams'] = nrdp.gibbon._parseQueryParams(value);
            } else if(nrdp.isReady && property == "cookie" && nrdp.gibbon.cookie != value) {
                evt = {
                    type: "cookiechange",
                    old: nrdp.gibbon.cookie
                };
            }

            this._syncData[property] = value;

            if(evt)
                nrdp._callEventListeners(this, evt);
        }
    },

    _handleEvent: function _handleEvent(event) {
        var handled = true;
        if(event.name == "inputEvent") {
            var type = event.data.type;
            if(type == "click" || type == "press" || type == "release") {
                var evt = { type: "key", time: event.time , data: event.data };
                if(nrdpPartner && nrdpPartner.Keys && !evt.data.uiEvent)
                    evt.data.uiEvent = nrdpPartner.Keys[evt.data.code];
                for(var i = 0; i < event.data.count; ++i) {
                    if(type == "click") {
                        evt.data.type = "press";
                        nrdp._callEventListeners(this, evt);
                        if(!event.data.repeat)
                            evt.data.type = "release";
                        else
                            evt = 0;
                    }
                    if(evt) {
                        nrdp._callEventListeners(this, evt);
                    }
                    break; //do just one?
                }
            } else if(type == "mouseChange") {
                nrdp._callEventListeners(this, { type: "mouse", time: event.time, data: event.data });
            } else if(type == "mouseMove" || type == "mousePress" || type == "mouseRelease") {
               if(event.data.widgets) {
                   for(var i = 0; i < event.data.widgets.length; ++i)
                       event.data.widgets[i].widget = nrdp.gibbon.findWidget(event.data.widgets[i].id);
               }
                nrdp._callEventListeners(this, { type: "mouse", time: event.time, data: event.data });
           } else if(type == "touchMove" || type == "touchPress" || type == "touchRelease") {
               if(event.data.widgets) {
                   for(var i = 0; i < event.data.widgets.length; ++i)
                       event.data.widgets[i].widget = nrdp.gibbon.findWidget(event.data.widgets[i].id);
               }
               nrdp._callEventListeners(this, { type: "touch", time: event.time, data: event.data });
           } else {
               nrdp.log.error("unhandled gibbonInputEvent " + type);
           }
        } else if(event.name == "requestFinished") {
            var cb = this._getValue(event.data.id);
            if(!event.data.refresh || event.data.state == "refresh")
                this._deleteValue(event.data.id);
            if(event.data.parsed && event.data.xml)
                nrdp._fixXml(event.data.xml);
            if(typeof cb == "function")
                cb(event.data);
        } else if(event.name == "timer") {
            var timer_id = event.data;
            var timer = this._getValue(timer_id);
            if(timer !== undefined) {
                if(timer.callback !== undefined)
                    timer.callback();
                if(timer.singleShot)
                    this._deleteValue(timer_id);
            }
        } else if(event.name == "getRenderedFps"
                  || event.name == "heapsize"
                  || event.name == "setCookie"
                  || event.name == "garbageCollected"
                  || event.name == "diskCacheCleared"
                  || event.name == "diskCacheDumped") {
            var cb = this._getValue(event.data.id);
            if(cb !== undefined) {
                if(event.name == "getRenderFps") {
                    cb(event.data.fps);
                } else if(event.name == "heapsize") {
                    cb(event.data.heapsize);
                } else if(event.name == "diskCacheDumped") {
                    cb(event.data.dump, event.data.stats);
                } else {
                    cb();
                }
            }
            this._deleteValue(event.data.id);
        } else if (event.name == "capacityReached") {
            nrdp._callEventListeners(this, { type: "capacityReached", data: event.data });
        } else {
            handled = false;
        }
        return handled;
    },

    _setValue: function _setValue(value) {
        do {
            ++nrdp.gibbon._callback;
            if(nrdp.gibbon._callback >= nrdp.gibbon.INT_MAX)
                nrdp.gibbon._callback = 1;
        } while(nrdp.gibbon._callbacks[nrdp.gibbon._callback] !== undefined)
        nrdp.gibbon._callbacks[nrdp.gibbon._callback] = value;
        return nrdp.gibbon._callback;
    },
    _getValue: function _getValue(id) {
        return nrdp.gibbon._callbacks[id];
    },
    _deleteValue: function _deleteValue(id) {
        delete nrdp.gibbon._callbacks[id];
    },
    _invoke: function _invoke(object, method, args) {
        nrdp._invoke("gibbon", "invoke", {subObject:object, subMethod:method, subArgs:args});
    },
    _setProperty: function _setProperty(object, property, value) {
        nrdp._invoke("gibbon", "setProperty", {subObject:object, subProperty:property, subValue:value});
    },
    _construct: function _construct(object, method, args) {
        nrdp._invoke("gibbon", "construct", {subObject:object, subMethod:method, subArgs:args});
    }
};
nrdp.addEventListener('init', function() {
    if(nrdp.gibbon.location != nrdp.system.bootURL) {
        nrdp.gibbon._loadSplash();
    }
});

nrdp._sendSyncdEvent = function(fn, that, event) {
    try {
        nrdp.gibbon.beginPendingSync();
        if(nrdp.gibbon._debugEvents)
            nrdp.log.error("Sending event(" + nrdp.gibbon._debugEvents + ") " + nrdp.gibbon._describeEvent(event));
        fn.call(that, event);
        if(nrdp.gibbon._debugEvents)
            nrdp.log.error("~Sending event(" + nrdp.gibbon._debugEvents++ + ")");
    } catch (x) {
        nrdp.gibbon.endPendingSync();
        throw x;
    }
    nrdp.gibbon.endPendingSync();
};

if(typeof nrdpPartner === "undefined")
    nrdpPartner = {};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.gibbon.scene = {
    classname: "ScreenBridge",

    _callbacks: {},
    _nextCallbackId: 1,

    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get suspend() { return (this._suspendPending === undefined) ? this._syncData.suspend : this._suspendPending; },
    set suspend(_suspend) {
        if(_suspend == this.suspend)
            return;

        if(this._suspendPending === undefined) {
            this._syncData.suspend = _suspend;
            nrdp._setProperty("gibbon.scene", "suspend", _suspend);
        }
        this._suspendPending = _suspend;
    },

    get widget() { return this.root; },
    set widget(_widget) { this.root = _widget; },
    get root() {
        var wid = this._syncData.root;
        return (typeof wid === "object") ? wid : null;
    },
    set root(_widget) {
        if(_widget && this.root && _widget._id == this.root._id)
            return;
        var oldRoot = this.root;
        this._syncData.root = _widget;
        if(_widget)
            _widget._createWidget();
        nrdp._setProperty("gibbon.scene", "root", _widget ? _widget._id : 0);
        if(nrdp.gibbon._showSplashScreen)
            nrdp.gibbon._showSplashScreen(!nrdp.gibbon.scene.root || !nrdp.gibbon.scene.root.visible);
        if(oldRoot)
            oldRoot._destroyWidget();
    },

    get overlay() {
        var wid = this._syncData.overlay;
        return (typeof wid === "object") ? wid : null;
    },
    set overlay(_widget) {
        if(_widget && this.overlay && _widget._id == this.overlay._id)
            return;
        var oldOverlay = this.overlay;
        this._syncData.overlay = _widget;
        if(_widget)
            _widget._createWidget();
        nrdp._setProperty("gibbon.scene", "overlay", _widget ? _widget._id : 0);
        if(oldOverlay)
            oldOverlay._destroyWidget();
    },

    get width() { return this._syncData.width; },
    get height() { return this._syncData.height; },

    dump: function dump(cb) {
        this.widget.dump(cb);
    },
    grab: function grab(cb) {
        var id = this._registerCallback(cb);
        nrdp._invoke("gibbon.scene", "grab", { id: id });
    },
    update: function update(widget) {
        nrdp._invoke("gibbon.scene", "update", { id: widget ? widget._id : this.widget._id });
    },

    _handleEvent: function _handleEvent(event) {
        if(event.name == "grab") {
            this._callCallback(event);
            return true;
        } else if(event.name == "suspendChanged") {
            this._syncData.suspend = event.data.suspended;
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
            if(this._suspendPending !== undefined) {
                var suspendPending = this._suspendPending;
                this._suspendPending = undefined;
                if(suspendPending !== event.data.suspended) {
                    this.suspend = suspendPending;
                }
            }
            return true;
        }
        return false;
    },
    _registerCallback: function _registerCallback(cb) {
        var id = this._nextCallbackId++;
        this._callbacks[id] = cb;
        return id;
    },
    _callCallback: function _callCallback(event) {
        if (event.data.id === undefined)
            return;
        var cb = this._callbacks[event.data.id];
        delete this._callbacks[event.data.id];
        if (cb)
            cb(event.data.data);
    },
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.gibbon.Image = function(widget, background, id) {
    if(!widget._syncData.images)
        widget._syncData.images = {};

    this._id = id;
    this._widget = widget;
    this._pushed = { };
    this.__push = undefined;
    this._created = false;
    this._background = background;
    this._name = nrdp.gibbon.Image._createName(this._id);
    this._path = widget._path + ".images." + this._name;
    this._pushed = {};
    if(!this._syncData)
        this._syncData = widget._syncData.images[this._name] = {};
};
nrdp.gibbon.Image._createName = function(id) { return "image" + id; };

nrdp.gibbon.Image.LAZY_DECODE = 0x01;
nrdp.gibbon.Image.LAZY_DOWNLOAD = 0x02;
nrdp.gibbon.Image.ALIGN_NORMAL = 0x00;
nrdp.gibbon.Image.ALIGN_LEFT = nrdp.gibbon.Image.ALIGN_NORMAL;
nrdp.gibbon.Image.ALIGN_TOP = nrdp.gibbon.Image.ALIGN_NORMAL;
nrdp.gibbon.Image.ALIGN_CENTER = 0x01;
nrdp.gibbon.Image.ALIGN_RIGHT = 0x02;
nrdp.gibbon.Image.ALIGN_BOTTOM = nrdp.gibbon.Image.ALIGN_RIGHT;
nrdp.gibbon.Image.ALIGN_TILE = 0x04;
nrdp.gibbon.Image.ALIGN_STRETCH = 0x08;
nrdp.gibbon.Image.ALIGN_ASPECT = 0x18;
nrdp.gibbon.Image._default = {
    src: "",
    purge: true,
    player: undefined,
    sendImageLoaded: false,
    sourceRect: { x: 0, y: 0, width: undefined, height: undefined },
    verticalAlignment: nrdp.gibbon.Image.ALIGN_NORMAL,
    horizontalAlignment: nrdp.gibbon.Image.ALIGN_NORMAL
};

nrdp.gibbon.Image.prototype = {
    classname: "ImageBridge",
    constructor: nrdp.gibbon.Image,
    _pushData: function _pushData(all) {
        if(!all && !this._created)
            all = true;

        var pushData;
        if(all) {
            for(var property in this._pushed) {
                if(!pushData)
                    pushData = {id: this._id };
                pushData[property] = this._pushed[property];
            }
        }
        if(this.__push) {
            if(!pushData)
                pushData = {id: this._id };
            for(var property in this.__push)
                this._pushed[property] = pushData[property] = this.__push[property];
            this.__push = undefined;
        }

        if(pushData && pushData["src"] && pushData["src"] instanceof Object && pushData["src"].lazy === true) {
            if(!this._background && (this._widget.width === undefined || this._widget.height === undefined))
                pushData["src"].lazy = nrdp.gibbon.Image.LAZY_DECODE;
            else
                pushData["src"].lazy = nrdp.gibbon.Image.LAZY_DOWNLOAD;
        }
        if(!this._created && pushData) {
            pushData.background = this._background;
            this._created = true;
        }
        return pushData;
    },
    _pull: function _pull(property) {
        if(this._syncData.hasOwnProperty(property))
            return this._syncData[property];
        return nrdp.gibbon.Image._default[property];
    },
    _push: function _push(property, value) {
        this._syncData[property] = value;
        if(!this._created || nrdp.gibbon._needPendingSync()) {
            nrdp.gibbon._addPendingSync(this._widget);
            if(!this.__push)
                this.__push = {};
            this.__push[property] = value;
            return false;
        }

        this._pushed[property] = value;
        nrdp.gibbon._setProperty(this._path, property, value);
        return true;
    },

    get sendImageLoaded() { return this._pull("sendImageLoaded"); },
    set sendImageLoaded(_sendImageLoaded) {
        if(_sendImageLoaded == this.sendImageLoaded)
            return;
        this._push("sendImageLoaded", _sendImageLoaded);
    },

    addEventListener: function addEventListener(evt, listener) {
        if(nrdp._addEventListener(this, evt, listener)) {
            if(evt == "imageLoaded")
                this.sendImageLoaded = true;
        }
    },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    get height() { return this._pull("height");  },
    get width() { return this._pull("width");  },

    get visible() { return this._pull("visible"); },
    set visible(_visible) {
        this._push("visible", _visible);
    },

    _setRect: function _setRect(name, _inVal) {
        var _rect;
        var syncRect = this._pull(name);
        for(var d in syncRect) {
            if(_inVal[d] !== undefined && _inVal[d] != syncRect[d]) {
                if(!_rect)
                    _rect = new Object();
                _rect[d] = _inVal[d];
            }
        }
        if(!_rect)
            return;
        for(d in syncRect) {
            if(_rect[d] === undefined)
                _rect[d] = syncRect[d];
        }
        this._push(name, _rect);
    },

    get sourceRect() {
        var rect = this._pull("sourceRect");
        if(rect.width === undefined)
            rect.width = this.width;
        if(rect.height === undefined)
            rect.height = this.height;
        return rect;
    },
    set sourceRect(_sourceRect_in) {
        this._setRect("sourceRect", _sourceRect_in);
    },

    get purge() { return this._pull("purge"); },
    set purge(_purge) {
        if(_purge == this.purge)
            return;
        this._push("purge", _purge);
    },

    get halign() { return this.horizontalAlignment; },
    set halign(_halign) { this.horizontalAlignment = _halign; },
    get horizontalAlignment() { return this._pull("horizontalAlignment"); },
    set horizontalAlignment(_halign) {
        if(_halign == this.halign)
            return;
        this._push("horizontalAlignment", _halign);
    },

    get valign() { return this.verticalAlignment; },
    set valign(_valign) { this.verticalAlignment = _valign; },
    get verticalAlignment() { return this._pull("verticalAlignment"); },
    set verticalAlignment(_valign) {
        if(_valign == this.valign)
            return;
        this._push("verticalAlignment", _valign);
    },

    reload: function reload() {
        var url = this.url;
        if(url instanceof Object) {
            if(!url.url || !url.url.length)
                return;
            url.lazy = false;
        } else {
            if(!url || !url.length)
                return;
        }
        this._push("src", url);
    },

    get group() { return this._pull("group"); },
    set group(_group) {
        if(_group == this.group)
            return;
        this._push("group", _group);
    },

    get player() { return this._pull("player"); },

    get url() { return this.src; },
    set url(_url) { this.src = _url; },
    get src() { return this._pull("src"); },
    set src(_url) {
        var url = this._pull("src");
        if(_url instanceof Object) {
            if(_url.url)
                _url.url = nrdp.gibbon._resolveUrl(_url.url);
            // not comparing typed arrays by content, only by reference
            if(url instanceof Object
               && ((_url.url && _url.url == url.url) || (_url.data && _url.data == url.data))
               && _url.lazy == url.lazy
               && _url.async == url.async
               && _url.timeout == url.timeout
               && _url.highPriority == url.highPriority
               && _url.connectTimeout == url.connectTimeout
               && _url.stallTimeout == url.stallTimeout) {
                if(_url.headers instanceof Object && url.headers instanceof Object) {
                    var same = true;
                    var prop;
                    for(prop in _url.headers) {
                        if(!_url.headers.hasOwnProperty(prop))
                            continue;
                        if(url.headers[prop] != _url.headers[prop]) {
                            same = false;
                            break;
                        }
                        if(same) {
                            for(prop in url.headers) {
                                if(!url.headers.hasOwnProperty(prop))
                                    continue;
                                if(_url.headers[prop] != url.headers[prop]) {
                                    same = false;
                                    break;
                                }
                            }
                            if(same)
                                return;
                        }
                    }
                } else if(!_url.headers && !url.headers) {
                    return;
                }
            } else if(!url && (!_url.url || !_url.url.length) && (!_url.data || !_url.data.length)) {
                return;
            }
            var tmp = _url;
            _url = { lazy: tmp.lazy, fast: tmp.fast };
            if(tmp.async !== undefined)
                _url.async = tmp.async;
            if(tmp.highPriority)
                _url.highPriority = tmp.highPriority;
            if(tmp.timeout)
                _url.timeout = tmp.timeout;
            if(tmp.connectTimeout)
                _url.connectTimeout = tmp.connectTimeout;
            if(tmp.stallTimeout)
                _url.stallTimeout = tmp.stallTimeout;
            if(tmp.url)
                _url.url = tmp.url;
            else if(tmp.data)
                _url.data = tmp.data;
            if(tmp.headers) {
                var headers = {};
                var hasHeaders = false;
                for(var header in tmp.headers) {
                    if(!tmp.headers.hasOwnProperty(header))
                        continue;
                    var val = tmp.headers[header];
                    if(typeof val === "string") {
                        hasHeaders = true;
                        headers[header] = val;
                    }
                }
                if(hasHeaders) {
                    _url.headers = headers;
                }
            }
        } else {
            _url = nrdp.gibbon._resolveUrl(_url);
            if(_url == url)
                return;
        }
        this._push("src", _url);
        // temporarily delete width/height; when the image comes back it will be populated again
        delete this._syncData.width;
        delete this._syncData.height;
        if(!this._created && this._widget._created && !nrdp.gibbon._needPendingSync())
            this._createImage(); //realize it!
    },

    _destroyImage: function _destroyImage(recursed) {
        if(!recursed && this._created)
            nrdp.gibbon._invoke(this._path, "destroy");
        //nrdp.log.error("DestroyImage(" + this._id + "): " + JSON.stringify(pushData));
        this._created = false;
    },
    _createImage: function _createImage() {
        if(!this._created) {
            var pushData = this._pushData();
            //nrdp.log.error("CreateImage(" + this._id + "): " + JSON.stringify(pushData));
            nrdp.gibbon._construct(this._widget._path, "image", { data: pushData });
            this._created = true;
        }
    },
    _handleEvent: function _handleEvent(event) {
        var handled = true;
        if(event.name == "imageLoaded") {
            if(event.data.success) {
                if(nrdp.gibbon._breaks !== undefined)
                    this._widget._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
                if(event.data.state == "player")
                    this._syncData.player = nrdp.player;
                this._syncData.width = event.data.width;
                this._syncData.height = event.data.height;
            }
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
        } else {
            handled = false;
        }
        return handled;
    }
};
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.gibbon.Widget = function(id) {
    if(!nrdp.gibbon.widgets)
        nrdp.gibbon.widgets = {};

    this._id = id;
    this._created = false;
    this._animations = { pending: {} };
    this._name = nrdp.gibbon.Widget._createName(this._id);
    this._path = "nrdp.gibbon.widgets." + this._name;
    this.__push = undefined;
    this._pushed = {};
    this._children = [];
    this._callbacks = {};
    this._syncData = {};
};
nrdp.gibbon.Widget._createName = function(id) { return "widget" + id; };

nrdp.gibbon.Widget._default = {
    push_warnedPendingSync: false,
    effects: undefined,
    name: undefined,
    x: undefined,
    y: undefined,
    width: undefined,
    height: undefined,
    minWidth: undefined,
    minHeight: undefined,
    maxWidth: undefined,
    maxHeight: undefined,
    padding: undefined,
    layout: undefined,
    parent: undefined,
    layoutStretch: 0,
    layoutSpacing: 0,
    opacity: 1.0,
    drawOrder: 0,
    scale: 1.0,
    clip: true,
    cache: undefined,
    opaque: false,
    erase: false,
    visible: true,
    loadImages: undefined,
    smoothScale: false,
    text: undefined,
    textFirstLine: 0,
    renderTextFirstLine: 0,
    textLines: undefined,
    sendRenderProperties: false,
    textCursorPosition: undefined,
    backgroundColor: { r: 0, g: 0, b: 0, a: 0 },
    scrollX: 0,
    scrollY: 0,
    transformOriginX: 0,
    transformOriginY: 0,
    textStyle: {
        size: 30,
        lineHeight: undefined,
        weight: "bold",
        variant: "normal",
        align: [ "left" ],
        color: { r: 0, g: 0, b: 0, a: 255 },
        shadow: {
            offsetX: 0,
            offsetY: 0,
            color: { r: 0, g: 0, b: 0, a: 255 }
        },
        truncation: {
            position: "none",
            ellipsis: "..."
        },
        edgeEffect: {
            type: "none",
            width: 0,
            lightColor: { r: 0, g: 0, b: 0, a: 255 },
            darkColor: { r: 0, g: 0, b: 0, a: 255 },
            outlineColor: { r: 0, g: 0, b: 0, a: 255 }
        },
        cursor: {
            visible: false,
            style: "line",
            color: {r: 0, g: 0, b: 0, a: 255 },
            interval: 500,
            width: 1
        },
        typography: {
            kerning: true,
            tracking: 0
        },
        wrap: false,
        underline: false,
        strikethrough: false,
        richText: false
    }
};


nrdp.gibbon.Widget.prototype = {
    classname: "WidgetBridge",
    constructor: nrdp.gibbon.Widget,

    _pushData: function _pushData(all) {
        var pushData;
        if(!this._created) {
            if(!this.parent && !this._isRoot)
                return undefined;
            all = true;
        }
        if(this._effects) {
            for(var effect in this._effects) {
                var effect_pushData = this._effects[effect]._pushData(all);
                if(effect_pushData) {
                    if(!pushData)
                        pushData = {id: this._id};
                    if(!pushData.effects)
                        pushData.effects = [];
                    pushData.effects.push(effect_pushData);
                }
            }
        }
        if(this._images) {
            for(var image in this._images) {
                var image_pushData = this._images[image]._pushData(all);
                if(image_pushData) {
                    if(!pushData)
                        pushData = {id: this._id};
                    if(!pushData.images)
                        pushData.images = [];
                    pushData.images.push(image_pushData);
                }
            }
        }
        if(all) {
            if(!pushData)
                pushData = {id: this._id};
            for(var push in this._pushed)
                pushData[push] = this._pushed[push];
        }
        if(this.__push) {
            if(!pushData)
                pushData = {id: this._id};
            for(var property in this.__push) {
                if(property == "parent") {
                    var parent = undefined;
                    if(this.__push.parent) {
                        parent = this.__push.parent._id;
                        var siblings = this.__push.parent._children;
                        for(var sibling = 0; sibling < siblings.length; ++sibling) {
                            if(siblings[sibling]._id == this._id) {
                                ++sibling;
                                if(sibling < siblings.length)
                                    parent = { id: parent, before: siblings[sibling]._id };
                                break;
                            }
                        }
                    }
                    pushData.parent = parent;
                } else if(property == "clone") {
                    pushData.clone = this.__push.clone ? this.__push.clone._id : undefined;
                } else {
                    pushData[property] = this.__push[property];
                }
                this._pushed[property] = pushData[property];
            }
            this.__push = undefined;
        }
        if(!this._created && pushData) {
            if(this._children.length) {
                pushData.children = [];
                for(var child = 0; child < this._children.length; ++child)
                    pushData.children.push(this._children[child]._pushData(all));
            }
            this._created = true;
            nrdp.gibbon.widgets[this._name] = this;
            pushData.create = true;
        }
        return pushData;
    },
    _pull: function _pull(property) {
        if(this._syncData.hasOwnProperty(property))
           return this._syncData[property];
        return nrdp.gibbon.Widget._default[property];
    },
    _push: function _push(property, value) {
        this._syncData[property] = value;
        if(!this._created || nrdp.gibbon._needPendingSync()) {
            if(!this.__push) {
                nrdp.gibbon._addPendingSync(this);
                this.__push = {};
            }
            this.__push[property] = value;
            return false;
        }

        this._pushed[property] = value;
        if(property == "parent" || property == "clone")
            value = value._id;
        if(!nrdp.gibbon.Widget.push_warnedPendingSync) {
            nrdp.gibbon.Widget.push_warnedPendingSync = true;
            nrdp.log.warn("gibbon widget property set without pending sync");
        }
        nrdp.gibbon._setProperty(this._path, property, value);
        return true;
    },

    get effects() {  return this._effects; },
    addEffect: function addEffect(type, params) {
        if(!this._effects)
            this._effects = {};
        var effectid = nrdp.gibbon._nextEffectId++;
        if(effectid > nrdp.gibbon.INT_MAX)
            effectid = 1;
        var effect = new nrdp.gibbon.Effect(this, type, effectid);
        effect.params = params ? params : {};
        this._effects[effect._name] = effect;
        if(this._created)
            effect._createEffect();
        return effect;
    },
    removeEffect: function removeEffect(effect) {
        for(var e in this._effects) {
            if(this._effects[e]._id == effect._id) {
                if(this._created)
                    nrdp.gibbon._invoke(this._path, "removeEffect", { effect: effect._id });
                delete this._effects[e];
                break;
            }
        }
        effect._destroyEffect();
    },

    addEventListener: function addEventListener(evt, listener) {
        if(nrdp._addEventListener(this, evt, listener)) {
            if(evt == "renderpropertychange") {
                this.sendRenderProperties = true;
            } else if(evt == "imageLoaded" || evt == "backgroundImageLoaded") {
                var image = (evt == "imageLoaded") ? this.image : this.backgroundImage;
                if(!image._widgetImageLoadedHandler) {
                    image._widgetImageLoadedHandler = function(event) {
                        var loadEvent = { type: evt, data: event.data };
                        nrdp._callEventListeners(image._widget, loadEvent);
                    };
                    image.addEventListener("imageLoaded", image._widgetImageLoadedHandler);
                }
            }
        }
    },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    _renderProperty: function _renderProperty(property) {
        if(!this.sendRenderProperties) {
            nrdp.log.error("RenderProperty(" + property + "): Used but WIDGET(" + this._id + ") has not set sendRenderProperties!");
            this.sendRenderProperties = true;
        }
        return this._pull(property);
    },
    get sendRenderProperties() { return this._pull("sendRenderProperties"); },
    set sendRenderProperties(_sendRenderProperties) {
        if(_sendRenderProperties == this.sendRenderProperties)
            return;
        this._push("sendRenderProperties", _sendRenderProperties);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get color() { return this.backgroundColor; },
    set color(_color) { this.backgroundColor = _color; },
    get backgroundColor() { return this._pull("backgroundColor"); },
    set backgroundColor(_color) {
        if(typeof _color === "string") {
            if(_color[0] == '#') {
                var r = parseInt(_color.substr(1, 2), 16); if(r == NaN) r = 0;
                var g = parseInt(_color.substr(3, 2), 16); if(g == NaN) g = 0;
                var b = parseInt(_color.substr(5, 2), 16); if(b == NaN) b = 0;
                var a = (_color.length == 9 ? parseInt(_color.substr(7, 2), 16) : 255); if(a == NaN) a = 255;
                _color = { r:r, g:g, b:b, a:a };
            } else {
                _color = undefined;
            }
        }
        if(!(_color instanceof Object))
            _color = { r:0, g:0, b:0, a:255 };
        if(_color.r == this.color.r && _color.g == this.color.g &&
           _color.b == this.color.b && _color.a == this.color.a)
            return;
        this._push("backgroundColor", { r: _color.r, g: _color.g, b: _color.b, a: _color.a });
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get padding() { return this._pull("padding"); },
    set padding(_padding) {
        if(_padding instanceof Object) {
            if(this.padding instanceof Object && _padding.top == this.padding.top && _padding.left == this.padding.left &&
               _padding.bottom == this.padding.bottom && _padding.right == this.padding.right && _padding.wrap == this.padding.wrap)
                return;
            _padding = { top: _padding.top, left: _padding.left, bottom: _padding.bottom, right: _padding.right, wrap: _padding.wrap };
        } else if(typeof _padding == "number") {
            if(_padding == this.padding)
                return;
        } else {
            return;
        }
        this._push("padding", _padding);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get name() { return this._pull("name"); },
    set name(_name) {
        if(_name == this.name)
            return;
        this._push("name", _name);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get image() {
        if(this._image === undefined) {
            var widget = this;
            this._image = this.addForegroundImage();
        }
        return this._image;
    },
    get backgroundImage() {
        if(this._backgroundImage === undefined) {
            var widget = this;
            this._backgroundImage = this.addBackgroundImage();
        }
        return this._backgroundImage;
    },
    get images() {  return this._images; },
    addForegroundImage: function addForegroundImage(obj) { return this.addImage(false, obj); },
    addBackgroundImage: function addBackgroundImage(obj) { return this.addImage(true, obj); },
    addImage: function addImage(background, obj) {
        if(!this._images)
            this._images = {};
        var imageid = nrdp.gibbon._nextImageId++;
        if(imageid > nrdp.gibbon.INT_MAX)
            imageid = 1;
        var image = new nrdp.gibbon.Image(this, background, imageid);
        if(obj) {
            for(var i in obj)
                image[i] = obj[i];
        }
        this._images[image._name] = image;
        if(background) {
            if(!this._backgroundImage)
                this._backgroundImage = image;
        } else if(!this._image) {
            this._image = image;
        }
        return image;
    },
    removeImage: function removeImage(image) {
        if(image._background) {
            if(this._backgroundImage && this._backgroundImage._id == image._id)
                this._backgroundImage = undefined;
        }  else if(this._image && this._image._id == image._id) {
            this._image = undefined;
        }
        for(var i in this._images) {
            if(this._images[i]._id == image._id) {
                if(this._created)
                    nrdp.gibbon._invoke(this._path, "removeImage", { image: image._id });
                delete this._images[i];
                break;
            }
        }
        image._destroyImage();
    },

    get renderX() { return this._renderProperty("renderX"); },
    get renderY() { return this._renderProperty("renderY"); },
    get renderWidth() { return this._renderProperty("renderWidth"); },
    get renderHeight() { return this._renderProperty("renderHeight"); },
    get renderTextLines() { return this._renderProperty("renderTextLines"); },
    get renderTextFirstLine() { return this._renderProperty("renderTextFirstLine"); },
    get renderTextCursorPosition() { return this._renderProperty("renderTextCursorPosition"); },

    get drawOrder() { return this._pull("drawOrder"); },
    set drawOrder(_drawOrder) {
        if(_drawOrder == this.drawOrder)
            return;
        this._push("drawOrder", _drawOrder);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get minHeight() { return this._pull("minHeight"); },
    set minHeight(_minHeight) {
        if(_minHeight == this.minHeight)
            return;
        this._push("minHeight", _minHeight);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get minWidth() { return this._pull("minWidth"); },
    set minWidth(_minWidth) {
        if(_minWidth == this.minWidth)
            return;
        this._push("minWidth", _minWidth);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get maxHeight() { return this._pull("maxHeight"); },
    set maxHeight(_maxHeight) {
        if(_maxHeight == this.maxHeight)
            return;
        this._push("maxHeight", _maxHeight);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get maxWidth() { return this._pull("maxWidth"); },
    set maxWidth(_maxWidth) {
        if(_maxWidth == this.maxWidth)
            return;
        this._push("maxWidth", _maxWidth);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get loadImages() { return this._pull("loadImages"); },
    set loadImages(_loadImages) {
        if(_loadImages == this.loadImages)
            return;
        this._push("loadImages", _loadImages);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get visible() { return this._pull("visible"); },
    set visible(_visible) {
        if(_visible == this.visible)
            return;
        this._push("visible", _visible);
        if(nrdp.gibbon._showSplashScreen && nrdp.gibbon.scene.root && nrdp.gibbon.scene.root._id == this._id)
            nrdp.gibbon._showSplashScreen(!this.visible);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get smoothScale() { return this._pull("smoothScale"); },
    set smoothScale(_smoothScale) {
        if(_smoothScale == this.smoothScale)
            return;
        this._push("smoothScale", _smoothScale);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get hidden() { return !this.visible; },
    set hidden(_hidden) { this.visible = !_hidden; },

    get opacity() {
        var value = this._pull("opacity");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set opacity(_opacity) {
        if(_opacity == this.opacity)
            return;
        this._setAnimatedValue("opacity", _opacity);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get clone() { return this._pull("clone"); },
    set clone(_clone) {
        if(!_clone && !this.clone)
            return;
        if(_clone && this.clone && _clone._id == this.clone._id)
            return;
        this._push("clone", _clone);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    _addChild: function _addChild(child) {
        this._children.push(child);
    },
    _removeChild: function _removeChild(child) {
        for(var ch in this._children) {
            if(this._children[ch]._id == child._id) {
                this._children.splice(ch, 1);
                break;
            }
        }
    },
    get _isRoot() {
        if(nrdp.gibbon.scene.root && nrdp.gibbon.scene.root._id == this._id)
            return true;
        else if(nrdp.gibbon.scene.overlay && nrdp.gibbon.scene.overlay._id == this._id)
            return true;
        return false;
    },
    get children() { return this._children; },
    get parent() { return this._pull("parent"); },
    set parent(_parent) {
        if(typeof _parent === "number")
            _parent = nrdp.gibbon.findWidget(_parent);
        if(!_parent && !this.parent)
            return;
        if(_parent && this.parent && _parent._id == this.parent._id)
            return;
        if(this.parent)
            this.parent._removeChild(this);
        if(_parent) {
            _parent._addChild(this);
            if(!this._push("parent", _parent) && this.parent._created && !nrdp.gibbon._addPendingSync(this))
                this._createWidget();
        } else {
            this._syncData.parent = undefined;
            if(this._created)
                nrdp.gibbon._setProperty(this._path, "parent", undefined);
            this._destroyWidget();
        }
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreakSubtree(_parent);
    },

    get scale() {
        var value = this._pull("scale");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set scale(_scale) {
        if(_scale == this.scale)
            return;
        this._setAnimatedValue("scale", _scale);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get text() { return this._pull("text"); },
    set text(_text) {
        if(_text === null || typeof _text === "undefined")
            _text = "";
        if(_text == this.text)
            return;
        this._push("text", _text);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get clip() { return this._pull("clip"); },
    set clip(_clip) {
        if(_clip == this.clip)
            return;
        this._push("clip", _clip);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get cache() { return this._pull("cache"); },
    set cache(_cache) {
        if(_cache == this.cache)
            return;
        this._push("cache", _cache);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get textFirstLine() { return this._pull("textFirstLine"); },
    set textFirstLine(_textFirstLine) {
        if(_textFirstLine == this.textFirstLine)
            return;
        this._push("textFirstLine", _textFirstLine);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get textCursorPosition() { return this._pull("textCursorPosition"); },
    set textCursorPosition(_textCursorPosition) {
         if(_textCursorPosition == this.textCursorPosition)
             return;
         this._push("textCursorPosition", _textCursorPosition);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get textStyle() { return this._pull("textStyle"); },
    set textStyle(_textStyle_in) {
        var syncStyle = this._pull("textStyle");
        var _textStyle = {
            family: (_textStyle_in.family !== undefined) ? _textStyle_in.family : syncStyle.family,
            size: (_textStyle_in.size !== undefined) ? _textStyle_in.size : syncStyle.size,
            weight: (_textStyle_in.weight !== undefined) ? _textStyle_in.weight : syncStyle.weight,
            variant: (_textStyle_in.variant !== undefined) ? _textStyle_in.variant : syncStyle.variant,
            lineHeight: (_textStyle_in.lineHeight !== undefined) ? _textStyle_in.lineHeight : syncStyle.lineHeight,
            wrap: (_textStyle_in.wrap !== undefined) ? _textStyle_in.wrap : syncStyle.wrap,
            maxLines: (_textStyle_in.maxLines !== undefined) ? _textStyle_in.maxLines : syncStyle.maxLines,
            underline: (_textStyle_in.underline !== undefined) ? _textStyle_in.underline : syncStyle.underline,
            strikethrough: (_textStyle_in.strikethrough !== undefined) ? _textStyle_in.strikethrough : syncStyle.strikethrough,
            richText: (_textStyle_in.richText !== undefined) ? _textStyle_in.richText : syncStyle.richText,
            locale: (_textStyle_in.locale !== undefined) ? _textStyle_in.locale : syncStyle.locale
        },
        color_in = _textStyle_in.color || syncStyle.color,
        shadow_in = _textStyle_in.shadow || syncStyle.shadow,
        truncation_in = _textStyle_in.truncation || syncStyle.truncation,
        edgeEffect_in = _textStyle_in.edgeEffect || syncStyle.edgeEffect,
        cursor_in = _textStyle_in.cursor || syncStyle.cursor,
        typography_in = _textStyle_in.typography || syncStyle.typography;

        if(color_in) {
            _textStyle.color = { r: color_in.r, g: color_in.g, b: color_in.b, a: color_in.a };
        }
        if(shadow_in) {
            _textStyle.shadow = {
                offsetX: shadow_in.offsetX,
                offsetY: shadow_in.offsetY
            };
            if(shadow_in.color instanceof Object) {
                _textStyle.shadow.color = {
                    r: shadow_in.color.r,
                    g: shadow_in.color.g,
                    b: shadow_in.color.b,
                    a: shadow_in.color.a
                };
            } else if(syncStyle.shadow.color) {
                _textStyle.shadow.color = {
                    r: syncStyle.shadow.color.r,
                    g: syncStyle.shadow.color.g,
                    b: syncStyle.shadow.color.b,
                    a: syncStyle.shadow.color.a
                };
            }
        }
        if(truncation_in) {
            _textStyle.truncation = {
                position: truncation_in.position,
                ellipsis: truncation_in.ellipsis
            };
        }
        if(cursor_in) {
            var defaultCursor = nrdp.gibbon.Widget._default.textStyle.cursor;
            _textStyle.cursor = {
                visible: cursor_in.visible === undefined ? defaultCursor.visible : cursor_in.visible,
                interval: cursor_in.interval === undefined ? defaultCursor.interval : cursor_in.interval,
                style: cursor_in.style === undefined ? defaultCursor.style : cursor_in.style,
                color: cursor_in.color === undefined ? defaultCursor.color : cursor_in.color,
                width: cursor_in.width === undefined ? defaultCursor.width : cursor_in.width
            };
        }
        if(typography_in) {
            _textStyle.typography = {
                kerning: typography_in.kerning,
                tracking: typography_in.tracking
            };
        }
        if(edgeEffect_in) {
            _textStyle.edgeEffect = {
                type: edgeEffect_in.type,
                width: edgeEffect_in.width
            };

            if(edgeEffect_in.type == "raised" || edgeEffect_in.type == "depressed") {
                if(edgeEffect_in.lightColor instanceof Object) {
                    _textStyle.edgeEffect.color1 = {
                        r: edgeEffect_in.lightColor.r,
                        g: edgeEffect_in.lightColor.g,
                        b: edgeEffect_in.lightColor.b,
                        a: edgeEffect_in.lightColor.a
                    };
                } else if(edgeEffect_in.color1) {
                    _textStyle.edgeEffect.color1 = {
                        r: edgeEffect_in.color1.r,
                        g: edgeEffect_in.color1.g,
                        b: edgeEffect_in.color1.b,
                        a: edgeEffect_in.color1.a
                    };
                }
                if(edgeEffect_in.darkColor instanceof Object) {
                    _textStyle.edgeEffect.color2 = {
                        r: edgeEffect_in.darkColor.r,
                        g: edgeEffect_in.darkColor.g,
                        b: edgeEffect_in.darkColor.b,
                        a: edgeEffect_in.darkColor.a
                    };
                } else if(edgeEffect_in.color2) {
                    _textStyle.edgeEffect.color2 = {
                        r: edgeEffect_in.color2.r,
                        g: edgeEffect_in.color2.g,
                        b: edgeEffect_in.color2.b,
                        a: edgeEffect_in.color2.a
                    };
                }
            }

            if(edgeEffect_in.type == "outline") { // Outline
                if(edgeEffect_in.outlineColor instanceof Object) {
                    _textStyle.edgeEffect.color1 = {
                        r: edgeEffect_in.outlineColor.r,
                        g: edgeEffect_in.outlineColor.g,
                        b: edgeEffect_in.outlineColor.b,
                        a: edgeEffect_in.outlineColor.a
                    };
                } else if(edgeEffect_in.color1) {
                    _textStyle.edgeEffect.color1 = {
                        r: edgeEffect_in.color1.r,
                        g: edgeEffect_in.color1.g,
                        b: edgeEffect_in.color1.b,
                        a: edgeEffect_in.color1.a
                    };
                }
            }

        }
        if(_textStyle_in.align instanceof Object)
            _textStyle.align = _textStyle_in.align;
        else if(_textStyle_in.align)
            _textStyle.align = _textStyle_in.align.split(' ');
        else if(syncStyle.align)
            _textStyle.align = syncStyle.align;
        for (var _s in _textStyle) {
            var s = _textStyle[_s];
            if(s !== undefined) {
                if(_s == "color") {
                    if(syncStyle[_s] &&
                       s.r == syncStyle[_s].r && s.g == syncStyle[_s].g &&
                       s.b == syncStyle[_s].b && s.a == syncStyle[_s].a)
                        s = 0;
                } else if(_s == "align") {
                    if(syncStyle[_s] && s.join(' ') == syncStyle[_s].join(' '))
                        s = 0;
                } else if(_s == "shadow") {
                    if(syncStyle[_s] && syncStyle[_s].color &&
                       s.color.r == syncStyle[_s].color.r && s.color.g == syncStyle[_s].color.g &&
                       s.color.b == syncStyle[_s].color.b && s.color.a == syncStyle[_s].color.a &&
                       s.offsetX == syncStyle[_s].offsetX && s.offsetY == syncStyle[_s].offsetY)
                        s = 0;
                } else if(_s == "truncation") {
                    if(syncStyle[_s] && s.position == syncStyle[_s].position && s.ellipsis == syncStyle[_s].ellipsis)
                        s = 0;
                } else if(_s == "cursor") {
                    if(syncStyle[_s] && s.interval == syncStyle[_s].interval &&
                       s.visible == syncStyle[_s].visible && s.style == syncStyle[_s].style && syncStyle[_s].color &&
                       s.width == syncStyle[_s].width &&
                       s.color.r == syncStyle[_s].color.r && s.color.g == syncStyle[_s].color.g &&
                       s.color.b == syncStyle[_s].color.b && s.color.a == syncStyle[_s].color.a)
                        s = 0;
                } else if(_s == "typography") {
                    if(syncStyle[_s] && s.kerning == syncStyle[_s].kerning && s.tracking == syncStyle[_s].tracking)
                        s = 0;
                } else if(_s == "edgeEffect") {
                    if(syncStyle[_s] && s.type == syncStyle[_s].type && s.width == syncStyle[_s].width &&
                       s.color1.r == syncStyle[_s].color1.r && s.color1.g == syncStyle[_s].color1.g &&
                       s.color1.b == syncStyle[_s].color1.b && s.color1.a == syncStyle[_s].color1.a &&
                       s.color2.r == syncStyle[_s].color2.r && s.color2.g == syncStyle[_s].color2.g &&
                       s.color2.b == syncStyle[_s].color2.b && s.color2.a == syncStyle[_s].color2.a)
                        s = 0;
                } else if(s == syncStyle[_s]) {
                    s = 0;
                }
            }
            if(s !== undefined) {
                this._push("textStyle", _textStyle);
                if (nrdp.gibbon._breaks !== undefined)
                    this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
                break;
            }
        }
    },

    get transformOriginX() { return this._pull("transformOriginX"); },
    set transformOriginX(_transformOriginX) {
        if(_transformOriginX == this.transformOriginX)
            return;
        this._push("transformOriginX", _transformOriginX);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get transformOriginY() { return this._pull("transformOriginY"); },
    set transformOriginY(_transformOriginY) {
        if(_transformOriginY == this.transformOriginY)
            return;
        this._push("transformOriginY", _transformOriginY);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get scrollX() {
        var value = this._pull("scrollX");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set scrollX(_scrollX) {
        if(_scrollX == this.scrollX)
            return;
        this._setAnimatedValue("scrollX", _scrollX);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get scrollY() {
        var value = this._pull("scrollY");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set scrollY(_scrollY) {
        if(_scrollY == this.scrollY)
            return;
        this._setAnimatedValue("scrollY", _scrollY);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get erase() { return this._pull("erase"); },
    set erase(_erase) {
        if(_erase == this.erase)
            return;
        this._push("erase", _erase);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get opaque() { return this._pull("opaque"); },
    set opaque(_opaque) {
        if(_opaque == this.opaque)
            return;
        this._push("opaque", _opaque);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get layout() { return this._pull("layout"); },
    set layout(_layout) {
        if(_layout instanceof Object) {
            if(this.layout instanceof Object &&
               typeof _layout.align == typeof this.layout.align &&
               _layout.layout == this.layout.layout) {
                if(_layout.align instanceof Array && _layout.align.join(" ") == this.layout.align.join(" "))
                   return;
                else if(_layout.align == this.layout.align)
                    return;
            }
            _layout = { layout: _layout.layout, align: _layout.align };
            if(_layout.align instanceof Array) //force a deep copy if its an array
                _layout.align = _layout.align.splice(0);
        } else if(_layout == this.layout) {
            return;
        }
        this._push("layout", _layout);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get layoutSpacing() { return this._pull("layoutSpacing"); },
    set layoutSpacing(_layoutSpacing) {
        if(_layoutSpacing == this.layoutSpacing)
            return;
        this._push("layoutSpacing", _layoutSpacing);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get layoutStretch() { return this._pull("layoutStretch"); },
    set layoutStretch(_layoutStretch) {
        if(_layoutStretch == this.layoutStretch)
            return;
        this._push("layoutStretch", _layoutStretch);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get rect() { return { x: this.x, y: this.y, width: this.width, height: this.height,
                          minWidth: this.minWidth, minHeight: this.minHeight, maxWidth: this.maxWidth, maxHeight: this.maxHeight  }; },
    set rect(_rect_in) {
        var _rect;
        var animate;
        if(_rect_in.hasOwnProperty("animate")) {
            animate = _rect_in.animate;
        } else if(!this._canAnimate()) {
            animate = false;
        }
        var rect_props = { x: 1, y: 1, width: 1, height: 1, minWidth: 0, minHeight: 0, maxWidth : 0, maxHeight: 0 };
        for(var d in rect_props) {
            if(d in _rect_in && _rect_in[d] != this[d]) {
                var value = _rect_in[d];
                if(rect_props[d] && _rect_in[d] !== undefined) {
                    var valueAnimation = animate ? animate : this._findAnimation(d);
                    if(valueAnimation) {
                        this.startAnimation(d, undefined, value, valueAnimation.duration, valueAnimation.ease, false, valueAnimation.flags);
                        continue;
                    }
                }
                this._push(d, value);
            }
        }
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get x() {
        var value = this._pull("x");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set x(_x) {
        if(_x == this.x)
            return;
        this._setAnimatedValue("x", _x);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get y() {
        var value = this._pull("y");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set y(_y) {
        if(_y == this.y)
            return;
        this._setAnimatedValue("y", _y);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get width() {
        var value = this._pull("width");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set width(_width) {
        if(_width == this.width)
            return;
        this._setAnimatedValue("width", _width);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    get height() {
        var value = this._pull("height");
        if(value && value instanceof Object)
            return value.value;
        return value;
    },
    set height(_height) {
        if(_height == this.height)
            return;
        this._setAnimatedValue("height", _height);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    dump: function dump(cb) {
        if(this._created) {
            if(cb !== undefined) {
                var id = this._registerCallback(cb);
                nrdp.gibbon._invoke(this._path, "dump", { id: id });
            } else {
                nrdp.gibbon._invoke(this._path, "dump");
            }
        }
    },
    _exec: function _exec(args) {
        if(this._created) 
            nrdp.gibbon._invoke(this._path, "exec", args);
    },
    grab: function grab(cb) {
        if(this._created) {
            var id = this._registerCallback(cb);
            nrdp.gibbon._invoke(this._path, "grab", { id: id });
        }
    },
    setDisplayFlags: function setDisplayFlags(_flags) { //compat
        var opaque = false;
        var erase = false;
        var alignment = nrdp.gibbon.Image.ALIGN_NORMAL;
        var flags = _flags.split(" ");
        for (var f in flags) {
            var flag = flags[f];
            if(flag == "opaque")
                opaque = true;
            else if(flag == "erase")
                erase = true;
            else if(flag == "scale")
                alignment = nrdp.gibbon.Image.ALIGN_STRETCH;
            else if(flag == "tile")
                alignment = nrdp.gibbon.Image.ALIGN_TILE;
            else if(flag == "center")
                alignment = nrdp.gibbon.Image.ALIGN_CENTER;
        }
        this.erase = erase;
        this.opaque = opaque;
        this.image.halign = alignment;
        this.image.valign = alignment;
    },

    _setAnimatedValue: function _setAnimatedValue(property, value) {
        if(!(value instanceof Object) && value !== undefined) {
            var implicitAnimation = this._findAnimation(property);
            if(implicitAnimation && this._canAnimate()) {
                this.startAnimation(property, undefined, value, implicitAnimation.duration, implicitAnimation.ease, false, implicitAnimation.flags);
                return;
            }
        }
        this._push(property, value);
    },
    _canAnimate: function _canAnimate() {
        if(!this.parent && !this._isRoot)
            return false;
        for(var p = this; p; p = p.parent) {
            if(!p.visible)
                return false;
        }
        return true;
    },
    _findAnimation: function _findAnimation(property) {
        var animations = this._animations;
        if(animations[property] && animations[property].duration)
            return animations[property];
        return false;
    },
    stopAnimation: function stopAnimation(property, end) {
        if(this._created) {
            if(end === undefined)
                end = true;
            nrdp.gibbon._invoke(this._path, "stopAnimation", { property: property, end: end });
        }
    },
    startAnimation: function startAnimation(property, start, end, duration, ease, append, flags) {
        if(append === undefined)
            append = false;
        if(start instanceof Object && start.relative)
            start.relative = start.relative._id;
        var value = { value: end, animate: { start: start, end: end, duration: duration, ease: ease, append: append, flags: flags } };
        if(start === undefined)
            value.animate.previous = this[property];
        if(append && this.__push) {
            var oldValue = this.__push[property];
            if(oldValue && oldValue instanceof Object) {
                if(oldValue.animate instanceof Array) {
                    oldValue.value = end;
                    oldValue.animate.push(value.animate);
                    value = oldValue;
                } else if(oldValue.animate instanceof Object) {
                    var animate = [];
                    animate.push(oldValue.animate);
                    animate.push(value.animate);
                    value.animate = animate;
                }
            }
        }
        if(this._animations.pending[property] === undefined)
            this._animations.pending[property] = 1;
        else
            ++this._animations.pending[property];
        this._push(property, value);
    },
    animate: function animate(property, duration, ease, flags) {
        if(!this._animations[property]) {
            if(!duration)
                return;
        } else if(this._animations[property].duration == duration &&
                  this._animations[property].ease == ease) {
            return;
        }
        this._animations[property] = { duration: duration, ease: ease, flags: flags };
    },
    _updateProperty: function _updateProperty(property, value) {
        //nrdp.log.console("Updated " + this._path + "::" + property);
        this._syncData[property] = value;

        var evt;
        if(property == "parent") {
            this._syncData[property] = nrdp.gibbon.widgets[nrdp.gibbon.Widget._createName(value)];
        } else if(property.lastIndexOf("render", 0) == 0 && nrdp._hasEventListener(this, "renderpropertychange")) {
            var renderProperty;
            if(property == "renderX")
                renderProperty = "x";
            else if(property == "renderY")
                renderProperty = "y";
            else if(property == "renderWidth")
                renderProperty = "width";
            else if(property == "renderHeight")
                renderProperty = "height";
            else if(property == "renderTextCursorPosition")
                renderProperty = "textCursorPosition";
            else if(property == "renderTextLines")
                renderProperty = "renderTextLines";
            else if(property == "renderTextFirstLine")
                renderProperty = "textFirstLine";
            if(renderProperty) {
                evt = {
                    type: "renderpropertychange",
                    property: property,
                    renderProperty: renderProperty,
                    value: value
                };
                //nrdp.log.error("EVENT: " + evt.type + " - p=" + evt.property + ", rP=" + evt.renderProperty + ", v=" + evt.value);
            } else {
                nrdp.log.error("Unhandled widget.renderProperty: " + property);
            }
        }
        if(evt)
            nrdp._callEventListeners(this, evt);
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
    },

    _maybeBreakSubtree: function _maybeBreakSubtree(obj) {
        if (obj === undefined)
            return;
        _maybeBreakSubtree(obj.parent);
        if (nrdp.gibbon._breaks[obj._id] && nrdp.gibbon._breaks[obj._id][nrdp.gibbon.DEBUGGER_SUBTREE_MODIFIED]) {
            nrdp.gibbon._breaks[obj._id][nrdp.gibbon.DEBUGGER_SUBTREE_MODIFIED]();
        }
    },

    _maybeBreak: function _maybeBreak(type) {
        if (nrdp.gibbon._breaks[this._id] && nrdp.gibbon._breaks[this._id][type]) {
            nrdp.gibbon._breaks[this._id][type]();
        }
        this._maybeBreakSubtree(this.parent);
    },

    _destroyWidget: function _destroyWidget(recursed) {
        if(this._isRoot)
            return;
        nrdp.gibbon._removePendingSync(this);
        for(var i in this._images)
            this._images[i]._destroyImage(true);
        for(var e in this._effects)
            this._effects[e]._destroyEffect(true);
        if(!recursed && this._created)
            nrdp.gibbon._invoke(this._path, "destroy");
        //nrdp.log.console("DestroyWidget(" + this._id + ")");
        this._created = false;
        this._animations.pending = {};
        for(var child in this._children)
            this._children[child]._destroyWidget(true);
        delete nrdp.gibbon.widgets[this._name];
        if (nrdp.gibbon._breaks !== undefined)
            this._maybeBreak(nrdp.gibbon.DEBUGGER_NODE_REMOVED);
    },
    _createWidget: function _createWidget() {
        if(!this._created) {
            var pushData = this._pushData(true);
            if(!pushData)
                pushData = { id: this._id };
            //nrdp.log.error("CreateWidget(" + this._id + "): " + JSON.stringify(pushData));
            nrdp.gibbon._construct("nrdp.gibbon", "Widget", { data: pushData });
            this._created = true;
            if(!pushData["children"] && this._children.length) {
                for(var child in this._children)
                    this._children[child]._createWidget();
            }
        }
    },
    _handleEvent: function _handleEvent(event) {
        var handled = true;
        if(event.name == "animationFinished") {
            var property = event.data.property;
            --this._animations.pending[property];
            //nrdp.log.error('Finished: ' + property + " " + this._animations.pending[property]);
            if(!this._animations.pending[property]) {
                if (nrdp.gibbon._breaks !== undefined)
                    this._maybeBreak(nrdp.gibbon.DEBUGGER_ATTRIBUTE_MODIFIED);
                this._pushed[property] = this._syncData[property] = event.data.value;
            }
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
        } else if(event.name == "text") {
            //nrdp.log.console("text: " + JSON.stringify(event.data));
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
        } else if(event.name == "grab" || event.name == "dump") {
            this._callCallback(event);
        } else {
            handled = false;
        }
        return handled;
    },
    _registerCallback: function _registerCallback(cb) {
        var id = nrdp.gibbon._setValue(cb);
        return id;
    },
    _callCallback: function _callCallback(event) {
        if(event.data.id === undefined)
            return;
        var cb = nrdp.gibbon._getValue(event.data.id);
        nrdp.gibbon._deleteValue(event.data.id);
        if(cb)
            cb(event.data.data);
    }
};


/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/* global nrdp */

nrdp.gibbon.Effect = function(widget, type, id) {
    if(!widget._syncData.effects)
        widget._syncData.effects = {};

    this._id = id;
    this._type = type;
    this._widget = widget;
    this._created = false;
    this._name = nrdp.gibbon.Effect._createName(this._id);
    this._path = widget._path + ".effects." + this._name;
    this.__pushParams = this.__push = undefined;
    this._pushed = {};
    this._syncData = widget._syncData.effects[this._name];
    if(!this._syncData)
        this._syncData = widget._syncData.effects[this._name] = {};
};
nrdp.gibbon.Effect._createName = function(id) { return "effect" + id; };

nrdp.gibbon.Effect._default = {
    visible: true,
    accelerated: undefined,
    params: undefined
};

nrdp.gibbon.Effect.prototype = {
    classname: "EffectBridge",
    constructor: nrdp.gibbon.Effect,
    _pushData: function _pushData(all) {
        if(!all && !this._created)
            all = true;

        var pushData;
        if(all) {
            for(var property in this._pushed) {
                if(!pushData)
                    pushData = {id: this._id };
                pushData[property] = this._pushed[property];
            }
        }
        if(this.__push) {
            if(!pushData)
                pushData = { id: this._id };
            for(var property in this.__push) {
                var value = this.__push[property];
                if(property == "params" && this.__pushParams) {
                    var params = {};
                    for(var param in value) {
                        if(!this.__pushParams[param]) {
                            delete value[param];
                        }
                    }
                    this.__pushParams = undefined;
                }
                this._pushed[property] = pushData[property] = value;
            }
            this.__push = undefined;
        }
        if(!this._created && pushData) {
            pushData.type = this._type;
            this._created = true;
        }
        return pushData;
    },
    _pull: function _pull(property) {
        if(this._syncData.hasOwnProperty(property))
            return this._syncData[property];
        return nrdp.gibbon.Effect._default[property];
    },
    _push: function _push(property, value) {
        this._syncData[property] = value;
        if(!this._created || nrdp.gibbon._needPendingSync()) {
            nrdp.gibbon._addPendingSync(this._widget);
            if(!this.__push)
                this.__push = {};
            this.__push[property] = value;
            return false;
        }

        this._pushed[property] = value;
        nrdp.gibbon._setProperty(this._path, property, value);
        return true;
    },
    _pushParams: function _push(params, param) {
        if(!param) {
            this.__pushParams = undefined;
            return this._push("params", params);
        }

        if(!this._created || nrdp.gibbon._needPendingSync()) {
            if(!this.__pushParams)
                this.__pushParams = {};
            this.__pushParams[param] = 1;
            this._push("params", params);
            return false;
        }

        this._syncData["params"] = params;
        this._pushed["params"] = params;
        if(param) {
            var property = {};
            property[param] = params[param];
            nrdp.gibbon._setProperty(this._path, "params", property);
        } else {
            nrdp.gibbon._setProperty(this._path, "params", params);
        }
        return true;
    },

    addEventListener: function addEventListener(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function removeEventListener(evt, listener) { nrdp._removeEventListener(this, evt, listener); },

    stopAnimation: function stopAnimation(property, end) {
        if(this._created) {
            if(end === undefined)
                end = true;
            nrdp.gibbon._invoke(this._path, "stopAnimation", { property: property, end: end });
        }
    },
    startAnimation: function startAnimation(property, start, end, duration, ease, append, flags) {
        if(append === undefined)
            append = false;
        if(start instanceof Object && start.relative)
            start.relative = start.relative._id;
        var params = this.params;
        var value = { value: end, animate: { start: start, end: end, duration: duration, ease: ease, append: append, flags: flags } };
        if(start === undefined) {
            var previous = params[property];
            if(previous && previous instanceof Object)
                previous = previous.value;
            value.animate.previous = previous;
        }
        if(append && this.__push && this.__push.params && this.__pushParams && this.__pushParams[property]) {
            var oldValue = this.__push.params[property];
            if(oldValue && oldValue instanceof Object) {
                if(oldValue.animate instanceof Array) {
                    oldValue.value = end;
                    oldValue.animate.push(value.animate);
                    value = oldValue;
                } else if(oldValue.animate instanceof Object) {
                    var animate = [];
                    animate.push(oldValue.animate);
                    animate.push(value.animate);
                    value.animate = animate;
                }
            }
        }
        params[property] = value;
        this._pushParams(params, property);
    },

    get type() { return this._type; },
    get accelerated() { return this._pull("accelerated"); },

    get visible() { return this._pull("visible"); },
    set visible(_visible) {
        this._push("visible", _visible);
    },

    get params() { return this._pull("params"); },
    set params(_params) {
        if((this.type == "mask" || this.type == "diff") && _params.image) //fixup
            _params.image.url = nrdp.gibbon._resolveUrl(_params.image.url);

        this._pushParams(_params);
        if(!this._created && this._widget._created && !nrdp.gibbon._needPendingSync())
            this._createEffect(); //realize it!
    },

    _destroyEffect: function _destroyEffect(recursed) {
        if(!recursed && this._created)
            nrdp.gibbon._invoke(this._path, "destroy");
        //nrdp.log.error("DestroyEffect(" + this._id + "): " + JSON.stringify(pushData));
        this._created = false;
    },
    _createEffect: function _createEffect() {
        if(!this._created) {
            var pushData = this._pushData();
            //nrdp.log.error("CreateEffect(" + this._id + "): " + JSON.stringify(pushData));
            nrdp.gibbon._construct(this._widget._path, "effect", { data: pushData });
            this._created = true;
        }
    },

    _handleEvent: function _handleEvent(event) {
        var handled = true;
        if (event.name == "animationFinished") {
            this._syncData[event.data.property] = event.data.value;
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
        } else if (event.name == "effectChanged") {
            var evt = { type: event.name, data: event.data };
            nrdp._callEventListeners(this, evt);
        } else {
            handled = false;
        }
        return handled;
    }
};


/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */

/*
 * all nrdp communication helpers are required to do four things:
 *
 * 1. add object to nrdp._backchannels for initialization
 * 2. provide nrdp._setProperty()
 * 3. provide nrdp._invoke()
 * 4. call nrdp._gotEvent() when an event comes in.
 */

(function() {
function _gotEvent(arg) {
    nrdp._sendSyncdEvent(nrdp._gotEvent, nrdp, arg);
};

function _setupBackchannel() {
    if (typeof nrdp_platform === "undefined" || typeof this.platform.jscBridgeEnabled === "undefined" || !this.platform.jscBridgeEnabled() )
        return false;
    return this.platform.jscBridgeInit(_gotEvent);
};

function _setProperty(subobj, prop, val) {
    var obj = subobj ? "nrdp." + subobj : "nrdp";
    this.platform.jscBridgeSetProperty(obj, prop, val);
}

function _invoke(obj, method, args) {
    obj = obj ? "nrdp." + obj : "nrdp";
    this.platform.jscBridgeInvoke(obj, method, args);
}

function _construct(obj, method, args) {
    obj = obj ? "nrdp." + obj : "nrdp";
    return this.platform.jscBridgeConstruct(obj, method, args);
}

function _console(msg) {
    this.platform.log(msg);
}

function _mono() {
    return this.platform.mono();
}

function _drmTime() {
    return this.platform.drmTime();
}

function _now() {
    return this.platform.now();
}

function _atob(s, returnTypedArray) {
    return this.platform.atob(s, returnTypedArray);
}

function _btoa(s, urlSafe, returnTypedArray) {
    return this.platform.btoa(s, urlSafe, returnTypedArray);
}

function _parseJSON(json) {
    return this.platform.parseJSON(json);
}

function _parseXML(xml) {
    var obj = this.platform.parseXML(xml);
    if (obj) nrdp._fixXml(obj);
    return obj;
}

function _compress(data, type, binary) {
    return this.platform.compress(data, type, binary);
}

function _uncompress(data, type, returntypedarray) {
    return this.platform.uncompress(data, type, returntypedarray);
}

function _random(typed_array) {
    return this.platform.random(typed_array);
}

function _gctag(s) {
    return this.platform.gctag(s);
}

var backchannel = {
    name: "GibbonJavaScriptCore",
    isNative: true,
    init: _setupBackchannel,
    console: _console,
    setProperty: _setProperty,
    construct: _construct,
    invoke: _invoke,
    mono: _mono,
    drmTime: _drmTime,
    now: _now,
    atob: _atob,
    btoa: _btoa,
    atoutf8: function atoutf8(s) {
        return this.platform.atoutf8(s);
    },
    utf8toa: function utf8toa(s) {
        return this.platform.utf8toa(s);
    },
    parseJSON: _parseJSON,
    parseXML: _parseXML,
    compress: _compress,
    uncompress: _uncompress,
    random: _random,
    gctag: _gctag,
    // I know this looks weird, but running in Chrome has a problem otherwise
    platform: typeof nrdp_platform === "undefined" ? undefined : nrdp_platform
};

nrdp._backchannels.unshift(backchannel);

})();
/*
 * (c) 1997-2014 Netflix, Inc.  All content herein is protected by
 * U.S. copyright and other applicable intellectual property laws and
 * may not be copied without the express permission of Netflix, Inc.,
 * which reserves all rights.  Reuse of any of this content for any
 * purpose without the permission of Netflix, Inc. is strictly
 * prohibited.
 */


nrdp.mdx = {
    _path: "mdx",
    addEventListener: function(evt, listener) { nrdp._addEventListener(this, evt, listener); },
    removeEventListener: function(evt, listener) { nrdp._removeEventListener(this, evt, listener); },
    _encodedList: [ { list: ['body', 'url', 'USN', 'friendlyName', 'serviceType', 'location'],
                      decodeFn: decodeURIComponent },
                    { list: [ 'responseHeaders' ],
                      decodeFn: function(obj) {
                          var decodedObj = {},
                          member;
                          for(member in obj) {
                              decodedObj[member] = decodeURIComponent(obj[member]);
                          }
                          return decodedObj;
                      }
                    }
                  ],
    get NOT_INITIALIZED() { return 0; },
    get INITIALIZED() { return 1; },

    get NOT_ADVERTISING() { return 0; },
    get ADVERTISING() { return 1; },

    get NOT_DISCOVERING() { return 0; },
    get DISCOVERING() { return 1; },

    get interfaceName() { return this._syncData ? this._syncData.interfaceName : undefined; },
    get localIPAddress() { return this._syncData ? this._syncData.localIPAddress : undefined; },
    get nativeVersion() { return this._syncData ? this._syncData.nativeVersion : undefined; },
    get state() { return this._syncData ? this._syncData.state : undefined; },

    MdxConfigure: function(advertisingPeriod,
                           advertisingTTL,
                           advertisingPort,
                           listeningPort,
                           numSsdpReplies,
                           msgLimit) {
        nrdp.mdx._fn("MdxConfigure",
                     {advertisingPeriod: advertisingPeriod,
                      advertisingTTL: advertisingTTL,
                      advertisingPort: advertisingPort,
                      listeningPort: listeningPort,
                      numSsdpReplies: numSsdpReplies,
                      msgLimit: msgLimit});
    },

    MdxInit: function(host, port, serviceType, uuid, asyncHttpRequests, webSocketServer) {
        nrdp.mdx._fn("MdxInit",
                     {host: encodeURIComponent(host),
                      port: port,
                      serviceType: encodeURIComponent(serviceType),
                      uuid: encodeURIComponent(uuid),
                      asyncHttpRequests: asyncHttpRequests,
                      webSocketServer: webSocketServer});
    },

    MdxDeinit: function() {
        nrdp.mdx._fn("MdxDeinit");
    },

    AddInterfaceName: function(name) {
        nrdp.mdx._fn("AddInterfaceName", {name: encodeURIComponent(name)});
    },

    InterfaceChanged: function(newInterface, connected, ipaddress, ssid) {
        nrdp.mdx._fn("InterfaceChanged", {newInterface: encodeURIComponent(newInterface), connected: connected, ipaddress: ipaddress, ssid:ssid});
    },

    SearchForMdxDevices: function(serviceType, headerPatterns, mx, numSsdpSearches) {
        nrdp.mdx._fn("SearchForMdxDevices",
                     {serviceType: encodeURIComponent(serviceType),
                      headerPatterns: nrdp.mdx._encodeArray(headerPatterns),
                      mx: mx,
                      numSsdpSearches: numSsdpSearches});
    },

    StopMdxDiscovery: function(cb) {
        nrdp.mdx._fn("StopMdxDiscovery", undefined, cb);
    },

    RevealTargetPresence: function(cb) {
        nrdp.mdx._fn("RevealTargetPresence", undefined, cb);
    },

    SetDeviceReplyHeaders: function(deviceReplyHeaders, cb) {
        nrdp.mdx._fn("SetDeviceReplyHeaders",
                     {deviceReplyHeaders: nrdp.mdx._encodeArray(deviceReplyHeaders)},
                    cb);
    },

    HideTargetPresence: function(cb) {
        nrdp.mdx._fn("HideTargetPresence", undefined, cb);
    },

    StartMdxAdvertising: function(cb) {
        nrdp.mdx._fn("StartMdxAdvertising", undefined, cb);
    },

    StopMdxAdvertising: function() {
        nrdp.mdx._fn("StopMdxAdvertising");
    },

    SendMdxHttpRequest: function(url, requestType, xid, curlTimeout, requestHeader, requestBody) {
        nrdp.mdx._fn("SendMdxHttpRequest",
                     {url: encodeURIComponent(url),
                      requestType: requestType,
                      xid: xid,
                      curltimeout: curlTimeout,
                      requestHeader: encodeURIComponent(requestHeader),
                      requestBody: encodeURIComponent(requestBody)});
    },

    SendSessionMessage: function(url, requestType, xid, curlTimeout, context, requestHeader, requestBody, message, plaintext) {
        nrdp.mdx._fn("SendSessionMessage",
                     {url: encodeURIComponent(url),
                      requestType: requestType,
                      xid: xid,
                      curltimeout: curlTimeout,
                      context: encodeURIComponent(context),
                      requestHeader: encodeURIComponent(requestHeader),
                      requestBody: encodeURIComponent(requestBody),
                      message: encodeURIComponent(message),
                      plaintext: encodeURIComponent(plaintext)});
    },

    SendWebSocketMessage: function(host, xid, body) {
        nrdp.mdx._fn("SendWebSocketMessage",
                     {host: encodeURIComponent(host),
                      xid : xid,
                      body: encodeURIComponent(body)});
    },

    ProcessSessionMessage: function(context, xid, message, messageHmac, ciphertext, cb) {
        nrdp.mdx._fn("ProcessSessionMessage",
                     { context: encodeURIComponent(context),
                       xid: xid,
                       message: encodeURIComponent(message),
                       messageHmac: encodeURIComponent(messageHmac),
                       ciphertext: encodeURIComponent(ciphertext)
                     },
                     cb);
    },

    DialGetDeviceInfo: function(url, USN, serviceType, timeout) {
        nrdp.mdx._fn("DialGetDeviceInfo",
                     {url: encodeURIComponent(url),
                      USN: encodeURIComponent(USN),
                      serviceType: encodeURIComponent(serviceType),
                      timeout: timeout});
    },

    beginContext: function(sharedSecret, context, cb) {
        // Note: the parameter "context" is only included in order to make the
        // function signature for nrdp.mdx.beginContext identical to the function
        // signature for nrdp.ntba.beginCustomContext, it is unused (it is unused
        // in the ntba version as well, but we don't bother to even pass it here)
        nrdp.mdx._fn("beginContext",
                     {sharedSecret: sharedSecret},
                     cb);
    },

    decrypt: function(context, data, cb) {
        nrdp.mdx._fn("decrypt",
                     { context: context,
                       data: data },
                     cb);
    },

    encrypt: function(context, data, cb) {
        nrdp.mdx._fn("encrypt",
                     { context: context,
                       data: data },
                     cb);
    },

    endContext: function(context, cb) {
        nrdp.mdx._fn("endContext",
                     {context: context},
                     cb);
    },

    hmac: function(context, data, cb) {
        nrdp.mdx._fn("hmac",
                     { context: context,
                       data: data },
                     cb);
    },

    _nextIdx: 1,
    _cbs: [],
    _fn: function(name, params, cb) {
        if (!this._syncData)
            return undefined;
        if (typeof cb === "function") {
            if (!params) params = {};
            params.idx = this._nextIdx++;
            this._cbs[params.idx] = cb;
        }
        nrdp._invoke("mdx", name, params);
        return params ? params.idx : undefined;
    },

    _isEncodedString: function(eventkey) {
        for ( index in nrdp.mdx._encodedStringList) {
            if (eventkey == nrdp.mdx._encodedStringList[index])
                return true;
        }
        return false;
    },

    _decodeEventData: function(data, key) {
        var list, encodedList, item, compareKey, value;
        // set here, so if not found in any lists, the value is returned as-is
        value = data[key];
        if(typeof value != null) {
            for (list in nrdp.mdx._encodedList) {
                if(nrdp.mdx._encodedList.hasOwnProperty(list)) {
                    encodedList = nrdp.mdx._encodedList[list];
                    for (item in encodedList.list) {
                        if(encodedList.list.hasOwnProperty(item)) {
                            compareKey = encodedList.list[item];
                            if (compareKey === key) {
                                return encodedList.decodeFn(value);
                            }
                        }
                    }
                }
            }
        }
        return value;
    },

    _handleEvent: function(event) {
        var mydata = {};

        for (key in event.data) {
            mydata[key] = nrdp.mdx._decodeEventData(event.data, key);
        }

        if (event.name === "returnValue") {
            if(!event.data || !event.data.idx) {
                return false;
            }

            if (typeof this._cbs[event.data.idx] === "function") {
                var cb = this._cbs[event.data.idx];
                delete this._cbs[event.data.idx];
                cb(mydata.data, (mydata.data ? mydata.data.idx : undefined));
            }

            return true;
        } else {
            nrdp._callEventListeners(this, mydata);
            return true;
        }
    },

    _encodeArray: function(arr) {
        var encodedArr = [],
        i;
        for(i in arr) {
            if(arr.hasOwnProperty(i)) {
                encodedArr.push(encodeURIComponent(arr[i]));
            }
        }
        return encodedArr;
    },
};
