/* Called in a background thread. Used to generate bitstrings of a specified length. These strings
         *  are represented as an array of bools - true = 1, false = 0. This method is more efficient.
         *  EX: ("11001")
         *
         * We use these bitstrings to represent all possible combinations of the sections (even invalid ones).
         *
         * @param e.Argument This value passed through the BackgroundWorker call is the length of the bitstring.
         */
        private void generateAllBitStrings(object sender, DoWorkEventArgs e)
        {
            Dictionary<string, List<int>> classes = new Dictionary<string, List<int>>();
            //Separate sections by class
            int i = 0;
            foreach (ClassSection section in possibleSections)
            {
                List<int> indices = new List<int>();
                if (!classes.Keys.Contains(section.parentClass))
                {
                    indices.Add(i);
                    classes.Add(section.parentClass, indices);
                }
                else
                {
                    classes.TryGetValue(section.parentClass, out indices);
                    indices.Add(i);
                }
                i++;
            }

            //Check each class for working combinations
            i = 0;
            Dictionary<int, List<DepartmentClass>> groups = new Dictionary<int, List<DepartmentClass>>();
            foreach (var kv in classes)
            {
                short n = (short)kv.Value.Count;            //The length of the bitstrings
                List<DepartmentClass> validClassCombinations = new List<DepartmentClass>();
                List<ClassSection> sectionPool = getSectionPool(kv.Value, possibleSections);

                /* Progress tracking */
                double targetStringCount = Math.Pow(2, n);  //This is the target number of strings to generate
                double totalResults = 0;

                /* Begin generating bitstrings */
                bool[] first = new bool[n];       //First string is all false
                for (short x= 0; x < n; x++)
                {
                    first[x] = false;
                }
                bool[] intermediate = first;

                List<ClassSection> firstSections = checkBitstring(first, sectionPool);
                if (firstSections != null)
                {
                    DepartmentClass temp = new DepartmentClass();
                    temp.sections = firstSections;
                    validClassCombinations.Add(temp);
                }
                totalResults++;

                //For user-facing progress updates
                currentClassRunning++;
                ulong rollingTotal = 0;
                short multiplier = 1;
                ulong progressStep = (ulong)Math.Ceiling(targetStringCount / 100);
                this.Dispatcher.BeginInvoke(delegate { progress.Value = 0; });

                MyStopwatch timer = new MyStopwatch();
                /* Generating loop */
                do
                {
                    //Start timing
                    if (rollingTotal == 0)
                    {
                        timer.start();
                    }

                    //Make the next bitstring
                    intermediate = generateNextBitstring(intermediate);

                    //Update timeleft
                    if (rollingTotal == Math.Pow(2, multiplier))
                    {
                        float averageTime = (float)timer.stop() / (float)Math.Pow(2, multiplier);
                        secondsRemaining = (int)((averageTime * (targetStringCount - totalResults)) / 1000.0);
                        multiplier++;
                        rollingTotal = 0;
                    }

                    //Update progress bar
                    if (totalResults % progressStep == 0)
                    {
                        this.Dispatcher.BeginInvoke(delegate { progress.Value += 1; });
                    }

                    //Increment counters
                    totalResults++;
                    rollingTotal++;

                    //Check the bitstring we generated
                    List<ClassSection> nextSections = checkBitstring(intermediate, sectionPool);
                    if (nextSections != null)
                    {
                        DepartmentClass temp = new DepartmentClass();
                        temp.sections = nextSections;
                        validClassCombinations.Add(temp);
                    }
                } while (totalResults < targetStringCount);

                groups.Add(i, validClassCombinations);
                i++;
            }

            i = 1;
            //Combination two classes and weed out the combinations that aren't valid
            while (groups.Keys.Count > 1)
            {
                //Tournament!
                List<DepartmentClass> champCombination = new List<DepartmentClass>();
                groups.TryGetValue(0, out champCombination);
                List<DepartmentClass> challengerCombination = new List<DepartmentClass>();
                groups.TryGetValue(i, out challengerCombination);

                List<DepartmentClass> winnersCircle = new List<DepartmentClass>();

                //Fight!
                foreach (DepartmentClass champ in champCombination)
                {
                    foreach (DepartmentClass challenger in challengerCombination)
                    {
                        List<ClassSection> contender = new List<ClassSection>();
                        foreach (ClassSection section in champ.sections)
                        {
                            contender.Add(section);
                        }
                        foreach (ClassSection section in challenger.sections)
                        {
                            contender.Add(section);
                        }
                        if (combinationSuccessful(contender))
                        {
                            DepartmentClass success = new DepartmentClass();
                            success.sections = contender;
                            winnersCircle.Add(success);
                        }
                    }
                }

                groups.Remove(i);
                groups.Remove(0);
                groups.Add(0, winnersCircle);
                i++;
            }

            //Prepare the confirmed classed to be displayed
            List<DepartmentClass> confirmed = new List<DepartmentClass>();
            groups.TryGetValue(0, out confirmed);
            i = 0;
            foreach (DepartmentClass parent in confirmed)
            {
                confirmedSections.Add(parent.sections);
            }
            //Report that we're done and ready for GUI update
            (sender as BackgroundWorker).ReportProgress(100, null);

            //Because the time remaining isn't an exact science, we cheat at the end and force the values to be where they should
            // at the end.
            secondsRemaining = 1;
            this.Dispatcher.BeginInvoke(delegate { progress.Value = 100; });
        }
        /**
         * Adds a list of courses to the possibility list that were picked from the class's list of sections
         *
         * @param sectionPool The class that contains the sections we want
         * @param randCount The number of random sections to return
         * @param randGen The random number generator to use. We make one if there isn't one specified
         *
         * @return The list of classes we picked
         */
        private List<ClassSection> pickSections(DepartmentClass sectionPool, int randCount, Random randGen = null)
        {
            List<ClassSection> finalSections = new List<ClassSection>();
            List<ClassSection> sectionsToRemove = new List<ClassSection>();
            //Add classes that are not excluded to the temp pool
            foreach (ClassSection section in sectionPool.sections)
            {
                //If course is not in exlusions
                if (!section.excluded)
                {
                    finalSections.Add(section);
                }
            }

            for (short i = 0; i < dayLimiters.Count; i++)
            {
                CheckBox chkDay = dayLimiters[i];

                if (chkDay.IsChecked == false) continue;

                string day = convertIndexToDay(i);

                Tuple<ComboBox, ComboBox> dayMaster = timeCombos[i];
                string time = ((ComboBoxItem)(dayMaster.Item1.SelectedItem)).Content + " - " + ((ComboBoxItem)(dayMaster.Item2.SelectedItem)).Content;

                foreach (ClassSection section in finalSections)
                {
                    if (section.time.ToLower() == "tba" || !section.days.ToLower().Contains(day)) continue;
                    if (doTimesIntersect(time, section.time))
                    {
                        sectionsToRemove.Add(section);
                    }
                }
            }

            //Remove the classes
            foreach (ClassSection removal in sectionsToRemove)
            {
                finalSections.Remove(removal);
            }

            //Now pick random selections from what's left
            if (limitRandom)
            {
                if (randCount >= finalSections.Count)
                {
                    //Do nothing and add all of the final sections to the possible sections
                }
                else
                {
                    if (randGen == null)
                    {
                        randGen = new Random((int)Math.Round(DateTime.Now.Ticks * 6911.0));
                    }

                    List<ClassSection> randomSections = new List<ClassSection>();
                    for (; randCount > 0; randCount--)
                    {
                        //Pick a number from zero to the sections count
                        int selection = (int)Math.Round(randGen.NextDouble()) * (finalSections.Count - 1);
                        //Remove the index we picked
                        randomSections.Add(finalSections[selection]);
                        finalSections.RemoveAt(selection);
                    }
                    finalSections.Clear();
                    foreach (ClassSection section in randomSections)
                    {
                        finalSections.Add(section);
                    }
                }
            }

            return finalSections;
        }