<commit_before>GRAVATAR_URL = 'https://www.gravatar.com/'
cache = {}
CocoModel = require './CocoModel'
ThangType = require './ThangType'
Level = require './Level'
utils = require 'core/utils'

# Pure functions for use in Vue
# First argument is always a raw User.attributes
# Accessible via eg. `User.broadName(userObj)`
UserLib = {
  broadName: (user) ->
    return '(deleted)' if user.deleted
    name = _.filter([user.firstName, user.lastName]).join(' ')
    return name if name
    name = user.name
    return name if name
    [emailName, emailDomain] = user.email?.split('@') or []
    return emailName if emailName
    return 'Anonymous'
  isSmokeTestUser: (user) -> utils.isSmokeTestEmail(user.email)
}

module.exports = class User extends CocoModel
  @className: 'User'
  @schema: require 'schemas/models/user'
  urlRoot: '/db/user'
  notyErrors: false

  isAdmin: -> 'admin' in @get('permissions', true)
  isArtisan: -> 'artisan' in @get('permissions', true)
  isInGodMode: -> 'godmode' in @get('permissions', true)
  isAnonymous: -> @get('anonymous', true)
  isSmokeTestUser: -> User.isSmokeTestUser(@attributes)
  displayName: -> @get('name', true)
  broadName: -> User.broadName(@attributes)

  getPhotoURL: (size=80) ->
    return "/db/user/#{@id}/avatar?s=#{size}"

  getRequestVerificationEmailURL: ->
    @url() + "/request-verify-email"

  getSlugOrID: -> @get('slug') or @get('_id')

  set: ->
    if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
      @set "jobProfileApprovedDate", (new Date()).toISOString()
    super arguments...

  @getUnconflictedName: (name, done) ->
    # deprecate in favor of @checkNameConflicts, which uses Promises and returns the whole response
    $.ajax "/auth/name/#{encodeURIComponent(name)}",
      cache: false
      success: (data) -> done(data.suggestedName)

  @checkNameConflicts: (name) ->
    new Promise (resolve, reject) ->
      $.ajax "/auth/name/#{encodeURIComponent(name)}",
        cache: false
        success: resolve
        error: (jqxhr) -> reject(jqxhr.responseJSON)

  @checkEmailExists: (email) ->
    new Promise (resolve, reject) ->
      $.ajax "/auth/email/#{encodeURIComponent(email)}",
        cache: false
        success: resolve
        error: (jqxhr) -> reject(jqxhr.responseJSON)

  getEnabledEmails: ->
    (emailName for emailName, emailDoc of @get('emails', true) when emailDoc.enabled)

  setEmailSubscription: (name, enabled) ->
    newSubs = _.clone(@get('emails')) or {}
    (newSubs[name] ?= {}).enabled = enabled
    @set 'emails', newSubs

  isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled

  isStudent: -> @get('role') is 'student'

  isTeacher: ->
    return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']

  isSessionless: ->
    # TODO: Fix old users who got mis-tagged as teachers
    # TODO: Should this just be isTeacher, eventually?
    Boolean((utils.getQueryVariable('dev', false) or me.isTeacher()) and utils.getQueryVariable('course', false))

  setRole: (role, force=false) ->
    oldRole = @get 'role'
    return if oldRole is role or (oldRole and not force)
    @set 'role', role
    @patch()
    application.tracker?.updateRole()
    return @get 'role'

  a = 5
  b = 100
  c = b

  # y = a * ln(1/b * (x + c)) + 1
  @levelFromExp: (xp) ->
    if xp > 0 then Math.floor(a * Math.log((1 / b) * (xp + c))) + 1 else 1

  # x = b * e^((y-1)/a) - c
  @expForLevel: (level) ->
    if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0

  @tierFromLevel: (level) ->
    # TODO: math
    # For now, just eyeball it.
    tiersByLevel[Math.min(level, tiersByLevel.length - 1)]

  @levelForTier: (tier) ->
    # TODO: math
    for tierThreshold, level in tiersByLevel
      return level if tierThreshold >= tier

  level: ->
    totalPoint = @get('points')
    totalPoint = totalPoint + 1000000 if me.isInGodMode()
    User.levelFromExp(totalPoint)

  tier: ->
    User.tierFromLevel @level()

  gems: ->
    gemsEarned = @get('earned')?.gems ? 0
    gemsEarned = gemsEarned + 100000 if me.isInGodMode()
    gemsPurchased = @get('purchased')?.gems ? 0
    gemsSpent = @get('spent') ? 0
    Math.floor gemsEarned + gemsPurchased - gemsSpent

  heroes: ->
    heroes = (me.get('purchased')?.heroes ? []).concat([ThangType.heroes.captain, ThangType.heroes.knight, ThangType.heroes.champion, ThangType.heroes.duelist])
    heroes.push ThangType.heroes['code-ninja'] if window.serverConfig.codeNinjas
    #heroes = _.values ThangType.heroes if me.isAdmin()
    heroes
  items: -> (me.get('earned')?.items ? []).concat(me.get('purchased')?.items ? []).concat([ThangType.items['simple-boots']])
  levels: -> (me.get('earned')?.levels ? []).concat(me.get('purchased')?.levels ? []).concat(Level.levels['dungeons-of-kithgard'])
  ownsHero: (heroOriginal) -> me.isInGodMode() || heroOriginal in @heroes()
  ownsItem: (itemOriginal) -> itemOriginal in @items()
  ownsLevel: (levelOriginal) -> levelOriginal in @levels()

  getHeroClasses: ->
    idsToSlugs = _.invert ThangType.heroes
    myHeroSlugs = (idsToSlugs[id] for id in @heroes())
    myHeroClasses = []
    myHeroClasses.push heroClass for heroClass, heroSlugs of ThangType.heroClasses when _.intersection(myHeroSlugs, heroSlugs).length
    myHeroClasses

  validate: ->
    errors = super()
    if errors and @_revertAttributes

      # Do not return errors if they were all present when last marked to revert.
      # This is so that if a user has an invalid property, that does not prevent
      # them from editing their settings.
      definedAttributes = _.pick @_revertAttributes, (v) -> v isnt undefined
      oldResult = tv4.validateMultiple(definedAttributes, @constructor.schema or {})
      mapper = (error) -> [error.code.toString(),error.dataPath,error.schemaPath].join(':')
      originalErrors = _.map(oldResult.errors, mapper)
      currentErrors = _.map(errors, mapper)
      newErrors = _.difference(currentErrors, originalErrors)
      if _.size(newErrors) is 0
        return
    return errors

  getAnnouncesActionAudioGroup: ->
    return @announcesActionAudioGroup if @announcesActionAudioGroup
    group = me.get('testGroupNumber') % 4
    @announcesActionAudioGroup = switch group
      when 0 then 'all-audio'
      when 1 then 'no-audio'
      when 2 then 'just-take-damage'
      when 3 then 'without-take-damage'
    @announcesActionAudioGroup = 'all-audio' if me.isAdmin()
    application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin()
    @announcesActionAudioGroup

  getCampaignAdsGroup: ->
    return @campaignAdsGroup if @campaignAdsGroup
    # group = me.get('testGroupNumber') % 2
    # @campaignAdsGroup = switch group
    #   when 0 then 'no-ads'
    #   when 1 then 'leaderboard-ads'
    @campaignAdsGroup = 'leaderboard-ads'<commit_msg>Remove unused a/b test getAnnouncesActionAudioGroup<commit_after>GRAVATAR_URL = 'https://www.gravatar.com/'
cache = {}
CocoModel = require './CocoModel'
ThangType = require './ThangType'
Level = require './Level'
utils = require 'core/utils'

# Pure functions for use in Vue
# First argument is always a raw User.attributes
# Accessible via eg. `User.broadName(userObj)`
UserLib = {
  broadName: (user) ->
    return '(deleted)' if user.deleted
    name = _.filter([user.firstName, user.lastName]).join(' ')
    return name if name
    name = user.name
    return name if name
    [emailName, emailDomain] = user.email?.split('@') or []
    return emailName if emailName
    return 'Anonymous'
  isSmokeTestUser: (user) -> utils.isSmokeTestEmail(user.email)
}

module.exports = class User extends CocoModel
  @className: 'User'
  @schema: require 'schemas/models/user'
  urlRoot: '/db/user'
  notyErrors: false

  isAdmin: -> 'admin' in @get('permissions', true)
  isArtisan: -> 'artisan' in @get('permissions', true)
  isInGodMode: -> 'godmode' in @get('permissions', true)
  isAnonymous: -> @get('anonymous', true)
  isSmokeTestUser: -> User.isSmokeTestUser(@attributes)
  displayName: -> @get('name', true)
  broadName: -> User.broadName(@attributes)

  getPhotoURL: (size=80) ->
    return "/db/user/#{@id}/avatar?s=#{size}"

  getRequestVerificationEmailURL: ->
    @url() + "/request-verify-email"

  getSlugOrID: -> @get('slug') or @get('_id')

  set: ->
    if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
      @set "jobProfileApprovedDate", (new Date()).toISOString()
    super arguments...

  @getUnconflictedName: (name, done) ->
    # deprecate in favor of @checkNameConflicts, which uses Promises and returns the whole response
    $.ajax "/auth/name/#{encodeURIComponent(name)}",
      cache: false
      success: (data) -> done(data.suggestedName)

  @checkNameConflicts: (name) ->
    new Promise (resolve, reject) ->
      $.ajax "/auth/name/#{encodeURIComponent(name)}",
        cache: false
        success: resolve
        error: (jqxhr) -> reject(jqxhr.responseJSON)

  @checkEmailExists: (email) ->
    new Promise (resolve, reject) ->
      $.ajax "/auth/email/#{encodeURIComponent(email)}",
        cache: false
        success: resolve
        error: (jqxhr) -> reject(jqxhr.responseJSON)

  getEnabledEmails: ->
    (emailName for emailName, emailDoc of @get('emails', true) when emailDoc.enabled)

  setEmailSubscription: (name, enabled) ->
    newSubs = _.clone(@get('emails')) or {}
    (newSubs[name] ?= {}).enabled = enabled
    @set 'emails', newSubs

  isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled

  isStudent: -> @get('role') is 'student'

  isTeacher: ->
    return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']

  isSessionless: ->
    # TODO: Fix old users who got mis-tagged as teachers
    # TODO: Should this just be isTeacher, eventually?
    Boolean((utils.getQueryVariable('dev', false) or me.isTeacher()) and utils.getQueryVariable('course', false))

  setRole: (role, force=false) ->
    oldRole = @get 'role'
    return if oldRole is role or (oldRole and not force)
    @set 'role', role
    @patch()
    application.tracker?.updateRole()
    return @get 'role'

  a = 5
  b = 100
  c = b

  # y = a * ln(1/b * (x + c)) + 1
  @levelFromExp: (xp) ->
    if xp > 0 then Math.floor(a * Math.log((1 / b) * (xp + c))) + 1 else 1

  # x = b * e^((y-1)/a) - c
  @expForLevel: (level) ->
    if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0

  @tierFromLevel: (level) ->
    # TODO: math
    # For now, just eyeball it.
    tiersByLevel[Math.min(level, tiersByLevel.length - 1)]

  @levelForTier: (tier) ->
    # TODO: math
    for tierThreshold, level in tiersByLevel
      return level if tierThreshold >= tier

  level: ->
    totalPoint = @get('points')
    totalPoint = totalPoint + 1000000 if me.isInGodMode()
    User.levelFromExp(totalPoint)

  tier: ->
    User.tierFromLevel @level()

  gems: ->
    gemsEarned = @get('earned')?.gems ? 0
    gemsEarned = gemsEarned + 100000 if me.isInGodMode()
    gemsPurchased = @get('purchased')?.gems ? 0
    gemsSpent = @get('spent') ? 0
    Math.floor gemsEarned + gemsPurchased - gemsSpent

  heroes: ->
    heroes = (me.get('purchased')?.heroes ? []).concat([ThangType.heroes.captain, ThangType.heroes.knight, ThangType.heroes.champion, ThangType.heroes.duelist])
    heroes.push ThangType.heroes['code-ninja'] if window.serverConfig.codeNinjas
    #heroes = _.values ThangType.heroes if me.isAdmin()
    heroes
  items: -> (me.get('earned')?.items ? []).concat(me.get('purchased')?.items ? []).concat([ThangType.items['simple-boots']])
  levels: -> (me.get('earned')?.levels ? []).concat(me.get('purchased')?.levels ? []).concat(Level.levels['dungeons-of-kithgard'])
  ownsHero: (heroOriginal) -> me.isInGodMode() || heroOriginal in @heroes()
  ownsItem: (itemOriginal) -> itemOriginal in @items()
  ownsLevel: (levelOriginal) -> levelOriginal in @levels()

  getHeroClasses: ->
    idsToSlugs = _.invert ThangType.heroes
    myHeroSlugs = (idsToSlugs[id] for id in @heroes())
    myHeroClasses = []
    myHeroClasses.push heroClass for heroClass, heroSlugs of ThangType.heroClasses when _.intersection(myHeroSlugs, heroSlugs).length
    myHeroClasses

  validate: ->
    errors = super()
    if errors and @_revertAttributes

      # Do not return errors if they were all present when last marked to revert.
      # This is so that if a user has an invalid property, that does not prevent
      # them from editing their settings.
      definedAttributes = _.pick @_revertAttributes, (v) -> v isnt undefined
      oldResult = tv4.validateMultiple(definedAttributes, @constructor.schema or {})
      mapper = (error) -> [error.code.toString(),error.dataPath,error.schemaPath].join(':')
      originalErrors = _.map(oldResult.errors, mapper)
      currentErrors = _.map(errors, mapper)
      newErrors = _.difference(currentErrors, originalErrors)
      if _.size(newErrors) is 0
        return
    return errors

  getCampaignAdsGroup: ->
    return @campaignAdsGroup if @campaignAdsGroup
    # group = me.get('testGroupNumber') % 2
    # @campaignAdsGroup = switch group
    #   when 0 then 'no-ads'
    #   when 1 then 'leaderboard-ads'
    @campaignAdsGroup = 'leaderboard-ads'
{'fraction_non_alphanumeric': 0.14458460849443905, 'fraction_numerical': 0.006195416884377099, 'mean_word_length': 3.5540448674371174, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 102, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4588078', 'n_tokens_mistral': 4578, 'n_tokens_neox': 4386, 'n_words': 1104}