# This custom authorizer should be injected into a Route to authorize
# a user with the Kinvey service.
#
# Initializes the current user and triggers a route event when finished.
# This event might be any of:
#
#   * 'allow:user'
#   * 'allow:guest'
#   * 'allow:created'
#   * 'allow:offline'
#   * 'deny:user'
#   * 'deny:guest'
#   * 'deny:offline'
#   * 'deny:error'
#
# A nonspecific 'deny' or 'allow' route event is also triggered.
#
# Add the following options to your route instance:
#
#   * kinvey [Object] Options hash to be passed into Kinvey.init()
#   * allowOffline [Boolean] Set true to resolve if the network is unreachable but a cached user exists.
#   * allowGuest [Boolean] Set true to resolve if no user is logged in.
#   * userClass [Function<Backbone.Model>] Your Kinvey User model.
#   * autoAccount [Boolean] If true, attempt to generate an account if online with no cached user.
#
# @param route [Object] A route object to authorize.
# @return promise [Promise] A promise to resolve upon authorization, or reject if access is denied.
#
# @note To use autoAccount you will need "chance" on your global scope.
# @see http://chancejs.com/#dice
#
define [
  'kinvey',
  'chance'
], (Kinvey) ->
  (route) ->
    $.Deferred (deferred) ->
      # Inherit the route's logging function, if any
      if route && route.options && route.options.logger
        logger = route.options.logger
      else
        logger = console

      # Trigger a 'allow' route event and resolve the deferred.
      # Also triggers a custom route event if passed as the first parameter.
      #
      # @param event [String] A custom route event to trigger
      #
      allow = (event) ->
        route.trigger event, route
        route.trigger 'allow', route
        deferred.resolve()

      # Trigger the 'deny' route event and reject the deferred.
      # Also triggers a custom route event if passed as the first parameter.
      #
      # @param event [String] A custom route event to trigger
      #
      deny = (event) ->
        route.trigger event, route
        route.trigger 'deny', route
        deferred.reject()

      # Attempt to generate an account if online with no cached user.
      #
      # @param credentials [Object] An object containing username and password (string) properties.
      #
      autoAccount = (credentials) ->
        logger.info 'Creating new autogenerated account', credentials
        user.save credentials,
          success: (model, response, options) ->
            logger.info 'Account created successfully', response
            allow 'allow:created'
          error: (model, xhr, options) ->
            logger.error 'Failed to create account', xhr
            route.pushError xhr
            deny 'deny:error'

      # Return a username for the automatically generated user.
      # Attempts to read a uuid from the device (as in Cordova
      # environments), otherwise generate a random guid.
      #
      # @return username [String]
      #
      autoUsername = -> chance.guid()

      # Generate and return a secure password (a 40char hex hash)
      #
      # @return password [String]
      #
      autoPassword = -> chance.hash()

      # Login to Kinvey with the given credentials
      #
      # @param credentials [Object] An object containing username and password (string) properties.
      #
      login = (credentials) ->
        user.login(credentials).then ->
          if user.isLoggedIn()
            allow 'allow:user'
          else
            deny 'deny:user'
        , (error) ->
          logger.error 'Failed to login with credentials', error
          route.pushError error
          deny 'deny:error'

      # Ensure Kinvey is in online mode and then initialize it.
      #
      # If not logged in, attempt to log in with cached credentials.
      #
      # Otherwise either generate an account or deny, depending on route options.
      #
      online = ->
        Kinvey.Sync.online()
        Kinvey.init(route.options.kinvey).then (activeUser) =>
          if activeUser
            logger.info 'Already logged in', activeUser
            allow 'allow:user'
          else
            cache = Kinvey.Backbone.User.accountCache
            username = cache.getUsername()
            password = cache.getPassword()

            if username && password
              logger.info 'Logging in with cached credentials', username, password
              login({ username: username, password: password })
            else
              logger.info 'Not logged in, no cached credentials'
              if route.options.allowGuest
                allow 'allow:guest'
              else
                if route.options.autoAccount
                  try
                    autoAccount username: autoUsername(), password: autoPassword()
                  catch error
                    logger.trace(error)
                    deny 'deny:guest'
                else
                  deny 'deny:guest'
        , (error) =>
          logger.error 'Kinvey init error', error
          route.pushError error
          deny 'deny:error'

      # Ensure Kinvey is in offline mode, and deny if allowOffline is disabled.
      #
      # Otherwise, check for an active user.
      #
      offline = ->
        Kinvey.Sync.offline()
        if route.options.allowOffline
          options = _.clone(route.options.kinvey)
          options.online = false
          Kinvey.init(options).then (activeUser) =>
            if Kinvey.Backbone.getActiveUser()
              allow 'allow:offline'
            else
              if route.options.allowGuest
                allow 'allow:guest'
              else
                deny 'deny:guest'
          , (error) =>
            logger.error 'Kinvey init error', error
            route.pushError error
            deny 'deny:error'
        else
          deny 'deny:offline'

      user = new Kinvey.Backbone.User
      if route.options.online() then online() else offline()
    .promise()
