internal static Dictionary<int, Tuple<int?, int>> MakeSchedule(
			DataContainer dataContainer,
			Dictionary<int, string> planningAssignments,
			FreeDaysCalculator freeDaysCalculator,
			FocusFactorCalculator focusFactorCalculator)
        {
            var result = new Dictionary<int, Tuple<int?, int>>();
            var usersBlockersDict = new Dictionary<string, Dictionary<int, BlockerData>>();

            var usersTasksDict = SeparateByUser(dataContainer, planningAssignments);
            short i = 0;
            var numbersToUsersMapping = usersTasksDict.ToDictionary(u => ++i, u => u.Key);
            i = 0;
            var usersToNumbersMapping = usersTasksDict.Keys.ToDictionary(u => u, u => ++i);

            var usersToProcess = new List<short>(numbersToUsersMapping.Keys);
            var processedUsers = new List<short>(2*numbersToUsersMapping.Keys.Count);

            while (usersToProcess.Count > 0)
            {
                string user = numbersToUsersMapping[usersToProcess[0]];
                var blockersFromOtherUsers = ScheduleUserTasks(
                    usersTasksDict[user],
                    user,
                    freeDaysCalculator,
                    focusFactorCalculator,
                    dataContainer,
                    planningAssignments,
                    result);

                var usersToRecalculate = ProcessBlockers(
                    blockersFromOtherUsers,
                    usersBlockersDict,
                    result,
                    user);

                foreach (short userNumber in usersToRecalculate
                    .Where(usersToNumbersMapping.ContainsKey)
                    .Select(u => usersToNumbersMapping[u]))
                {
                    if (usersToProcess.Count > 0 && usersToProcess[usersToProcess.Count - 1] == userNumber)
                        continue;
                    usersToProcess.Add(userNumber);
                }
                if (usersToProcess.Count == 2*numbersToUsersMapping.Keys.Count)
                    processedUsers.RemoveAt(0);
                processedUsers.Add(usersToProcess[0]);
                usersToProcess.RemoveAt(0);

                if (CheckCycle(processedUsers))
                    throw new InvalidOperationException("Cycle blockers collision!");
            }
            return result;
        }
        private static Dictionary<string, HashSet<int>> ScheduleUserTasks(
			IEnumerable<Tuple<WorkItem, WorkItem>> userTasks,
			string user,
			FreeDaysCalculator freeDaysCalculator,
			FocusFactorCalculator focusFactorCalculator,
			DataContainer dataContainer,
			Dictionary<int, string> planningAssignments,
			Dictionary<int, Tuple<int?, int>> scheduledTasksDict)
        {
            var nonBlockedTasks = new List<Tuple<WorkItem, WorkItem>>();
            var activeBlockedTasks = new List<Tuple<WorkItem, WorkItem>>();
            var proposedBlockedTasks = new List<Tuple<WorkItem, WorkItem>>();
            foreach (Tuple<WorkItem, WorkItem> tuple in userTasks)
            {
                if (tuple.Item1.IsActive())
                {
                    if (!dataContainer.BlockersDict.ContainsKey(tuple.Item1.Id))
                        nonBlockedTasks.Add(tuple);
                    else
                        activeBlockedTasks.Add(tuple);
                }
                else if (tuple.Item1.IsProposed())
                {
                    if (!dataContainer.BlockersDict.ContainsKey(tuple.Item1.Id))
                        nonBlockedTasks.Add(tuple);
                    else
                        proposedBlockedTasks.Add(tuple);
                }
            }

            var schedule = AppendNonBlockedTasks(
                nonBlockedTasks,
                user,
                scheduledTasksDict,
                freeDaysCalculator,
                focusFactorCalculator);

            var result = new Dictionary<string, HashSet<int>>();

            AppendBlockedTasks(
                proposedBlockedTasks,
                schedule,
                dataContainer,
                planningAssignments,
                focusFactorCalculator,
                scheduledTasksDict,
                result);
            AppendBlockedTasks(
                activeBlockedTasks,
                schedule,
                dataContainer,
                planningAssignments,
                focusFactorCalculator,
                scheduledTasksDict,
                result);

            int currentDay = 0;
            foreach (var pair in schedule)
            {
                bool isTaskActive = pair.Item1.Item1.IsActive();
                int startDayIndex = isTaskActive ? 0 : currentDay;
                int vacationDaysCount = freeDaysCalculator.GetVacationsDaysCount(
                    user,
                    startDayIndex,
                    pair.Item2);
                scheduledTasksDict[pair.Item1.Item1.Id] = new Tuple<int?, int>(startDayIndex, pair.Item2 + vacationDaysCount);
                currentDay += vacationDaysCount;
                currentDay += isTaskActive ? Math.Max(pair.Item2 - currentDay, 0) : pair.Item2;
            }

            return result;
        }
        private static int GetDaysCount(
			WorkItem task,
			string user,
			Dictionary<int, Tuple<int?, int>> scheduledTasksDict,
			FreeDaysCalculator freeDaysCalculator,
			FocusFactorCalculator focusFactorCalculator)
        {
            if (task.IsActive())
            {
                DateTime? finishDate = task.FinishDate();
                int finish = scheduledTasksDict.ContainsKey(task.Id)
                    ? scheduledTasksDict[task.Id].Item2
                    : (finishDate == null
                        ? 0
                        : freeDaysCalculator.GetDaysCount(finishDate.Value, user));
                double? remaining = task.Remaining();
                if (remaining != null && remaining > 0)
                {
                    int finishByRemaining = focusFactorCalculator.CalculateDaysByTime(remaining.Value, user);
                    if (finish < finishByRemaining)
                        finish = finishByRemaining;
                }
                return finish;
            }
            double? estimate = task.Estimate();
            return estimate == null
                ? 0
                : focusFactorCalculator.CalculateDaysByTime(estimate.Value, user);
        }
        private static List<Tuple<Tuple<WorkItem, WorkItem>, int>> AppendNonBlockedTasks(
			ICollection<Tuple<WorkItem, WorkItem>> nonBlockedTasks,
			string user,
			Dictionary<int, Tuple<int?, int>> scheduledTasksDict,
			FreeDaysCalculator freeDaysCalculator,
			FocusFactorCalculator focusFactorCalculator)
        {
            var result = new List<Tuple<Tuple<WorkItem, WorkItem>, int>>(nonBlockedTasks.Count);
            result.AddRange(
                nonBlockedTasks
                    .OrderBy(i =>
                        i, new TaskPriorityComparer())
                    .Select(pair =>
                        new Tuple<Tuple<WorkItem, WorkItem>, int>(
                            pair,
                            GetDaysCount(
                                pair.Item1,
                                user,
                                scheduledTasksDict,
                                freeDaysCalculator,
                                focusFactorCalculator))));
            return result;
        }