function RimzuCache(msCacheDuration) {
    this.msCacheDuration = msCacheDuration;
    this.users = new Array();
}

RimzuCache.prototype.isUserInfoCached = function(userId) {
    if ((this.users[userId] == undefined) ||
        (this.users[userId].userInfoWhen == undefined)) {
        return false;
    }
    
    var msInCache =
        new Date().getTime() -
        this.users[userId].userInfoWhen.getTime();
        
    return ((msInCache >= 0) && (msInCache < this.msCacheDuration));
}

RimzuCache.prototype.isUserContactsCached = function(userId) {
    if ((this.users[userId] == undefined) ||
        (this.users[userId].userContactsWhen == undefined)) {
        return false;
    }
    
    var msInCache =
        new Date().getTime() -
        this.users[userId].userContactsWhen.getTime();
        
    return ((msInCache >= 0) && (msInCache < this.msCacheDuration));
}

RimzuCache.prototype.getUserInfo = function(userId) {
    return this.users[userId].userInfo;
}

RimzuCache.prototype.getUserContacts = function(userId) {
    return this.users[userId].userContacts;
}

RimzuCache.prototype.setUserInfo = function(userId, userInfo) {
    if (this.users[userId] == undefined) {
        this.users[userId] = new Object();
    }
    
    this.users[userId].userInfo = userInfo;
    this.users[userId].userInfoWhen = new Date();
}

RimzuCache.prototype.setUserContacts = function(userId, userContacts) {
    if (this.users[userId] == undefined) {
        this.users[userId] = new Object();
    }
    
    if (userContacts == undefined) {
        this.users[userId].userContacts = undefined;
        this.users[userId].userContactsWhen = undefined;
        return;
    }

    this.users[userId].userContacts = userContacts;
    this.users[userId].userContactsWhen = new Date();
    
    for (var i = 0; i < userContacts.length; ++i) {
        if (this.isUserContactsCached(userContacts[i])) {
            var contactContactIds = this.getUserContacts(userContacts[i]);
            if (!isInArray(contactContactIds, userId)) {
                this.setUserContacts(userContacts[i], undefined);
            }
        }
    }
}

RimzuCache.prototype.isInArray = function(array, obj) {
    for (var i = 0; i < array.length; ++i) {
        if (array[i] == obj) {
            return true;
        }    
    }
    return false;
}

///////////////////////////////////////////////////////////////////////////////

function CachedRimzu(rimzu, msCacheDuration) {
    this.rimzu = rimzu;
    this.msCacheDuration = msCacheDuration;
}

CachedRimzu.prototype.getNumUsers = function(callback, context) {
    return this.rimzu.getNumUsers(callback, context);
}


CachedRimzu.prototype.requestInvitation = function(email, callback, context) {
    return this.rimzu.requestInvitation(email, callback, context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.initCache = function(myUserId) {
    this.myUserId = myUserId;
    this.rimzuCache = new RimzuCache(this.msCacheDuration);
}

CachedRimzu.prototype.resetCache = function() {
    this.myUserId = undefined;
    this.rimzuCache = undefined;
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.login = function(user, password, callback, context) {
    if (callback != undefined) {
        var contextWrapper = { thisObj : this, callback : callback, context : context };
        return this.rimzu.login(user, password, this.loginCallback, contextWrapper);
    } else {
        var result = this.rimzu.login(user, password);
        this.initCache(result);
        return result;
    }
}

CachedRimzu.prototype.loginCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        contextWrapper.thisObj.initCache(result);
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.newUser = function(email, token, user, password, callback, context) {
    if (callback != undefined) {
        var contextWrapper = { thisObj : this, callback : callback, context : context };
        return this.rimzu.newUser(email, token, user, password, this.newUserCallback, contextWrapper);
    } else {
        var result = this.rimzu.newUser(email, token, user, password);
        this.initCache(result);
        return result;
    }
}

CachedRimzu.prototype.newUserCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        contextWrapper.thisObj.initCache(result);
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.logout = function(callback, context) {
    this.resetCache(result);
    return this.rimzu.logout(callback, context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.getUserId = function(callback, context) {
    if (this.myUserId == undefined) {
        return this.rimzu.getUserId(callback, context);
    }
    
    if (callback != undefined) {
        callback(false, this.myUserId, context);
        return 0;
    } else {
        return this.myUserId;    
    }
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.getUsersInfo = function(userIds, callback, context) {
    var userInfos = new Array();
    for (var i = 0; i < userIds.length; ++i) {
        if (this.rimzuCache.isUserInfoCached(userIds[i])) {
            userInfos.push(this.rimzuCache.getUserInfo(userIds[i]));
        } else {
            break;            
        }
    }

    if (callback != undefined) {
        if (userInfos.length == userIds.length) {
            return callback(false, userInfos, context);
        } else {
            var contextWrapper = { thisObj : this, callback : callback, context : context, userIds : userIds };
            return this.rimzu.getUsersInfo(userIds, this.getUsersInfoCallback, contextWrapper);
        }
    } else {
        if (userInfos.length == userIds.length) {
            return userInfos;
        } else {
            result = this.rimzu.getUsersInfo(userIds);
            for (var i = 0; i < userIds.length; ++i) {
                this.rimzuCache.setUserInfo(userIds[i], result[i]);            
            }
            return result;
        }
    }
}

CachedRimzu.prototype.getUsersInfoCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        for (var i = 0; i < contextWrapper.userIds.length; ++i) {
            contextWrapper.thisObj.rimzuCache.setUserInfo(contextWrapper.userIds[i], result[i]);
        }
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.getUserInfo = function(userId, callback, context) {
    if (callback != undefined) {
        if (this.rimzuCache.isUserInfoCached(userId)) {
            return callback(false, this.rimzuCache.getUserInfo(userId), context);
        } else {
            var contextWrapper = { thisObj : this, callback : callback, context : context, userId : userId};
            return this.rimzu.getUserInfo(userId, this.getUserInfoCallback, contextWrapper);
        }
    } else {
        if (this.rimzuCache.isUserInfoCached(userId)) {
            return this.rimzuCache.getUserInfo(userId);
        } else {
            result = this.rimzu.getUserInfo(userId);
            this.rimzuCache.setUserInfo(userId, result);
            return result;
        }
    }
}

CachedRimzu.prototype.getUserInfoCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        contextWrapper.thisObj.rimzuCache.setUserInfo(contextWrapper.userId, result);
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.setUserInfo = function(userInfo, callback, context) {
    if (callback != undefined) {
        var contextWrapper = { thisObj : this, callback : callback, context : context, userInfo : userInfo };
        return this.rimzu.setUserInfo(user, password, this.setUserInfoCallback, contextWrapper);
    } else {
        var result = this.rimzu.setUserInfo(userInfo);
        this.rimzuCache.setUserInfo(this.myUserId, userInfo);
        return result;
    }
}

CachedRimzu.prototype.setUserInfoCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        contextWrapper.thisObj.rimzuCache.setUserInfo(
            contextWrapper.thisObj.myUserId, contextWrapper.userInfo);
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.changePassword = function(newPassword, callback, context) {
    return this.rimzu.changePassword(newPassword, callback, context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.getContacts = function(userId, callback, context) {
    if (callback != undefined) {
        if (this.rimzuCache.isUserContactsCached(userId)) {
            return callback(false, this.rimzuCache.getUserContacts(userId), context);
        } else {
            var contextWrapper = { thisObj : this, callback : callback, context : context, userId : userId};
            return this.rimzu.getContacts(userId, this.getUserContactsCallback, contextWrapper);
        }
    } else {
        if (this.rimzuCache.isUserContactsCached(userId)) {
            return this.rimzuCache.getUserContacts(userId);
        } else {
            result = this.rimzu.getContacts(userId);
            this.rimzuCache.setUserContacts(userId, result);
            return result;
        }
    }
}

CachedRimzu.prototype.getUserContactsCallback = function(isError, result, contextWrapper) {
    if (!isError) {
        contextWrapper.thisObj.rimzuCache.setUserContacts(contextWrapper.userId, result);
    }
    contextWrapper.callback(isError, result, contextWrapper.context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.inviteUserToContacts = function(userId, callback, context) {
    if (this.myUserId == undefined) {
        return this.rimzu.inviteUserToContacts(userId, callback, context);
    }
    
    this.rimzuCache.setUserContacts(this.myUserId, undefined);
    // TODO: also invalidate userId's cache?
    return this.rimzu.inviteUserToContacts(userId, callback, context);
}


CachedRimzu.prototype.inviteEmailToContacts = function(email, callback, context) {
    if (this.myUserId == undefined) {
        return this.rimzu.inviteEmailToContacts(email, callback, context);
    }
    
    this.rimzuCache.setUserContacts(this.myUserId, undefined);
    // TODO: if the email belongs to a cached user, invalidate her cached contacts?
    return this.rimzu.inviteEmailToContacts(email, callback, context);
}


CachedRimzu.prototype.removeUserFromContacts = function(userId, callback, context) {
    if (this.myUserId == undefined) {
        return this.rimzu.removeUserFromContacts(userId, callback, context);
    }
    
    this.rimzuCache.setUserContacts(this.myUserId, undefined);
    return this.rimzu.removeUserFromContacts(userId, callback, context);
}

///////////////////////////////////////////////////////////////////////////////

CachedRimzu.prototype.getPendingInvitations = function(callback, context) {
    return this.rimzu.getPendingInvitations(callback, context);
}


CachedRimzu.prototype.rejectInvitation = function(userId, callback, context) {
    return this.rimzu.rejectInvitation(userId, callback, context);
}


CachedRimzu.prototype.requestQuestion = function(callback, context) {
    return this.rimzu.requestQuestion(callback, context);
}


CachedRimzu.prototype.answerQuestion = function(answerUserId, callback, context) {
    return this.rimzu.answerQuestion(answerUserId, callback, context);
}


CachedRimzu.prototype.getLatestRankings = function(callback, context) {
    // TODO
    return this.rimzu.getLatestRankings(callback, context);
}


CachedRimzu.prototype.getRankings = function(rankingDate, callback, context) {
    // TODO
    return this.rimzu.getRankings(rankingDate, callback, context);
}

