/*
 * browserplus.js
 *
 * THIS FILE IS GENERATED BY THE BUILD SYSTEM.  EDIT THE CORRESPONDING .cmakeIn FILE. 
 *
 * Provides a gateway between user js and bp browser plugins.
 * 
 * Copyright 2007-2008 Yahoo! Inc. All rights reserved.
 *
 */

// Note: Members beginning with '_' are not intended for client use.

if (typeof YAHOO == "undefined" || !YAHOO) {
    var YAHOO = {};
}

// handle multiple inclusions of this file without resetting state.
YAHOO.bp = (typeof YAHOO.bp != "undefined" && YAHOO.bp) ? YAHOO.bp : {
    listActiveServices: function (cb) {
        if (cb == null || cb.constructor != Function) {
            throw new Error("YAHOO.bp.services() invoked without " +
                            " required callback parameter.");
        } 
        return YAHOO.bp._corePlugin().EnumerateServices(cb);
    },

    getPlatformInfo: function() {
        if (YAHOO.bp._corePlugin() === null) {
            throw new Error("YAHOO.bp.getPlatformInfo() invoked, " +
                            "but init() has not completed successfully.");
        }
        return YAHOO.bp._corePlugin().Info();
    },

    isServiceLoaded: function(name, version) {
        return ((name != undefined && YAHOO.bp.hasOwnProperty(name)) &&
				(version == undefined || YAHOO.bp[name].hasOwnProperty(version)));
    },

    describeService: function(args, cb) {
        if (cb == null || cb.constructor != Function) {
            throw new Error("YAHOO.bp.services() invoked without " +
                            " required callback parameter");
        } 
        if (YAHOO.bp._corePlugin() === null) {
            throw new Error("YAHOO.bp.describeService() invoked, " +
                            "but init() has not completed successfully.");
        }
        return YAHOO.bp._corePlugin().DescribeService(args, cb);
    },

    isServiceActivated: function(args, cb) {
        return YAHOO.bp._corePlugin().DescribeService(
            args,
            (function() {
                var _cb = cb;
                return function(res) {
                    _cb(res.success);
                }
            })());
    },

    isInitialized: function() {
        return (YAHOO.bp._initState === 'succeeded');
    },

    /**
     * require attains a description of corelets from BPCore via
     * the browser plugin and adds functions appropriate to those
     * corelets to the corresponding YAHOO.bp.CORELET_NAME object.
     * the return value is the full description of the coreles.
     */           
    require: function(args, callback)
    {
        if (callback == null || callback.constructor != Function) {
            throw new Error("YAHOO.bp.require() invoked without required " +
                            "callback parameter");
        } 

        function createAPI(details) {
            // now we walk the details and populate the YAHOO.bp
            // object with callable functions!
            var corelet = details.value.name;
            var version = details.value.versionString;
            
            if (!YAHOO.bp[corelet]) {
                YAHOO.bp[corelet] = {};
                YAHOO.bp[corelet].corelet = corelet;
                YAHOO.bp[corelet].version = version;
            }
            if (!YAHOO.bp[corelet][version]) {
                YAHOO.bp[corelet][version] = {};
                YAHOO.bp[corelet][version].corelet = corelet;
                YAHOO.bp[corelet][version].version = version;
            }
            // for the core corelet we now update the version string
            if (corelet == "core") {
                YAHOO.bp[corelet].version = version;
            }
            
            if (details.value.functions) {
                for (var i = 0; i < details.value.functions.length; i++) {
                    var funcName = details.value.functions[i].name;
                    var afunc =
                        (function() {
                            var func = funcName;
                            var permaclt = corelet;
                            var permaver = version;
                            return function(args, cb) {
                                if (cb == null || cb.constructor != Function)
                                {
                                    throw new Error("YAHOO.bp." +
                                                    corelet + "." + func +
                                                    "() invoked without " +
                                                    " required callback " +
                                                    "parameter");
                                }
                                
                                return YAHOO.bp._corePlugin().ExecuteMethod(
                                    permaclt, permaver,
                                    func, args, cb);
                            };
                        })();

                    YAHOO.bp[corelet][version][funcName] = afunc;

                    // now if this is the first version of
                    // this corelet loaded, we'll plunk a
                    // "symlink" in the versionless path for
                    // convenience

                    if (version == YAHOO.bp[corelet].version) {
                        YAHOO.bp[corelet][funcName] = afunc;
                    }
                }
            }
        }

        var proxyCallback =
            (function() {
                var l_callback = callback;
                var l_createAPIFunc = createAPI;
                return function(details) {
                    // return the error code if there is one,
                    // otherwise 
                    if (details.success) {
                        for (var k = 0; k < details.value.length; k++) {
                            YAHOO.bp.describeService(
                                {service: details.value[k].service,
                                 version: details.value[k].version},
                                l_createAPIFunc);
                        }
                    }   
                    // we leverage an undocumented semantic of describe
                    // here.  we know that by the time we get here, all
                    // APIs will have been created.
                    l_callback(details);
                    l_callback = l_createAPIFunc = null;
                };
            })();
        YAHOO.bp._corePlugin().RequireService(args, proxyCallback);
        return true;
    },

    /**
     * init is called by user js and is responsible for loading
     * the BrowserPlus plugin.  args is an optional arguments map,
     * cb is required callback.  Currently known arguments in "args"
     * are locale: <locale-string>, e.g. {locale: "en_GB"}
     */
    init: function(args, cb) {
        // refresh npapi plugins.  makes sure that we work if 
        // platform update has occurred since browser started
        if (this._detectBrowser().browser == "Safari") {
            navigator.plugins.refresh(false);
        }
        
        var lArgs = null;
        var lCb = null;
        if (cb == null) {
            // only one arg in 'args', it must be callback
            lCb = args;
        } else {
            lArgs = args;
            lCb = cb;
        }
        if (lCb == null || lCb.constructor != Function) {
            throw new Error("YAHOO.bp.init() invoked without " +
                            " required callback parameter");
        }

        if (this._detectBrowser().browser == "Safari" &&
            this._detectBrowser().os == "Windows")
        {
            lCb({success: false, error: "bp.unsupportedBrowser"});
            return;
        }
        
        // if caller didn't specify locale, use browser's
        if (lArgs == null) {
            lArgs = new Object;
        }
        if (typeof(lArgs.locale) == 'undefined') {
            lArgs.locale = this._detectBrowser().locale;
        }
        
        if (YAHOO.bp._initState == 'succeeded') {
            lCb({success: true});
            return;
        } 

        if (YAHOO.bp._initState == 'inprogress') {
            YAHOO.bp._initCallbacks.push(lCb);
            return;
        }
        YAHOO.bp._initState = 'inprogress';     

        // make sure that an unload handler is installed to prevent caching
        // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching
        if (typeof(window.onunload) == 'undefined') {
            window.onunload = function() {};
        }
        
        var success = false;

        if (YAHOO.bp._setupPlugin() && YAHOO.bp._corePlugin() !== null) {
            success = true; 
        }

        if (!success) {
            YAHOO.bp._initState = 'uninitialized';
            lCb({ success: false, error: "bp.notInstalled" });
            return;
        } else {
            YAHOO.bp._initCallbacks.push(lCb);

            // Must let browser regain control for plugin to be fully ready, then
            // we can call Initialize().  Especially true on Firefox3, which will
            // destroy and reinstantiate the plugin during reflow *after* we've 
            // called Initialize().  Invoking _doInit from a timeout prevents
            // this behavior.
            var _doInit = (function() {
                var _args = lArgs;
                return function() {
                    var proxyInitCallback = function(res) {
                        YAHOO.bp._initState = res.success ? 'succeeded' : 'uninitialized';
                        // clear callbacks
                        var cbs = YAHOO.bp._initCallbacks;
                        YAHOO.bp._initCallbacks = [];
                        // call all callbacks
                        for (var i = 0; i < cbs.length; i++) cbs[i](res);
                    };
                    YAHOO.bp._corePlugin().Initialize(_args, proxyInitCallback);
                };
            })();
            
            // give control back to browser
            setTimeout(_doInit, 0);
        }
    },

    // Any asynchronous query may be canceled.  This guarantees no
    // further callbacks will be called
    cancelTransaction: function(tid) {
        // XXX: call into the plugin.
        // XXX: for now this action simply causes results to be a
        //      ignored.  We should also stop the corelet from doing
        //      work with a new protocol message.
    },

    //////////////////////////////////////////////////////////////////////////
    //
    // Internals  -  NOT FOR CLIENT USE!!
    //
    //////////////////////////////////////////////////////////////////////////
    
    // Embed an instance of the bp plugin in the current document.
    _setupPlugin: function () {
        if (this._isPluginNPAPI()) {
            return YAHOO.bp._setupPluginNPAPI();
        } else if (this._isPluginActiveX()) {
            return YAHOO.bp._setupPluginActiveX();
        } else {
            return false;
        }
    },

    _pluginLoaded: false,
    
    _npPluginName: "bpPlugin",

    // a bit of state to gracefully hanlde partial initialization
    // _initState may be "uninitialized", "inprogress", or "succeeded"
    _initState: "uninitialized",
    _initCallbacks: [],

    // browserplus.js can work with many different platform versions,
    // it will default to the newest.
    _supportedMimeTypes: [
        "application/x-yahoo-browserplus_2", 
    ],

    _setupPluginNPAPI: function() {
        // check for presence of browserplus plugin and embed it via
        // an innerHTML property in a div.  This causes the plugin to
        // load synchronously.
        var mtStr = null;
        for (var i in YAHOO.bp._supportedMimeTypes)
        {
            mtStr = YAHOO.bp._supportedMimeTypes[i];
            var mt = navigator.mimeTypes[mtStr];
            if (typeof(mt) == "object" && typeof(mt.enabledPlugin) == "object")
            {
                break;
            }
            mtStr = null;
        }        
        if (mtStr == null) return false;

        var div = document.createElement("div");
        div.style.visibility = "hidden";
        div.style.borderStyle = "hidden";
        div.style.width = 0;
        div.style.height = 0;
        div.style.border = 0;
        div.style.position = "absolute";
        div.style.top = 0;
        div.style.left = 0;
        div.innerHTML = '<object type="' + mtStr + '" ' +
                        'id="' + YAHOO.bp._npPluginName + '" ' +
                        'name="' + YAHOO.bp._npPluginName + '">' +
                        '</object>';
        document.documentElement.appendChild(div);
        
        YAHOO.bp._pluginLoaded = true;
        return true;                    
    },    

    // Returns true if plugin already exists or is created successfully.
    _setupPluginActiveX: function() {
        // If plugin already embedded we're done. 
        if (this._getAxPluginElement()) {
            return true;
        }

        for (var i in YAHOO.bp._supportedMimeTypes)    
        {
            try {
                // Create control.
                var elem = document.createElement("object");
                elem.id = "Yahoo! BrowserPlus plugin";
                elem.type = YAHOO.bp._supportedMimeTypes[i];

                // If this addon disabled by the user, this statement throws.
                elem.style.display = 'none';

                // TODO: use document.head?
                document.body.appendChild(elem);

                // Verify that the activex control is really working:
                // If the control is uncreatable this will throw.
                document.getElementById("Yahoo! BrowserPlus plugin").Ping();        
                YAHOO.bp._pluginLoaded = true;
                return true;
            }
            catch(e) {
                // TODO: log or set verbose status here?
                // Remove the dom element, it is just a shell not
                // connected to the plugin.
                document.body.removeChild(elem);
            }
        }
        return false;
    },

    _corePlugin: function() {
        if (this._isPluginNPAPI()) {
            return document.getElementById( this._npPluginName );
        } else if (this._isPluginActiveX()) {
            return this._getAxPluginElement();
        } else {
            return null;
        }
    },

    _isPluginNPAPI: function() {
        return (this._detectBrowser().browser == "Safari" ||
                this._detectBrowser().browser == "Firefox");
    },

    _isPluginActiveX: function() {
        return (this._detectBrowser().browser == "Explorer");
    },

   _getAxPluginElement: function() {
        if (this._pluginLoaded) {
            return document.getElementById("Yahoo! BrowserPlus plugin");
        } else {
            return null;
        }
    },

    
    // Browser Detection
    //
    // code adapted from: http://www.quirksmode.org/js/detect.html
    // thanks!
    _detectBrowser: function() {
        var dataBrowser = [
            {
                string: navigator.vendor,
                subString: "Apple",
                identity: "Safari"
            },
            {
                prop: window.opera,
                identity: "Opera"
            },
            {
                string: navigator.userAgent,
                subString: "Firefox",
                identity: "Firefox"
            },
            {
                string: navigator.userAgent,
                subString: "MSIE",
                identity: "Explorer",
                versionSearch: "MSIE"
            }
        ];

        var dataOS = [
            {
                string: navigator.platform,
                subString: "Win",
                identity: "Windows"
            },
            {
                string: navigator.platform,
                subString: "Mac",
                identity: "Mac"
            },
            {
                string: navigator.platform,
                subString: "Linux",
                identity: "Linux"
            }
        ];

        function searchString(data) {
            for (var i=0;i<data.length;i++){
                var dataString = data[i].string;
                var dataProp = data[i].prop;
                this.versionSearchString = data[i].versionSearch || data[i].identity;
                if (dataString) {
                    if (dataString.indexOf(data[i].subString) != -1) {
                        return data[i].identity;
                    } 
                } else if (dataProp) {
                    return data[i].identity;
                }
            }
            return null;
        }
        
        function searchVersion(dataString) {
            var index = dataString.indexOf(this.versionSearchString);
            if (index == -1) {
                return null;
            } else {
                return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
            }
        }


        if (!this.browser) {
            this.browser = searchString(dataBrowser) || "An unknown browser";
            this.version = searchVersion(navigator.userAgent) || searchVersion(navigator.appVersion) || "an unknown version";
            this.OS = searchString(dataOS) || "an unknown OS";
        }
        this.locale = navigator.language;

        return {
            browser: this.browser,
            version: this.version,
            os: this.OS,
            locale: this.locale
        };
    }
};

// an alias for backwards compatibility
if (typeof BrowserPlus == "undefined" || !BrowserPlus) {
    var BrowserPlus = YAHOO.bp;
}

