/*
    $Rev: 86021 $
    XXX: REQUIREMENTS
        -- OOP.js
*/

function fixWallPostBug() {
    /* FB has a bug with FB.ui.stream.publish() when it does a POST
        instead of a GET. (a POST will occur when request > 2000 chars).

        FB's code doesn't encode the params during a POST.

        http://forum.developers.facebook.com/viewtopic.php?id=60382
        http://bugs.developers.facebook.com/show_bug.cgi?id=10180
        http://github.com/facebook/connect-js/issues/#issue/65
    */

    // save off original
    origPostTarget = FB.Content.postTarget;

    // insert *working* version
    FB.Content.postTarget = function(opts) {
        FB.Array.forEach(opts.params, function(val, key) {
            if (typeof val == "object" || typeof val == "array") {
                opts.params[key] = FB.JSON.stringify(val);
            }
        });

        // call original after params are properly encoded
        origPostTarget(opts);
    };
}

function getTokenCookie(appId) {
    /*
       Retrieve the fbs_[APP_ID] cookie, and return it as an object
       (for easy access).
    */
    var cookies = document.cookie.split(';');
    var cookieName = 'fbs_' + appId;

    for (var i=0; i < cookies.length; i++) {
        var cookie = cookies[ i ];

        if (cookie.indexOf(cookieName) > 0) {
            return _makeTokenObject(cookie, cookieName);
        }
    }

    return {};
}

function _makeTokenObject(cookie, cookieName) {
    /*
       Turn the fbs_[APP_ID] cookie into an object
       whose properties are easily accessible.
    */
    var tokenObject = {};

    var cookieCrumbs = (cookie.
            replace(cookieName + '=', '').
            replace(/"/g, '')).split('&');

    // loop over name-value-pairs; add to token object
    for (var j=0; j < cookieCrumbs.length; j++) {
        var crumb = cookieCrumbs[ j ].split('=');
        tokenObject[ crumb[ 0 ].replace(/ /g, '') ] = crumb[ 1 ];
    }

    return tokenObject;
}


function clone(obj){
    /*
       http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone

       Clone an object and return it. This is very useful for an ``attachment``
       that gets used multiple times on a page (and needs to do ``.replace()``)
    */
    if (obj == null || typeof(obj) != 'object') {
        return obj;
    }
    var temp = new obj.constructor();
    for (var key in obj) {
        temp[key] = clone(obj[key]);
    }
    return temp;
}


var DEFAULTPIC = '/agbeta/ecards/fb-silhouette-50x50.jpg';


var CommonInterface = {
    /*
       XXX: base class

       A base module that holds common functionality for Facebook
       clients (visible website modules). This is more geared towards
       being able to post to a Facebook wall, but it also has methods
       for retrieving data from Facebook, and methods to display one's
       Facebook friends.
    */

    init: function(friendRenderer, pageAttrs, FBAttrs) {
        this.friendRenderer = friendRenderer;

        this.userTemplate = pageAttrs.userTemplate;
        this.friendTemplate = pageAttrs.friendTemplate;
        this.ahost = pageAttrs.ahost;
        this.site = pageAttrs.site;
        this.source = pageAttrs.source;

        this.processUsagePage = pageAttrs.processUsagePage;
        this.attachment = FBAttrs.attachment;
        this.perms = FBAttrs.perms;

        this.tokenObject = undefined; // set by checkStatus()
        this.appId = undefined; // set by initFacebook()

        this._initPageIds(pageAttrs.pageIds);

        // "{n}" is how FB.Data.query does param substitution
        this.FQLQueries = {
            user: 'SELECT name, birthday, pic_square_with_logo, uid ' +
                'FROM user WHERE uid={0}',

            friends: 'SELECT name, birthday, birthday_date, ' +
                'pic_square_with_logo, uid ' +
                'FROM user WHERE uid IN ' +
                    '(SELECT uid2 FROM friend WHERE uid1={0}) ' +
                'ORDER BY name',

            friendsAndBirthdays: 'SELECT name, birthday, birthday_date, ' +
                'pic_square_with_logo, uid ' +
                'FROM user WHERE birthday_date != "" and uid IN ' +
                    '(SELECT uid2 FROM friend WHERE uid1={0}) ' +
                'ORDER BY birthday_date ASC',

            permissions: 'SELECT {0} from permissions WHERE uid={1}'
        };
    },

    _initPageIds: function(pageIds) {
        /* Given a dictionary of DOM node IDs, initialize the IDs that we need
           for filling in various parts of the page (with FB data).
        */
        if (!pageIds) {
            pageIds = {};
        }

        this.signedInId = pageIds.signedIn;
        this.signedOutId = pageIds.signedOut;
        this.pageLoadId = pageIds.pageLoad;
        this.userContainerId = pageIds.userContainer;
        this.friendListContainerId = pageIds.friendListContainer;
        this.searchBoxId = pageIds.searchBox;
    },

    login: function(perms_, callback){
        /*
           Pop the FB login dialog. If permissions string passed
           in, use those. Else, use perms assigned to class.

           If callback passed in, assign that to the FB login callback.
        */
        var permissions = perms_ ? perms_ : this.perms;

        FB.login(
            function(response) {
                if(callback) {
                    callback(response);
                }
            },
            { perms: permissions }
        );
    },

    flashProxy: function(id, connection) {
        /* Due to security issues in Flash, this method will proxy
           the call for in Flash's stead and return the data.
        */
        if (!connection)
            connection = '';

        var fullConnection = '/' + id + '/' + connection;

        FB.api(fullConnection,
                function(response) {
                    try {
                        getFlashHandle().returnFacebookData(
                            response, id, connection);
                    }
                    catch (err) { OOP.log(err); }
                });
    },

    logout: function(callback) {
        /* Log the user out of facebook. If a callback is passed in, assign
           that to the FB logout callback.

                -- If the desire is to reload that page, GO to the page
                   again instead of calling ``reload()``.

                   This is because reload doesn't always completely
                   reload the page.  *shrug*
        */

        // reload the page ?
        if (callback && callback.toString() == 'reload') {
            callback = function() {
                window.location = window.location.toString();
            }
        }

        FB.logout(
            function(response) {
                if (callback) {
                    callback(response);
                }
            }
        );
    },

    onLoggedOut: function() {
        /*
           This will only be called if not defined in child, or if child
           calls ``this.parent()`` (which it absolutely should)
        */
        this.tokenObject = undefined;
        this._showLoggedOut();
    },

    onLoggedIn: function() {
        /*
           This will only be called if not defined in child, or if child
           calls ``this.parent()`` (which it absolutely should)
        */
        this._showLoggedIn();
    },

    getAccessToken: function() {
        /* Convenience function for Flash show calling into us.
           Also a nice helper method in general.

           Returns the ``access_token`` set by FB after logging in.
        */
        try {
            return this.tokenObject.access_token;
        } catch (err) { return ''; } // user is probably logged out
    },

    initFacebook: function(appId) {
        /*
           Initialize the Facebook environ/api.
        */
        this.appId = appId;

        FB.init({
            appId: appId,
            status: true,
            cookie: true,
            xfbml: true
        });

        // is user logged in ?
        FB.getLoginStatus(OOP.hitch(this.handleLoginStatus, this));

        // respond to session changes
        FB.Event.subscribe('auth.sessionChange',
                OOP.hitch(this.handleLoginStatus, this));

    },

    handleLoginStatus: function(response) {
        /* Wire up what to call when user is logged in/out.
               -- Also save off token cookie (as an object)
        */
        if (response.session){
            /* Logged in and has necessary permissions ?
            */
            this.tokenObject = getTokenCookie(this.appId);
            this.onLoggedIn();

            // has necessary permission ?
            //  -- If yes, this.onLoggedOn(); else, this.onLoggedOut();
            // XXX: FB continues to make baby Jesus cry :(
            //this._checkPermissionsThenAct(response.perms);
        }
        else {
            /* User is logged out (or doesn't have necessary perms).
                -- if defined in child, it will first be called there.
                -- to call this object's version, child needs to call
                    ``this.parent()`` (inside onLoggedOut)
            */
            this.onLoggedOut();
        }
    },

    _checkPermissionsThenAct: function(responsePerms) {
        /* Query FB to see what perms the user has granted our app.
            -- loop through perms that are returned;

        */
        if (!this.perms) {
            this.onLoggedOn();
        }

        if (responsePerms) {
            this.__checkPermissionsFromSession(responsePerms);
        }
        else {
            var permissionQuery = FB.Data.query(this.FQLQueries.permissions,
                    this.perms, this.tokenObject.uid);
            permissionQuery.wait(OOP.hitch(
                        this.__checkPermissionsFromDB, this));
        }
    },

    __checkPermissionsFromSession: function(perms) {
        /* Loop through perms (as returned by sessions.perms) and check them
           against what this app needs.
        */
        // (code still needed)
    },

    __checkPermissionsFromDB: function(perms) {
        /* Given permissions (as a result of an FQL call), loop over perms
           and perform appropriate action.

            -- If missing perms, performed logged-out action(s)
               Else, perform logged-in action(s)
        */
        for (var perm in perms[0]) {
            // XXX: FB has a bug with "friends_birthday"
            if (perm == "uid" || perm == "friends_birthday") {
                continue;
            }

            // missing a perm; fail
            if (perms[0][perm] == "0") {
                this.onLoggedOut();
                return;
            }
        }

        // Permissions check out; perform logged-in action(s)
        this.onLoggedIn();
    },


    renderUser: function() {
        /* Display a user's profile picture and user name.
        */
        var user = FB.api('/me', OOP.hitch(this._renderUserCallback, this));
    },

    _renderUserCallback: function(user) {
        /* Given a user object (via call to GRAPH), pull out the necessary bits
           to swap into the userTemplate.
        */
        var picSource = 'http://graph.facebook.com/' + user.id + '/picture';
        var userContainerElem = document.getElementById(this.userContainerId);

        userContainerElem.innerHTML = this.userTemplate.
            replace(/NAME/g, user.name).
            replace(/PICTURE/g, picSource);
    },

    renderFriends: function (users, friendTemplate, numShow, numCols) {
        /* Build HTML markup for a user's friends; display grid or list

           -- If ``numCols`` is specified, a graph will be rendered;
              otherwise, it will be a single list.
        */
        if (!numShow || numShow == 'all') {
            numShow = users.length;
        }

        return this.friendRenderer.makeMarkup(
                users, friendTemplate, numShow, numCols);
    },

    searchUsers: function(users, term, callback) {
        /*  Given a list of users and a search term, find
            users whose name contains that term. (searches first + last);

            If a callback is passed in, call it with refined user list,
            else return list.
        */
        term = term.replace(/ /g, '');

        var foundUsers = [];
        for (var i=0; i < users.length; i++) {

            // strip spaces from name (in a safeguarded fashion)
            // if 'null' encountered, provide a default
            var name = users[i].name;
            if (!name) {
                name = "name not found";
            } else {
                name = name.toLowerCase().replace(/ /g, '');
            }

            if (name.indexOf(term.toLowerCase()) > -1) {
                foundUsers.push(users[i]);
            }
        }

        if (!callback) {
            return foundUsers;
        }

        callback(foundUsers);
    },

    postProduct: function(friendId) {
        /* Post a product to a friend's (or the user's) FB Wall.
                -- Insert usage
                -- Retrieve usagenum
                -- Swap usagenum into attachment
                -- Pop dialog

                If ``friendId`` is empty, it will post to user's wall.
        */

        // make copy so we can re-use ``this.attachment``
        var attachment = clone(this.attachment);

        // Insert record + retrieve usage number
        var usageNumber = this.insertUsageRecord(
                this.ahost + this.processUsagePage,
                document.facebookHiddens);

        // Swap usagenum into attachment
        this._attachUsageNumber(attachment, usageNumber);

        // Pop dialog (for posting to FB Wall)
        FB.ui(
                {
                    method: 'stream.publish',
                    target_id: friendId,
                    message: '',
                    user_message_prompt: '',
                    attachment: attachment,
                    action_links: attachment.action_links || [{
                        'text': this.site,
                        'href': this.ahost + '?source=' + this.source
                    }]
                });
    },

    insertUsageRecord: function(ajaxPage, formElements) {
        /* Contact a .pd page (on our site) to insert a usage record
           into our DB. return the usage number.
        */
        var ajax = new Requester(ajaxPage, 'POST', false, false);
        var qs = ajax.formToQuery(formElements);

        //XXX: needs some serious error handling
        ajax.sendRequest(qs);

        // strip newlines
        var usageNumber = ajax.req.responseText.replace(/\n/g, '');

        return usageNumber;
    },

    _attachUsageNumber: function(attachment, usageNumber) {
        /* Replace (in the known spots) 'USAGENUM' with the actual
           order usage number.

                known places:
                    * main ``href``
                    * ``properties`` dict
                    * ``media`` list (of dicts)
                        * copy over from ``attachment.href``
        */

        // main ``href``
        attachment.href = attachment.href.
            replace(/USAGENUM/g, usageNumber);

        // ``properties`` dict
        for (var item_ in attachment.properties) {
            attachment.properties[item_].href = attachment.
                properties[item_].href.
                    replace(/USAGENUM/g, usageNumber);
        }

        // ``media`` list
        for (var i=0; i < attachment.media.length; i++) {
            attachment.media[i].href = attachment.href;
        }
    },

    _showLoggedIn: function() {
        /* SHOW logged in and HIDE logged out.
        */
        this.__toggleLoginState('block', 'none');
    },

    _showLoggedOut: function() {
        /* HIDE logged in and SHOW logged out.
        */
        this.__toggleLoginState('none', 'block');
    },

    __toggleLoginState: function(signedInStyle, signedOutStyle) {
        // hide the page-loading div (if present)
        try {
            if (this.pageLoadId) {
                document.getElementById(this.pageLoadId).
                    style.display = 'none';
            }
        } catch (err) { OOP.log(err); }

        // display/hide the logged in/out sections appropriately
        try {
            if (this.signedInId) {
                document.getElementById(this.signedInId).
                    style.display = signedInStyle;
            }

            if (this.signedOutId) {
            document.getElementById(this.signedOutId).
                style.display = signedOutStyle;
            }

        } catch (err) { OOP.log(err); }
    }
};
