/**
 * The TwitterConnector is responsible for managing the tuner's communication
 * with the twitter api
 *
 * Somewhat helpful documentation for the twitter api and the oauth protocol
 *      http://apiwiki.twitter.com/Twitter-API-Documentation
 *      http://developer.netflix.com/docs/Security#0_18325
 */


function TwitterConnector(ck, proxyUrlPrefix) {
    this._ck = ck;
    this._proxyUrlPrefix = proxyUrlPrefix;

    this._requestToken = null;
    this._userToken = null;
    this._userTokenSecret = null;
    this._userMessage = null;
}

TwitterConnector.TWITTER_TIMEOUT = 10000;
TwitterConnector.REQUEST_TOKEN_URL = 'http://twitter.com/oauth/request_token';
TwitterConnector.AUTHORIZE_URL = 'http://twitter.com/oauth/authorize';
TwitterConnector.ACCESS_TOKEN_URL = 'http://twitter.com/oauth/access_token';
TwitterConnector.USER_STATUS_UPDATE_URL = 'http://twitter.com/statuses/update.json';

TwitterConnector.init = function(ck, proxyUrlPrefix) {
    TwitterConnector._instance = new TwitterConnector(ck, proxyUrlPrefix);
}

TwitterConnector.getInstance = function() {
    return TwitterConnector._instance;
}

TwitterConnector.prototype.share = function(/*String*/ userMessage, /*null|String*/ userToken, /*null|String*/ userTokenSecret, /* String */ uid) {
    this._userToken = userToken;
    this._userTokenSecret = userTokenSecret;
    this._uid = uid;
    this._userMessage = userMessage;

    if (isEmpty(userToken)) {
        // if we don't have a user token, we care about localconnection failures
        // if we don't have a user token, the listener will need to authenticate,
        // and after authentication, the browser needs to call back into the tuner
        // to save the credentials.
        Pandora.markTwitterFacebookConnectorAvailable("TwitterConnector_onLocalConnectionVerification");
    } else {
        // if we have credentials, 99% of the time the browser won't need to call back
        // into the tuner, and so we're not so worried if the localconnection is out
        // so there's no need to timeout this call
        this.onLocalConnectionVerification();
    }
};

TwitterConnector.prototype.onLocalConnectionVerification = function() {
    //Do we have tokens?
    if (!isEmpty(this._userToken)) {
        //YES - We have user tokens. Attempting to publish...

        // Twitter doesn't have an explicit checkPermissions API, so we'll attempt to publish
        // (publish may fail for lack of permissions)
        this.publish();
    } else {
        //NO - We don't have user tokens. Start OAuth flow...
        this.fetchRequestToken();
    }
};


//http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-oauth-request_token
TwitterConnector.prototype.fetchRequestToken = function() {
    var oauth = new OAuthSimple(this._ck, this._uid);

    oauth.setAction("GET");

    var requestPayload = oauth.sign({
        path       : TwitterConnector.REQUEST_TOKEN_URL,
        parameters : {},
        signatures :
        {
            consumer_key  : this._ck,
            shared_secret : '' + this._uid
        }
    });

    //http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-oauth-authorize
    this.xhrCall("/oauth/" + requestPayload.signed_url.substring(requestPayload.signed_url.indexOf("request_token")), false, function(responseText) {
        if (responseText == null || responseText.indexOf("oauth_token") != 0)
        {
            Pandora.onTwitterPublishFailed("request token request returned with out error, but with no data");
            return;
        }

        // use this request token to fetch an auth token
        // twitter will prompt the listener to authorize our app.  after the connection
        // is made or denied, twitter will call oauthAuthorizationCallback.jsp
        // oauthAuthorizationCallback.jsp then calls onRequestTokenFetch()
        Pandora.hideFacebookTwitterShareWaitLightbox();
        var twitterOauthWindow = window.open(TwitterConnector.AUTHORIZE_URL + "?" + responseText, "oauth");
        if (twitterOauthWindow == null){
            onPopupBlocker();
            Pandora.onTwitterPublishFailed("auth blocked by popup blocker");
        }else{
            twitterOauthWindow.focus();
        }
    });
};

// called by oauthAuthorizationCallback.jsp with the request token from twitter
TwitterConnector.prototype.onRequestTokenFetch = function(requestToken) {
    if (isEmpty(requestToken)) {
        Pandora.onTwitterPublishFailed("unable to fetch request token");
        return;
    }

    //Authorization Success. Now go get access tokens...
    this._requestToken = requestToken;
    
    var oauth = new OAuthSimple(this._ck, this._uid);
    oauth.setAction("POST");

    var accessTokenRequestPayload = oauth.sign({
        path       : TwitterConnector.ACCESS_TOKEN_URL,
        parameters :
        {
            oauth_token: this._requestToken
        },
        signatures :
        {
            consumer_key  : this._ck,
            shared_secret : '' + this._uid
        }
    });


    this.xhrCall("/oauth/" + accessTokenRequestPayload.signed_url.substring(accessTokenRequestPayload.signed_url.indexOf("access_token")), false, function(responseText) {
        if (!isEmpty(responseText)) {
            //AuthorizationSuccess. Now go get access tokens...
            var tokens = responseText.split("&");
            var userToken = ("" + tokens[0]).split("=")[1];
            var userTokenSecret = ("" + tokens[1]).split("=")[1];

            if (isEmpty(userToken) || isEmpty(userTokenSecret)) {
                //Authorization Denied\n
                this.invalidateUserTokens();
                Pandora.onTwitterPublishFailed("unable to fetch access tokens [" + responseText + "]");
                return;
            }

            this._userToken = userToken;
            this._userTokenSecret = userTokenSecret;

            this.publish();
            Pandora.setTwitterShareUserTokens(userToken, userTokenSecret);
        } else {
            //Authorization Denied\n
            Pandora.onTwitterPublishFailed("unable to fetch access tokens [" + responseText + "]");
        }
    });
};

// tweet it!
TwitterConnector.prototype.publish = function() {
    var oauth = new OAuthSimple(this._ck, this._uid);
    oauth.setAction("POST");

    var payload = oauth.sign({
        path       : TwitterConnector.USER_STATUS_UPDATE_URL,
        parameters :
        {
            status : this._userMessage,
            suppress_response_codes : true
        },
        signatures :
        {
            consumer_key  : this._ck,
            shared_secret : '' + this._uid,
            oauth_token: this._userToken,
            oauth_secret: this._userTokenSecret
        }
    });

    this.xhrCall("/statuses/" + payload.signed_url.substring(payload.signed_url.indexOf("update")), true, function(responseText)
    {
        if (isEmpty(responseText)) {
            Pandora.onTwitterPublishFailed("Twitter failed to respond");
            return;
        }

        var responseData = eval('(' + responseText + ')');
        if (responseData["error"] != null)
        {
            var errorMessage = responseData["error"];
            if (errorMessage.toLowerCase().indexOf("could not authenticate you") != -1)
            {
                // this case represents bad tokens, implying that the user has revoked access
                // from the twitter account. Reaching here means that the user now intends
                // to publish a tweet, so we need to discard our bad tokens and re-request.
                this.invalidateUserTokens();
                this.fetchRequestToken();
            } else {
                // treat any other error cases as failures
                Pandora.onTwitterPublishFailed(responseData["error"]);
            }
            return;
        }

        Pandora.onTwitterPublishSuccess();
    });
};

TwitterConnector.prototype.xhrCall = function(url, isPost, callback) {
    var xhr = this.__buildXMLHttpRequest();

    var timedout = false;
    var timeoutTimeout = setTimeout(function() {
        timedout = true;
        Pandora.onTwitterPublishFailed("twitter post timed out");
    }, TwitterConnector.TWITTER_TIMEOUT);

    xhr.onreadystatechange = bind(this, function() {
        if (xhr.readyState != 4) {
            // only allow 'loaded' states through
            return;
        }

        // we made it!  so kill the timeout timeout
        clearTimeout(timeoutTimeout);

        if (xhr.status == 500) {
            this.invalidateUserTokens();
            this.fetchRequestToken();
        }

        if (xhr.status != 200) {
            Pandora.onTwitterPublishFailed("Twitter publish failed. response code [" + xhr.status + "]");
            return;
        }

        // we already timed out, don't hit the callback
        if (timedout) {
            return;
        }

        callback.apply(this, [xhr.responseText]);
    });

    xhr.open(isPost ? "POST" : "GET", this._proxyUrlPrefix + url, true);
    if (isPost) {
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }

    xhr.send(null);
};

//override for testing
TwitterConnector.prototype.__buildXMLHttpRequest = function() {
    return new XMLHttpRequest();
};

TwitterConnector.prototype.invalidateUserTokens = function() {
    this._requestToken = null;
    this._userToken = null;
    this._userTokenSecret = null;
    Pandora.setTwitterShareUserTokens("", "");
};

/////////////////////////////
/// Tuner Callback hooks ////
/////////////////////////////
function TwitterConnector_share(/* [userMessage, userToken, userTokenSecret] */ params) {
    TwitterConnector.getInstance().share(params[0], params[1], params[2], params[3]);
}
function TwitterConnector_onRequestTokenFetch(requestToken) {
    Pandora.showFacebookTwitterShareConfirmation();
    TwitterConnector.getInstance().onRequestTokenFetch(requestToken);
}
function TwitterConnector_onLocalConnectionVerification() {
    TwitterConnector.getInstance().onLocalConnectionVerification();
}

