 ObjectId = mongoose.Types.ObjectId
 
 getFeedback = (obj, callback) ->
+  # Call type-specific method
   switch obj.hit.type
     when 'relevance judgments'
       return getRelevanceFeedback(obj, callback)
     when 'image tagging'
-      return callback("No image tagging feedback yet")
+      return getTaggingFeedback(obj, callback)
     else
       return callback("No feedback logic for type: #{obj.hit.type}", null)
   callback("No Feedback yet", null)
+
+# Get feedback for Image-Tagging
+# Relies on manual annotations saved to db.tagqualities
+getTaggingFeedback = (obj, callback) ->
+  TagQuality = require './models/tagquality'
+
+  async.parallel({
+    qJudgments: (cb) -> TagQuality.find().exec(cb)
+    # Get a list with user:user, tags:[{item,tag}] per row
+    byUser: (cb) -> TaskSet.aggregate(
+      {$match:{hit_id:{$in:obj.allHitType}}},
+      {$unwind: '$tasks'}, {$unwind:'$tasks.contribution.tags'},
+      {$project:{
+        item:'$tasks.item.id',
+        user:1,
+        tag: $toLower: '$tasks.contribution.tags'
+      }},
+      {$group:{_id:{user:'$user'}, tags:{$push:{item:'$item', tag:'$tag'}}}}
+    ).exec(cb)
+    # REMOVING FOR A HARDCODED VALUE
+    #singleCountTags: (cb) -> TaskSet.aggregate(
+    #  {$match:{hit_id:{$in:obj.allHitType}}},
+    #  {$unwind: '$tasks'},
+    #  {$unwind:'$tasks.contribution.tags'},
+    #  {$project:{
+    #    item:'$tasks.item.id',
+    #    tag:{$toLower:'$tasks.contribution.tags'}}},
+    #  {$group:{_id:{item:'$item', tag:'$tag'}, count:{$sum:1}}},
+    #  {$match:{count:1}},
+    #  {$group:{_id:null, tags:{$push:'$_id'}}}
+    #).exec(cb)
+  }, (err, results) ->
+    if (err)
+      return console.error err
+    # Make quality judgements into an associative array for quick lookup
+    qual = results.qJudgments.reduce(((map, doc)->
+      item = String(doc.item_id)
+      if not map[item]
+        map[item] = {}
+      map[item][doc.tag] = doc.quality
+      return map
+    ),{})
+
+    # REMOVING FOR A HARDCODED VALUE
+    # For unseen items, set a default score based on the average rating of
+    # items that have only been seen once
+    #singleItemJudgments = _.map(results.singleCountTags[0].tags, (tag) ->
+    #  return qual[tag.item][tag.tag]
+    #)
+    #unseenScore = _.sum(singleItemJudgments)/singleItemJudgments.length
+
+    # Hardcoded. The average for once tagged items is 2.5, so round down
+    unseenScore = 2
+
+    byWorker = _.map(results.byUser, (user) ->
+      scores =_.map(user.tags, (row) ->
+        quality = parseInt(qual[String(row.item)][row.tag])
+        if quality not in [0,1,2,3,4]
+          quality = unseenScore
+        quality
+      )
+      avgScore = _.sum(scores)/scores.length
+      {user:user._id.user, avgScore:avgScore}
+    )
+    
+    allScores = _.map(byWorker, (w) -> w.avgScore).sort((a,b)-> a-b)
+    worker = _.find(byWorker, (w) -> w.user is obj.opts.user)
+    performanceFeedback =
+      numWorkers: allScores.length
+      min: _.min(allScores)
+      max: _.max(allScores)
+      worker:
+        performance: worker.avgScore
+        rank: 1+allScores.indexOf(worker.avgScore)
+
+    return callback(null, performanceFeedback)
+  )
 
 getRelevanceFeedback = (obj, callback) ->
   # This part of the aggregation pipeline will return unwound rows of

      item_id:'$tasks.item.id'
    }},
    {$unwind: '$judgment'}]
  
  # This will return all the judgments per result
  # i.e. {_id:QUERY,ITEM_ID, judgments;['Not', 'Somewhat','Not', ...]}
  byItemQuery = unwindJudgments.concat([

      {user:user, scores: scores, avgScore:avgScore}
    )
    allScores = _.map(byWorker, (w) -> w.avgScore).sort((a,b)-> a-b)
    worker = _.find(byWorker, (w) -> w.user is obj.opts.user)
    performanceFeedback =
      numWorkers: allScores.length
      min: _.min(allScores)
      max: _.max(allScores)
      worker:
        performance: worker.avgScore
        rank: 1+allScores.indexOf(worker.avgScore)

    callback(null, performanceFeedback)
  )
