/// <summary>
        /// Create a mini gauge image with the given data.
        /// </summary>
        /// <param name="count">The side counts that determine the needle angle.</param>
        /// <param name="doubleScale">If true the needle angle will be exaggerated x 2.</param>
        /// <returns>An image containing the gauge needle (doesn't include the gauge background).</returns>
        private Bitmap DrawGauge(SideCount count, bool doubleScale)
        {
            const int radius = 25;
            const int left   = 5;
            const int top    = 5;


            // calculate needle angle

            float angle;

            if (count.Allied != 0 || count.Axis != 0)
            {
                angle = ((float)count.Axis / (count.Allied + count.Axis)) * 180;
            }
            else
            {
                angle = 90;
            }

            if (doubleScale)
            {
                angle += (angle - 90); // exaggerate angle x2
            }
            if (angle < 10)
            {
                angle = 10;
            }
            if (angle > 170)
            {
                angle = 170;
            }


            // get points to draw

            PointF needleCenter = new PointF(left + radius - 0.5F, top + radius - 1);
            PointF needleStart  = Misc.AngleOffset(needleCenter, angle - 90, radius * 0.4);
            PointF needleEnd    = Misc.AngleOffset(needleCenter, angle - 270, radius * 1.1);


            // init graphics resources

            Pen penNeedle = new Pen(Color.FromArgb(192, 192, 192), 2F);

            Bitmap   img = new Bitmap(60, 40);
            Graphics g   = Graphics.FromImage(img);

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;


            // draw gauge

            g.DrawLine(penNeedle, needleStart, needleEnd);

            g.Dispose();
            return(img);
        }
        /// <summary>
        /// Updates the various server status controls with the given data.
        /// </summary>
        public void UpdateWidget()
        {
            if (this.game == null)
            {
                return;
            }


            // calc time ranges

            DateTime now            = DateTime.Now;
            DateTime dateGraphEnd   = new DateTime(now.Year, now.Month, now.Day, now.Hour, (now.Minute / 5) * 5, 0); // interger division = round to last 5 min interval
            DateTime dateGraphStart = dateGraphEnd.AddHours(-1);
            DateTime dateGaugeStart = now.AddHours(-1);

            /* For both capture and death counts below, there are two subtly different
             * time ranges used between the gauges and the graphs. The gauges are updated
             * every minute and show counts for the last hour, as normal. The graphs however
             * are only updated every 5 minutes and contain 12 datapoints each representing
             * a 5-minute window. To avoid the window changing and jumping around, they are
             * all aligned to 5 min intervals (0, 5, 10, 15, etc).
             */


            // count captures

            SideCount countCaptures = new SideCount();            // past 60 mins

            SideCount[] countCapturesGrouped = new SideCount[12]; // past 5-65 mins

            foreach (GameEvent gameEvent in game.Events)
            {
                ICaptureFacilityGameEvent capGameEvent = gameEvent as ICaptureFacilityGameEvent;

                if (capGameEvent == null)
                {
                    continue;
                }
                if (gameEvent.EventTime < dateGaugeStart)
                {
                    continue;
                }

                if (capGameEvent.NewOwner.Side == Side.Allied)
                {
                    countCaptures.Add(1, 0);
                }
                else
                {
                    countCaptures.Add(0, 1);
                }

                if (gameEvent.EventTime < dateGraphStart || gameEvent.EventTime > dateGraphEnd)
                {
                    continue;
                }

                int age = (int)Math.Round((dateGraphEnd - gameEvent.EventTime).TotalMinutes / 5) - 1; // 0 - 11
                if (age < 0 || age > 11)
                {
                    continue;                // shouldn't happen
                }
                if (capGameEvent.NewOwner.Side == Side.Allied)
                {
                    countCapturesGrouped[age].Add(1, 0);
                }
                else
                {
                    countCapturesGrouped[age].Add(0, 1);
                }
            }


            // count deaths

            SideCount countDeaths = new SideCount();            // past 60 mins

            SideCount[] countDeathsGrouped = new SideCount[12]; // past 5-65 mins

            foreach (MapCell cell in game.MapCells.Values)
            {
                countDeaths.Add(cell.Deaths);

                SideCount[] cellDeaths = cell.DeathsGrouped;
                for (int i = 0; i < cellDeaths.Length; i++)
                {
                    countDeathsGrouped[i].Add(cellDeaths[i]);
                }
            }


            // swap deaths for kills (not 100% accurate, but near enough)

            SideCount countKills = new SideCount(countDeaths.Axis, countDeaths.Allied);

            SideCount[] countKillsGrouped = new SideCount[countDeathsGrouped.Length];

            for (int i = 0; i < countKillsGrouped.Length; i++)
            {
                countKillsGrouped[i] = new SideCount(countDeathsGrouped[i].Axis, countDeathsGrouped[i].Allied);
            }


            // count cps, fbs, AOs

            SideCount countChokePoints = new SideCount();
            SideCount countFirebases   = new SideCount();
            SideCount countAOs         = new SideCount();

            foreach (ChokePoint cp in game.ValidChokePoints)
            {
                if (cp.Owner.Side == Side.Allied)
                {
                    countChokePoints.Allied++;
                    if (cp.HasAO)
                    {
                        countAOs.Axis++;
                    }
                }
                else if (cp.Owner.Side == Side.Axis)
                {
                    countChokePoints.Axis++;
                    if (cp.HasAO)
                    {
                        countAOs.Allied++;
                    }
                }
            }

            foreach (Firebase fb in game.Firebases.Values)
            {
                if (!fb.IsOpen)
                {
                    continue;
                }

                if (fb.Link.Side == Side.Allied)
                {
                    countFirebases.Allied++;
                }
                else if (fb.Link.Side == Side.Axis)
                {
                    countFirebases.Axis++;
                }
            }


            // update controls

            if (game.Servers.ContainsKey(1))
            {
                lblPopulationValue.Text = Misc.EnumString(game.Servers[1].Population);
                int pop = (int)game.Servers[1].Population; // 0 - 6
                picGaugePopulation.Image = DrawGauge(new SideCount(6 - pop, pop), false);

                if (game.Servers[1].Online)
                {
                    picServerState.Image = Resources.server_online;
                }
                else if (game.Servers[1].Locked)
                {
                    picServerState.Image = Resources.server_locked;
                }
                else if (game.Servers[1].Offline)
                {
                    picServerState.Image = Resources.server_offline;
                }
                else
                {
                    picServerState.Image = Resources.server_other;
                }

                lblServerState.Text = game.Servers[1].State;
                GameStatus.ToolTip.SetToolTip(lblServerState, game.Servers[1].StateInfo);
                GameStatus.ToolTip.SetToolTip(picServerState, game.Servers[1].StateInfo);
            }

            lblKillsAllied.Text = countKills.Allied.ToString();
            lblKillsAxis.Text   = countKills.Axis.ToString();
            picGaugeKills.Image = DrawGauge(countKills, true);

            lblCapturesAllied.Text = countCaptures.Allied.ToString();
            lblCapturesAxis.Text   = countCaptures.Axis.ToString();
            picGaugeCaptures.Image = DrawGauge(countCaptures, true);

            picGraphKills.Image    = DrawGraph(countKillsGrouped, true);
            picGraphCaptures.Image = DrawGraph(countCapturesGrouped, false);

            lblChokePointsAllied.Text = countChokePoints.Allied.ToString();
            lblChokePointsAxis.Text   = countChokePoints.Axis.ToString();
            picGaugeChokePoints.Image = DrawGauge(countChokePoints, false);

            lblFirebasesAllied.Text = countFirebases.Allied.ToString();
            lblFirebasesAxis.Text   = countFirebases.Axis.ToString();
            picGaugeFirebases.Image = DrawGauge(countFirebases, false);

            lblAOsAllied.Text = countAOs.Allied.ToString();
            lblAOsAxis.Text   = countAOs.Axis.ToString();
            picGaugeAOs.Image = DrawGauge(countAOs, false);
        }