﻿/// <reference name="MicrosoftAjax.js"/>

// RaceStateManager (Knsb.Live.Web)
// Product:  KNSB Live Web Framework
// Assembly: Knsb.Live.Web
//
// Copyright © 2008 - 2009 Emando Software.  All rights reserved.
// Contact: j.h.stokking@emando.nl

Type.registerNamespace("LiveWeb");

LiveWeb.RaceStateManager = function() {
    LiveWeb.RaceStateManager.initializeBase(this);
    var manager = this;
    this._delay = 4;
    this._clock = $create(LiveWeb.Clock, { targetInterval: 100 }, { update: function(sender, e) { manager.handleClockUpdate(sender, e); } });
    this._stateManager = null;
    this._colors = null;
    this._uiRace = null;
    this._serverRace = null;
    this._compareData = [];
    this._uiRaceUpdateTID = null;
}

LiveWeb.RaceStateManager.prototype =
{
    get_delay: function() { return this._delay; },
    set_delay: function(value) {
        this._delay = value;
    },
    get_clock: function() { return this._clock; },
    get_uiRace: function() { return this._uiRace; },
    get_stateManager: function() { return this._stateManager; },
    set_stateManager: function(value) {
        this._stateManager = value;
    },
    get_colors: function() { return this._colors; },
    set_colors: function(value) {
        this._colors = value;
    },

    add_raceSwitch: function(handler) { this.get_events().addHandler("raceSwitch", handler); },
    remove_raceSwitch: function(handler) { this.get_events().removeHandler("raceSwitch", handler); },
    add_racePrepare: function(handler) { this.get_events().addHandler("racePrepare", handler); },
    remove_racePrepare: function(handler) { this.get_events().removeHandler("racePrepare", handler); },
    add_raceStart: function(handler) { this.get_events().addHandler("raceStart", handler); },
    remove_raceStart: function(handler) { this.get_events().removeHandler("raceStart", handler); },
    add_raceComplete: function(handler) { this.get_events().addHandler("raceComplete", handler); },
    remove_raceComplete: function(handler) { this.get_events().removeHandler("raceComplete", handler); },
    add_passage: function(handler) { this.get_events().addHandler("passage", handler); },
    remove_passage: function(handler) { this.get_events().removeHandler("passage", handler); },
    add_lapsUpdate: function(handler) { this.get_events().addHandler("lapsUpdate", handler); },
    remove_lapsUpdate: function(handler) { this.get_events().removeHandler("lapsUpdate", handler); },
    add_clockUpdate: function(handler) { this.get_events().addHandler("clockUpdate", handler); },
    remove_clockUpdate: function(handler) { this.get_events().removeHandler("clockUpdate", handler); },

    onRaceSwitch: function(race) {
        var handler = this.get_events().getHandler("raceSwitch");
        if (handler) {
            handler(this, { race: race });
        }
    },
    onRacePrepare: function(race) {
        var handler = this.get_events().getHandler("racePrepare");
        if (handler) {
            handler(this, { race: race });
        }
    },
    onRaceStart: function(race) {
        var handler = this.get_events().getHandler("raceStart");
        if (handler) {
            handler(this, { race: race });
        }
    },
    onRaceComplete: function(race) {
        var handler = this.get_events().getHandler("raceComplete");
        if (handler) {
            handler(this, { race: race });
        }
    },
    onPassage: function(track, index, lapTime, totalTime, compareTotalTime, compareLapTime) {
        var handler = this.get_events().getHandler("passage");
        if (handler) {
            handler(this, { track: track, index: index, lapTime: lapTime, totalTime: totalTime, compareTotalTime: compareTotalTime, compareLapTime: compareLapTime });
        }
    },
    onLapsUpdate: function(race) {
        var handler = this.get_events().getHandler("lapsUpdate");
        if (handler) {
            handler(this, { race: race });
        }
    },
    onClockUpdate: function() {
        var handler = this.get_events().getHandler("clockUpdate");
        if (handler) {
            handler(this, Sys.EventArgs.Empty);
        }
    },

    reset: function() {
        this._clock.stop();
        if (this._uiRaceUpdateTID != null) {
            clearTimeout(this._uiRaceUpdateTID);
            this._uiRaceUpdateTID = null;
        }
        this._uiRace = null;
        this._serverRace = null;
        this._compareData = [];
    },
    queueData: function(matchIndex, race, delayUpdate) {
        if (race == null) {
            this._serverRace = null;
            this._uiRace = null;
            return;
        }
        var raceDelay = this._delay;
        if (race.definition != null) {
            if (this._serverRace == null || this._serverRace.id != race.definition.id) {
                Sys.Debug.trace("Race changed to " + matchIndex + "." + race.definition.pair + ".");
                var match = null;
                var matches = this._stateManager.get_matches();
                for (var i = 0; i < matches.length; i++) {
                    if (matches[i].index == matchIndex) {
                        match = matches[i];
                        break;
                    }
                }
                this._serverRace = {
                    id: race.definition.id,
                    pair: race.definition.pair,
                    match: match,
                    state: null,
                    uiState: null,
                    falseStart: false,
                    innerSkater: race.definition.innerSkater,
                    outerSkater: race.definition.outerSkater,
                    laps: [[], []]
                };
            }
            this.syncAllLaps(this._serverRace, race.laps);
            if (this._serverRace.state != race.definition.state || race.clock < this._clock.get_seconds()) {
                this._serverRace.state = race.definition.state;
                switch (race.definition.state) {
                    case LiveWeb.RaceState.NotStarted:
                        raceDelay = this.switchRace(this._serverRace, delayUpdate);
                        break;
                    case LiveWeb.RaceState.Preparing:
                        raceDelay = this.prepareRace(this._serverRace, delayUpdate);
                        break;
                    case LiveWeb.RaceState.Live:
                        raceDelay = this.startRace(race.clock, this._serverRace, delayUpdate);
                        break;
                    case LiveWeb.RaceState.Completed:
                        raceDelay = this.completeRace(this._serverRace, delayUpdate);
                        break;
                }
            }
        }
        this.syncAllLaps(this._serverRace, race.laps);
        var updateLaps = false;
        if (race.compareData.inner != null) {
            this._compareData[LiveWeb.Track.Inner] = race.compareData.inner;
            updateLaps = true;
        }
        if (race.compareData.outer != null) {
            this._compareData[LiveWeb.Track.Outer] = race.compareData.outer;
            updateLaps = true;
        }
        if (this._serverRace != null && this._serverRace == this._uiRace && (this._serverRace.uiState == LiveWeb.RaceState.Completed || updateLaps)) {
            this.onLapsUpdate(this._serverRace);
        }
    },
    syncAllLaps: function(race, laps) {
        if (laps.inner != null) {
            Sys.Debug.trace("Syncing inner laps...");
            this.syncLaps(race.laps[LiveWeb.Track.Inner], laps.inner);
        }
        if (laps.outer != null) {
            Sys.Debug.trace("Syncing outer laps...");
            this.syncLaps(race.laps[LiveWeb.Track.Outer], laps.outer);
        }
    },
    syncLaps: function(currentLaps, newLaps) {
        for (var i = 0; i < newLaps.length; i++) {
            var currentLap = currentLaps[i];
            var newLap = newLaps[i];
            if (currentLap == null || newLap == null || currentLap.totalTime != newLap.totalTime) {
                Sys.Debug.trace("Adding or updating lap.");
                currentLaps[i] = newLap;
                currentLaps[i].shown = false;
            }
        }
    },
    handleClockUpdate: function(sender, e) {
    if (this.showAllLaps(false)) {
            var allInnerShown = this._uiRace.innerSkater == null || (this._uiRace.laps[LiveWeb.Track.Inner].length == this._uiRace.match.lapCount && !this._uiRace.laps[LiveWeb.Track.Inner][this._uiRace.match.lapCount - 1].isEmpty);
            var allOuterShown = this._uiRace.outerSkater == null || (this._uiRace.laps[LiveWeb.Track.Outer].length == this._uiRace.match.lapCount && !this._uiRace.laps[LiveWeb.Track.Outer][this._uiRace.match.lapCount - 1].isEmpty);
            if (allInnerShown && allOuterShown) {
                Sys.Debug.trace("Completing race because all laps are shown.");
                this.completeUIRace(this._uiRace);
            }
        }
        this.onClockUpdate();
    },
    resetAllLaps: function() {
        if (this._uiRace == null) {
            return;
        }
        this.resetLaps(this._uiRace.laps[LiveWeb.Track.Inner]);
        this.resetLaps(this._uiRace.laps[LiveWeb.Track.Outer]);
        if (this._compareData[LiveWeb.Track.Inner] != null) {
            this.resetLaps(this._compareData[LiveWeb.Track.Inner].laps);
        }
        if (this._compareData[LiveWeb.Track.Outer] != null) {
            this.resetLaps(this._compareData[LiveWeb.Track.Outer].laps);
        }
    },
    resetLaps: function(laps) {
        if (laps != null) {
            for (var i = 0; i < laps.length; i++) {
                laps[i].shown = false;
            }
        }
    },
    showAllLaps: function() {
        if (this._uiRace == null) {
            return;
        }
        var allInnerLapsShown = this.showLaps(LiveWeb.Track.Inner);
        var allOuterLapsShown = this.showLaps(LiveWeb.Track.Outer);
        return allInnerLapsShown && allOuterLapsShown;
    },
    showLaps: function(track) {
        var seconds = this._clock.get_seconds();
        var laps = this._uiRace.laps[track];
        var compareData = this._compareData[track];
        var allShown = true;
        var count = compareData != null ? Math.max(laps.length, compareData.laps.length) : laps.length;
        for (var i = 0; i < count; i++) {
            var lap = laps[i];
            var showLap = lap != null && (lap.isEmpty || seconds >= lap.totalTime || (this._uiRace.uiState == LiveWeb.RaceState.Completed && !this._uiRace.falseStart));
            var compareLap = compareData != null ? compareData.laps[i] : null;
            var showCompareLap = compareLap != null && (compareData.raceId != this._uiRace.id || compareLap.isEmpty || seconds >= compareLap.totalTime || (this._uiRace.uiState == LiveWeb.RaceState.Completed && !this._uiRace.falseStart));
            if (showCompareLap) {
                if (showLap && (!lap.shown || !compareLap.shown)) {
                    lap.shown = true;
                    compareLap.shown = true;
                    this.onPassage(track, i, lap.lapTime, lap.totalTime, compareLap.totalTime, compareLap.lapTime);
                }
                else if (!compareLap.shown) {
                    compareLap.shown = true;
                    this.onPassage(track, i, null, null, compareLap.totalTime, compareLap.lapTime);
                }
            }
            else if (showLap && !lap.shown) {
                lap.shown = true;
                this.onPassage(track, i, lap.lapTime, lap.totalTime, null, null);
            }
            if (lap != null) {
                allShown = allShown && lap.shown;
            }
        }
        return allShown;
    },
    cancelUIRaceUpdate: function() {
        if (this._uiRaceUpdateTID != null) {
            Sys.Debug.trace("Cancelling scheduled UI race update.");
            clearTimeout(this._uiRaceUpdateTID);
            this._uiRaceUpdateTID = null;
        }
    },
    switchRace: function(race, delay) {
        Sys.Debug.trace("Switching to race " + race.match.index + "." + race.pair + ".");
        if (delay) {
            var manager = this;
            this._uiRaceUpdateTID = setTimeout(function() { manager.switchUIRace(race); }, this._delay * 1000);
        }
        else {
            this.switchUIRace(race);
        }
        return this._delay;
    },
    switchUIRace: function(race) {
        Sys.Debug.trace("UI: Switching to race " + race.match.index + "." + race.pair + ".");
        this._uiRaceUpdateTID = null;
        this._uiRace = race;
        race.uiState = LiveWeb.RaceState.NotStarted;
        this.onRaceSwitch(race);
        this._clock.stop();
        this._clock.reset();
    },
    prepareRace: function(race, delay) {
        Sys.Debug.trace("Race " + race.match.index + "." + race.pair + " preparing.");
        if (delay) {
            var manager = this;
            this._uiRaceUpdateTID = setTimeout(function() { manager.prepareUIRace(race); }, this._delay * 1000);
        }
        else {
            this.prepareUIRace(race);
        }
        return this._delay;
    },
    prepareUIRace: function(race) {
        Sys.Debug.trace("UI: Race " + race.match.index + "." + race.pair + " preparing.");
        this._uiRaceUpdateTID = null;
        this._uiRace = race;
        race.uiState = LiveWeb.RaceState.Preparing;
        this.onRacePrepare(race);
        this._clock.stop();
        this._clock.reset();
    },
    startRace: function(clock, race, delay) {
        Sys.Debug.trace("Race " + race.match.index + "." + race.pair + " started; clock at " + clock + " s.");
        race.falseStart = false;
        race.laps = [[], []];
        this.cancelUIRaceUpdate();
        if (delay) {
            var manager = this;
            var clockOffset = Math.max(clock - this._delay, 0);
            var startDelay = Math.max(this._delay - clock, 0);
            Sys.Debug.trace("Clock offset = " + clockOffset + "; Start delay = " + startDelay + ".");
            this._uiRaceUpdateTID = setTimeout(function() { manager.startUIRace(race, clockOffset); }, startDelay * 1000);
            return startDelay;
        }
        else {
            this.startUIRace(race, Math.max(clock - this._delay, 0));
            return this._delay;
        }
    },
    startUIRace: function(race, clockOffset) {
        Sys.Debug.trace("UI: Race " + race.match.index + "." + race.pair + " started.");
        this._uiRaceUpdateTID = null;
        this._uiRace = race;
        race.falseStart = false;
        race.uiState = LiveWeb.RaceState.Live;
        this._clock.reset();
        this.onRaceStart(race);
        this._clock.start(clockOffset);
    },
    completeRace: function(race, delay) {
        Sys.Debug.trace("Race " + race.match.index + "." + race.pair + " completed.");
        if (delay) {
            var manager = this;
            this._uiRaceUpdateTID = setTimeout(function() { manager.completeUIRace(race); }, this._delay * 1000);
        }
        else {
            this.completeUIRace(race);
        }
        return this._delay;
    },
    completeUIRace: function(race) {
        Sys.Debug.trace("UI: Race " + race.match.index + "." + race.pair + " completed.");
        this._uiRaceUpdateTID = null;
        this._uiRace = race;
        race.uiState = LiveWeb.RaceState.Completed;
        this.onRaceComplete(race);
        Sys.Debug.trace("Stopping clock in completeUIRace.");
        this._clock.stop();
    },
    updateMatchResults: function(matchIndex, draw, matchResult, ranking, delayUpdate) {
        Sys.Debug.trace("Match results for match " + matchIndex + " updated.");
        if (delayUpdate) {
            var manager = this;
            this._updateMatchResultsTID = setTimeout(function() {
                manager._updateMatchResultsTID = null;
                manager.onMatchResultsUpdate(matchIndex, draw, matchResult, ranking);
            }, this._delay * 1000);
        }
        else {
            if (this._updateMatchResultsTID != null) {
                clearTimeout(this._updateMatchResultsTID);
                this._updateMatchResultsTID = null;
            }
            this.onMatchResultsUpdate(matchIndex, draw, matchResult, ranking);
        }
    },
    updateMessage: function(text, delayUpdate, delay) {
        Sys.Debug.trace("Message changed to '" + text + "'.");
        if (delayUpdate) {
            var manager = this;
            this._updateMessageTID = setTimeout(function() {
                manager._updateMessageTID = null;
                manager.onMessageUpdate(text);
            }, delay * 1000);
        }
        else {
            this.onMessageUpdate(text);
        }
    }
};

LiveWeb.RaceStateManager.registerClass("LiveWeb.RaceStateManager", Sys.Component);