//Save our timetables to the disk so we don't have to perform the calculation again
        //Possibly dangerous if someone manages to come up with ~100,000 different subject options of significant size, but I find this unlikely
        //Otherwise this will fill up 10GB of disk and break whatever it's running on
        private void saveResult(TimetableOptionsModel model, TimetableBuildResultModel result)
        {
            string path = getModelFilePath(model);

            using (FileStream fileStream = System.IO.File.Open(path, FileMode.CreateNew))
            {
                using (GZipStream compressedStream = new GZipStream(fileStream, CompressionLevel.Optimal))
                {
                    using (StreamWriter writer = new StreamWriter(compressedStream))
                    {
                        JsonSerializerSettings jsonSettings = new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        };

                        writer.Write(JsonConvert.SerializeObject(result, jsonSettings));
                    }
                }
            }
        }
        public async Task <IActionResult> BuildTimetable([FromBody] TimetableOptionsModel model)
        {
            if (model.SubjectCodes.Count > 4)
            {
                return(StatusCode(403));
            }

            if (System.IO.File.Exists(getModelFilePath(model)))
            {
                return(File(loadResult(model), "application/json; charset=utf-8"));
            }

            Stopwatch responseStopwatch = new Stopwatch();

            responseStopwatch.Start();

            List <Subject> subjects = await subjectsFromSubjectCodes(model.SubjectCodes);

            IEnumerable <ClassInfo> classInfos         = subjects.SelectMany(subject => subject.ClassInfos);
            IEnumerable <ClassInfo> originalClassInfos = subjects.SelectMany(subject => subject.OriginalClassInfos);

            //Assign unique ids to each of the 'original' class infos, allowing for compression to work later
            int id = 0;

            foreach (var classInfo in originalClassInfos)
            {
                classInfo.ID = id++;
            }

            //Create a new generator with our sorting options and cancellation token
            Generator g = new Generator
            {
                SortLaterStarts   = model.LaterStarts,
                SortLessDays      = model.LessDays,
                CancellationToken = HttpContext.RequestAborted
            };

            long possiblePermutations = Generator.PossiblePermutationsCount(classInfos);

            List <Timetable> timetables = new List <Timetable>();

            //Check what algorithm to use, if we have over 5M permutations use the expanding algorithm
            if (possiblePermutations > 5 * 1000 * 1000)
            {
                timetables = g.GeneratePermutationsExpanding(classInfos);
            }
            else
            {
                timetables = g.GenerateTimetablesBruteForce(classInfos);
            }

            //Take 25,000 of our timetables and compress them
            var compressedTimetables = timetables.Take(25000).Select(t => new CompressedTimetable(t)).ToList();

            var result = new TimetableBuildResultModel(compressedTimetables, originalClassInfos.ToList());

            responseStopwatch.Stop();

            //Only save results for meaningfully long requests (10secs)
            if (responseStopwatch.ElapsedMilliseconds > 10 * 1000)
            {
                saveResult(model, result);
            }

            return(Json(result));
        }
        public async Task <IActionResult> BuildTimetable([FromBody] TimetableOptionsModel model)
        {
            if (model.SubjectCodes.Count > 5)
            {
                return(Json(new TimetableBuildResultModel("Too many subjects selected.")));
            }

            if (System.IO.File.Exists(getModelFilePath(model)) && checkCacheDate(model))
            {
                return(File(loadResult(model), "application/json; charset=utf-8"));
            }

            List <Subject> subjects = await subjectsFromSubjectCodes(model.SubjectCodes);

            IEnumerable <ClassInfo> classInfos         = subjects.SelectMany(subject => subject.ClassInfos);
            IEnumerable <ClassInfo> originalClassInfos = subjects.SelectMany(subject => subject.OriginalClassInfos);

            var allScheduledClasses = classInfos.SelectMany(ci => ci.AllScheduledClasses);


            //Create a new generator with our sorting options and cancellation token
            Generator g = new Generator
            {
                SortLaterStarts   = model.LaterStarts,
                SortLessDays      = model.LessDays,
                CancellationToken = HttpContext.RequestAborted
            };

            long possiblePermutations = Generator.PossiblePermutationsCount(classInfos);

            //Trying to generate when there's more than a trillion possibilities is a bad idea
            if (possiblePermutations > (long)1000 * 1000 * 1000 * 1000)
            {
                return(Json(new TimetableBuildResultModel("Can't generate timetables: too many possible timetables.")));
            }


            List <Timetable> timetables = new List <Timetable>();

            //Check what algorithm to use, if we have over 5M permutations use the expanding algorithm
            if (possiblePermutations > 5 * 1000 * 1000)
            {
                timetables = g.GeneratePermutationsExpanding(classInfos);
            }
            else
            {
                timetables = g.GenerateTimetablesBruteForce(classInfos);
            }

            int numberGenerated = timetables.Count;

            //Take 25,000 timetables and compress them
            var result = new TimetableBuildResultModel(timetables.Take(25000).ToList(),
                                                       numberGenerated,
                                                       allScheduledClasses.ToList(),
                                                       originalClassInfos.ToList());

            saveResult(model, result);

            return(Json(result));
        }