// twister_following.js
// 2013 Miguel Freitas
//
// Manage list of following users. Load/Save to localstorage and DHT.
// Provides random user suggestions to follow.

var followingUsers = [];
var _isFollowPublic = {};
var _followsPerPage = 200;
var _maxFollowingPages = 50;
var _followingSeqNum = 0;
var _followSuggestions = [];
var _searchingPartialUsers = "";
var _searchKeypressTimer = undefined;
var _lastSearchUsersResults = [];
var _lastLoadFromDhtTime = 0;

// load followingUsers from localStorage
function loadFollowingFromStorage() {
    var ns=$.initNamespaceStorage(defaultScreenName);
    if( ns.localStorage.isSet("followingUsers") )
        followingUsers = ns.localStorage.get("followingUsers");
    if( ns.localStorage.isSet("isFollowPublic") )
        _isFollowPublic = ns.localStorage.get("isFollowPublic");
    if( ns.localStorage.get("followingSeqNum") > _followingSeqNum)
        _followingSeqNum = ns.localStorage.get("followingSeqNum");
    if( ns.localStorage.isSet("lastLoadFromDhtTime") )
        _lastLoadFromDhtTime = ns.localStorage.get("lastLoadFromDhtTime");
    // follow ourselves
    if(followingUsers.indexOf(defaultScreenName) < 0) {
        followingUsers.push(defaultScreenName);
    }
}

// save list of following to localStorage
function saveFollowingToStorage() {
    var ns=$.initNamespaceStorage(defaultScreenName);
    ns.localStorage.set("followingUsers", followingUsers);
    ns.localStorage.set("isFollowPublic", _isFollowPublic);
    ns.localStorage.set("followingSeqNum", _followingSeqNum);
    ns.localStorage.set("lastLoadFromDhtTime", _lastLoadFromDhtTime);
}

// load public list of following users from dht resources
// "following1", "following2" etc.
// it will stop loading when resource is empty
// callback is called as: doneCb(doneArg, followingList, seqNum)
function loadFollowingFromDht(username, pageNumber, followingList, seqNum, doneCb, doneArg) {
    if( !pageNumber ) pageNumber = 1;

    dhtget( username, "following" + pageNumber, "s",
           function(args, following, rawdata) {
               if( rawdata ) {
                   var seq = parseInt(rawdata[0]["p"]["seq"]);
                   if( seq > args.seqNum ) args.seqNum = seq;
               }

               if( following ) {
                   for( var i = 0; i < following.length; i++ ) {
                       if( args.followingList.indexOf(following[i]) < 0 ) {
                           args.followingList.push(following[i]);
                       }
                    }
               }

               if( following && following.length && args.pageNumber < _maxFollowingPages) {
                   loadFollowingFromDht(username, args.pageNumber,
                                        args.followingList, args.seqNum,
                                        args.doneCb, args.doneArg);
               } else {
                   if( args.doneCb )
                       args.doneCb(args.doneArg, args.followingList, args.seqNum);
               }
           }, {pageNumber:pageNumber+1, followingList:followingList, seqNum:seqNum,
               doneCb:doneCb, doneArg:doneArg});
}

// get number of following from dht and set item.text()
function getNumFollowing( username, item ) {
    loadFollowingFromDht( username, 1, [], 0,
                          function(args, following, seqNum) {
                             item.text( following.length );
                         }, null);
}

function loadFollowingIntoList( username, html_list ) {
    loadFollowingFromDht( username, 1, [], 0,
        function(args, following, seqNum) {
            html_list.html("");
            $.each(following, function(i, following_user){
                var following_user_li = $( "#following-by-user-template" ).children().clone(true);
                
                // link follower to profile page
                $(following_user_li.children()[0]).attr("data-screen-name", following_user);
                $(following_user_li.children()[0]).attr("href", $.MAL.userUrl(following_user));

                following_user_li.find(".following-screen-name b").text(following_user);
                getAvatar( following_user, following_user_li.find(".mini-profile-photo") );
                var $followingName = following_user_li.find(".mini-following-name");
                $followingName.text(following_user);
                getFullname( following_user, $followingName );

                html_list.append( following_user_li );
            });
        }, null);
}

// load following list from localStorage and then from the dht resource
function loadFollowing(cbFunc, cbArg) {
    loadFollowingFromStorage();
    updateFollowing();

    var curTime = new Date().getTime() / 1000;

    // optimization to avoid costly dht lookup everytime the home is loaded
    if( curTime > _lastLoadFromDhtTime + 3600*24 ||
        document.URL.indexOf("following") >= 0 ) {
        var numFollow = followingUsers.length;
        loadFollowingFromDht( defaultScreenName, 1, [], _followingSeqNum,
                              function(args, following, seqNum) {
                                 var curTime = new Date().getTime() / 1000;
                                 _lastLoadFromDhtTime = curTime;

                                 for( var i = 0; i < following.length; i++ ) {
                                     if( followingUsers.indexOf(following[i]) < 0 ) {
                                         followingUsers.push(following[i]);
                                     }
                                     _isFollowPublic[following[i]] = true;
                                  }

                                 if( args.numFollow != followingUsers.length ||
                                     seqNum != _followingSeqNum ) {
                                     _followingSeqNum = seqNum;
                                     // new following loaded from dht
                                     saveFollowingToStorage();
                                     updateFollowing();
                                 }

                                 if( args.cbFunc )
                                     args.cbFunc(args.cbArg);
                             }, {numFollow:numFollow, cbFunc:cbFunc, cbArg:cbArg} );
    } else {
        if( cbFunc )
            cbFunc(cbArg);
    }
}

// save list of following to dht resource. each page ("following1", following2"...)
// constains up to _followsPerPage elements. alternatively we might keep track
// of total strings size to optimize the maximum storage (8kb in node.cpp, but 4kb is
// probably a good target).
function saveFollowingToDht() {
    var following = [];
    var pageNumber = 1;
    for( var i = 0; i < followingUsers.length; i++ ) {
        if( followingUsers[i] in _isFollowPublic &&
            _isFollowPublic[followingUsers[i]] ) {
            following.push(followingUsers[i]);
        }
        if( following.length == _followsPerPage || i == followingUsers.length-1) {
            dhtput( defaultScreenName, "following" + pageNumber, "s",
                   following, defaultScreenName, _followingSeqNum+1 );
            pageNumber++;
            following = [];
        }
    }
    dhtput( defaultScreenName, "following" + pageNumber, "s",
           following, defaultScreenName, _followingSeqNum+1 );

    _followingSeqNum++;
}

// save following to local storage, dht and json rpc
function saveFollowing(cbFunc, cbArg) {
    saveFollowingToStorage();
    saveFollowingToDht();
    updateFollowing(cbFunc, cbArg);
}

// update json rpc with current list of following
function updateFollowing(cbFunc, cbArg) {
    twisterRpc("follow", [defaultScreenName,followingUsers],
               function(args, ret) {
                   if( args.cbFunc )
                       args.cbFunc(args.cbArg, true);
               }, {cbFunc:cbFunc, cbArg:cbArg},
               function(args, ret) {
                   console.log("ajax error:" + ret);
                   if( args.cbFunc )
                       args.cbFunc(args.cbArg, false);
               }, cbArg);
}

// follow a new single user.
// it is safe to call this even if username is already in followingUsers.
// may also be used to set/clear publicFollow.
function follow(user, publicFollow, cbFunc, cbArg) {
    if( followingUsers.indexOf(user) < 0 ) {
        followingUsers.push(user);
    }
    if( publicFollow == undefined || publicFollow )
        _isFollowPublic[user] = true;
    else
        delete _isFollowPublic[user];
    saveFollowing(cbFunc, cbArg);
}

// unfollow a single user
function unfollow(user, cbFunc, cbArg) {
    var i = followingUsers.indexOf(user);
    if( i >= 0 ) {
        followingUsers.splice(i,1);
    }
    delete _isFollowPublic[user];
    saveFollowing();

    twisterRpc("unfollow", [defaultScreenName,[user]],
               function(args, ret) {
                   if( args.cbFunc )
                       args.cbFunc(args.cbArg, true);
               }, {cbFunc:cbFunc, cbArg:cbArg},
               function(args, ret) {
                   console.log("ajax error:" + ret);
                   if( args.cbFunc )
                       args.cbFunc(args.cbArg, false);
               }, {cbFunc:cbFunc, cbArg:cbArg});
}

// check if public following
function isPublicFollowing(user) {
    if( followingUsers.indexOf(user) < 0 ) {
        return false;
    }
    if( (user in _isFollowPublic) && _isFollowPublic[user] == true )
        return true;
    else
        return false;
}

// check if following list is empty
function followingEmptyOrMyself() {
    if( followingUsers.length == 0 ||
        followingUsers.length == 1 && followingUsers[0] == defaultScreenName )
        return true;
    else
        return false;
}

// randomly choose a user we follow, get "following1" from him and them
// choose a suggestion from their list. this function could be way better, but
// that's about the simplest we may get to start with.
function getRandomFollowSuggestion(cbFunc, cbArg) {

    if( followingEmptyOrMyself() ) {
        cbFunc(cbArg, null, null);
        return;
    }

    var i;
    do{
        var i = parseInt( Math.random() * followingUsers.length );
    } while( i < followingUsers.length && followingUsers[i] == defaultScreenName);

    if( i < followingUsers.length ) {
        loadFollowingFromDht( followingUsers[i], 1, [], 0,
           function(args, following, seqNum) {
             if( following ) {
                 var suggested = false;
                 var j = parseInt( Math.random() * following.length );
                 for( ; j < following.length; j++ ) {
                     if( followingUsers.indexOf(following[j]) < 0 &&
                         _followSuggestions.indexOf(following[j]) < 0 ) {
                         args.cbFunc(args.cbArg, following[j], args.followedBy);
                         _followSuggestions.push(following[j]);
                         suggested = true;
                         break;
                     }
                 }
                 if( !suggested ) {
                     args.cbFunc(args.cbArg, null, null);
                 }
             }
           }, {cbFunc:cbFunc, cbArg:cbArg, followedBy:followingUsers[i]});
    } else {
        cbFunc(cbArg, null, null);
    }
}

// adds following users to the interface (following.html)
function showFollowingUsers(){
    var $notFollowing = $(".not-following-any");
    if( followingEmptyOrMyself() ) {
        $notFollowing.show();
    } else {
        $notFollowing.hide();
    }

    var $followingList = $(".following-list");
    var $template = $("#following-user-template").detach();

    $followingList.empty();
    $followingList.append($template);

    for( var i = 0; i < followingUsers.length; i++ ) {
        var resItem = $template.clone(true);
        resItem.removeAttr('id');
        resItem.show();
        resItem.find(".mini-profile-info").attr("data-screen-name", followingUsers[i]);
        resItem.find(".following-screen-name").text(followingUsers[i]);
        resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(followingUsers[i]));
        resItem.find("a.unfollow").attr("href",$.MAL.unfollowUrl(followingUsers[i]));
        resItem.find("a.direct-messages-with-user").attr("href", $.MAL.dmchatUrl(followingUsers[i]));
        resItem.find(".public-following").prop("checked",isPublicFollowing(followingUsers[i]));
        getAvatar(followingUsers[i],resItem.find(".mini-profile-photo"));
        getFullname(followingUsers[i],resItem.find(".mini-profile-name"));
        if( followingUsers[i] == defaultScreenName ) {
            resItem.find("button").hide();
        }
        
        resItem.appendTo($followingList);
    }
    $.MAL.followingListLoaded();
}


function processSuggestion(arg, suggestion, followedBy) {
    var dashboard = $(".follow-suggestions");
    if( suggestion ) {
        var item = $("#follow-suggestion-template").clone(true);
        item.removeAttr("id");

        item.find(".twister-user-info").attr("data-screen-name", suggestion);

        item.find(".twister-user-name").attr("href", $.MAL.userUrl(suggestion));
        item.find(".twister-by-user-name").attr("href", $.MAL.userUrl(followedBy));
        item.find(".twister-user-tag").text("@" + suggestion);

        getAvatar(suggestion,item.find(".twister-user-photo"));

        getFullname(suggestion,item.find(".twister-user"));
        $spanFollowedBy = item.find(".followed-by");
        $spanFollowedBy.text(followedBy);
        getFullname(followedBy,$spanFollowedBy);

        dashboard.append(item);
    }
}

function closeSearchDialog()
{
    var $this = $(".userMenu-search-field");//$( this );
    $( this ).siblings().slideUp( "fast" );
    removeUsersFromDhtgetQueue( _lastSearchUsersResults );
    _lastSearchUsersResults = [];
}

function userSearchKeypress(event) {
    var partialName = $(".userMenu-search-field").val().toLowerCase();

    var searchResults = $(".search-results");
    
    if ( partialName.substr( 0, 1 ) == '#' ) {
      
        if(searchResults.is(":visible"))
            searchResults.slideUp( "fast" );

        if ( event.which == 13 )
            openHashtagModalFromSearch(partialName.substr(1));

        return;
    }

    if ( partialName.substr( 0, 1 ) == '@' ) {
        partialName = partialName.substr( 1 );
    }

    //var partialName = item.val();

    if( !partialName.length ) {
        closeSearchDialog();
    } else {
        if( _searchKeypressTimer !== undefined )
            clearTimeout(_searchKeypressTimer);

        if( _searchingPartialUsers.length ) {
            _searchingPartialUsers = partialName;
        } else {
            _searchKeypressTimer = setTimeout( function() {
                    _searchKeypressTimer = undefined;
                    searchPartialUsername(partialName);
                }, 600);
        }
    }
}

function searchPartialUsername(partialName) {
    _searchingPartialUsers = partialName;
    twisterRpc("listusernamespartial", [partialName,10],
               function(partialName, ret) {
                   processDropdownUserResults(partialName, ret)
               }, partialName,
               function(cbArg, ret) {
                   console.log("ajax error:" + ret);
               }, {});
}

function processDropdownUserResults(partialName, results){

    if( partialName != _searchingPartialUsers ) {
        searchPartialUsername( _searchingPartialUsers );
        return;
    }

    removeUsersFromDhtgetQueue( _lastSearchUsersResults );
    _lastSearchUsersResults = results;

    var typeaheadAccounts = $(".userMenu-search-profiles");
    var template = $("#search-profile-template").detach();

    typeaheadAccounts.empty();
    typeaheadAccounts.append(template);

    if( results.length ) {
        for( var i = 0; i < results.length; i++ ) {
            if( results[i] == defaultScreenName )
                continue;

            var resItem = template.clone(true);
            resItem.removeAttr('id');
            resItem.show();
            resItem.find(".mini-profile-info").attr("data-screen-name", results[i]);
            resItem.find(".mini-screen-name b").text(results[i]);
            resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(results[i]));
            getAvatar(results[i],resItem.find(".mini-profile-photo"));
            getFullname(results[i],resItem.find(".mini-profile-name"));
            resItem.appendTo(typeaheadAccounts);
        }

        $.MAL.searchUserListLoaded();
    } else {
        closeSearchDialog();
    }
    _searchingPartialUsers = "";
}

function userClickFollow(e) {
    e.stopPropagation();
    e.preventDefault();

    var $this = $(this);
    var $userInfo = $this.closest("[data-screen-name]");
    var username = $userInfo.attr("data-screen-name");

    follow(username, true, function() {
        // delay reload so dhtput may do it's job
        window.setTimeout("location.reload();",500);
    });
}

function initUserSearch() {
    var $userSearchField = $( ".userMenu-search-field" );
    $userSearchField.keyup( userSearchKeypress );
    $userSearchField.bind( "click", userSearchKeypress );
    $userSearchField.clickoutside( closeSearchDialog );

    $("button.follow").bind( "click", userClickFollow );
}

function followingListUnfollow(e) {
    e.stopPropagation();
    e.preventDefault();

    var $this = $(this);
    var username = $this.closest(".mini-profile-info").attr("data-screen-name");

    unfollow(username, function() {
        showFollowingUsers();
    });
}

function followingListPublicCheckbox(e) {
    e.stopPropagation();

    var $this = $(this);
    var username = $this.closest(".mini-profile-info").attr("data-screen-name");
    var public = false;
    $this.toggleClass( "private" );
    if( $this.hasClass( "private" ) ) {
        $this.text( polyglot.t("Private") );
    } else {
        $this.text( polyglot.t("Public") );
        public = true;
    }

    follow(username, public);
}


function requestSwarmProgress() {
    twisterRpc("getlasthave", [defaultScreenName],
           function(args, ret) {processSwarmProgressPartial(ret);}, null,
           function(args, ret) {console.log("ajax error:" + ret);}, null);
}

function processSwarmProgressPartial(lastHaves)
{
    if( defaultScreenName in lastHaves ) {
        incLastPostId(lastHaves[defaultScreenName]);
    }

    twisterRpc("getnumpieces", [defaultScreenName],
           function(args, ret) {processSwarmProgressFinal(args.lastHaves, ret);},
               {lastHaves:lastHaves},
           function(args, ret) {console.log("ajax error:" + ret);}, null);
}

function processSwarmProgressFinal(lastHaves, numPieces)
{
    for( var user in lastHaves ) {
        if( lastHaves.hasOwnProperty(user) && numPieces.hasOwnProperty(user) ) {
            var $userDiv = $(".mini-profile-info[data-screen-name='" + user + "']");
            if( $userDiv.length ) {
                var $status = $userDiv.find(".swarm-status");
                $status.text(polyglot.t("download_posts_status", { portion: numPieces[user] + "/" + (lastHaves[user]+1) }));
                $status.fadeIn();
            }
        }
    }
    window.setTimeout("requestSwarmProgress();",2000);
}

function followingChangedUser() {
    followingUsers = [];
    _isFollowPublic = {};
    _followingSeqNum = 0;
    _followSuggestions = [];
    _lastLoadFromDhtTime = 0;
}

function initInterfaceFollowing() {
    initInterfaceCommon();
    initUserSearch();
    initInterfaceDirectMsg();

    $("button.unfollow").bind( "click", followingListUnfollow );
    $(".public-following").bind( "click", followingListPublicCheckbox );

    $(".mentions-from-user").bind( "click", openMentionsModal );

    initUser( function() {
        if( !defaultScreenName ) {
            alert(polyglot.t("username_undefined"));
            $.MAL.goLogin();
            return;
        }
        checkNetworkStatusAndAskRedirect();

        $(".postboard-loading").fadeIn();
        loadFollowing( function(args) {
                          showFollowingUsers();
                          requestSwarmProgress();
        });
        initMentionsCount();
        initDMsCount();
    });
}
