text: """ Module for interacting with the achievements """
  
  import imp
  import pymongo
  
  import api
  
  from os.path import join
  from datetime import datetime
  from voluptuous import Schema, Required, Range
  from api.common import check, InternalException, SevereInternalException, validate, safe_fail, WebException
  
  from api.annotations import log_action
  
  achievement_schema = Schema({
      Required("name"): check(
          ("The achievement's display name must be a string.", [str])),
      Required("score"): check(
          ("Score must be a positive integer.", [int, Range(min=0)])),
      Required("event"): check(
          ("Type must be a string.", [str])),
      Required("description"): check(
          ("The description must be a string.", [str])),
      Required("processor"): check(
          ("The processor path must be a string.", [str])),
      Required("hidden"): check(
          ("An achievement's hidden state is either True or False.", [
              lambda hidden: type(hidden) == bool])),
      Required("image"): check(
          ("An achievement's image path must be a string.", [str])),
      Required("smallimage"): check(
          ("An achievement's smallimage path must be a string.", [str])),
      "disabled": check(
          ("An achievement's disabled state is either True or False.", [
              lambda disabled: type(disabled) == bool])),
      "multiple": check(
          ("Whether an achievement can be earned multiple times is either True or False.", [
              lambda disabled: type(disabled) == bool])),
      "aid": check(
          ("You should not specify a aid for an achievement.", [lambda _: False])),
      "_id": check(
          ("Your achievements should not already have _ids.", [lambda id: False]))
  })
  
  def get_all_events(show_disabled=False):
      """
      Gets the set of distinct achievement events.
  
      Args:
          show_disabled: Whether to include events that are only on disabled achievements
      Returns:
          The set of distinct achievement events.
      """
  
      db = api.common.get_conn()
  
      match = {}
      if not show_disabled:
          match.update({"disabled": False})
  
      return db.achievemets.find(match).distinct("event")
  
  def get_achievement(aid=None, name=None, show_disabled=False):
      """
      Gets a single achievement
  
      Args:
          aid: the achievement id
          name: the name of the achievement
          show_disabled: Boolean indicating whether or not to show disabled achievements.
      """
  
      db = api.common.get_conn()
  
      match = {}
  
      if aid is not None:
          match.update({'aid': aid})
      elif name is not None:
          match.update({'name': name})
      else:
          raise InternalException("Must supply aid or display name")
  
      if not show_disabled:
          match.update({"disabled": False})
  
      db = api.common.get_conn()
      achievement = db.achievements.find_one(match, {"_id":0})
  
      if achievement is None:
          raise SevereInternalException("Could not find achievement! You gave " + str(match))
  
      return achievement
  
  def get_all_achievements(event=None, show_disabled=False):
      """
      Gets all of the achievements in the database.
  
      Args:
          event: Optional parameter to restrict which achievements are returned
          show_disabled: Boolean indicating whether or not to show disabled achievements.
      Returns:
          List of achievements from the database
      """
  
      db = api.common.get_conn()
  
      match = {}
      if event is not None:
          match.update({'event': event})
  
      if not show_disabled:
          match.update({'disabled': False})
  
      return list(db.achievements.find(match, {"_id":0}).sort('score', pymongo.ASCENDING))
  
  def get_earned_achievement_instances(tid=None, uid=None, aid=None):
      """
      Gets the solved achievements for a given team or user.
  
      Args:
          tid: The team id
          event: Optional parameter to restrict which achievements are returned
      Returns:
          List of solved achievements
      """
  
      db = api.common.get_conn()
  
      match = {}
  
      if uid is not None:
          match.update({"uid": uid})
      elif tid is not None:
          match.update({"tid": tid})
  
      if aid is not None:
          match.update({"aid": aid})
  
      return list(db.earned_achievements.find(match, {"_id":0}))
  
  def get_earned_aids(tid=None, uid=None, aid=None):
      """
      Gets the solved aids for a given team or user.
  
      Args:
          tid: The team id
          event: Optional parameter to restrict which achievements are returned
      Returns:
          List of solved achievement ids
      """
  
      return set([a["aid"] for a in get_earned_achievement_instances(tid=tid, uid=uid, aid=aid)])
  
  def set_earned_achievements_seen(tid=None, uid=None):
      """
      Sets all earned achievements from a team or user seen.
  
      Args:
          tid: the team id
          uid: the user id
      """
  
      db = api.common.get_conn()
  
      match = {}
  
      if tid is not None:
          match.update({"tid": tid})
      elif uid is not None:
          match.update({"uid": uid})
      else:
          raise InternalException("You must specify either a tid or uid")
  
      db.earned_achievements.update(match, {"$set": {"seen": True}}, multi=True)
  
  def get_earned_achievements_display(tid=None, uid=None):
      """
      Gets the achievement display for a given user/team.
      Includes instance specific information.
  
      Args:
          tid: The team id
          tid: The user id
      Returns:
          A list of enabled achievements the team has earned.
      """
  
      instance_achievements = get_earned_achievement_instances(tid=tid, uid=uid)
      set_earned_achievements_seen(tid=tid, uid=uid)
  
      for instance_achievement in instance_achievements:
          achievement = get_achievement(aid=instance_achievement["aid"])
  
          #Make sure not to override name or description.
          achievement.pop("name")
          achievement.pop("description")
  
          instance_achievement.update(achievement)
  
          #Make sure to remove sensitive data
          instance_achievement.pop("data", None)
  
      return instance_achievements
  
  def get_earned_achievements(tid=None, uid=None):
      """
      Gets the solved achievements for a given team or user.
  
      Args:
          tid: The team id
          tid: The user id
      Returns:
          List of solved achievement dictionaries
      """
  
      achievements = get_earned_achievement_instances(tid=tid, uid=uid)
      set_earned_achievements_seen(tid=tid, uid=uid)
  
      for achievement in achievements:
          achievement.update(get_achievement(aid=achievement["aid"]))
          achievement.pop("data")
  
      return achievements
  
  def set_achievement_disabled(aid, disabled):
      """
      Updates a achievement's availability.
  
      Args:
          aid: the achievement's aid
          disabled: whether or not the achievement should be disabled.
      Returns:
          The updated achievement object.
      """
  
      return update_achievement(aid, {"disabled": disabled})
  
  def get_processor(aid):
      """
      Returns the processor module for a given achievement.
  
      Args:
          aid: the achievement id
      Returns:
          The processor module
      """
  
      try:
          path = get_achievement(aid=aid, show_disabled=True)["processor"]
          base_path = api.config.get_settings()["achievements"]["processor_base_path"]
          return imp.load_source(path[:-3], join(base_path, path))
      except FileNotFoundError:
          raise InternalException("Achievement processor is offline.")
  
  @log_action
  def process_achievement(aid, data):
      """
      Determines whether or not an achievement has been earned.
      Should not be called directly.
  
      Args:
          aid: the achievement id
          data: additional data dictionary
      """
  
      if data.get("uid", None) is None:
          data["uid"] = api.user.get_user()["uid"]
  
      if data.get("tid", None) is None:
          data["tid"] = api.user.get_user(uid=data["uid"])["tid"]
  
      get_achievement(aid=aid, show_disabled=True)
      processor = get_processor(aid)
  
      return processor.process(api, data)
  
  def insert_earned_achievement(aid, data):
      """
      Store earned achievement for a user/team.
  
      Args:
          aid: the achievement id
          data: the data necessary to assess the achievement
                must include tid, uid
      """
  
      db = api.common.get_conn()
  
      tid, uid = data.pop("tid"), data.pop("uid")
      name, description = data.pop("name"), data.pop("description")
  
      db.earned_achievements.insert({
          "aid": aid,
          "tid": tid,
          "uid": uid,
          "data": data,
          "name": name,
          "description": description,
          "timestamp": datetime.utcnow().timestamp(),
          "seen": False
      })
  
  def process_achievements(event, data):
      """
      Process achievements of a type with data.
  
      Args:
          event: event type, e.g., submit
          data: dictionary with additional information necessary for assessment
      """
  
      if data.get("uid", None) is None:
          data["uid"] = api.user.get_user()["uid"]
  
      if data.get("tid", None) is None:
          data["tid"] = api.user.get_user(uid=data["uid"])["tid"]
  
      eligible_achievements = [
          achievement for achievement in get_all_achievements(event=event) \
              if achievement["aid"] not in get_earned_aids(tid=data["tid"]) \
              or achievement.get("multiple", False)]
  
      for achievement in eligible_achievements:
          aid = achievement["aid"]
  
          acquired, instance_info = process_achievement(aid, data)
  
          info = {
              "name":achievement.get("name"),
              "description": achievement.get("description")
          }
  
          info.update(instance_info)
          data.update(info)
          if acquired:
              insert_earned_achievement(aid, data)
  
  def insert_achievement(achievement):
      """
      Inserts an achievement object into the database.
  
      Args:
          achievement: the achievement object loaded from json.
      Returns:
          The achievment's aid.
      """
  
      db = api.common.get_conn()
      validate(achievement_schema, achievement)
  
      achievement["disabled"] = achievement.get("disabled", False)
  
      achievement["aid"] = api.common.hash(achievement["name"])
  
      if safe_fail(get_achievement, aid=achievement["aid"]) is not None:
          raise WebException("achievement with identical aid already exists.")
  
  
      if safe_fail(get_achievement, name=achievement["name"]) is not None:
          raise WebException("achievement with identical name already exists.")
  
      db.achievements.insert(achievement)
      api.cache.fast_cache.clear()
  
      return achievement["aid"]
  
  def update_achievement(aid, updated_achievement):
      """
      Updates a achievement with new properties.
  
      Args:
          aid: the aid of the achievement to update.
          updated_achievement: an updated achievement object.
      Returns:
          The updated achievement object.
      """
  
      db = api.common.get_conn()
  
      if updated_achievement.get("name", None) is not None:
          if safe_fail(get_achievement, name=updated_achievement["name"]) is not None:
              raise WebException("Achievement with identical name already exists.")
  
      achievement = get_achievement(aid=aid, show_disabled=True).copy()
      achievement.update(updated_achievement)
  
      # pass validation by removing/readding aid
      achievement.pop("aid")
      validate(achievement_schema, achievement)
      achievement["aid"] = aid
  
      db.achievements.update({"aid": aid}, achievement)
      api.cache.fast_cache.clear()
  
      return achievement
  