Inheritance: INotifyPropertyChanged
        public DisplayMatch(OrganizerViewModel ovm, ObservableMatch match, DisplayType displayType)
        {
            Match = match;
            MatchDisplayType = displayType;

            //Modify ViewModel state when an action is initiated
            Action startAction = () =>
            {
                ovm.ErrorMessage = null;
                ovm.IsBusy = true;
            };

            //Modify ViewModel state when an action is completed
            Action endAction = () =>
            {
                ovm.IsBusy = false;
            };

            //Modify ViewModel state when an action comes back with an exception
            Action<Exception> errorHandler = ex =>
            {
                if (ex.InnerException is ChallongeApiException)
                {
                    var cApiEx = (ChallongeApiException)ex.InnerException;

                    if (cApiEx.Errors != null) ovm.ErrorMessage = cApiEx.Errors.Aggregate((one, two) => one + "\r\n" + two);
                    else ovm.ErrorMessage = string.Format("Error with ResponseStatus \"{0}\" and StatusCode \"{1}\". {2}", cApiEx.RestResponse.ResponseStatus,
                        cApiEx.RestResponse.StatusCode, cApiEx.RestResponse.ErrorMessage);
                }
                else
                {
                    ovm.ErrorMessage = ex.NewLineDelimitedMessages();
                }

                ovm.IsBusy = false;
            };

            Player1Wins = Command.CreateAsync(() => true, () => Match.ReportPlayer1Victory(SetScore.Create(1, 0)), startAction, endAction, errorHandler);
            Player2Wins = Command.CreateAsync(() => true, () => Match.ReportPlayer2Victory(SetScore.Create(0, 1)), startAction, endAction, errorHandler);

            Player1WinsScored = Command.CreateAsync<SetScore[]>(_ => true, scores => Match.ReportPlayer1Victory(scores), _ => startAction(), _ => endAction(), (_, ex) => errorHandler(ex));
            Player2WinsScored = Command.CreateAsync<SetScore[]>(_ => true, scores => Match.ReportPlayer2Victory(scores), _ => startAction(), _ => endAction(), (_, ex) => errorHandler(ex));

            Player1ToggleMissing = Command.CreateAsync(() => true, () => Match.Player1.IsMissing = !Match.Player1.IsMissing, startAction, endAction, errorHandler);
            Player2ToggleMissing = Command.CreateAsync(() => true, () => Match.Player2.IsMissing = !Match.Player2.IsMissing, startAction, endAction, errorHandler);

            AssignStation = Command.CreateAsync<Station>(_ => true, s => Match.AssignPlayersToStation(s.Name), _ => startAction(), _ => endAction(), (_, ex) => errorHandler(ex));
            CallMatchAnywhere = Command.CreateAsync(() => true, () => Match.AssignPlayersToStation("Any"), startAction, endAction, errorHandler);
            CallMatch = Command.CreateAsync<Station>(_ => true, s =>
            {
                if (!match.IsMatchInProgress)
                {
                    if (s != null) Match.AssignPlayersToStation(s.Name);
                    else Match.AssignPlayersToStation("Any");
                }
            }, _ => startAction(), _ => endAction(), (_, ex) => errorHandler(ex));
            UncallMatch = Command.CreateAsync(() => true, () => Match.ClearStationAssignment(), startAction, endAction, errorHandler);
        }
        public void NewAssignment(string stationName, ObservableParticipant player1, ObservableParticipant player2, ObservableMatch match)
        {
            _uiContext.Post(new SendOrPostCallback(new Action<object>(o => {
                if (Application.Current.Windows.OfType<OrganizerWindow>().Count() > 0)
                {
                    var view = Application.Current.Windows.OfType<OrganizerWindow>().First() as OrganizerWindow;

                    Station station;
                    if (Stations.Instance.Dict.TryGetValue(stationName, out station))
                    {
                        if (station.isPrimaryStream())
                        {
                            if (view.playersSwapped)
                                view.swapPlayers();

                            view.p1Score.Text = "0";
                            view.p2Score.Text = "0";
                            view.p1Name.Text = player1.OverlayName;
                            view.p2Name.Text = player2.OverlayName;
                            view.round.Text = match.RoundNamePreferred;

                            if (match.isWinnersGrandFinal)
                            {
                                if (match.Player1IsPrereqMatchLoser)
                                {
                                    view.p1Name.Text += " (L)";
                                    view.p2Name.Text += " (W)";
                                }
                                else
                                {
                                    view.p1Name.Text += " (W)";
                                    view.p2Name.Text += " (L)";
                                }
                            }
                        }
                    }
                }
            })), null);
        }
        public void AssignOpenMatchesToStations(ObservableMatch[] matches)
        {
            if (Dict == null) return;

            var allOpenMatches = matches.Where(m => !m.IsMatchInProgress).ToArray();
            var allOpenStations = Dict.Values.Where(s => s.Status == StationStatus.Open && s.Type != StationType.NoAssign).OrderBy(s => s.Type).ThenBy(s => s.Order).ToArray();

            var openStationCount = allOpenStations.Length;

            //If there are no stations or no matches, return
            if (openStationCount == 0 || allOpenMatches.Length == 0) return;

            //Get list of matches that should be considered for assignment
            var orderedMatches = allOpenMatches.OrderBy(m => m.RoundOrder).ThenBy(m => m.IsWinners).ThenBy(m => m.Identifier).ToArray();
            var lastMatch = orderedMatches.Select((m, i) => new { Match = m, Index = i }).Take(openStationCount).Last();

            //Get matches that will be considered for assignment. This will prioritize earlier rounds but still allow the most recent round there are stations for to consider all matches in that round
            var matchesToConsider = orderedMatches.TakeWhile((m, i) => i <= lastMatch.Index || (m.IsWinners == lastMatch.Match.IsWinners && m.RoundOrder == lastMatch.Match.RoundOrder)).ToList();

            var streamStations = allOpenStations.Where(s => s.Type == StationType.Stream || s.Type == StationType.Recording).ToArray();
            var streamStationCount = streamStations.Length;

            //Organize matches by seed to put best matches on stream
            var seedPrioritizedMatches = matchesToConsider.OrderBy(m => m.Player1.Seed + m.Player2.Seed).ThenBy(m => (new[] { m.Player1.Seed, m.Player2.Seed }).Min()).ToArray();

            //Combine stream stations with highest priority seed matches
            var streamAssignments = seedPrioritizedMatches.Zip(streamStations, (m, s) => new { Match = m, Station = s }).ToArray();

            //Remove assigned matches from matches to assign
            foreach (var sa in streamAssignments) matchesToConsider.Remove(sa.Match);

            //Assign remaining matches to remaining stations
            var normalStations = allOpenStations.Where(s => s.Type == StationType.Premium || s.Type == StationType.Standard || s.Type == StationType.Backup).ToArray();
            var normalAssignments = matchesToConsider.Zip(normalStations, (m, s) => new { Match = m, Station = s }).ToArray();

            //Commit assignments to challonge
            foreach (var pair in streamAssignments.Concat(normalAssignments)) pair.Match.AssignPlayersToStation(pair.Station.Name);
        }
        public void NewAssignment(string stationName, ObservableParticipant player1, ObservableParticipant player2, ObservableMatch match)
        {
            _uiContext.Post(new SendOrPostCallback(new Action <object>(o => {
                if (Application.Current.Windows.OfType <OrganizerWindow>().Count() > 0)
                {
                    var view = Application.Current.Windows.OfType <OrganizerWindow>().First() as OrganizerWindow;

                    Station station;
                    if (Stations.Instance.Dict.TryGetValue(stationName, out station))
                    {
                        if (station.isPrimaryStream())
                        {
                            if (view.playersSwapped)
                            {
                                view.swapPlayers();
                            }

                            view.p1Score.Text = "0";
                            view.p2Score.Text = "0";
                            view.p1Name.Text  = player1.OverlayName;
                            view.p2Name.Text  = player2.OverlayName;
                            view.round.Text   = match.RoundNamePreferred;

                            if (match.isWinnersGrandFinal)
                            {
                                if (match.Player1IsPrereqMatchLoser)
                                {
                                    view.p1Name.Text += " (L)";
                                    view.p2Name.Text += " (W)";
                                }
                                else
                                {
                                    view.p1Name.Text += " (W)";
                                    view.p2Name.Text += " (L)";
                                }
                            }
                        }
                    }
                }
            })), null);
        }