/**
 * Mini Scoreboard
 * Vertical "mini" implementation of MLB scoreboard, initially for use on
 * MLB.com home page.
 *
 * Version History
 * 1.0.0 - Initial release
 * 1.1.0 - Including playoffs
 * 1.2.0 - Including postseason editorial
 * 1.3.0
 *
 * @requires bam.services.MasterScoreboard
 * @requires bam.services.TicketingClient
 * @requires bam.services.PostseasonScoreboard
 * @requires jQuery.template
 * @requires jQuery.loadable
 * @requires jQuery.bindable
 * @author Dave Furfero david.furfero@mlb.com
 * @version 1.3.0
 */
;(function (window, $, bam) {

  var count = 0,
      instance,
      
      // Shortcuts
      proxy    = $.proxy,
      rehash   = $.rehash,
      pollable = $.pollable,
      deep     = $.deep,
      extend   = $.extend;

  /**
   * MiniScoreboard class
   * Creates instance of MiniScoreboard widget
   * If called without the new keyword, use a singleton instance.
   * @param String|Object element Selector or jQuery instance of container
   *        element
   * @param Object options Configuration object
   * @alias bam.widget.MiniScoreboard
   */
  function MiniScoreboard (element, options) {

    if (!(this instanceof MiniScoreboard)) {
      if (!instance) {
        instance = new MiniScoreboard(element, options);
      }
      return instance;
    }

    var elem = this.element = $(element),
        dfd  = this.dfd = new $.Deferred(),
        promise = this.promise,
        opts = this.options = extend(true, {
          compName:                     'MLB MiniScoreboard',
          //date:                      new Date(),
          polling:                      true,
          masterScoreboardInterval:     30  * 1000, // 30 seconds
          ticketingClientInterval:      120 * 1000, // 2 minutes
          postseasonScoreboardInterval: 60  * 1000, // 1 minute
          displayScoresLink:            true,
          displayAllTimesET:            true,
          displayHeader:                true,
          displayFooter:                true,
          enableTicketing:              true,
          enablePostseason:             false,
          i18n:                         'en',
          tracking:                     true,
          renderer:                     bam.widget.MiniScoreboard.renderer,
          loadGOTD:                     bam.widget.MiniScoreboardSponsorship
        }, options);

      opts.date = this.normalizeDate(opts.date);
      //opts.date = this.normalizeDate('10/7/2011');
      
    elem.addClass('bam-minisb')
      .attr('id', elem.attr('id') || 'bam-minisb-' + (++count));

    if (opts.enableTicketing) {
      this.initTicketingClient();
    }

    if (opts.enablePostseason) {
      this.initPostseasonScoreboard();
    }
    
    if (opts.tracking) {
      var track = proxy(this, 'track');
      elem
        .delegate('a.yesterday',         'click', 'Yesterday\'s Scores Click', track)
        .delegate('a.tickets',           'click', 'Tickets Click',             track)
        .delegate('a.gameday',           'click', 'Gameday Click',             track)
        .delegate('a.audio',             'click', 'Audio Click',               track)
        .delegate('a.mlbtv',             'click', 'MLB.TV Click',              track)
        .delegate('a.wrap',              'click', 'Wrap Click',                track)
        .delegate('a.box',               'click', 'Boxscore Click',            track)
        .delegate('a.link_to_probables', 'click', 'Probable Pitchers Click',   track);
    }

    // Handle affiliate ID passing
    elem.delegate('a.audio, a.mlbtv', 'click mouseout', function (evt) {
      bam.tracking.clickOrigin = evt.type === 'click' ? 'MSB' : '';
    });
    
    //handle preview gameday click issue in IE
    elem
    .delegate('div.probables', 'click', gotoGameday)
    
    //create gameday object and launch gameday
    function gotoGameday(){
      var gamedaylink = {
        gid  : $(this).attr("id"),
        mode : 'preview'
      }
      launchGameday(gamedaylink);
    }
    
    this.initMasterScoreboard();
  }


  MiniScoreboard.prototype = {
    
    normalizeDate: function (date) {
      // If undefined, set date current date/time Eastern and display
      // previous day's scoreboard until 11am ET (or specified time)
      // Floor value of date to 00:00:00.000 for simpler comparison later on
      return $.floorDate('day', typeof date === 'undefined' ? bam.getFlipDisplayDate() : date);
    },


    track: function (evt) {

      var compName     = this.options.compName,
          compActivity = compName + ': ' + evt.data,
          isGotD       = $(evt.currentTarget).closest('.gotd').length > 0;

      if (isGotD) {
        compActivity += ' Free Game';
      }

      bam.tracking.track({
        async: {
          compName:     compName,
          compActivity: compActivity,
          actionGen:    true
        }
      });
    },

    /**
     * Initialize MasterScoreboard
     * - create instance
     * - implement pollable behavior
     * - assign callback to store rehashed data
     */
    initMasterScoreboard: function () {

      var opts = this.options,
      
      // Create local instance of MasterScoreboard data service
      masterScoreboard = this.masterScoreboard = new bam.services.MasterScoreboard();

      // If polling is enabled, add pollable behavior to the data service
      // and start polling it's load method at the configured interval
      if (opts.polling) {
        pollable(masterScoreboard)
          .start('load', opts.masterScoreboardInterval, [opts.date]);
      }
      
      // Bind callback to MasterScoreboard.load success event
      masterScoreboard.bind('onLoadSuccess',
        proxy(this, 'handleMasterScoreboardLoadSuccess'));

      // Make initial request
      masterScoreboard.load(opts.date);
      
    },


    /**
     * Enhance MasterScoreboard data to include lookup hashes
     * @todo there is an implied connection between master_scoreboard game
     * type and the presence of data in PostseasonScoreboard. We need to bind
     * these services at the data level
     */
    handleMasterScoreboardLoadSuccess: function (evt, data) {
      // Shortcuts
      var opts = this.options,
      ticketingClient = this.ticketingClient,
      postseasonScoreboard = this.postseasonScoreboard,
      
      // Organize games by type for displaying separate lists
      gamesByType = data.gamesByType = rehash(data.games, 'game_type');

      // Ensure that each type is an array of games
      $.each(gamesByType, function (type, games) {
        gamesByType[type] = $.ensureArray(games);
      });

      // Organize games by type for displaying separate lists
      data.gamesByBeforeGame = rehash(data.games, 'status.is_before_game');

      // Determine postseason mode for scoreboard header display. Maintain
      // the lowest available playoff tier until completed.
      data.postseasonMode = gamesByType.D && 'ds' ||
        gamesByType.L && 'cs' || gamesByType.W && 'ws';

      // Add lookup hash for joining with PostseasonScoreboard data
      data.gamesByGamedayId = rehash(data.games, 'gameday');

      // Cache money
      this.masterScoreboardCache = data;

      // If there are no games with "before game" status, stop the
      // TicketingClient poller
      if (ticketingClient && opts.polling && !data.gamesByBeforeGame['true']) {
        ticketingClient.stop('load');
      }

      // If there are no postseason games, stop the PostseasonScoreboard
      // poller
      if (postseasonScoreboard && opts.polling && !data.postseasonMode) {
        postseasonScoreboard.stop('load');
      }

      this.render();
    },

    initWorldSeriesSchedule: function (year) {

      // Create local instance of WorldSeriesSchedule data service
      var worldSeriesSchedule = this.worldSeriesSchedule = new bam.services.WorldSeriesSchedule();

      // Bind callback to WorldSeriesSchedule.load success event
      worldSeriesSchedule.bind('onLoadSuccess',
          proxy(this, 'handleWorldSeriesScheduleLoadSuccess'));

      // Make initial request
      worldSeriesSchedule.load(year);
    },
    
    handleWorldSeriesScheduleLoadSuccess: function (evt, data) {
      this.worldSeriesScheduleCache = data;
      this.render();
    },
    
    /**
     * Initialize TicketingClient
     * - create instance
     * - implement pollable behavior
     * - assign callback to store rehashed data
     */
    initTicketingClient: function () {

      var opts = this.options,

      // Create local instance of TicketingClient data service
      ticketingClient = this.ticketingClient = new bam.services.TicketingClient({
          // Pass i18n to TicketingClient for MLB en español
          i18n: opts.i18n
        });

      // If polling is enabled, add pollable behavior to the data service
      // and start polling it's load method at the configured interval
      if (opts.polling) {
        pollable(ticketingClient)
          .start('load', opts.ticketingClientInterval, [opts.date]);
      }
      
      // Bind callback to TicketingClient.load success event
      ticketingClient.bind('onLoadSuccess',
        proxy(this, 'handleTicketingClientLoadSuccess'));

      // Make initial request
      ticketingClient.load(opts.date);
      
    },


    /**
     * Rehash and cache TicketingClient data
     */
    handleTicketingClientLoadSuccess: function (evt, data) {

      if (data.length) {

        // Create hash of ticketing data for faster merging with
        // MasterScoreboard
        // TicketingClient schedule_id === MasterScoreboard game_pk
        this.ticketingClientCache = rehash(data, 'game_pk');

        // Can safely call render as the MasterScoreboard check is in render
        this.render();
      }
    },

     
    /**
     * Initialize PostseasonScoreboard
     * - create instance
     * - implement pollable behavior
     * - assign callback to store rehashed data
     */
    initPostseasonScoreboard: function () {

      var opts = this.options,

      // Create local instance of PostseasonScoreboard data service
      postseasonScoreboard = this.postseasonScoreboard = new bam.services.PostseasonScoreboard({
          // Pass i18n to PostseasonScoreboard for MLB en español
          i18n: opts.i18n});
      
      // If polling is enabled, add pollable behavior to the data service
      // and start polling it's load method at the configured interval
      if (opts.polling) {
        pollable(postseasonScoreboard)
          .start('load', opts.postseasonScoreboardInterval, [opts.date]);
      }

      // Bind callback to PostseasonScoreboard.load success event
      postseasonScoreboard.bind('onLoadSuccess',
        proxy(this, 'handlePostseasonScoreboardLoadSuccess'));

      // Make initial request
      postseasonScoreboard.load(opts.date);
    },

    reGamedayDate: /^(\d{4})_(\d{2})_(\d{2}).*/,

    /**
     * Rehash and cache PostseasonScoreboard data
     */
    handlePostseasonScoreboardLoadSuccess: function (evt, data) {

      var self    = this,
          gamedayId,
          current = this.options.date.valueOf(),
          dates   = [],
          date,
          sajax,
          games,
          i=1,
	      n,
          
          // @todo refactor this hack
          // This variable is used to hold the postseasonScoreboardCache
          // in case we need to load more data. Deleting the
          // postseasonScoreboardCache will help defer the render until the
          // game data is available
          HACK;
        
           
      // Create hash of postseason data for faster merging with
      // MasterScoreboard
      // PostseasonScoreboard game.id === MasterScoreboard gameday
      this.postseasonScoreboardCache = rehash(data, 'game.id');

      // Check gamedayIds for days other than this.options.date
      for (gamedayId in this.postseasonScoreboardCache) {
        date = new Date(gamedayId.substr(0, 10).replace(/_/g, '/'));
        date = this.normalizeDate(date);
        if (date.valueOf() !== current) {
            dates.push(date);
        }
      }
      
      dates.sort();
      n = dates.length;
		for(;i<n;i++){
			if(dates[i].valueOf() === dates[(i-1)].valueOf()){
				dates.splice(i,1);
				i=0;
				n=dates.length;
			}
		}
		
      //console.log(dates);
      
      // If there are games on dates other than this.options.date, use $.sajax
      // to load a secondary cache
      if (dates.length) {
        sajax = $.sajax();
        games = [];

        // @todo refactor this hack
        HACK = this.postseasonScoreboardCache;
        delete self.postseasonScoreboardCache;

        // Push each additional query onto a sajax stack
        $.each(dates, function (i, date) {
          sajax.text(extend(self.masterScoreboard.loadableConfig(date), {
            success: function (data) {
              games.push.apply(games, data.games);
              if (i === dates.length - 1) {
                // Create supplementary hash keyed on gameday for joining with
                // PostseasonScoreboard data
                self.supplementaryCache = rehash(games, 'gameday');

                // @todo refactor this hack
                self.postseasonScoreboardCache = HACK;
                self.promise = self.dfd.resolve();
                self.render();
              }
            }
          }));

        });

      } else {
        // Can safely call render as the MasterScoreboard check is in render
        this.render();
      }

    },


    /**
     * Merge cache data and render to the Miniscoreboard DOM element
     */
    render: function () {
      
      // Shortcuts
      var masterScoreboardCache     = this.masterScoreboardCache,
          postseasonScoreboardCache = this.postseasonScoreboardCache,
          ticketingClientCache      = this.ticketingClientCache,
          supplementaryCache        = this.supplementaryCache,
          worldSeriesScheduleCache  = this.worldSeriesScheduleCache;
      
      if (!masterScoreboardCache || masterScoreboardCache.postseasonMode && !postseasonScoreboardCache) {
        return;
      }

      var games = masterScoreboardCache.games,
          i, n, game, postseasonGames, gamedayId,
          opts = this.options;

      // Augment games with ticketing data, if available
      for (i = 0, n = games.length; i < n; ++i) {
        game = games[i];
        game.ticketing = ticketingClientCache &&
          ticketingClientCache[game.game_pk];
      }

      // If postseason games exist, reorder them according to editorial
      if (postseasonScoreboardCache) {
        postseasonGames = [];
        for (i in postseasonScoreboardCache) {

          // If game is not in MasterScoreboard cache or supplementary
          // cache, create a stub to hold postseason editorial
          game = masterScoreboardCache.gamesByGamedayId[i] ||
            supplementaryCache && supplementaryCache[i] || {};

          // Augment postseason games with editorial content
          game.postseason = postseasonScoreboardCache[i];
          postseasonGames.push(game);
        }
        // overwrite game.status-sorted postseasonGames with editorial-sorted
        masterScoreboardCache.postseasonGames = postseasonGames;

        // @todo refactor this hack - need to recalculate postseasonMode if
        // no games were available in masterscoreboardCache before
        // supplementary cache was filled
        var postseasonGamesByType = rehash(postseasonGames, 'game_type');
        masterScoreboardCache.postseasonMode = postseasonGamesByType.D && 'ds' ||
          postseasonGamesByType.L && 'cs' || postseasonGamesByType.W && 'ws';

        // If we're in World Series mode, we need to load the schedule data
        // as well, but only once. (Cache is currently 1hr and page refreshes
        // every 5 minutes :-P ~ ~)
        if (masterScoreboardCache.postseasonMode === 'ws' && !worldSeriesScheduleCache) {
          var year = (new Date(masterScoreboardCache.date)).getFullYear();
          this.initWorldSeriesSchedule(year);
        }

        // Getting dirty...
        // Need to bind to game and calculate game necessity
        if (worldSeriesScheduleCache) {

          var wins = {},
              gamesByGamedayId = rehash(postseasonGames, 'gameday');

          $.map(worldSeriesScheduleCache, function (game) {

            var awayNameAbbrev = game.away.name_abbrev,
                homeNameAbbrev = game.home.name_abbrev,
                gamedayId      = game.gd_game_id,
                status,
                runsAway,
                runsHome;

            // Ignore games after series is over
            if (wins[awayNameAbbrev] === 4 || wins[homeNameAbbrev] === 4) {
              return null;
            }

            // Bind to game with matching gamedayId
            if (gamedayId in gamesByGamedayId) {
              game.game = gamesByGamedayId[gamedayId];
            }

            // Calculate game necessity
            // Would be so much nicer if we had a consistent game data interface
            // Use game versions if available (updated more frequently)
            status = deep(game, "game.status.status") || game.game_status_text;

            if (status === "Final" || status === "Game Over" || status === "Completed Early") {

              runsAway = +(deep(game, "game.linescore.r.away") || game.away.score);
              runsHome = +(deep(game, "game.linescore.r.home") || game.home.score);

              if (runsAway > runsHome) {
                wins[awayNameAbbrev] = ~~+wins[awayNameAbbrev] + 1;
              } else if (runsHome > runsAway) {
                wins[homeNameAbbrev] = ~~+wins[homeNameAbbrev] + 1;
              }
            }

            // Yay! Save a Math.min if game was necessary anyway
            game.is_necessary = game.ser_game_nbr <= 4 || game.ser_game_nbr <= 4 + Math.min(wins[awayNameAbbrev], wins[homeNameAbbrev]);

            return game;
          });

          masterScoreboardCache.worldSeriesSchedule = worldSeriesScheduleCache;
        }
      }

      // Set some display options -- hacky using this.masterScoreboardCache,
      // will have to refactor all this smack into a template view at some 
      // point
      masterScoreboardCache.displayScoresLink = opts.displayScoresLink;
      masterScoreboardCache.displayAllTimesET = opts.displayAllTimesET;
      masterScoreboardCache.displayHeader     = opts.displayHeader;
      masterScoreboardCache.displayFooter     = opts.displayFooter;
      masterScoreboardCache.i18n              = opts.i18n;
      masterScoreboardCache.currentGame       = opts.game_id;

      //console.log(masterScoreboardCache);

      this.element.html(opts.renderer(masterScoreboardCache,true));
      
      //set gotd background
      opts.loadGOTD();
    }
  };


  // Bind event handler to ticket links
  $('.bam-minisb a.tickets').live('click', function (e) {
    e.preventDefault();
    var elem = $(this);
    if (!elem.hasClass('disabled')) {
      openTIXXWindow(this.href, elem.attr('data-away-or-home'), ['miniscoreboard', 'A1']);
    }
  });
  

  // Expose MiniScoreboard to global bam namespace
  bam.namespace('widget').MiniScoreboard = MiniScoreboard;

  
})(window, jQuery, bam);
