         fn(cb)
 
 
+###*
+Admin token manager
+
+@class AdminTokenManager
+###
 class AdminTokenManager
 
     ###*
-    @param {Function|String} [options.fetchNew] function to return new admin token (or promise of it). When string is given, the value is used for the admin access token. Default value is 'loopback-with-admin-access-token'
-    @param {Function|Array(String)} [options.fetchAll] function to return initial admin tokens (or promise of it). When string[] is given, these value are used for the admin access token.
+    @param {Function|Array(String)} [options.fetch] function to return admin tokens (or promise of it). When string[] is given, these value are used for the admin access token.
     @param {Number} [options.intervalHours] Interval hours to fetch new admin token.
-    @param {Number} [options.maxTokens] The limit of the number of admin access tokens. Default is the length of the result of fetchAll()
     ###
     constructor: (options = {}) ->
 
-        { @fetchNew, @fetchAll, @intervalHours, @maxTokens } = options
-
-        if not @fetchNew? or typeof @fetchNew isnt 'function'
-            tokenStr = @fetchNew?.toString() ? DEFAULT_TOKEN
-            @fetchNew = -> Promise.resolve(tokenStr)
-
-        if typeof @fetchAll isnt 'function'
-            @fetchAll = ->
-                Promise.resolve @fetchNew()
-                .then (tokenStr) -> [tokenStr]
-
-        @tokens = []
-
-
-
+        { fetch, @intervalHours } = options
+
+        @fetch = @constructor.createFetchFunction(fetch)
+
+        @tokensById = {}
+
+
+
+    ###*
+    Set fetched tokens as admin tokens.
+
+    @public
+    @method init
+    @param {Object} models app.models in LoopBack
+    @return {Promise}
+    ###
     init: (@models) ->
 
         @createAdminUser()

                role.principals.create principal, cb



    update: ->

        Promise.resolve @fetchNew()

        .then (tokenStr) =>
            token = new AdminToken(tokenStr)
            @setNew(token)

        .then (succeeded) =>
            @deleteOldest() if succeeded



    setNew: (token) ->

        { AccessToken } = @models

        @exists(token).then (exists) =>

            ____("token: #{token.id} already exists.") if exists
            return false if exists

            ____("saving token: #{token.id}")
            promisify (cb) =>
                AccessToken.create token, cb

            .then (savedToken) =>
                @tokens.push savedToken
                return true



    exists: (token) ->

        { AccessToken } = @models

        promisify (cb) =>
            AccessToken.exists token.id, cb



    deleteOldest: ->

        { AccessToken } = @models

        token = @tokens[0]

        promisify (cb) =>
            AccessToken.destroyById token.id, cb

        .then =>
            @tokens.shift()




class AdminToken

    constructor: (@id) ->
        @userId = ADMIN_USER.id
        @ttl = ONE_YEAR
        @createdAt = new Date().getTime()




            @createAdminRole()

        .then =>
            @fetch()

        .then (tokenStrs) =>

            if not @validTokenStrs(tokenStrs)
                throw @invalidTokenError(tokenStrs)

            ____("tokens: #{tokenStrs.join(',')}")

            @updateTokens(tokenStrs)



    ###*
    Refresh admin tokens.

    @public
    @method refreshTokens
    @return {Promise}
    ###
    refreshTokens: ->

        @fetch().then (tokenStrs) =>

            if not @validTokenStrs(tokenStrs)

                console.error("""
                    AdminTokenManager: Fetched tokens are not valid!

                    Results: #{tokenStrs}

                    """)
                return Promise.resolve(false)

            @updateTokens(tokenStrs)


    ###*
    Save new tokens and destroy old tokens.
    @private
    ###
    updateTokens: (tokenStrs) ->

        tokens = tokenStrs.map (tokenStr) -> new AdminToken(tokenStr)

        Promise.all(tokens.map (token) => @setNew token).then =>

            promises = []

            for tokenStr of @tokensById when tokenStr not in tokenStrs
                promises.push @destroy(tokenStr)

            Promise.all promises


    ###*
    set new token
    @private
    ###
    setNew: (token) ->

        { AccessToken } = @models

        @findById(token.id).then (foundToken) =>

            if foundToken?
                ____("token: #{token.id} already exists.")

                if foundToken.userId isnt ADMIN_USER
                    console.error """
                        AdminTokenManager: The token `#{token.id}` is already exist for non-admin user. Skip creating.
                    """
                    console.error()

                return false

            ____("saving token: #{token.id}")
            promisify (cb) =>
                AccessToken.create token, cb

            .then => true

        .then (tokenIsSavedNow) =>
            @tokensById[token.id] = token



    ###*
    Destroy the token
    @private
    ###
    destroy: (tokenStr) ->

        @findById(tokenStr).then (foundToken) =>
            # check if the token to be deleted is admin token
            if foundToken.userId isnt ADMIN_USER.id
                console.error """
                    AdminTokenManager: The token `#{token.id}` is not the admin token. Skip destroying.
                """
                return false

            { AccessToken } = @models

            promisify (cb) =>
                AccessToken.destroyById tokenStr, cb

            .then =>
                delete @tokensById[tokenStr]


    ###*
    Find AccessToken model by tokenStr
    @private
    ###
    findById: (tokenStr) ->

        { AccessToken } = @models

        promisify (cb) =>
            AccessToken.findById tokenStr, cb



    ###*
    Create admin user, called once in 'init' function.
    @private
    ###
    createAdminUser: ->
        ____("creating admin user. id: #{ADMIN_USER.id}")
        { User } = @models
