-# https://github.com/petkaantonov/bluebird/issues/63#issuecomment-34140791
-_ = require("lodash")
-q = require("bluebird")
+errify = require "errify"
+
+capitalize = (str) -> str[0].toUpperCase() + str[1..]
+
 
 class RedisModel
   constructor: (@redis) ->
 
-  getActiveKeys: (callback) ->
-    @redis.keys "bull:*:active", callback
-
-  getCompletedKeys: (callback) ->
-    @redis.keys "bull:*:completed", callback
-
-  getFailedKeys: (callback) ->
-    @redis.keys "bull:*:failed", callback
-
-  getWaitingKeys: (callback) ->
-    @redis.keys "bull:*:wait", callback
-
-  getStuckKeys: (callback) ->
+  activeKeys: (queue = "*", callback) ->
+    @redis.keys "bull:#{queue}:active", callback
+
+  completeKeys: (queue = "*", callback) ->
+    @redis.keys "bull:#{queue}:completed", callback
+
+  failedKeys: (queue = "*", callback) ->
+    @redis.keys "bull:#{queue}:failed", callback
+
+  waitKeys: (queue = "*", callback) ->
+    @redis.keys "bull:#{queue}:wait", callback
+
+  stuckKeys: (queue = "*", callback) ->
     ideally = errify callback
     #TODO: Find better way to do this. Being lazy at the moment.
-    await getAllKeys ideally defer keys
-    await formatKeys keys, ideally defer keyList
+    await @allKeys queue, ideally defer keys
+    await @formatKeys keys, ideally defer keyList
 
     count = 0
     keys  = {}
     for key in keyList when key.status is stuck
-      results[keyList[i].type] or= []
-      results[keyList[i].type].push keyList[i].id
+      keys[key.type] or= []
+      keys[key.type].push key.id
       count++
 
     callback null, {keys, count}
 
   #Returns indexes of completed jobs
-  getStatus: (status, callback) ->
-    ideally = errify callback
-
-    getStatusKeysFunction = switch status
-    when "complete" then getCompletedKeys
-    when "active"   then getActiveKeys
-    when "failed"   then getFailedKeys
-    when "wait"     then getWaitingKeys
-    when "stuck"    then getStuckKeys()
-    else
-      return callback new Error "UNSUPPORTED STATUS: #{status}"
-
-    await getStatusKeysFunction ideally defer statusKeys
-
-    keys = []
+  statusKeys: (status, callback) ->
+    ideally = errify callback
+
+    await @["#{status}Keys"] ideally defer statusKeys
+
+    keys  = {}
     multi = []
 
     for key in statusKeys
-      keys[(key.split ":")[1]] = []
-      # This creates an array/object thing with keys of the job type
+      [_, type]  = key.split ":"
+      keys[type] = null
+
       if status is "active" or status is "wait"
         multi.push ["lrange", key, 0, -1]
       else
         multi.push ["smembers", key]
 
-    await (@redis.multi multi).exec ideally defer data
+    await (@redis.multi multi).exec ideally defer arrayOfArrays
     statusKeyKeys = Object.keys(keys)
     # Get the keys from the object we created earlier...
     count = 0
-    for datum, i in data
-      keys[statusKeyKeys[i]] = datum
-      count += datum.length
+    for array, i in arrayOfArrays
+      keys[statusKeyKeys[i]] = array
+      count += array.length
 
     callback null, {keys, count}
 
   #Returns all JOB keys in string form (ex: bull:video transcoding:101)
-  getAllKeys = (callback) ->
-    ideally = errify callback
-    await @redis.keys "bull:*:[0-9]*" ideally defer keysWithLocks
-    (keyWithLock for keyWithLock in keysWithLocks when keyWithLock.substring(keyWithLock.length - 5, keyWithLock.length) isnt ":lock")
-
-  getFullKeyNamesFromIds = (list, callback) ->
+  allKeys: (queue = "*", callback) ->
+    ideally = errify callback
+
+    await @redis.keys "bull:#{queue}:[0-9]*" ideally defer keysWithLocks
+    result = (keyWithLock for keyWithLock in keysWithLocks when keyWithLock[-5..] isnt ":lock")
+    callback null, result
+
+  fullKeyNamesFromIds: (list, callback) ->
     return callback() unless list and Array.isArray list
-
-    ideally = errify callback
-    keys = (["keys", "bull:*:#{item}"] for item in list)
-    await @redis.multi(keys).exec ideally defer arrayOfArrays
-    callback null, (array[0] for array in arrayOfArrays when array.length is 1)
+    ideally = errify callback
+
+    multi = (["keys", "bull:*:#{item}"] for item in list)
+    await (@redis.multi multi).exec ideally defer arrayOfArrays
+    result = (array[0] for array in arrayOfArrays when array.length is 1)
+    callback null, result
 
   #Returns the job data from a list of job ids
-  getJobsInList = (list) ->
+  jobsInList: (list, callback) ->
     return callback() unless list
 
-    ideally = errify callback
-    jobs = []
-    if keys = list.keys
-      objectKeys = Object.keys(keys)
-      fullNames = []
-
-      for key, val in keys
-        for i in val
-          fullNames.push "bull:" + key + ":" + keys[objectKeys[i]][k]
+    if allkeys = list.keys
+      fullNames = "bull:#{type}:#{key}" for key in keys for type, keys of allkeys
       callback null, fullnames
     else
       #Old list type
-      getFullKeyNamesFromIds list, callback
+      @fullKeyNamesFromIds list, callback
 
   #Returns counts for different statuses
-  getStatusCounts = (callback) ->
-    ideally = errify callback
-    await getStatus "active",   ideally defer active
-    await getStatus "complete", ideally defer completed
-    await getStatus "failed",   ideally defer failed
-    await getStatus "wait",     ideally defer pendingKeys
-    await getAllKeys ideally defer allKeys
-
-    active: active.count
+  statusCounts: (callback) ->
+    ideally = errify callback
+    await @statusKeys "active",   ideally defer active
+    await @statusKeys "complete", ideally defer completed
+    await @statusKeys "failed",   ideally defer failed
+    await @statusKeys "wait",     ideally defer pendingKeys
+    await @allKeys                ideally defer allKeys
+
+    active:   active.count
     complete: completed.count
-    failed: failed.count
-    pending: pendingKeys.count
-    total: allKeys.length
-    stuck: allKeys.length - (active.count + completed.count + failed.count + pendingKeys.count)
+    failed:   failed.count
+    pending:  pendingKeys.count
+    total:    allKeys.length
+    stuck:    allKeys.length - (active.count + completed.count + failed.count + pendingKeys.count)
 
   #Returns all keys in object form, with status applied to object. Ex: {id: 101, type: "video transcoding", status: "pending"}
-  formatKeys = (keys, callback) ->
+  formatKeys: (keys, callback) ->
     return unless keys
-
-    ideally = errify callback
-
-    await getStatus "failed",   ideally defer failedJobs
-    await getStatus "complete", ideally defer completedJobs
-    await getStatus "active",   ideally defer activeJobs
-    await getStatus "wait",     ideally defer pendingJobs
+    ideally = errify callback
+
+    await @statusKeys "failed",   ideally defer failedJobs
+    await @statusKeys "complete", ideally defer completedJobs
+    await @statusKeys "active",   ideally defer activeJobs
+    await @statusKeys "wait",     ideally defer pendingJobs
 
     keyList = for key in keys
-      [type, id] = key.split ":"
+      [_, type..., id] = key.split ":"
+      type = type.join ":"
+
       status = "stuck"
       if activeJobs.keys[type] and id not in activeJobs.keys[type]
         status = "active"

      "failed"
      "wait"
    ]
    #I could add stuck, but(callback)  I won"t support mass modifying "stuck" jobs because it"s very possible for things to be in a "stuck" state temporarily, while transitioning between states

    ideally = errify (message) -> callback {success: false, message}

    console.log validTypes.indexOf(type)
    unless type in validTypes
      return callback success: false, message: "Invalid type: #{type} not in list of supported types"

    await getStatus type, ideally defer allKeys
      multi = []
      allKeyObjects = Object.keys(allKeys.keys)

      i = 0
      ii = allKeyObjects.length
      while i < ii
        firstPartOfKey = "bull:" + allKeyObjects[i] + ":"
        k = 0
        kk = allKeys.keys[allKeyObjects[i]].length
        while k < kk
          item = allKeys.keys[allKeyObjects[i]][k]
          #Brute force remove from everything
          multi.push ["lrem", firstPartOfKey + "active", 0, item]
          multi.push ["srem", firstPartOfKey + "completed", item]
          multi.push ["srem", firstPartOfKey + "failed", item]
          #Add to pending
          multi.push ["rpush", firstPartOfKey + "wait", item]

      await (@redis.multi multi).exec ideally defer data
      callback success: true, message: "Successfully made all #{type} jobs pending."

  #Makes a job with a specific ID pending, requires the type of job as the first parameter and ID as second.
  makePendingById = (type, id, callback) ->
    unless id
      return callback success: false, message: "There was no ID provided."
    unless type
      return callback success: false, message: "There was no type provided."

    ideally = errify (message) -> callback {success: false, message}

    firstPartOfKey = "bull:#{type}:"

    multi = []
    multi.push ["lrem", firstPartOfKey + "active", 0, id]
    multi.push ["lrem", firstPartOfKey + "wait", 0, id]
    multi.push ["srem", firstPartOfKey + "completed", id]
    multi.push ["srem", firstPartOfKey + "failed", id]
    #Add to pending
    multi.push ["rpush", firstPartOfKey + "wait", id]
    await (@redis.multi multi).exec ideally defer data
    callback success: true, message: "Successfully made #{type} job ##{id} pending."

  #Deletes all jobs in a specific status
  deleteJobByStatus = (type, callback) ->
    type = type.toLowerCase()
    validTypes = [
      "active"

        status = "pending"
      {id, type, status}

    keyList = keyList.sort (a, b) ->
      aid = parseInt a.id
      bid = parseInt b.id
      if aid < bid then -1
      else if aid > bid then 1
      else 0

    callback null, keyList

  commandRemoveFromStateLists: (prefix, id) -> [
    ["lrem", "#{prefix}active",     0, id]
    ["lrem", "#{prefix}wait",       0, id]
    ["srem", "#{prefix}completed",  id]
    ["srem", "#{prefix}failed",     id]
  ]

  #Removes one or  more jobs by ID, also removes the job from any state list it's in
  remove: (list) ->
    return unless list
    #Expects {id: 123, type: "video transcoding"}
    multi = []
    for item in list
      prefix = "bull:#{item.type}:"
      multi.push ["del", "#{prefix}#{item.id}"]
      multi.concat @commandRemoveFromStateLists prefix, item.id

    (@redis.multi multi).exec()

  #Makes all jobs in a specific status pending
  makePendingByType: (type, callback) ->
    type = type.toLowerCase()
    validTypes = [
      "active"
