/**
  * Distribute seats for several courses in a course set using the given
  * user priorities.
  *
  * @param CourseSet $courseSet The course set containing the courses
  * that seats shall be distributed for.
  * @see CourseSet
  */
 private function distributeByPriorities($courseSet)
 {
     Log::DEBUG('start seat distribution for course set: ' . $courseSet->getId());
     $limited_admission = $courseSet->getAdmissionRule('LimitedAdmission');
     //all users with their priorities
     $claiming_users = AdmissionPriority::getPriorities($courseSet->getId());
     //all users which have bonus/malus
     $factored_users = $courseSet->getUserFactorList();
     //all users with their max number of courses
     $max_seats_users = array_combine(array_keys($claiming_users), array_map(function ($u) use($limited_admission) {
         return $limited_admission->getMaxNumberForUser($u);
     }, array_keys($claiming_users)));
     //unlucky users get a bonus for the next round
     $bonus_users = array();
     //users / courses für later waitlist distribution
     $waiting_users = array();
     //number of already distributed seats for users
     $distributed_users = array();
     $prio_mapper = function ($users, $course_id) use($claiming_users) {
         $mapper = function ($u) use($course_id) {
             return isset($u[$course_id]) ? $u[$course_id] : null;
         };
         return array_filter(array_map($mapper, array_intersect_key($claiming_users, array_flip($users))));
     };
     //sort courses by highest count of prio 1 applicants
     $stats = AdmissionPriority::getPrioritiesStats($courseSet->getId());
     $courses = array_map(function ($a) {
         return $a['h'];
     }, $stats);
     arsort($courses, SORT_NUMERIC);
     $max_prio = AdmissionPriority::getPrioritiesMax($courseSet->getId());
     //count already manually distributed places
     $distributed_users = $this->countParticipatingUsers(array_keys($courses), array_keys($claiming_users));
     Log::DEBUG('already distributed users: ' . print_r($distributed_users, 1));
     //walk through all prios with all courses
     foreach (range(1, $max_prio) as $current_prio) {
         foreach (array_keys($courses) as $course_id) {
             $current_claiming = array();
             $course = Course::find($course_id);
             $free_seats = $course->getFreeSeats();
             //find users with current prio for this course, if they still need a place
             foreach ($claiming_users as $user_id => $prio_courses) {
                 if ($prio_courses[$course_id] == $current_prio && $distributed_users[$user_id] < $max_seats_users[$user_id]) {
                     //exclude participants
                     if (!$course->getParticipantStatus($user_id)) {
                         $current_claiming[$user_id] = 1;
                         if (isset($factored_users[$user_id])) {
                             $current_claiming[$user_id] *= $factored_users[$user_id];
                         }
                     } else {
                         Log::DEBUG(sprintf('user %s is already %s in course %s, ignoring', $user_id, $course->getParticipantStatus($user_id), $course->id));
                     }
                 }
             }
             //give maximum bonus to users which were unlucky before
             foreach (array_keys($current_claiming) as $user_id) {
                 if ($bonus_users[$user_id] > 0) {
                     $current_claiming[$user_id] = $bonus_users[$user_id] * count($current_claiming) + 1;
                     $bonus_users[$user_id]--;
                 }
             }
             Log::DEBUG(sprintf('distribute %s seats on %s claiming with prio %s in course %s', $free_seats, count($current_claiming), $current_prio, $course->id));
             Log::DEBUG('users to distribute: ' . print_r($current_claiming, 1));
             $current_claiming = $this->rollTheDice($current_claiming);
             Log::DEBUG('the die is cast: ' . print_r($current_claiming, 1));
             $chosen_ones = array_slice(array_keys($current_claiming), 0, $free_seats);
             Log::DEBUG('chosen ones: ' . print_r($chosen_ones, 1));
             $this->addUsersToCourse($chosen_ones, $course, $prio_mapper($chosen_ones, $course->id));
             foreach ($chosen_ones as $one) {
                 $distributed_users[$one]++;
             }
             if ($free_seats < count($current_claiming)) {
                 $remaining_ones = array_slice(array_keys($current_claiming), $free_seats);
                 foreach ($remaining_ones as $one) {
                     $bonus_users[$one]++;
                     $waiting_users[$current_prio][$course_id][] = $one;
                 }
             }
         }
     }
     //distribute to waitlists if applicable
     Log::DEBUG('waiting list: ' . print_r($waiting_users, 1));
     foreach ($waiting_users as $current_prio => $current_prio_waiting_courses) {
         foreach ($current_prio_waiting_courses as $course_id => $users) {
             $users = array_filter($users, function ($user_id) use($distributed_users, $max_seats_users) {
                 return $distributed_users[$user_id] < $max_seats_users[$user_id];
             });
             $course = Course::find($course_id);
             Log::DEBUG(sprintf('distribute waitlist of %s with prio %s in course %s', count($users), $current_prio, $course->id));
             if (!$course->admission_disable_waitlist) {
                 if ($course->admission_waitlist_max) {
                     $free_seats_waitlist = $course->admission_waitlist_max - $course->getNumWaiting();
                     $free_seats_waitlist = $free_seats_waitlist < 0 ? 0 : $free_seats_waitlist;
                 } else {
                     $free_seats_waitlist = count($users);
                 }
                 $waiting_list_ones = array_slice($users, 0, $free_seats_waitlist);
                 Log::DEBUG('waiting list ones: ' . print_r($waiting_list_ones, 1));
                 $this->addUsersToWaitlist($waiting_list_ones, $course, $prio_mapper($waiting_list_ones, $course->id));
                 foreach ($waiting_list_ones as $one) {
                     $distributed_users[$one]++;
                 }
             } else {
                 $free_seats_waitlist = 0;
             }
             if ($free_seats_waitlist < count($users)) {
                 $remaining_ones = array_slice($users, $free_seats_waitlist);
                 Log::DEBUG('remaining ones: ' . print_r($remaining_ones, 1));
                 $this->notifyRemainingUsers($remaining_ones, $course, $prio_mapper($remaining_ones, $course->id));
             }
         }
     }
 }