QR.cooldown =
  seconds: 0
  delays:
    deletion: 60 # cooldown for deleting posts/files

  # Called from Main
  init: ->
    return unless Conf['Quick Reply']
    @data = Conf['cooldowns']
    @changes = $.dict()
    $.sync 'cooldowns', @sync

  # Called from QR
  setup: ->
    # Read cooldown times
    $.extend QR.cooldown.delays, g.BOARD.cooldowns()

    # The longest reply cooldown, for use in pruning old reply data
    QR.cooldown.maxDelay = 0
    for type, delay of QR.cooldown.delays when type not in ['thread', 'thread_global']
      QR.cooldown.maxDelay = Math.max QR.cooldown.maxDelay, delay

    QR.cooldown.isSetup = true
    QR.cooldown.start()

  start: ->
    {data} = QR.cooldown
    return unless (
      Conf['Cooldown'] and
      QR.cooldown.isSetup and
      !QR.cooldown.isCounting and
      Object.keys(data[g.BOARD.ID] or {}).length + Object.keys(data.global or {}).length > 0
    )
    QR.cooldown.isCounting = true
    QR.cooldown.count()

  sync: (data) ->
    QR.cooldown.data = data or $.dict()
    QR.cooldown.start()

  add: (threadID, postID) ->
    return unless Conf['Cooldown']
    start = Date.now()
    boardID = g.BOARD.ID
    QR.cooldown.set boardID, start, {threadID, postID}
    QR.cooldown.set 'global', start, {boardID, threadID, postID} if threadID is postID
    QR.cooldown.save()
    QR.cooldown.start()

  addDelay: (post, delay) ->
    return unless Conf['Cooldown']
    cooldown = QR.cooldown.categorize post
    cooldown.delay = delay
    QR.cooldown.set g.BOARD.ID, Date.now(), cooldown
    QR.cooldown.save()
    QR.cooldown.start()

  addMute: (delay) ->
    return unless Conf['Cooldown']
    QR.cooldown.set g.BOARD.ID, Date.now(), {type: 'mute', delay}
    QR.cooldown.save()
    QR.cooldown.start()

  delete: (post) ->
    return unless QR.cooldown.data
    cooldowns = (QR.cooldown.data[post.board.ID] or= $.dict())
    for id, cooldown of cooldowns
      if !cooldown.delay? and cooldown.threadID is post.thread.ID and cooldown.postID is post.ID
        QR.cooldown.set post.board.ID, id, null
    QR.cooldown.save()

  secondsDeletion: (post) ->
    return 0 unless QR.cooldown.data and Conf['Cooldown']
    cooldowns = QR.cooldown.data[post.board.ID] or $.dict()
    for start, cooldown of cooldowns
      if !cooldown.delay? and cooldown.threadID is post.thread.ID and cooldown.postID is post.ID
        seconds = QR.cooldown.delays.deletion - (Date.now() - start) // $.SECOND
        return Math.max seconds, 0
    0

  categorize: (post) ->
    if post.thread is 'new'
      type: 'thread'
    else
      type: if !!post.file then 'image' else 'reply'
      threadID: +post.thread

  mergeChange: (data, scope, id, value) ->
    if value
      (data[scope] or= $.dict())[id] = value
    else if scope of data
      delete data[scope][id]
      delete data[scope] if Object.keys(data[scope]).length is 0

  set: (scope, id, value) ->
    QR.cooldown.mergeChange QR.cooldown.data, scope, id, value
    (QR.cooldown.changes[scope] or= $.dict())[id] = value

  save: ->
    {changes} = QR.cooldown
    return unless Object.keys(changes).length
    $.get 'cooldowns', $.dict(), ({cooldowns}) ->
      for scope of QR.cooldown.changes
        for id, value of QR.cooldown.changes[scope]
          QR.cooldown.mergeChange cooldowns, scope, id, value
        QR.cooldown.data = cooldowns
      $.set 'cooldowns', cooldowns, ->
        QR.cooldown.changes = $.dict()

  clear: ->
    QR.cooldown.data = $.dict()
    QR.cooldown.changes = $.dict()
    QR.cooldown.auto = false
    QR.cooldown.update()
    $.queueTask $.delete, 'cooldowns'

  update: ->
    return unless QR.cooldown.isCounting

    save = false
    nCooldowns = 0
    now = Date.now()
    {type, threadID} = QR.cooldown.categorize QR.posts[0]
    seconds = 0

    if Conf['Cooldown'] then for scope in [g.BOARD.ID, 'global']
      cooldowns = (QR.cooldown.data[scope] or= $.dict())

      for start, cooldown of cooldowns
        start = +start
        elapsed = (now - start) // $.SECOND
        if elapsed < 0 # clock changed since then?
          QR.cooldown.set scope, start, null
          save = true
          continue

        # Explicit delays from error messages
        if cooldown.delay?
          if cooldown.delay <= elapsed
            QR.cooldown.set scope, start, null
            save = true
          else if (cooldown.type is type and cooldown.threadID is threadID) or cooldown.type is 'mute'
            # Delays only apply to the given post type and thread.
            seconds = Math.max seconds, cooldown.delay - elapsed
          continue

        # Clean up expired cooldowns
        maxDelay = if cooldown.threadID isnt cooldown.postID
          QR.cooldown.maxDelay
        else
          QR.cooldown.delays[if scope is 'global' then 'thread_global' else 'thread']
        if QR.cooldown.customCooldown
          maxDelay = Math.max maxDelay, parseInt(Conf['customCooldown'], 10)
        if maxDelay <= elapsed
          QR.cooldown.set scope, start, null
          save = true
          continue

        if (type is 'thread') is (cooldown.threadID is cooldown.postID) and cooldown.boardID isnt g.BOARD.ID
          # Only cooldowns relevant to this post can set the seconds variable:
          #   reply cooldown with a reply, thread cooldown with a thread.
          # Inter-board thread cooldowns only apply on boards other than the one they were posted on.
          suffix = if scope is 'global'
            '_global'
          else
            ''
          seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed

          # If additional cooldown is enabled, add the configured seconds to the count.
          if QR.cooldown.customCooldown
            seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed

      nCooldowns += Object.keys(cooldowns).length

    QR.cooldown.save if save

    if nCooldowns
      clearTimeout QR.cooldown.timeout
      QR.cooldown.timeout = setTimeout QR.cooldown.count, $.SECOND
    else
      delete QR.cooldown.isCounting

    # Update the status when we change posting type.
    # Don't get stuck at some random number.
    # Don't interfere with progress status updates.
    update = seconds isnt QR.cooldown.seconds
    QR.cooldown.seconds = seconds
    QR.status() if update

  count: ->
    QR.cooldown.update()
    QR.submit() if QR.cooldown.seconds is 0 and QR.cooldown.auto and !QR.req
