import nextTick from './nextTick'
import { runReaction as taskRunner } from './observer'

export const TARGET_FPS = 60
const PRIORITY = Symbol('task priority')
let taskProcessingQueued = false

export const priorities = {
  SYNC: 'sync',
  CRITICAL: 'critical',
  HIGH: 'high',
  LOW: 'low'
}

const DEFAULT_PRIORITY = priorities.CRITICAL

const validPriorities = new Set(['sync', 'critical', 'high', 'low'])

const queue = {
  [priorities.CRITICAL]: new Set(),
  [priorities.HIGH]: new Set(),
  [priorities.LOW]: new Set()
}

export function setTaskPriority (task, priority) {
  // remove task from previous queue and add to new priority queue
  // if it is set to sync -> run the task right away??!
  priority = priority || DEFAULT_PRIORITY
  if (!validPriorities.has(priority)) {
    throw new Error(`Invalid priority: ${priority}`)
  }
  task[PRIORITY] = priority
}

export function queueTask (task) {
  const priority = task[PRIORITY]
  if (priority === priorities.SYNC) {
    task()
  } else {
    queue[priority].add(task)
    if (!taskProcessingQueued) {
      nextTick(runQueuedTasks)
      taskProcessingQueued = true
    }
  }
}

export function unqueueTask (task) {
  const priority = task[PRIORITY]
  const queueWithPriority = queue[priority]
  if (queueWithPriority) {
    queueWithPriority.delete(task)
  }
}

export function runQueuedTasks () {
  const startDate = Date.now()
  const interval = 1000 / TARGET_FPS

  // critical tasks must all execute before the next frame
  const criticalTasks = queue[priorities.CRITICAL]
  criticalTasks.forEach(taskRunner)
  criticalTasks.clear()
  // high-prio tasks can run if there is free time remaining
  processQueue(priorities.HIGH, startDate, interval)
  // low-prio tasks can run if there is free time and no more high-prio tasks
  const isQueueEmpty = processQueue(priorities.LOW, startDate, interval)

  if (!isQueueEmpty) {
    nextTick(processQueuedTasks)
  } else {
    taskProcessingQueued = false
  }
}

function processQueue (priority, startDate, interval) {
  const queueWithPriority = queue[priority]
  const iterator = queueWithPriority[Symbol.iterator]()
  let task = iterator.next()
  while (startDate - Date.now() < interval) {
    if (task.done) {
      return true
    }
    taskRunner(task.value)
    queueWithPriority.delete(task)
    task = iterator.next()
  }
}

@@ -219,24 +219,8 @@
 = %7B%0A
-  SYNC: 'sync',%0A
   CR
@@ -239,16 +239,16 @@
 tical',%0A
+
   HIGH: 
@@ -354,16 +354,8 @@
 et(%5B
-'sync', 
 'cri
@@ -618,64 +618,8 @@
 eue%0A
-  // if it is set to sync -%3E run the task right away??!%0A
   pr
@@ -857,70 +857,8 @@
 TY%5D%0A
-  if (priority === priorities.SYNC) %7B%0A    task()%0A  %7D else %7B%0A  
   qu
@@ -881,18 +881,16 @@
 d(task)%0A
-  
   if (!t
@@ -916,18 +916,16 @@
 ) %7B%0A    
- 
- 
 nextTick
@@ -941,18 +941,16 @@
 dTasks)%0A
-  
     task
@@ -973,22 +973,16 @@
  = true%0A
-    %7D%0A
   %7D%0A%7D%0A%0Ae

remove SYNC priority