     var date: NSDate
     var totalAvailableWork: Float
     var workSessions: [TaskWorkSession]
-    
+
     init(date: NSDate, totalAvailableWork: Float) {
         self.date = date
         self.totalAvailableWork = totalAvailableWork
         self.workSessions = []
     }
-    
+
     var workScheduled: Float {
         return self.workSessions.map({ $0.amountOfWork }).reduce(0.0, combine: +)
     }
-    
+
     var workLeftToBeScheduled: Float {
         return (self.totalAvailableWork - self.workScheduled)
+    }
+}
+
+// ---------------------- //
+// ----- SCHEDULING ----- //
+// ---------------------- //
+
+func scheduleTasksForUser(user: User) {
+    // ----- GETTING TASKS TO SCHEDULE ----- //
+
+    // First of all, get a list of tasks that are elegible to be scheduled
+    // and reset them appropriately. Tasks that are eleigible are: not
+    // marked as compelete, due in the future, have work left to do.
+
+    let tasksToSchedule = user.outstandingTasks
+    for task in tasksToSchedule {
+        task.dropped = false
+        task.workSessions = task.workSessions.filter({ $0.hasBeenCompleted })
+    }
+
+    // ----- DROPPING TASKS ----- //
+
+    // Sort outstanding tasks by due date
+
+    let tasksSortedByDueDate = tasksToSchedule.sort() { $0.dueDate.compare($1.dueDate) == .OrderedAscending }
+
+    // Go through all of the due dates, seeing if all of the tasks due on or before
+    // the given date are schedulable. If not, drop the longest, low priority tasks
+    // in that interval until it is schedulable. This maximizes the number of tasks
+    // that get done.
+
+    let lastDueDate = tasksSortedByDueDate.last!.dueDate
+    var currentDueDate = now.dateByAddingTimeInterval(24 * 60 * 60) // Start with due date tomorrow
+
+    while currentDueDate.compare(lastDueDate) != .OrderedDescending {
+        let workTimeAvailable = user.availableWorkTimeBetweenNowAnd(date: currentDueDate)
+        let estimatedWork = user.workToDoBetweenNowAnd(date: currentDueDate)
+
+        if estimatedWork > workTimeAvailable {
+            // This due date isn't schedulable, drop tasks
+            var workDropped: Float = 0.0
+
+            // First get a list of tasks due on or before the current date
+            var tasksDue = user.tasks.filter({ $0.dueDate.compare(currentDueDate) != .OrderedDescending })
+
+            // Then sort that list of tasks by estimated work, then in reverse by priority
+            tasksDue.sortInPlace({ $0.workEstimate > $1.workEstimate })
+            tasksDue.sortInPlace({ $0.priority < $1.priority })
+
+            // Now go through the tasks and drop them until it's schedulable
+            for task in tasksDue {
+                workDropped += task.workEstimate
+                task.dropped = true
+
+                if (estimatedWork - workDropped) <= workTimeAvailable {
+                    break
+                }
+            }
+        }
+
+        currentDueDate = currentDueDate.dateByAddingTimeInterval(24 * 60 * 60)
+    }
+
+    // ----- SCHEDULING TASKS ----- //
+
+    // Goals/rules of scheduling:
+    //      Tasks are scheduled to be completed the day before they're due (unless the only day left is its due date)
+    //      All tasks meet their deadlines (we know there's enough working time once we get here)
+    //      Tasks are started as late as possible, with low priority tasks scheduled later than high priority tasks
+    //      Long tasks can be split over several days
+
+    // Sort the tasks so that the latest, shortest, lowest-priority tasks are first
+
+    var sortedTasks = user.notDroppedTasks.sort({ $0.workEstimate < $1.workEstimate })
+    sortedTasks.sortInPlace({ $0.priority < $1.priority })
+    sortedTasks.sortInPlace({ $0.dueDate.compare($1.dueDate) == .OrderedDescending })
+
+    // Schedule them tasks
+
+    for task in sortedTasks {
+        var dayToScheduleOn: WorkDay?
+
+        // First try to find the latest day before the task's due date
+        // that has available work
+        var currentDay = user.workDayBeforeDay(user.workDayForDate(task.dueDate))
+        while currentDay.date.compare(now) != .OrderedAscending {
+            if currentDay.workLeftToBeScheduled > 0.0 {
+                dayToScheduleOn = currentDay
+                break
+            }
+            currentDay = user.workDayBeforeDay(currentDay)
+        }
+
+        // If that didn't work, check the task's due date
+        if dayToScheduleOn == nil {
+            let day = user.workDayForDate(task.dueDate)
+            if day.workLeftToBeScheduled > 0.0 {
+                dayToScheduleOn = day
+            }
+        }
+
+        // If it still can't be scheduled, something is wrong
+        let confirmedDayToScheduleOn = dayToScheduleOn!
+
+        // Otherwise, schedule the task, splitting it up as needed
+
+        currentDay = confirmedDayToScheduleOn
+        while task.workNotScheduled > 0.0 && currentDay.date.compare(now) != .OrderedAscending {
+            let workForNewWorkSession = min(currentDay.workLeftToBeScheduled, task.workNotScheduled)
+            if workForNewWorkSession > 0.0 {
+                task.addWorkSession(currentDay, amountOfWork: workForNewWorkSession)
+            }
+
+            currentDay = user.workDayBeforeDay(currentDay)
+        }
+
+        // If we get here and the task still hasn't been totally scheduled, something is wrong
+        if task.workNotScheduled > 0.0 {
+            // Normally I would just throw something but that's acting funny in a playground
+            dateFormatter.dateFromString("garbage")!
+        }
+    }
+}
+
+// ------------------- //
+// ----- TESTING ----- //
+// ------------------- //
+
+// Things to test:
+//      [x] A bunch of tasks due within a few days of each other
+//      [x] Splitting long tasks over several days
+//      [x] Conforming to when the user says they can work on tasks
+//      [ ] Many tasks spread out over a long period of time (over a few months)
+//      [x] Dropping lower-priority tasks in favor of higher priority ones (if it comes down to that)
+//      [ ] Not dropping tasks that don't need to be dropped
+//      [ ] Rescheduling after modifying tasks (completing work sessions, changing the estimate, dropping tasks manually, etc)
+//      [ ] Rescheduling after modifying work days' available work times
+//      [ ] Detecting when the user manually changed a work day's available work time outside of their schedule (when changing their schedule)
+//              -- May want to just save this for when it's in the app, need a UI to take care of that
+
+// Quick function for displaying the user's schedule
+
+func printScheduleForUser(user: User) {
+    for day in user.workDays {
+        if day.workScheduled == 0.0 {
+            continue
+        }
+        print("\(day.date) - \(day.totalAvailableWork)")
+
+        for workSession in day.workSessions {
+            print("\(workSession.parentTask.title) - \(workSession.amountOfWork)")
+        }
+        print("")
     }
 }
 

            let newWorkDay = WorkDay(date: date, totalAvailableWork: self.workAvailableOnDate(date))
            self.workDays.append(newWorkDay)
            self.workDays.sortInPlace({ $0.date.compare($1.date) == .OrderedAscending })
            
            return newWorkDay
        }
    }
    
    func workDayBeforeDay(day: WorkDay) -> WorkDay {
        return self.workDayForDate(day.date.dateByAddingTimeInterval(-24 * 60 * 60))
    }
    
    func workDayAfterDay(day: WorkDay) -> WorkDay {
        return self.workDayForDate(day.date.dateByAddingTimeInterval(24 * 60 * 60))
    }
    
    func availableWorkTimeBetweenNowAnd(date date: NSDate) -> Float {
        var currentDay: NSDate? = now.copy() as? NSDate
        var totalAvailableWorkTime: Float = 0.0
        
        while currentDay!.compare(date) == .OrderedAscending {
            let workDay = self.workDayForDate(currentDay!)
            totalAvailableWorkTime += workDay.totalAvailableWork
            currentDay = currentDay!.dateByAddingTimeInterval(24 * 60 * 60)
        }
        
        return totalAvailableWorkTime
    }
    
    func workToDoBetweenNowAnd(date date: NSDate) -> Float {
        return self.notDroppedTasks.filter({ $0.dueDate.compare(date) != .OrderedDescending }).map({ $0.workLeftToDo }).reduce(0.0, combine: +)
    }
    
    var droppedTasks: [Task] {
        return tasks.filter({ $0.dropped })
    }
    
    var notDroppedTasks: [Task] {
        return tasks.filter({ !$0.dropped })
    }
}


//: Playground - noun: a place where people can play

import UIKit

// Make a date formatter for easy date creation later

