/// <summary>
        /// Calculates the hp drop rate via some insane looping through the map.
        /// Oh god.
        /// </summary>
        /// <returns></returns>
        internal double CalculateHpDropRate()
        {
            double testDrop = 0.05;

            double lowestHpEver =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 195, 160, 60);
            double lowestHpComboEnd =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 170, 80);
            double lowestHpEnd =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 180, 80);
            double HpRecoveryAvailable =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 8, 4, 0);

            do
            {
                TotalHitsPossible = 0;

                HpBar.Reset();

                ComboCounter.HitCombo          = 0;
                Player.currentScore.TotalScore = 0;

                double lowestHp = HpBar.CurrentHp;
                int    lastTime = hitObjectManager.hitObjects[0].StartTime - hitObjectManager.PreEmpt;
                bool   fail     = false;

                int comboTooLowCount = 0;


                int breakCount  = player.eventManager.eventBreaks.Count;
                int breakNumber = 0;

                for (int i = 0; i < hitObjectManager.hitObjectsCount; i++)
                {
                    HitObject h = hitObjectManager.hitObjects[i];

                    //Find active break (between current and lastTime)
                    int localLastTime = lastTime;

                    int breakTime = 0;
                    if (breakCount > 0 && breakNumber < breakCount)
                    {
                        Event e = player.eventManager.eventBreaks[breakNumber];
                        if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
                        {
                            breakTime = e.Length;
                            breakNumber++;
                        }
                    }

                    HpBar.ReduceCurrentHp(testDrop * (h.StartTime - lastTime - breakTime));

                    lastTime = h.EndTime;


                    if (HpBar.CurrentHp < lowestHp)
                    {
                        lowestHp = HpBar.CurrentHp;
                    }

                    if (HpBar.CurrentHp <= lowestHpEver)
                    {
                        //Debug.Print("Overall score drops below " + lowestHpEver + " at " + lastTime + " (" + testDrop + ", lowest " + lowestHp + ")");
                        fail      = true;
                        testDrop *= 0.96;
                        break;
                    }

                    if (h is HitCircleFruitsTick)
                    {
                        IncreaseScoreHit(IncreaseScoreType.FruitTick, h);
                    }
                    else if (h is HitCircleFruitsTickTiny)
                    {
                        IncreaseScoreHit(IncreaseScoreType.FruitTickTiny, h);
                    }
                    else if (h is HitCircleFruitsSpin)
                    {
                        HpBar.IncreaseCurrentHp(HpMultiplierNormal / 2);
                    }
                    else
                    {
                        if (i == hitObjectManager.hitObjectsCount - 1 ||
                            hitObjectManager.hitObjects[i + 1].NewCombo)
                        {
                            IncreaseScoreHit(IncreaseScoreType.Hit300g, h);
                            if (HpBar.CurrentHp < lowestHpComboEnd)
                            {
                                if (++comboTooLowCount > 2)
                                {
                                    HpMultiplierComboEnd *= 1.07;
                                    HpMultiplierNormal   *= 1.03;
                                    fail = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            IncreaseScoreHit(IncreaseScoreType.Hit300, h);
                        }
                    }

                    if (!(h is HitCircleFruitsTickTiny) && !(h is HitCircleFruitsSpin))
                    {
                        TotalHitsPossible++;
                    }

                    h.MaxHp = HpBar.CurrentHp;
                }

                if (!fail && HpBar.CurrentHp < lowestHpEnd)
                {
                    //Debug.Print("Song ends on " + currentHp + " - being more lenient");
                    fail                  = true;
                    testDrop             *= 0.94;
                    HpMultiplierComboEnd *= 1.01;
                    HpMultiplierNormal   *= 1.01;
                }

                double recovery = (HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / hitObjectManager.hitObjectsCount;
                if (!fail && recovery < HpRecoveryAvailable)
                {
                    //Debug.Print("Song has average " + recovery + " recovery - being more lenient");
                    fail                  = true;
                    testDrop             *= 0.96;
                    HpMultiplierComboEnd *= 1.02;
                    HpMultiplierNormal   *= 1.01;
                }

                if (fail)
                {
                    continue;
                }

                if (GameBase.TestMode)
                {
                    PlayerTest pt = player as PlayerTest;
                    if (pt != null)
                    {
                        pt.testMaxScore = Player.currentScore.TotalScore;
                        pt.testMaxCombo = ComboCounter.HitCombo;
                    }
                }

                Debug.Print("Loaded Beatmap " + BeatmapManager.Current.Filename);
                Debug.Print(string.Empty);
                Debug.Print("           stars: " + BeatmapManager.Current.DifficultyEyupStars);
                Debug.Print("           stars: " + BeatmapManager.Current.DifficultyEchoStars);
                Debug.Print("     slider rate: " + BeatmapManager.Current.DifficultySliderMultiplier);
                Debug.Print(" playable length: " + BeatmapManager.Current.DrainLength);
                Debug.Print("      hitobjects: " + BeatmapManager.Current.ObjectCount);
                Debug.Print("      hitcircles: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Normal)).Count);
                Debug.Print("         sliders: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Slider)).Count);
                Debug.Print("        spinners: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Spinner)).Count);
                Debug.Print("      drain rate: {0:00.00}%/s", (testDrop * 60) / 2);
                Debug.Print("       lowest hp: {0:00.00}%", lowestHp / 2);
                Debug.Print("normal multiplier: {0:00.00}x", HpMultiplierNormal);
                Debug.Print("combo multiplier: {0:00.00}x", HpMultiplierComboEnd);
                Debug.Print(" excess hp recov: {0:00.00}%/hitobject",
                            (float)(HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / 2 / hitObjectManager.hitObjects.Count);
                Debug.Print("    max final hp: {0:00.00}%", HpBar.CurrentHp / 2);
                Debug.Print("       max combo: " + TotalHitsPossible);
                Debug.Print(string.Empty);

                return(testDrop);
            } while (true);
        }