//  HULU CONFIDENTIAL MATERIAL. DO NOT DISTRIBUTE.
//  Copyright (C) 2009-2010 Hulu, LLC
//  All Rights Reserved
/**
 * htvProfile.js 
 * 
 * Houses profile data and performs actions based on your profile
 * http://code.hulu.com/wiki/SiteApi
 */
var $htv;
/*jslint maxerr: 1000, nomen: false, evil: false, immed: true, plusplus: false, regexp: false */
/*global LOG, describe, _makeSiteApiRequest, _handleOK, _handleQueueCheckResult */
/*global _onSiteApiResult, _handleInviteRequestResult, _twinkieUserRequestResult */
/*global _handleTokenRenewResult, _isLoggedIn, _handleAuthenticateDeviceToken */
/*global _handleCheckAuthenticationCode, _makePlaybackProgressRequest, _makeTwinkieUserRequest */
/*global _handleXapiSubscriptionsResult, _handleNonceResult, _handleAuthenticateResult */
/*global _handleAuthenticateSuccess, _handleAuthenticationCode, _handleXapiQueueResult */
/*global _handleNextVideoResult */


$htv.Profile = (function () {
    var _state, _requestQueue, _authenticationCode, _authenticationTimer, _authenticationPollingEnabled, _lastAuthenticationTime, SECURE_API_ACTIONS, USE_STAGING, POLLING_INTERVAL;
    
    
    _state = {};
    _state.queue = {};
    _state.subscriptions = {};
    _state.history = {};
    
    // we can populate this using a call eventually.
    _state.playback = {};
    
    _requestQueue = [];
    
    _lastAuthenticationTime = -1;
    _authenticationCode = null;
    _authenticationTimer = null;
    _authenticationPollingEnabled = false;
    
    SECURE_API_ACTIONS = {
        nonce: true,
        authenticate: true,
        authentication_code: true,
        authenticate_code: true,
        authentication_passed: true,
        renew: true,
        request_invite: true
    };

    POLLING_INTERVAL = 10000;
    
    USE_STAGING = false;
    
    function _renewToken() {
        _makeSiteApiRequest("renew", null, {}, null, null, _handleTokenRenewResult, {
            xmlparse: true
        });
    }
    
    function _setPlaybackProgress(content_id, viewed_sec, duration_sec, force_post, callback) {
        var params, key, body;
        if (_state.playback[content_id]) {
            _state.playback[content_id].viewed = viewed_sec;
            _state.playback[content_id].duration = duration_sec;
        } else {
            _state.playback[content_id] = {
                viewed: viewed_sec,
                duration: duration_sec,
                lastPostedPosition: 0
            };
        }
        
        if (force_post || (Math.abs(viewed_sec - _state.playback[content_id].lastPostedPosition) * 1000 >= $htv.Constants.Endpoints.pb_interval)) {
            _state.playback[content_id].lastPostedPosition = viewed_sec;
            
            // Only send update to tracker service if logged in
            if (_isLoggedIn()) {
                params = {
                    content_id: content_id,
                    position: viewed_sec,
                    token: _state.token,
                    device_name: $htv.Platform.properties.friendly_name,
                    device_id: $htv.Platform.properties.deviceid
                };
                body = "";
                for (key in params) {
                    if (params.hasOwnProperty(key)) {
                        body = body + key + "=" + $htv.Utils.urlEncode(params[key]) + "&";
                    }
                }
                body = body.slice(0, -1);
                
                
                $htv.Platform.loadURL($htv.Constants.Endpoints.pb_tracker + "/position", this, function (responseData, options) {
                    if (callback) {
                        callback();
                    }
                }, {
                    body: body,
                    content_type: "application/x-www-form-urlencoded",
                    method: "POST",
                    xmlparse: false
                });
            }
        }
    }
    
    function _dismissResume(content_id) {
        
        $htv.Platform.loadURL($htv.Constants.Endpoints.pb_tracker + "/dismiss_prompt", this, function (responseData, options) { }, {
            body: "token=" + _state.token + "&content_id=" + content_id,
            content_type: "application/x-www-form-urlencoded",
            method: "POST",
            xmlparse: false
        });
    }
    
    // TODO:  we use content_id (rarely pid now) to play videos. however, the rest of the api uses
    // video_ids.
    function _getPlaybackProgress(content_id) {
        if (_state.playback[content_id]) {
            return _state.playback[content_id];
        } else {
            return {viewed: 0, duration: 0};
        }
    }
    
    function _fetchUserData() {
        _makePlaybackProgressRequest(); 
        _makeTwinkieUserRequest($htv.Constants.MENU_PREFIX + "user_queue", _handleXapiQueueResult);
        _makeTwinkieUserRequest($htv.Constants.MENU_PREFIX + "user_subscriptions", _handleXapiSubscriptionsResult);
    }
    
    function _handlePlaybackProgressResult(result) {
        var i, items = [], video, videos = [];

         

        // parse json response
        try {
            videos = $htv.Platform.parseJSON(result).videos;
        } catch (e) {
            
        }
        
        for (i = 0; i < videos.length; i++) {
            video = videos[i];
            try {
                _state.playback[video.content_id] = {
                    viewed: parseFloat(video.position, 10),
                    duration: parseFloat(video.duration, 10),
                    lastPostedPosition: parseFloat(video.position, 10)
                    
                    /* Other fields availabe, if needed:
                    last_played_device_name: video.last_played_device_name,
                    last_played_at: video.last_played_at, // convert from XML Schema format
                    should_resume: parseInt(video.should_resume) === 1,
                    should_prompt: parseInt(video.should_prompt) === 1
                    */
                };
                
                if (i === 0 && parseInt(video.should_resume, 10) === 1 && parseInt(video.should_prompt, 10) === 1) {
                    $htv.Controller.fireEvent("STARTUP_SHOULD_RESUME", {content_id : video.content_id});
                }
                
                // Just in case (there was a temporary bug in pb_tracker)
                if (isNaN(_state.playback[video.content_id].viewed)) {
                    _state.playback[video.content_id].viewed = 0;
                }
                if (isNaN(_state.playback[video.content_id].duration)) {
                    // Viewed is useless without duration
                    _state.playback[video.content_id].viewed = 0;
                    _state.playback[video.content_id].duration = 0;
                }
                if (isNaN(_state.playback[video.content_id].lastPostedPosition)) {
                    _state.playback[video.content_id].lastPostedPosition = 0;
                }
            } catch (err) {                
                
                describe(err);
            }
        }        
    }
    
    function _handleXapiQueueResult(result) {
        
        for (var i = 0; i < result.length; i++) {
            try {
                _state.queue[result[i].video_id] = true;
            } catch (e) {
                
                describe(e);
            }
        }
    }
    
    function _handleXapiSubscriptionsResult(result) {
        
        var i, subscription, subscription_string;
        for (i = 0; i < result.length; i++) {
            subscription = {
                episodes: "none",
                clips: "none",
                error: ""
            };
            subscription_string = result[i].subscribed_types;
            if (subscription_string.indexOf("episodes") !== -1) {
                if (subscription_string.indexOf("episodes:1") !== -1) {
                    subscription.episodes = "first_run_only";
                } else {
                    subscription.episodes = "all";
                }
            }
            if (subscription_string.indexOf("clips") !== -1) {
                subscription.clips = "all";
            }
            
            _state.subscriptions[result[i].show_id] = subscription;
        }
    }
    
    function _makeTwinkieUserRequest(path, callback) {
        // TODO: could use paged twinkie collection probably to be more cache-aware
        var url = $htv.Constants.Endpoints.menu + "/menu/" + path + "?cb=" + new Date().getTime() + "&limit=100&user_id=" + _state.token;
        url = $htv.Utils.applyGlobalContext(url);
        $htv.Platform.loadURL(url, this, _twinkieUserRequestResult, {
            xmlparse: true,
            cb: callback
        });
        
    }

    function _makePlaybackProgressRequest(callback) {
        var url = $htv.Constants.Endpoints.pb_tracker +
            "/videos?format=json&limit=" + 100 +
            "&token=" + _state.token;
        
        $htv.Platform.loadURL(url, this, _handlePlaybackProgressResult, {
            xmlparse: false
        });
    }
    
    function _isVideoInQueue(video_id) {
        return _state.queue.hasOwnProperty("" + video_id);
    }
    
    function _twinkieUserRequestResult(responseData, options) {
        var i, item, items = [], result;
        // do xml stuff for twinkie
        try {
            result = $htv.Platform.evaluateXPath(responseData, "items/item");
        } catch (e) {
            
        }
        for (i = 0; i < result.length; i++) {
            item = $htv.Platform.getXPathResultAt(result, i);
            items.push({
                video_id: $htv.Platform.evaluateXPathString(item, "data/video_id"),
                content_id: $htv.Platform.evaluateXPathString(item, "data/content_id"),
                duration: $htv.Platform.evaluateXPathInt(item, "data/duration"),
                show_id: $htv.Platform.evaluateXPathString(item, "data/show_id"),
                subscribed_types: $htv.Platform.evaluateXPathString(item, "data/subscribed_types")
            });
        }
        options.cb.call(this, items);
    }
    
    
    function _handleTokenRenewResult(responseData) {
        var expires_at;
        
        _state.token = $htv.Platform.evaluateXPathString(responseData, "response/token");
        expires_at = $htv.Platform.evaluateXPathString(responseData, "response/token-expires-at");
        // do some date parsing here since JS date parsing is impl-dependent and we know our format
        _state.expires_date = $htv.Utils.parseDate(expires_at).toUTCString();
        
        $htv.Platform.writeLocalData("profile", "expires_date", _state.expires_date);
        $htv.Platform.writeLocalData("profile", "username", _state.username);
        $htv.Platform.writeLocalData("profile", "userid", _state.userid);
        $htv.Platform.writeLocalData("profile", "token", _state.token);
    }
    
    // get a token.  maybe this needs to be called on every api call. ?
    function _login(username, password, receiver, successCallback, failureCallback) {
        
        _state.logged_in = false;
        _state.token = "";
        _state.username = "";
        _state.userid = 0;
        
        // Record as logged-out until success
        $htv.Controller.fireEvent("PROFILE_STATE_CHANGED", { state: _state });
        
        _makeSiteApiRequest("nonce", null, {}, receiver, successCallback, _handleNonceResult, {
            xmlparse: true,
            login: username,
            password: password,
            failureCB: failureCallback
        });
    }
    
    function _logout() {
        // TODO: make site api call to de-authenticate device
        
        // Record as logged-out until success
        _state.logged_in = false;
        _state.token = "";
        _state.username = "";
        _state.userid = 0;
        $htv.Controller.fireEvent("PROFILE_STATE_CHANGED", { state: _state });
        
        // clear device token.
        $htv.Platform.writeLocalData("profile", "device_token", null);
        $htv.Platform.writeLocalData("profile", "authentication_code", null);
        $htv.Platform.recordEvent("user logged out");
    }
    
    function _handleNonceResult(responseData, options) {
        
        var nonce = $htv.Platform.evaluateXPathString(responseData, "nonce");
        
        _makeSiteApiRequest("authenticate", null, {
            nonce: nonce,
            login: options.login,
            password: options.password,
            friendly_name: $htv.Platform.properties.friendly_name,
            serial_number: $htv.Platform.properties.deviceid 
        }, options.closure.recv, options.closure.cb, _handleAuthenticateResult, {failureCB: options.failureCB, xmlparse: true });
    }
    
    function _handleAuthenticateResult(responseData, options) {
        
        
        var token = $htv.Platform.evaluateXPathString(responseData, "response/token");
        if (token && token.length > 0) {
            _handleAuthenticateSuccess(responseData);
            options.closure.cb.call(options.closure.recv);
        }
        else {
            options.failureCB.call(options.closure.recv); 
        }
    }
    
    // Called by _handleAuthenticateResult when htvProfile.login() is used.
    // Also called externally by ActivationView once code-based authentication succeeds
    function _handleAuthenticateSuccess(responseData) {
        var device_token, expires_at;
        //update the user state from the response
        _state.logged_in = true;
        _state.token = $htv.Platform.evaluateXPathString(responseData, "response/token");
        _state.username = $htv.Platform.evaluateXPathString(responseData, "response/username");
        _state.userid = $htv.Platform.evaluateXPathInt(responseData, "response/id");
        _state.display_name = $htv.Platform.evaluateXPathString(responseData, "response/display-name");
        _state.first_name = $htv.Platform.evaluateXPathString(responseData, "response/first-name");
        _state.plan_id = $htv.Platform.evaluateXPathInt(responseData, "response/plid");
        _state.plus_user = $htv.Platform.evaluateXPathString(responseData, "response/plu");
        _state.on_hold = $htv.Platform.evaluateXPathString(responseData, "response/plh");
        _state.age = $htv.Platform.evaluateXPathString(responseData, "response/age");

        device_token = $htv.Platform.evaluateXPathString(responseData, "response/device-token");
        expires_at = $htv.Platform.evaluateXPathString(responseData, "response/token-expires-at");
        // do some date parsing here since JS date parsing is impl-dependent and we know our format
        _state.expires_date = $htv.Utils.parseDate(expires_at).toUTCString();
        
        
        
        
        
        //write profile data to file
        $htv.Platform.writeLocalData("profile", "expires_date", _state.expires_date);
        $htv.Platform.writeLocalData("profile", "username", _state.username);
        $htv.Platform.writeLocalData("profile", "userid", _state.userid);
        $htv.Platform.writeLocalData("profile", "token", _state.token);       
        if (!$htv.Utils.stringIsNullOrEmpty(device_token)) {
            $htv.Platform.writeLocalData("profile", "device_token", device_token);
            $htv.Platform.writeLocalData("profile", "authentication_code", null);
            
        }
        
        $htv.Controller.fireEvent("PROFILE_STATE_CHANGED", { state: _state });
        
        _fetchUserData();
    }
    
    function _getUserToken() {
        return _state.token;
    }
    
    function _getDisplayName() {
        return _state.display_name;
    }
    
    function _getFirstName() {
        return _state.first_name;
    }
    
    function _isLoggedIn() {
        return !$htv.Utils.stringIsNullOrEmpty(_state.token);
    }
    
    function _isPlusUser() {
        return !$htv.Utils.stringIsNullOrEmpty(_state.plus_user) && _state.plus_user.toLowerCase() === "true";
    }

    function _accountOnHold() {
        return !$htv.Utils.stringIsNullOrEmpty(_state.on_hold) && _state.on_hold.toLowerCase() === "true";
    }

    function _getAge() {
        return parseInt(_state.age, 10);
    }

    function _getAuthenticationCode(receiver, callback) {
        var saved_code = $htv.Platform.readLocalData("profile", "authentication_code");
        if (saved_code && saved_code.length > 0) {
            
            callback.call(receiver, {
                success: true,
                error: "",
                code: saved_code
            });
        } else {
            
            _makeSiteApiRequest("authentication_code", null, {
                friendly_name: $htv.Platform.properties.friendly_name,
                serial_number: $htv.Platform.properties.deviceid,
                affiliate_name: $htv.Platform.properties.distro.toLowerCase(),
                product_name: $htv.Platform.properties.model
            }, receiver, callback, _handleAuthenticationCode, {
                xmlparse: true
            });
        }
    }
    
    function _handleAuthenticationComplete() {
        
        if (_authenticationTimer !== null) {
            _authenticationTimer.destroy();
            _authenticationTimer = null;
        }
        _lastAuthenticationTime = -1;
        _authenticationCode = null;
        
        if ($htv.Profile.isPlusUser()) { 
            if ($htv.Profile.accountOnHold()) { 
                $htv.Platform.recordEvent("plus-account-hold user logged in");
                $htv.Controller.showAccountOnHoldDialog();
            } else { 
                $htv.Platform.recordEvent("plus user logged in");
                $htv.Controller.popAllAndPushView("MainView", {});
            }    
        } else {
            $htv.Platform.recordEvent("non-plus user logged in");
        }
    }
    
    function _handleAuthenticationCode(data, options) {
        
        var result, authCode;
        result = {
            success: false,
            error: "",
            code: ""
        };
        authCode = $htv.Platform.evaluateXPathString(data, "response/code");
        if (authCode && authCode.length > 0) {
            result.code = authCode;
            result.success = true;

            
            $htv.Platform.writeLocalData("profile", "authentication_code", result.code);
        } else {
            result.error = $htv.Platform.evaluateXPathString(data, "error");
            result.success = false;
        }
        
        describe(result);
        // parse to check for OK and set success boolean
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _checkAuthenticationCode(code, receiver, callback) {
        _makeSiteApiRequest("authentication_passed", null, { code: code, serial_number: $htv.Platform.properties.deviceid }, receiver, callback, _handleCheckAuthenticationCode, { xmlparse: true });
    }
    
    function _onAuthenticationTest(result) {
        
        if (result.success === true) {
            _handleAuthenticationComplete();
        }
    }
    
    function _onAuthenticationTimer() {
        
        _lastAuthenticationTime = (new Date()).getTime();
        _checkAuthenticationCode(_authenticationCode, this, _onAuthenticationTest);
    }
    
    function _onAuthenticationCodeLoaded(result) {
        
        if (_authenticationPollingEnabled === false) {
            
            return;
        }
        if (result.success) {
            _authenticationCode = result.code;
            
            _authenticationTimer.start();
            
            var now = (new Date()).getTime();
            if (now - _lastAuthenticationTime > POLLING_INTERVAL) {
                
                _lastAuthenticationTime = now;
                _checkAuthenticationCode(_authenticationCode, this, _onAuthenticationTest);
            }
        }
    }
    
    // this should be called twice in every view that does polling
    // 1. when the view is initialized (make sure not to call _getAuthenticationCode twice / if initialize for the view calls it, enable polling in the handler)
    // 2. handler for VIEW_DID_REAPPEAR
    function _enableAuthenticationPolling() {
        
        _authenticationPollingEnabled = true;
        if (_authenticationTimer === null) {
            
            _authenticationTimer = $htv.Platform.createTimer(POLLING_INTERVAL, this, _onAuthenticationTimer);
        }
        _getAuthenticationCode(this, _onAuthenticationCodeLoaded);
    }
    
    // should be called in handler for VIEW_WILL_DISAPPEAR
    function _disableAuthenticationPolling() {
        _authenticationPollingEnabled = false;
        if (_authenticationTimer !== null) {
            
            _authenticationTimer.stop();
        }
    }
    
    function _handleCheckAuthenticationCode(data, options) {
        var result, token;
        
        result = {
            success: false,
            error: ""
        };
        
        // If the response contains a token, then the code has been logged in
        token = $htv.Platform.evaluateXPathString(data, "response/token");
        if (token && token.length > 0) {
            // TODO: confirm that the user has a Plus account
            
            // Provide response data to htvProfile to be used later
            _handleAuthenticateSuccess(data);
            result.success = true;
        } else {
            result.error = $htv.Platform.evaluateXPathString(data, "error");
            result.success = false;

            if (result.error === "Code not found.") {
                
                $htv.Platform.writeLocalData("profile", "authentication_code", null);
            }
        }
        
        describe(result);
        // parse to check for OK and set success boolean
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _authenticateDeviceToken(device_token, receiver, callback) {
        var cb = function () {
            callback.call(receiver, false);
        };

        _makeSiteApiRequest("authenticate_code", null, { device_token: device_token}, receiver, callback, _handleAuthenticateDeviceToken, { xmlparse: true, errorCallback: cb });
    }
    
    function _handleAuthenticateDeviceToken(data, options) {
        var result, token;
        result = {success: false};
        token = $htv.Platform.evaluateXPathString(data, "response/token");
        if (token && token.length > 0) {
            result.success = true;
            _handleAuthenticateSuccess(data);
        } else {
            result.error = $htv.Platform.evaluateXPathString(data, "error");
        }
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _getNextVideo(content_id, type, receiver, callback) {
        _makeSiteApiRequest("user", "nextvideo", {
            content_id: content_id,
            type: type,
            device: "living_room"
        },
        receiver, callback, _handleNextVideoResult, {xmlparse: true});
    }
    
    function _handleNextVideoResult(data, options) {
        var result, video_id, type;
        result = {error: ""};
        
        video_id = $htv.Platform.evaluateXPathString(data, "next-video/video-id");
        
        if (video_id) {
            if (video_id !== "nil") {
                result.video_id = video_id;
                result.type = $htv.Platform.evaluateXPathString(data, "next-video/type");    
            }
            else {
                result.error = "Video Id was nil";
            }
        }
        else {
            result.error = $htv.Platform.evaluateXPathString(data, "error");
        }
        describe(result);
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _addToQueue(video_id, receiver, callback) {
        // todo: not a very good place but _handleOK is being shared by a few things.  should
        // wait for the response.
        _state.queue["" + video_id] = true;
        _makeSiteApiRequest("queue", "add", {id: video_id}, receiver, callback, _handleOK);
    }
   
    function _removeFromQueue(video_id, receiver, callback) {
        delete _state.queue["" + video_id];
        _makeSiteApiRequest("queue", "remove", {id: video_id}, receiver, callback, _handleOK);
    }
    
    function _checkInQueue(video_id, receiver, callback) {
        _makeSiteApiRequest("queue", "check", {id: video_id}, receiver, callback, _handleQueueCheckResult);
    }
    
   
    function _getQueuedVideoInfo(video_id, receiver, callback) {
        // not implemented.  not sure what this does.
        return {};
    }
    
    // NOTE: if type isn't episodes or clips or episodes_first_run_only, it is 'everything'.  
    function _addSubscription(show_id, type, receiver, callback) {
        // todo: pretty much assume this was done..
        var subscription = {
            episodes: "none",
            clips: "none",
            error: ""
        };
        if (_state.subscriptions.hasOwnProperty(show_id)) {
            subscription = _state.subscriptions[show_id];
        }
        if (type === "clips") {
            subscription.clips = "all";
        }
        if (type === "episodes") {
            subscription.episodes = "all";
        }
        if (type === "episodes_first_run_only") {
            subscription.episodes = "first_run_only";
        }
        _state.subscriptions[show_id] = subscription;
        _makeSiteApiRequest("subscription", "add", {type: type, id: show_id}, receiver, callback, _handleOK);   
    }
    
    function _removeSubscription(show_id, type, receiver, callback) {
        var subscription = {
            episodes: "none",
            clips: "none",
            error: ""
        };
        if (_state.subscriptions.hasOwnProperty(show_id)) {
            subscription = _state.subscriptions[show_id];
        }
        if (type === "clips") {
            subscription.clips = "none";
        }
        if (type === "episodes") {
            subscription.episodes = "none";
        }
        _state.subscriptions[show_id] = subscription;
        _makeSiteApiRequest("subscription", "remove", {type: type, id: show_id}, receiver, callback, _handleOK);
    }
    
    function _getSubscriptionInfo(show_id, receiver, callback) {
        if (_state.subscriptions.hasOwnProperty(show_id)) {
            callback.call(receiver, _state.subscriptions[show_id]);
        } else {
            callback.call(receiver, {
                episodes: "none",
                clips: "none",
                error: ""
            });
            
        }
        // todo: actually, this could be sourced from pd_user_subscriptions as well.....
        // _makeSiteApiRequest("subscription", "get", {id: show_id}, receiver, callback, _handleSubscriptionInfoResult);
    }
    
    function _recordAnonymousVideoViewStart(video_id, receiver, callback) {
        // todo: this is actually a get call.. doesnt use token.
    }
    
    function _recordVideoViewStart(video_id, receiver, callback) {
        _makeSiteApiRequest("event", null, {event_type: "view", id: video_id, target_type: "video"}, receiver, callback, _handleOK);
    }
    
    function _recordVideoViewComplete(video_id, receiver, callback) {
        _makeSiteApiRequest("event", null, {event_type: "view_complete", id: video_id, target_type: "video"}, receiver, callback, _handleOK); 
        _state.history[video_id] = true;
        if (_isVideoInQueue(video_id) === true) {
            delete _state.queue["" + video_id];
        }
    }
    
    function _rateShow(show_id, rating, receiver, callback) {
        _makeSiteApiRequest("vote", null, {target_type: "show", id: show_id, rating: rating}, receiver, callback, _handleOK);
    }
    
    function _rateVideo(video_id, rating, receiver, callback) {
        _makeSiteApiRequest("vote", null, {target_type: "video", id: video_id, rating: rating}, receiver, callback, _handleOK);
    }
    
    // Used when consumers of makeSiteApiRequest want to handle the response directly
    function _handleDirectly(data, options) {
        
        options.closure.cb.call(options.closure.recv, data);
    }
    
    function _handleOK(data, options) {
        var result, match;
        result = {
            success: false,
            error: ""
        };
        result.success = (data.search(/<ok><\/ok>/) !== -1);
        if (!result.success) {
            match = data.match(/<error>(.+)<\/error>/);
            describe(match);
            if (match.length > 0) {
                result.error = match[1];
            }
        }
        
        describe(result);
        // parse to check for OK and set success boolean
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _handleQueueCheckResult(data, options) {
        var match, result, pos;
        
        result = {
            queued: false,
            error: ""
        };
        
        pos = data.search(/<queued type="boolean">(.+)<\/queued>/);
        if (pos > -1) {
            
            result.queued = data.slice(pos + 23, pos + 27) === "true";
        }
        else {
            match = data.match(/<error>(.+)<\/error>/);
            describe(match);
            if (match.length > 0) {
                result.error = match[1];
            }
        }
        
        describe(result);
        // parse to check for OK and set success boolean
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
    
    function _handleSubscriptionInfoResult(data, options) {
        var match, result;
        result = {
            episodes: "none",
            clips: "none",
            error: ""
        };
        match = data.match(/<episodes>(.+)<\/episodes>/);
        if (match.length > 1) {
            
            result.episodes = match[1];
            match = data.match(/<clips>(.+)<\/clips>/);
            if (match.length > 1) {
                result.clips = match[1];
            }
        }
        else {
            match = data.match(/<error>(.+)<\/error>/);
            describe(match);
            if (match.length > 0) {
                result.error = match[1];
            }
        }
        // parse to check for OK and set success boolean
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, result);
        }
    }
   
    function _makeInviteRequest(receiver, callback, code, email) {
        var params, cb;
        params = {
            code: code,
            serial_number: $htv.Platform.properties.deviceid,
            email: email
        };

        cb = function (urlStatus, errorMsg) {
            callback.call(receiver, false);
        };

        _makeSiteApiRequest("request_invite", null, params, receiver, callback,
                _handleInviteRequestResult, {method: "POST", errorCallback: cb});
    }

    function _handleInviteRequestResult(data, options) {    
        if (options.closure.cb !== undefined) {
            options.closure.cb.call(options.closure.recv, true);
        }
    }

    function _makeSiteApiRequest(object, operation, params, receiver, callback, handler, options) {
        var i, key, base, body, keySortArray, sigGen, sig, closure;
        body = "";
        keySortArray = [];
        sigGen = "";
        sig = "";
        closure = {
            recv: receiver,
            cb: callback
        };
        
        // Apply default URL loading options, if not specified by caller
        if (!options) {
            options = {};
        }
        if (!options.hasOwnProperty("xmlparse")) {
            options.xmlparse = false;
        }
        if (!options.hasOwnProperty("method")) {
            options.method = "POST";
        }
        
        // Choose HTTPS base url for secure actions
        base = $htv.Constants.Endpoints.api + "/";
        if (SECURE_API_ACTIONS[object]) {
            base = $htv.Constants.Endpoints.sapi + "/";
        }
        if (USE_STAGING) {
            // TODO: if using staging, fill these params 
            options.auth_username = "";
            options.auth_password = "";
            base = "";
        }
        
        // push params that are always there
        params.app = $htv.Constants.SITE_APP;
        if (_state.logged_in === true) {
            params.token = _state.token;
        }
        if (operation !== null) {
            params.operation = operation;
        }
        params.error_as_200 = 1;
        for (key in params) {
            if (params.hasOwnProperty(key)) {
                keySortArray.push(key);
            }
        }
        sigGen = $htv.Constants.SITE_SEC + object;
        
        keySortArray.sort();
        for (i = 0; i < keySortArray.length; i++) {
            sigGen = sigGen + keySortArray[i] + params[keySortArray[i]];
        }
        
        params.sig = $htv.Libs.SHA1.SHA1(sigGen);
        for (key in params) {
            if (params.hasOwnProperty(key)) {
                body = body + key + "=" + $htv.Utils.urlEncode(params[key]) + "&";
            }
        }
        body = body.slice(0, -1);
        
        
        // Add required URL loading options
        options.body = body;
        options.closure = closure;
        options.resultHandler = handler;
        options.content_type = "application/x-www-form-urlencoded";
        $htv.Platform.loadURL(base + object, this, _onSiteApiResult, options);
    }
    
    function _onSiteApiResult(responseData, options) {
        
        if (options !== undefined) {
            options.resultHandler.call(this, responseData, options);
        } else {
            
            describe(responseData);
        }
    }
    
    function _getPreference(prefName) {
        // todo: namespace the preferences
        return $htv.Platform.readLocalData("preferences", prefName);
    }
    
    function _setPreference(prefName, value) {
        $htv.Platform.writeLocalData("preferences", prefName, value);
    }
    
    return {
        accountOnHold: _accountOnHold,
        addSubscription: _addSubscription,
        addToQueue: _addToQueue,
        authenticateDeviceToken: _authenticateDeviceToken,
        checkAuthenticationCode: _checkAuthenticationCode,
        checkInQueue: _checkInQueue,
        disableAuthenticationPolling: _disableAuthenticationPolling,
        dismissResume: _dismissResume,
        enableAuthenticationPolling: _enableAuthenticationPolling,
        getAge: _getAge,
        getAuthenticationCode: _getAuthenticationCode,
        getNextVideo: _getNextVideo,
        getQueuedVideoInfo: _getQueuedVideoInfo,
        getSubscriptionInfo: _getSubscriptionInfo,
        getUserToken: _getUserToken,
        getPreference: _getPreference,
        setPreference: _setPreference,
        getDisplayName: _getDisplayName,
        getFirstName: _getFirstName,
        handleAuthenticateSuccess: _handleAuthenticateSuccess,
        getPlaybackProgress: _getPlaybackProgress,
        isLoggedIn: _isLoggedIn,
        isPlusUser: _isPlusUser,
        isVideoInQueue: _isVideoInQueue,
        login: _login,
        logout: _logout,
        makeInviteRequest: _makeInviteRequest,
        rateShow: _rateShow,
        rateVideo: _rateVideo,
        recordVideoViewStart: _recordVideoViewStart,
        recordVideoViewComplete: _recordVideoViewComplete,
        recordAnonymousVideoViewStart: _recordAnonymousVideoViewStart,
        removeFromQueue: _removeFromQueue,
        removeSubscription: _removeSubscription,
        setPlaybackProgress: _setPlaybackProgress
    };
}());

