var connUtils = new ConnectionUtils();
var connManager = new ConnectionManager();

function ConnectionStore() {
    this._clientId = null;
    this._prevServiceChannels = new Array();
    this._newServiceChannels = new Array();
    this._prevAuctionChannels = new Array();
    this._newAuctionChannels = new Array();

    // getters & setters

    this.getClientId = function() {
        return this._clientId;
    };

    this.setClientId = function(clientId) {
    	this._clientId = clientId;
    };

    this.getNewAuctionChannels = function() {
        return this._newAuctionChannels;
    };

    this.setNewAuctionChannels = function(newAuctionChannels) {
        this._newAuctionChannels = newAuctionChannels;
    };

    this.getPrevAuctionChannels = function() {
        return this._prevAuctionChannels;
    };

    this.setPrevAuctionChannels = function(prevAuctionChannels) {
        this._prevAuctionChannels = prevAuctionChannels;
    };

    this.getNewServiceChannels = function() {
        return this._newServiceChannels;
    };

    this.setNewServiceChannels = function(newServiceChannels) {
        this._newServiceChannels = newServiceChannels;
    };

    this.getPrevServiceChannels = function() {
        return this._prevServiceChannels;
    };

    this.setPrevServiceChannels = function(prevServiceChannels) {
        this._prevServiceChannels = prevServiceChannels;
    };

    // helper methods

    this.reset = function(clientId) {
        this._clientId = clientId;
        this._prevServiceChannels = new Array();
        this._newServiceChannels = new Array();
        this._prevAuctionChannels = new Array();
        this._newAuctionChannels = new Array();
    };

    this.getOutdatedAuctionChannels = function() {
        return connUtils.connDifference(this.getPrevAuctionChannels(), this.getNewAuctionChannels());
    };

    this.getAdditionalAuctionChannels = function() {
        return connUtils.connDifference(this.getNewAuctionChannels(), this.getPrevAuctionChannels());
    };

    this.getOutdatedServiceChannels = function() {
        return connUtils.connDifference(this.getPrevServiceChannels(), this.getNewServiceChannels());
    };

    this.getAdditionalServiceChannels = function() {
        return connUtils.connDifference(this.getNewServiceChannels(), this.getPrevServiceChannels());
    };

    this.getReusableServiceChannels = function() {
        return connUtils.connDifference(this.getPrevServiceChannels(), this.getOutdatedServiceChannels());
    };

    this.getReusableAuctionChannels = function() {
        return connUtils.connDifference(this.getPrevAuctionChannels(), this.getOutdatedAuctionChannels());
    };

}

function ConnectionUtils() {
    /**
     * Returns the ids of subscriptions from 'setA' that ar not in 'setB'
     *
     * @param setA an array of subscription ids
     * @param setB an array of subscription ids
     */
    this.connDifference = function(setA, setB) {
        var cache = new Object();
        var diff = new Array();
        var channel = [];
        for (var i = 0; i < setA.length; i++) {
            channel = setA[i];
            cache[channel[0]] = channel[1];
        }
        for (var j = 0; j < setB.length; j++) {
            channel = setB[j];
            if (cache[channel[0]] != undefined) {
                delete cache[channel[0]];
            }
        }
        for (channel in cache) {
            diff.push([channel, cache[channel]]);
        }
        return diff;
    };

    this.dumpChannels = function(channels) {
        var dump = new String();
        for (var i = 0; i < channels.length; i++) {
            var channel = channels[i][0];
            dump = dump.concat("['", channel, "']", ",");
        }
        dump = dump.substring(0, dump.length - 1);
        console.debug(dump);
    };
}

function ConnectionManager() {
    this._connStore = new ConnectionStore();
    this._channelsToFunctions = {
        "/auctions/search" : "updateSearchResults",
        "/auctions/cmd" : "handleUpdateMessage",
        "/service/cmd" : "handleSystemCommands",
        "/carousel/rotate" : "updateCarousel",
                "default" : "updateAuctionData"
    };
    this._DEBUG = true;

    this.getConnStore = function() {
        return this._connStore;
    };

    this._unsubscribeFromChannels = function(channels) {
        for (var i = 0; i < channels.length; i++) {
            dojox.cometd.unsubscribe(channels[i]);
        }
    };

    this._subscribeToChannels = function(channels, sendMessage) {
        var newChannels = new Array();
        for (var i = 0; i < channels.length; i++) {
            var channel = channels[i][0];
            var functionName = this._channelsToFunctions[channel];
            if (functionName == undefined) {
                functionName = this._channelsToFunctions["default"];
            }
            var id = dojox.cometd.subscribe(channel, cometListener, functionName, {deliverMessage: sendMessage});
            newChannels.push(id);
        }
        return newChannels;
    };

    /**
     * Extracts from view the list of displayed auctions.
     *
     * @private
     * @return an array of auction channels
     */
    this._getDisplayedAuctions = function() {
        var displayedAuctions = new Array();
        var auctions = dojo.query("input[id^='auction_id_']");
        for (var i = 0; i < auctions.length; i++) {
            var auctionId = auctions[i].value;
            if (auctionId != "") {
                displayedAuctions.push(["/auction/" + auctionId, true]);
            }
        }
        if(cometUtils.isCarouselEnabled()){
        	var carouselAuctions = dojo.query("input[id^='carousel_auction_id_']");
        	for (var i = 0; i < carouselAuctions.length; i++) {
        		var auctionId = carouselAuctions[i].value;
        		if (auctionId != "") {
        			displayedAuctions.push(["/auction/" + auctionId, true]);
        		}
        	}
        }
        return displayedAuctions;
    };

    this._getRequiredServiceChannels = function() {
        var channels = new Array();
        channels.push(["/service/cmd", true]);
        if(cometUtils.isCarouselEnabled()){
        	 channels.push(["/carousel/rotate", true]);
        }      
        if (cometUtils.isBidwatchPage() || cometUtils.isSearchPage()) {
            channels.push(["/auctions/search", true]);
            channels.push(["/auctions/cmd", true]);
        }
        return channels;
    };

    this._manageServiceChannels = function() {
        // find service channels that are no longer needed
        var outdatedServiceChannels = this.getConnStore().getOutdatedServiceChannels();
        // find service channels from previous request that could be reused
        var reusableServiceChannels = this.getConnStore().getReusableServiceChannels();
        // find service channels required for current request and not present in previous request.
        var additionalServiceChannels = this.getConnStore().getAdditionalServiceChannels();

        // unsubscribe from outdated service channels
        this._unsubscribeFromChannels(outdatedServiceChannels);
        // resubscribe to reusable channels (without additional server notification)
        this._subscribeToChannels(reusableServiceChannels, false);
        // subscribe to new service channels
        var newServiceChannels = this._subscribeToChannels(additionalServiceChannels, true);

        // find connections that remain active (reused connections and additional connections)
        var activeServiceChannels = newServiceChannels.concat(reusableServiceChannels);
        this.getConnStore().setNewServiceChannels(activeServiceChannels);

        if (this._DEBUG) {
            console.log("UNSUBSCIBE FROM OUTDATED SERVICE CHANNELS:");
            connUtils.dumpChannels(outdatedServiceChannels);
            console.log("SUBSCRIBE TO REUSABLE SERVICE CHANNELS:");
            connUtils.dumpChannels(reusableServiceChannels);
            console.log("SUBSCRIBE TO ADDITIONAL SERVICE CHANNELS:");
            connUtils.dumpChannels(additionalServiceChannels);
            console.log("ACTIVE SERVICE CHANNELS:");
            connUtils.dumpChannels(activeServiceChannels);
        }
    };

    this._manageAuctionChannels = function(hasPageRefreshed) {
        // find auction channels that are no longer needed
        var outdatedAuctionChannels = this.getConnStore().getOutdatedAuctionChannels();
        // find auction channels from previous request that can be reused
        var reusableAuctionChannels = this.getConnStore().getReusableAuctionChannels();
        // find auction channels needed by current request and no present in previous request
        var additionalAuctionChannels = this.getConnStore().getAdditionalAuctionChannels();

        // unsubscribe from outdated auction channels
        this._unsubscribeFromChannels(outdatedAuctionChannels);
        // if the page has refreshed reusable channels must be resubscribed
        if(hasPageRefreshed) {
            // resubscribe to reusable channels (without additional server notification)
            this._subscribeToChannels(reusableAuctionChannels, false);
        }
        // subscribe to new auction channels
        var newAuctionChannels = this._subscribeToChannels(additionalAuctionChannels, true);

        // find auction connections that remain active (reused connections and additional connections)
        var activeAuctionChannels = newAuctionChannels.concat(reusableAuctionChannels);
        this.getConnStore().setNewAuctionChannels(activeAuctionChannels);

        if (this._DEBUG) {
            console.log("UNSUBSCRIBE FROM OUTDATED AUCTION CHANNELS:");
            connUtils.dumpChannels(outdatedAuctionChannels);
            if(hasPageRefreshed) {
                console.log("SUBSCRIBE TO REUSABLE AUCTION CHANNELS:");
                connUtils.dumpChannels(reusableAuctionChannels);
            }
            console.log("SUBSCRIBE TO ADDITIONAL AUCTION CHANNELS:");
            connUtils.dumpChannels(additionalAuctionChannels);
            console.log("ACTIVE AUCTION CHANNELS:");
            connUtils.dumpChannels(activeAuctionChannels);
        }
    };

    this.manageAllConnections = function(clientId) {
        // reset all existing connections if client has changed
        if (this.getConnStore().getClientId() != clientId) {
        	if(this._DEBUG) {
        		console.log("CLIENT ID HAS CHANGED. RESETING ALL CHANNELS.");
        	}
            this.getConnStore().reset(clientId);
        }
        // previous channels are restored in constructor
        dojox.cometd.startBatch();

        // get service channels for current request
        var requiredServiceChannels = this._getRequiredServiceChannels();
        this.getConnStore().setNewServiceChannels(requiredServiceChannels);
        // manage service channels
        this._manageServiceChannels();


        // get displayed auctions
        var displayedAuctionChannels = this._getDisplayedAuctions();
        if(this._DEBUG) {
        	console.debug("DISPLAYED AUCTIONS:");
        	connUtils.dumpChannels(displayedAuctionChannels);
        }
        this.getConnStore().setNewAuctionChannels(displayedAuctionChannels);
        // manage auction channels
        this._manageAuctionChannels(true);

        dojox.cometd.endBatch();
    };

    this.manageAuctionConnections = function() {
        dojox.cometd.startBatch();

        // get latest active channels
        var activeAuctionChannels = this.getConnStore().getNewAuctionChannels();
        // move active channels to previous auction channels
        this.getConnStore().setPrevAuctionChannels(activeAuctionChannels);
        // get displayed auctions
        var displayedAuctionChannels = this._getDisplayedAuctions();
        // set new auction channels to displayed auctions
        this.getConnStore().setNewAuctionChannels(displayedAuctionChannels);
        // manage auction channels
        this._manageAuctionChannels(false);

        dojox.cometd.endBatch();
    };


    // NOTE: Because the constructor invokes instance methods it has to be defined AFTER class methods.
    var cookie = dojo.cookie("org.cometd.reload");
    if (cookie) {
        var data = dojo.fromJson(cookie);
        // restore client id
        this.getConnStore().setClientId(data.handshakeResponse.clientId);
        // restore service channels from cookie
        this.getConnStore().setPrevServiceChannels(data.serviceChannels);
        // restore auction channels from cookies
        this.getConnStore().setPrevAuctionChannels(data.auctionChannels);

        if (this._DEBUG) {
        	console.log(cookie);
        	console.debug("RESTORED CLIENT ID: " + this.getConnStore().getClientId());
            console.debug("RESTORED SERVICE CHANNELS:");
            connUtils.dumpChannels(this.getConnStore().getPrevServiceChannels());
            console.debug("RESTORED AUCTION CHANNELS:");
            connUtils.dumpChannels(this.getConnStore().getPrevAuctionChannels());
        }
    }

}
