(function() {

  var floorDate = function(floor /*, date, clone */) {

    var date = (typeof arguments[1] === 'undefined') ? new Date() :
      (arguments[2] === true) ? new Date(arguments[1]) : arguments[1];
  
    switch(floor) {
      case 'year':   date.setMonth(0);
      case 'month':  date.setDate(1);
      case 'day':    date.setHours(0);
      case 'hour':   date.setMinutes(0);
      case 'minute': date.setSeconds(0);
      default:       date.setMilliseconds(0);
    }
    return date;
  };


  var TimedCache = function(ttl) {
    this.ttl = ttl;
    this.cache = {};
  };

  TimedCache.prototype = {

    set: function(key, value) {
      this.cache[key] = {
        timestamp: new Date(),
        value: value
      };
    },

    get: function(key) {
      if (key in this.cache) {
        if (this.isExpired(key) === true) {
          this.destroy[key];
        } else {
          return this.cache[key].value;
        }
      }
    },

    has: function(key) {
      return (key in this.cache);
    },

    isExpired: function(key) {
      if (key in this.cache) {
        return ((new Date()) - this.ttl > this.cache[key].timestamp);
      }
    },

    destroy: function(key) {
      this.cache[key] = undefined;
      delete this.cache[key];
    },

    empty: function() {
      this.cache = {};
    }
  };



  /**
   * ScheduleApp
   *
   * @author Dave Furfero <david.furfero@mlb.com>
   * @version 1.0
   */


  var ScheduleApp = (function() {

    var DT = bam.datetime,
        ONE_DAY_IN_MILLISECONDS = 86400000,
        container,
        config,
        cache,
        Event = $({});

    var getClubColor = function(fileCode) {
      var club;
      if (fileCode in clubProps) {
        return clubProps[fileCode].primary;
      }
    };

    /**
     * Table cell decorators & related functions
     */
    var gameTimeAsYMD = function(timestamp) {
      var date = new Date(parseInt(timestamp, 10));
      return DT.toYMD(date);
    };

    var gameTimeAsTime = function(timestamp, offset) {
      var date = new Date(parseInt(timestamp, 10));
      date.setHours(date.getUTCHours() + parseInt(offset, 10));
      return DT.formatDate(date, 'h:mma').toLowerCase();
    };

    var teamNameAbbrevMap = {
      'Diamondbacks': 'D-backs'
    };
    
    var renderFull = function(name, split, fileCode) {
      if (name in teamNameAbbrevMap) {
        name = teamNameAbbrevMap[name];
      }
      if (split === true) {
        name = name + ' (ss)';
      }
      
      return (fileCode in clubProps) ? '<a href="/index.jsp?c_id=' + fileCode + '">' + name + '</a>' : name;
    };

    var renderAwayFull = function(val, game) {
      var team = game.away;
      return renderFull(team.full, team.split, team.file_code);
    };

    var renderHomeFull = function(val, game) {
      var team = game.home;
      return renderFull(team.full, team.split, team.file_code);
    };

    var renderGameStatus = function(val, game) {

      var html,
          status = game.game_status.charAt(0);

      switch (status) {

        /* T Suspended */
        /* U Suspended */
        case 'T':
        case 'U':
          if (!game.is_suspension_resumption) {
            html = 'Suspended';            
            break;
          }

        /* S Scheduled */
        /* P Pre-game */
        case 'S':
        case 'P':

          
          var time = (game.game_time_is_tbd) ? 'TBD' : gameTimeAsTime(game.game_time, game.game_time_offset_eastern);

          if (game.game_dh !== null) {
            html = 'GM ' + game.game_num + ': ' + time;
          } else {
            if (game.preview !== null && game.preview !== '') {
              html = '<a href="' + game.preview +'" title="Game Preview">' + time + '</a>';
            } else {
              html = time;
            }
          }
        break;

        /* I In Progress */
        case 'I':

          var url, title;

          /* Pre-season */
          if ('SE'.indexOf(game.game_type) > -1) {
            url = '/mlb/scoreboard/index.jsp?ymd=' + gameTimeAsYMD(game.game_time);
            title = 'Scoreboard';

          /* Regular season/Post-season */
          } else {
            url = 'javascript:launchGameday({gid:\'' + game.game_id.replace(/\//g,'_').replace(/-/g,'_') + '\'});';
            title = 'Gameday';
          }

          html = '<a href="' + url + '" title="' + title + '">In progress <img src="/mlb/images/audio/gda_2003/icon_live.gif" alt="LIVE" width="10" height="10"/></a>';
        break;

        /* D Postponed */
        case 'D':
          html = 'Postponed';
        break;

        /* O Game Over */
        /* F Final */
        case 'O':
        case 'F':

          var home       = game.home,
              away       = game.away,
              wrapup     = game.wrapup,
              homeCode   = home.display_code || home.full,
              awayCode   = away.display_code || away.full,
              homeResult = homeCode + ' ' + parseInt(home.result, 10),
              awayResult = awayCode + ' ' + parseInt(away.result, 10),
              result = ((homeResult > awayResult) ? [homeResult, awayResult] : [awayResult, homeResult]).join(', ');

          html = (wrapup !== null) ? '<a href="' + wrapup + '" title="">' + result + '</a>' : result;

        break;

        /* C Cancelled */
        case 'C':
          html = 'Cancelled';
        break;

        /* Q Forfeit: Game Over */
        /* R Forfeit: Final */
        case 'Q':
        case 'R':
          html = 'Forfeit';
        break;
      }

      return html;
    };

    var renderPitcher = function(id, name, stat /*, title */) {
      var html, title = arguments[3] || '';
      if (name !== null) {
        html = '<a href="/team/player.jsp?player_id=' + id + '" title="' + title + '">' + name + ' (' + stat + ')</a>';
      } else {
        html = 'TBD';
      }
      return html;
    };

    var renderProbable = function(p) {
      var html,
          code = p.file_code,
          color = getClubColor(code);
          
      if (p.probable_report !== null) {
        html = '<div class="probable_report_target">';
        html += renderPitcher(p.probable_id, p.probable, p.probable_stat);
        html += '</div>';
        html += '<div class="probable_report" rel="' + color + '">';
        html += '<img src="/images/logos/50x50/' + code + '.gif" alt="" width="50" height="50"/>';
        html += '<h4>Probable Report</h4>';
        if (p.probable !== null) {
          html += '<em>' + p.probable_name_display_first_last + ' (' + p.probable_stat + ', ' + p.probable_era + ')</em>';
        }
        html += '<p>';
        html +=  p.probable_report;
        html += '</p>';
        html += '</div>';
      } else {
        html = renderPitcher(p.probable_id, p.probable, p.probable_stat, p.probable + ' stats page');
      };
      return html;
    };

    var renderAwayProbable = function(val, game) {
      return renderProbable(game.away);
    };

    var renderHomeProbable = function(val, game) {
      return renderProbable(game.home);
    };

    var renderWinningPitcher = function(val, game) {
      var p = game.pitcher;
      return (p.win !== null) ? renderPitcher(p.win_id, p.win, p.win_stat, p.win + ' stats page') : '';
    };

    var renderLosingPitcher = function(val, game) {
      var p = game.pitcher;
      return (p.loss !== null) ? renderPitcher(p.loss_id, p.loss, p.loss_stat, p.loss + ' stats page') : '';
    };

    var renderMLBTV = function(val, game) {
      var url = game.video_uri;
      return (val === true && url) ? '<a href="' + url + '" title="Ver"><img src="/images/icons/mlbtv.gif" width="15" height="12"/></a>' : '';
    };

    var renderTickets = function(val, game) {
      var url = game.home.tickets;
      return (url) ? '<a href="' + url + '" title="Compra Boletos"><img src="/images/schedule/icon_ticket.gif" width="13" height="13"/></a>' : '';
    };

    var renderAudio = function(val, game) {
      var url = game.home.audio_uri || game.away.audio_uri;
      return (url) ? '<a href="' + url + '" title="Escuchar"><img src="/images/icons/audio.gif" width="15" height="12"/></a>' : '';
    };



    /**
     * Table column definitions
     */

    var columnDefinitions = {

      /**
       * Pre-season table column definition
       */
      preSeason:  [{
         field: 'away_full',
         title: 'Visitante',
         decorator: renderAwayFull
       }, {
         field: 'home_full',
         title: 'Local',
         decorator: renderHomeFull
       }, {
         field: 'time_result',
         title: 'Hora (ET)/Resultados',
         decorator: renderGameStatus
       }, {
         field: 'game_location',
         title: 'Lugar'
       }, {
         field: 'mlbtv',
         title: 'MLB.TV',
         decorator: renderMLBTV
       }, {
         field: 'tickets',
         title: 'Boletos',
         decorator: renderTickets
       }],

      /**
       * Pre-game table column definition
       */
      preGame: [{
        field: 'away_full',
        title: 'Visitante',
        decorator: renderAwayFull
      }, {
        field: 'home_full',
        title: 'Local',
        decorator: renderHomeFull
      }, {
        field: 'time_result',
        title: 'Hora (ET)',
        decorator: renderGameStatus
      }, {
        field: 'away_probable',
        title: 'Lanzador Visitante',
        decorator: renderAwayProbable
      }, {
        field: 'home_probable',
        title: 'Lanzador Local',
        decorator: renderHomeProbable
      }, {
        field: 'mlbtv',
        title: 'MLB.TV',
        decorator: renderMLBTV
      }, {
        field: 'tickets',
        title: 'Boletos',
        decorator: renderTickets
      }],

      /**
       * Post-game table column definition
       */
      postGame: [{
        field: 'away_full',
        title: 'Visitante',
        decorator: renderAwayFull
      }, {
        field: 'home_full',
        title: 'Local',
        decorator: renderHomeFull
      }, {
        field: 'time_result',
        title: 'Resultados',
        decorator: renderGameStatus
      }, {
        field: 'pitcher_win',
        title: 'Ganador',
        decorator: renderWinningPitcher
      }, {
        field: 'pitcher_loss',
        title: 'Perdedor',
        decorator: renderLosingPitcher
      }, {
        field: 'mlbtv',
        title: 'MLB.TV',
        decorator: renderMLBTV
      }, {
        field: 'audio',
        title: 'Audio',
        decorator: renderAudio
      }]
    };


    /**
     *
     */
    var renderBody = function(rows, cols) {

      var html = '<tbody>',
          r, nRows, row,
          c, nCols, col,
          field,
          fn;

      for (r = 0, nRows = rows.length; r < nRows; r = r + 1) {

        row = rows[r];

        html += '<tr class="' + (r % 2 ? 'trB' : 'trA') + '">';

        for (c = 0, nCols = cols.length; c < nCols; c = c + 1) {

          col   = cols[c];
          field = col.field;
          fn    = col.decorator;

          html += '<td class="' + field + '">';
          html += (fn ? fn(row[field], row) : row[field]);
          html += '</td>';
        }

        html += '</tr>';
      }

      html += '</tbody>';

      return html;
    };



    /**
     *
     */
    var renderHead = function(cols) {

      var html = '<thead>',
          c, nCols, col;

      html += '<tr>';

      for (c = 0, nCols = cols.length; c < nCols; c = c + 1) {
        col = cols[c];
        html += '<th class="titleRowRs ' + col.field + '">' + col.title + '</th>';
      }

      html += '</tr></thead>';

      return html;
    };


    /**
     *
     */
    var renderTable = function(rows, cols) {
      return '<table class="dataTable" width="100%" cellspacing="1" cellpadding="2" border="0">' + renderHead(cols) + renderBody(rows, cols) + '</table>';
    };


    /**
     *
     */
    var load = function(url, el) {

      var html = cache.get(url);

      if (typeof html !== 'undefined') {
        el.append(html).show();
        return;
      }

      /**
       *
       */
      $.ajax({
        url: url,
        dataType: 'json',

        error: function(xhr, status, error) {          
          var html = '<div class="error">Los datos est&aacute;n actualmente indisponibles.</div>';

          // Cache error --- should only do under certain conditions,
          // like 404 vs timeout
          cache.set(url, html);
          el.append(html).show();
        },

        /**
         *
         */
        success: function(games) {

          var preSeasonGames = [],
              preGameGames   = [],
              postGameGames  = [],
              g, nGames, game,
              type, status;

          for (g = 0, nGames = games.length; g < nGames; g = g + 1) {

            game   = games[g];
            type   = game.game_type;
            status = game.game_status.charAt(0);

            /**
             * Type:
             * S Spring Training
             * E Exhibition
             */
            if ('SE'.indexOf(type) > -1) {
        		  preSeasonGames.push(game);

            /**
             * T Suspended
             * U Suspended
             */
            } else if ('TU'.indexOf(status) > -1) {
              if (game.is_suspension_resumption) {
                preGameGames.push(game);
              } else {
                postGameGames.push(game);
              }

            /**
             * Status:
             * O Game Over
             * F Final
             * D Postponed
             * Q Forfeit: Game Over
             * R Forfeit: Final
             * C Cancelled
             */
            } else if ('OFDQRC'.indexOf(status) > -1) {
              postGameGames.push(game);

            /**
             * Status:
             * S Scheduled
             * P Pre-game
             * I In Progress
             */
            } else if ('SPI'.indexOf(status) > -1) {
              preGameGames.push(game);

            /**
             * Status:
             * X Unknown
             * W Writing
             */
            } else {
              // WTW?!
            }
          }

          /**
           * Render day
           */
          var html = '';

          if (preSeasonGames.length > 0) {
            html += '<div class="preSeasonGames">';
            html += renderTable(preSeasonGames, columnDefinitions.preSeason);
            html += '</div>';
          }

          if (postGameGames.length > 0) {
            html += '<div class="postGameGames">';
            html += renderTable(postGameGames, columnDefinitions.postGame);
            html += '</div>';
          }

          if (preGameGames.length > 0) {
            html += '<div class="preGameGames">';
            html += renderTable(preGameGames, columnDefinitions.preGame);
            html += '</div>';
          }

          if (preSeasonGames.length === 0 && postGameGames.length === 0 && preGameGames.length === 0) {
            html = '<div class="noGames">Los datos est&aacute;n actualmente indisponibles.</div>';
          }

          cache.set(url, html);
          el.append(html).show();

          Event.trigger('load', el);
        }
      });
    };

    return {

      init: function(el, cfg) {

        container = $(el);


        /**
         * Extend default configuration
         */
        config = $.extend({
          xOffset:  16,           // Number of pixels to offset
          days:     3,            // Number of days to display
          ttl:      150           // Time-to-live for data cache (in seconds)
        }, cfg || {});


        /**
         * Create timed cache object
         */
        cache = new TimedCache(config.ttl * 1000);

        /**
         *
         */
        $('.probable_report_target')
          .live('mouseover', function() {
            Event.trigger('probable', this);
          });
      },



      fetch: function(d) {

        var startDate, startTime, d, nDays, date, ymd, url, h2, div, str;

        var today = floorDate('day', new Date()).getTime();

        
        /**
         * Set date
         */
        startDate = (typeof d !== 'undefined') ? new Date(d) : new Date();
        startTime = startDate.getTime();

        /**
         * Empty schedule container
         */
        container.empty();

        /**
         * Create a container and fetch data for each day's schedule
         */
        for (d = 0, nDays = config.days; d < nDays; d = d + 1) {

          date = new Date(startTime + (d * ONE_DAY_IN_MILLISECONDS));
          ymd  = DT.toYMD(date);
          str = 'Todos Tiempos ET.';
          if (date.getTime() >= today) {
            str += ' Sujeto a cambiar.';
          }
          url  = '/components/schedule/schedule_' + ymd + '.json';
          div = $('<div><h2 class="scheduleDate">' + DT.formatDate(date, 'EEEE MMMM d, yyyy') +
            '</h2><p class="allTimesEastern">' + str + '</p></div>').hide();

          div.appendTo(container);

          load(url, div);
        }

      },

      onProbable: function(data, fn) {
        var args = Array.prototype.slice.call(arguments, 0);
        args.unshift('probable');
        Event.bind.apply(Event, args);
      },

      onLoad: function(data, fn) {
        var args = Array.prototype.slice.call(arguments, 0);
        args.unshift('load');
        Event.bind.apply(Event, args);
      }

    };
  })();




















  /**
   *
   */
  var ScheduleAppDateSelector = (function() {


    var today,
        currentDate,
        numDays = 3,
        calendar = bam.dateSelector,
        showCalendarButton,
        getPreviousButton,
        getTodayButton,
        getNextButton,
        Event = $({});

    
    /**
     * Displays the date selector calendar widget
     *
     * @method
     * @memberOf ScheduleAppDateSelector
     * @static
     * @private
     * @param {Event} e jQuery Event facade
     */
    var showCalendar = function(e) {
      e.preventDefault();

      calendar.show(currentDate.getMonth() + 1, currentDate.getFullYear());

      var offset = showCalendarButton.offset();

      $(calendar.$elem).css({
        position: 'absolute',
        top:      offset.top + showCalendarButton.height() + 'px',
        left:     offset.left + 'px'
      });

    };


    /**
     * Decrements the current date by a configured number of days
     *
     * @method
     * @memberOf ScheduleAppDateSelector
     * @static
     * @private
     * @param {Event} e jQuery Event facade
     */
    var getPrevious = function(e) {
      e.preventDefault();
      currentDate.setDate(currentDate.getDate() - numDays);
      Event.trigger('change', currentDate);
    };


    /**
     * Sets the current date to today
     *
     * @method
     * @memberOf ScheduleAppDateSelector
     * @static
     * @private
     * @param {Event} e jQuery Event facade
     */
    var getToday = function(e) {
      e.preventDefault();
      currentDate = new Date(today);
      Event.trigger('change', currentDate);
    };


    /**
     * Increments the current date by a configured number of days
     *
     * @method
     * @memberOf ScheduleAppDateSelector
     * @static
     * @private
     * @param {Event} e jQuery Event facade
     */
    var getNext = function(e) {
      e.preventDefault();
      currentDate.setDate(currentDate.getDate() + numDays);
      Event.trigger('change', currentDate);
    };


    /**
     * Sets the current date to a user-selected date
     *
     * @method
     * @alias bam.dateSelector.processSelection
     * @memberOf ScheduleAppDateSelector
     * @static
     * @private
     */
    calendar.processSelection = function() {
      this.hide();
      currentDate = new Date(this.selectedDates.pop().replace(/^(\d{4})(\d{2})(\d{2})$/, '$2/$3/$1'));
      Event.trigger('change', currentDate);
    };


    return {

      /**
       * Initialize the date selector widget for the schedule app
       *
       * @method
       * @alias ScheduleAppDateSelector.init
       * @memberOf ScheduleAppDateSelector
       * @public
       * @param {Date} date Date object
       */
      init: function(date) {

        /**
         * Initialize dates
         */
        today = new Date();
        currentDate = date ? new Date(date) : new Date(today);

        /**
         * Cache interface elements and assign event handlers
         */
        showCalendarButton = $('#showCalendar').click(showCalendar);
        getPreviousButton  = $('#getPrevious').click(getPrevious);
        getTodayButton     = $('#getToday').click(getToday);
        getNextButton      = $('#getNext').click(getNext);

        /**
         * Setup calendar
         */
        calendar.showResetLink = false;
        calendar.setToday(bam.datetime.toYMD(today));

        Event.trigger('change', currentDate);

        /**
         * Set fragment identifier on change
         */
        Event.bind('change', function(e, date) {
          location.hash = 'date=' + bam.datetime.toShortDate(date);
        });
      },

      onChange: function(data, fn) {
        Event.bind('change', data, fn);
      }

    };
  })();



  /**
   * Class description
   *
   * @class
   * @inherits ParentClass
   * @param {ParamType} paramName Parameter Description
   * @throws {ErrorType} Error Description
   * @returns {ReturnType} Return Description
   * @constructor
   * @author Author Name author@email.com
   * @version 1.0
   */

  $.fn.qtip.styles.mlb = {
    width:        300,
    border: {     
      width:      4,
      radius:     4,
      color:      '#C00'
    },            
    tip:          true,            
    title: {      
      background: '#C00',
      color:      '#FFF'
    },            
    background:   '#FFF',
    color:        '#333',
    padding:      10,
    textAlign:    'left',
    classes: {
      tooltip: 'qtip-mlb'
    }
  };


  var ScheduleAppTooltipManager = (function() {

    var config;


    return {
      
      init: function(cfg) {
        
        config = $.extend({
          position: {
            adjust: {
              screen:   true
            },
            corner: {
              tooltip:  'bottomLeft',
              target:   'topRight'
            }
          },
          show: {
            delay:      500,
            solo:       true,
            when:       false,
            ready:      true,
            effect: {
              type:     'fade',
              length:   100
            }
          },
          hide: {
            when:       'mouseout',
            delay:      1000
          },
          style: {
            border:     {},
            name:       'mlb'
          }
        }, cfg || {});
      },
      
      show: function(el) {
        var $this = $(el),
            html = $this.next('.probable_report');

        if (html.size() === 0) {
          return;
        }

        if ($this.data('qtip')) {
          $this.qtip('destroy');
        }


        var cfg = $.extend({}, config);

        cfg.content = html;

        // Show tooltips on the outside by default so you can see probable matchup
        if ($this.parent().hasClass('away_probable')) {
          cfg.position.corner = {
            tooltip: 'bottomRight',
            target:  'topLeft'
          };
        } else {
          cfg.position.corner = {
            tooltip: 'bottomLeft',
            target:  'topRight'
          };
        }

        // Override border color with team color
        cfg.style.border.color = html.attr('rel');

        $this.qtip(cfg);
      }
    };
    
  })();


  /**
   * Add objects to bam namespace
   */
  // bam.widget = bam.widget || {};
  // bam.widget.ScheduleApp = ScheduleApp;
  // bam.widget.ScheduleAppDateSelector = ScheduleAppDateSelector;
  // bam.widget.ScheduleAppTooltipManager = ScheduleAppTooltipManager;




  // Main
  $(function() {

    // var ScheduleApp               = bam.widget.ScheduleApp,
    //     ScheduleAppDateSelector   = bam.widget.ScheduleAppDateSelector,
    //     ScheduleAppTooltipManager = bam.widget.ScheduleAppTooltipManager,

    bam.loadSync(bam.homePath + 'bam.url.js');

    var date = bam.url.parseFragmentIdentifiers().date;

    // Initialize the ScheduleApp module
    ScheduleApp.init('#scheduleContainer');

    // Subscribe the ScheduleApp fetch method to
    // the DateSelector's onChange event
    ScheduleAppDateSelector.onChange(function(e, date) {
      ScheduleApp.fetch(date);
    });

    // Initialize the DateSelector module
    ScheduleAppDateSelector.init(date);

    // Initialize the Tooltip Manager
    ScheduleAppTooltipManager.init();

    // Subscribe the Tooltip show method to 
    // the ScheduleApp's onProbable event
    ScheduleApp.onProbable(function(e, el) {
      ScheduleAppTooltipManager.show(el);
    });
    
  });



})();
