/* 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; });
        }
        /* 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)
        {
            short n = Convert.ToInt16(e.Argument as string);          //The length of the bitstrings

            /* Progress reporting */
            double nsquared = Math.Pow(2,n);                          //This is the target number of strings to generate
            //TODO cleanup
            double percentStep = nsquared / 100;
            if (percentStep == 0) percentStep = 1;
            double percentJump = nsquared / percentStep;
            if (percentJump == 0) percentJump = 1;
            else percentJump = 100 / percentJump;
            //Start timekeeping
            //MyStopwatch timer = new MyStopwatch();

            double totalResults = 0.0;            //Total results for progress reporting
            int multiplier = 2;                   //Increases the time between timeleft updates (exponential)
            double rollingTotal = 0.0;            //Total results generated that resets after timeleft update

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

            checkBitstring(first);
            totalResults++;

            /* Generating loop */
            MyStopwatch timer = new MyStopwatch();
            do
            {
                if (rollingTotal == 0)
                {
                    timer.start();
                }
                //Make the next bitstring
                intermediate = generateNextBitstring(intermediate);

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

                //Check the bitstring we generated
                checkBitstring(intermediate);

                //Update the time left in exponential time
                if (rollingTotal == Math.Pow(2, multiplier))
                {
                    float averageTime = (float)timer.stop() / (float)Math.Pow(2, multiplier);
                    secondsRemaining = (int)((averageTime * (nsquared - totalResults)) / 1000.0) + 2;
                    multiplier++;
                    rollingTotal = 0;
                }

                //Update the progress bar and report progress to update the GUI with valid combinations
                if (isBetween(0.0, totalResults % percentStep, 0.99))
                {
                    this.Dispatcher.BeginInvoke(delegate { progress.Value += percentJump; });
                    (sender as BackgroundWorker).ReportProgress(0, null);
                }

                //If we're performing too quickly, we need to slow down for the other threads to catch up.
                if ((nsquared < 10000) && isBetween(0, totalResults % 2, 0.99))
                {
                    Thread.Sleep(5);
                }
            } while (totalResults < nsquared);
            timer.stop();
            Debug.WriteLine("Process took "+ (float)(timer.interval / 1000f) + " seconds.");
            /* After generating loop */

            //Because the math for the percent step/jump isn't 100% accurate, when we are done checking combinations
            // we should "cheat" and set the progress bar to 100.
            this.Dispatcher.BeginInvoke(delegate { progress.Value = 100; });
            (sender as BackgroundWorker).ReportProgress(100, null);

            //Cancel the time reporting functions
            counterDown.CancelAsync();
            secondsRemaining = 0;
            this.Dispatcher.BeginInvoke(updateTimeLabel);
        }
 /* Called when the submit button is clicked. Initiliazes some data and starts the schedule
  *  generation process.
  */
 private void btnSubmit_Click(object sender, RoutedEventArgs e)
 {
     //Clear all of the generated tabs
     tabBase.SelectedIndex = 0;
     for (int tabIter = (tabBase.Items.Count-1); tabIter > 1; tabIter--)
     {
         tabBase.Items.RemoveAt(tabIter);
     }
     totalClassCount = lstFinalClasses.Items.Count;
     currentClassRunning = 0;
     possibleSections = new List<ClassSection>();              //All of the possible courses
     confirmedSections = new List<List<ClassSection>>();
     progress.Value = 0;                                 //Reset the progress bar
     tabCount = 0;                                       //Reset the tab numberings.
     secondsRemaining = 0;                               //Reset timeleft
     sectionsInGrid = new Dictionary<Grid, List<ClassSection>>();  //Reset the grid dictionary
     //Start finding bitstrings in the background
     BackgroundWorker bitstringworker = new BackgroundWorker();
     bitstringworker.WorkerSupportsCancellation = false;
     bitstringworker.WorkerReportsProgress = true;
     bitstringworker.DoWork += new DoWorkEventHandler(generateAllBitStrings);
     bitstringworker.ProgressChanged += new ProgressChangedEventHandler(reportProgress);
     //Count how many courses there are so we know how many bitstrings to generate
     int totalCourses = 0;
     const int MAXCOURSES = 23;
     Random randomGen = new Random();
     foreach (DepartmentClass finalClass in lstFinalClasses.Items)
     {
         //Random number of courses to pick from:
         // Take max courses. Remove the courses we have already. Make sure one section is leftover for the last class.
         int randomNumberOfSectionsFromThisClass = 0;
         if (finalClass != lstFinalClasses.Items.Last())
         {
             randomNumberOfSectionsFromThisClass = (int)Math.Round(randomGen.NextDouble() * (MAXCOURSES - totalCourses - 1));
         }
         else
         {
             randomNumberOfSectionsFromThisClass = MAXCOURSES - totalCourses;
         }
         foreach (ClassSection section in finalClass.sections)
         {
             //If course is not in exlusions
             if (!section.excluded)
             {
                 //temporary crappy code to fill in the parent class information for sections
                 section.parentClass = finalClass.ToString();
                 //end temporary crappy code
             }
         }
         List<ClassSection> possible = pickSections(finalClass, randomNumberOfSectionsFromThisClass, randomGen);
         MyStopwatch timeout = new MyStopwatch();
         timeout.start();
         while (!allLinksPresent(possible) && !ignoreLinks)
         {
             if (!limitRandom)
             {
                 YesNoPopup popupLinkRemoved = new YesNoPopup("You have excluded a linked section in " + possible[0].parentClass + ".\nWould you like to exclude the sections that require it?");
                 popupLinkRemoved.show();
                 popupLinkRemoved.onYes =
                     delegate
                     {
                         removeMissingLinks(possible);
                         btnSubmit_Click(sender, e);
                     };
                 popupLinkRemoved.onNo =
                     delegate
                     {
                         ignoreLinks = true;
                         btnSubmit_Click(sender, e);
                     };
                 return;
             }
             possible = pickSections(finalClass, randomNumberOfSectionsFromThisClass);
             // After 2 seconds, just try submitting again in case there's a situation where
             // there's not enough random sections to pick all the links. IE: 3 linked, but only allowed 2
             if (timeout.stop() > 2000)
             {
                 btnSubmit_Click(sender, e);
                 return;
             }
             //Slow down the repetitions - gives a little more randomness.
             Thread.Sleep(100);
         }
         totalCourses += possible.Count;
         foreach (ClassSection section in possible)
         {
             possibleSections.Add(section);
         }
     }
     if (totalCourses < 1)
     {
         InfoPopup popupNoCourses = new InfoPopup("There are 0 sections included!");
         popupNoCourses.show();
     }
     //Start counting down.
     counterDown = new BackgroundWorker();
     counterDown.WorkerSupportsCancellation = true;
     counterDown.WorkerReportsProgress = false;
     counterDown.DoWork += new DoWorkEventHandler(decreaseTimeRemaining);
     counterDown.RunWorkerAsync();
     //Start generating bitstrings in background
     bitstringworker.RunWorkerAsync("" + totalCourses);
     //Disable the submit button while we're generating.
     btnSubmit.IsEnabled = false;
     //Reset link ignore
     ignoreLinks = false;
 }