/*
 * Copyright (C) by Netcetera AG.
 * All rights reserved.
 *
 * The copyright to the computer program(s) herein is the property of
 * Netcetera AG, Switzerland.  The program(s) may be used and/or copied
 * only with the written permission of Netcetera AG or in accordance
 * with the terms and conditions stipulated in the agreement/contract
 * under which the program(s) have been supplied.
 *
 */

/**
 * This is the class responsible for keeping information about the current station
 * and the departures that are currently shown on the screen. I has methods for 
 * resizing the elements, adding new departures, removing the old ones, and animating them.
 */

var Display = $.klass({
  
  station: undefined,
  numDepartures: 6,
  
  initialize: function(config, station) {
    this.station = station;
    
    this.station.addObserver($.bind(this.update, this));
    
    this.numDepartures = config.opts.numDepartures || this.numDepartures;
  },
    
  /**
   * Deletes the current row.
   */
  scrap: function() {
    this.departureRows().remove();
  },
  
  /**
   * Main function that updates the screen.
   */
  update: function() {
    
    var now = DateTime.now().setToMinuteStart();
    var deps = this.station.getDepartures(now, this.numDepartures);
    
    document.title = this.station.stationName;
    $("#name").text(this.station.stationName);
    
    var removed = this.numberOfDeparturesToRemove(now);
    this.addDepartures(this.onlyNew(deps).slice(0, this.numDepartures - removed ), now);
    this.removeOldDepartures(now);
    this.updateTimes(now);
    
    var failure = this.station.failure();
    if (deps.length == 0 && failure) {
      ErrorMessage.show(failure);
    } else {
      ErrorMessage.hide();
    }
  },
  
  /**
   * Filters only the new departures.
   * @param deps, the departures got from the Station class.
   */
  onlyNew: function(deps) {
    var drows = this.departureRows();
    
    return $.grep(deps, function(dep) {
      var result = true;
      drows.each(function() {
        if ($(this).departure() && $(this).departure().sameAs(dep)) {
          result = false;
        }
      });
      return result;
    });
  },
  
  /**
   * Returns all the departures that are currently shown. 
   */  
  departureRows: function() {
    return $("#schedule tbody:not(.labels) tr:not(.proto)");
  },
  
  /**
   * Returns the prototype row, according to which, all the other rows are created.
   */
  proto: function() {
    return $("#schedule tr.proto");
  },
  
  numberOfDeparturesToRemove: function(now) {
    return this.departureRows().expired(now).size();
  },

  /**
   * Removes the old departures from view with some effect. 
   * @param now(Date), date object set to current time 
   */
  removeOldDepartures: function(now) {
		var daparturesForRemoval = this.departureRows().expired(now);
		this.departureRows().expired(now).animatedRemove();
  },
    
  addDepartures: function(deps, now) {
    var me = this;
    $.each(deps, function() {
      me.proto().clone().removeClass("proto")
        .insertBefore("#schedule tr.proto")
        .makeDepartureRow(this, now);
    });
    this.updateSizes();
  },

  updateTimes: function(now) {
    this.departureRows().notExpired(now).updateTimes(now);
  },
  
  getBorderHeight: function() {
    return Math.ceil(this.cellHeight() / 40);
  },
  
  getHeaderFontSize: function() {
    return $("#main").height() / 6.5;
  },
  
  getLabelFontSize: function() {
    return (this.cellHeight() / 2) * 3/4;
  },
  
  getDepartureFontSize: function() {
    return this.cellHeight() / 2;
  },
  
  getRightPaddingForTimeInfo: function() {
    return ($(document).width() * 2) / 100;
  },
  
  getFooterFontSize: function() {
    return this.cellHeight() / 2;
  },
  
  getTimeDateBoxWidth: function() {
    return $(document).width() - ($("#image-90-opacity").width() * 1.3);
  },
  
  getErrorFontSize: function() {
    return $("#error").height() / 2;
  },
  
  cellHeight: function() {
    if (jQuery.browser.mozilla == true) {
      return Math.ceil($("#main").height() / (this.numDepartures + 0.5));
    } 
    return Math.ceil($("#main").height() / (this.numDepartures + 0.5) * 39/40); //<-- the "39/40" coefficient is to compensate for the border for the other browsers
  },
  
  updateSizes: function() {
    
    this.updateSizes2();
    if ((jQuery.browser.msie == true) && (jQuery.browser.version.slice(0,1) == "6")) {
      var me = this;
      setTimeout(function() {
        me.updateSizes2();
      }, 0);
    }
  },
  
  /**
   * This function is fired after a resize event. Calculates sizes according to the cell height
   */
  updateSizes2: function() {
  
    $(".departures .line-icon").height(this.cellHeight());
    $(".departures .end-station").height(this.cellHeight());
    $(".departures .time-info").height(this.cellHeight());
    $(".departures .time-info").css("padding-right", this.getRightPaddingForTimeInfo());
    $("#schedule .line-icon").width(this.cellHeight());
    $("#schedule .labels td").height(this.cellHeight() / 2);
    $("#schedule tr td").css("border-bottom-width", this.getBorderHeight());
    $("#datetime-box").width(this.getTimeDateBoxWidth());
    $("#error").css("padding-top", this.getErrorFontSize() * 2/3);
     
    $("#schedule .labels tr td").css("font-size", this.getLabelFontSize());
    $("#schedule .departures tr td").css("font-size", this.getDepartureFontSize());
    $("#header").css("font-size", this.getHeaderFontSize());
    $("#datetime").css("font-size", this.getFooterFontSize())
    $("#error").css("font-size", this.getErrorFontSize());
   
    this.checkSizes();

  },
  
  /**
   * If some of the sizes are not correct, i.e. the there is overlapping and some of the elements
   * are not the proper size, this method reduces the font size until everything is ok.
   */
  checkSizes: function() {
    for ( var int = 1; int <= 5; int++) {
      if ($('#name').height() > $('#header').height()) {
        $("#header").css("font-size", this.getHeaderFontSize() * Math.pow((4/5),int));
      }
    }
    
    $("#name").css("margin-top", ($("#header").height() - $('#name').height()) / 2);
    var cellHeight = this.cellHeight();
    
    this.departureRows().each(function() {
      for ( var int = 1; int <= 5; int++) {
        if (($(this).children(".end-station").innerHeight() > cellHeight  * 1.1) ||
            ($(this).children(".time-info").innerHeight() > cellHeight * 1.1)) {
          $(this).children(".end-station").css("font-size", cellHeight * Math.pow((3/4),int));
        }
      }
    });
    
    for ( var int = 1; int <= 5; int++) {
      if ($('#datetime').width() + this.getRightPaddingForTimeInfo()> this.getTimeDateBoxWidth()) {
        $("#datetime").css("font-size", this.getFooterFontSize() * Math.pow((4/5),int));
      }
    }
    
    $('#datetime').css("right", this.getRightPaddingForTimeInfo());
  }
});

/**
 * jQuery departure row extensions.
 */
$.fn.extend({
  
  /**
   * Filters all elements that have an associated departure and that departure is expired.
   * @param now(aDate), returns all the departures that are older than the given date
   */
  expired: function(aDate) {
    return this.filter(function() {
      return this.departure && this.departure.time < aDate;
    });
  },
  
  /**
   * Filters all elements that have an associated departure and that departure is not expired.
   * @param now(aDate), returns all the departures that are not older than the given date
   */
  notExpired: function(aDate) {
    return this.filter(function() {
      return this.departure && this.departure.time >= aDate;
    });
  },
  
  departure: function() {
    return this.get(0).departure;
  },
  
  /**
   * Fills the cloned row with information. 
   * @param dep, one departure
   * @param now(Date), date object set to current time 
   */
  makeDepartureRow: function(dep, now) {
    return this
      .each(function() {this.departure = dep;})
      .addClass("departure-row")
      .children(".line-icon")
        .css("color", dep.line.color())
        .css("background-color", dep.line.backgroundColor())
        .text(dep.line.line_name)
      .end()
      .children(".end-station")
        .text(dep.end_station.name)
      .end();
  },
  
  
  
  /**
   * This is called every minute to change the time info on the rows with an animation. 
   * The animation applies only for those rows that have a change in their time info. 
   * @param now(Date), date object set to current time 
   */
  updateTimes: function(now) {
    this.children(".time-info").children("div").each(function() {
      var dep = $(this).parent().parent().departure();
      var update = false;
      
      if ($(this).children(".weekday").text() && (dep.formattedTime(now).search(/weekday/) < 0)) { 
        update = true; 
      } else if (dep.formattedTime(now).search(/in/) >= 0) { 
        update = true; 
      }
      if (!$(this).text()) { update = true; }
        
      if (update) {
        $(this).fadeOut(400, function() {
          
          $(this).html(dep.formattedTime(now)).fadeIn(400, function() { });
        });
      }
    });
    return this;
  },
  
  /**
   * Animates the fade-out of the leaving row and its slide-up
   */
  animatedRemove: function() {
    var el = $(this);
    this.children("td").each(function() {  
      var color = $('body').css('background-color');
      var style = $(this).attr("style") + 
        " opacity: 0; color: " + color + "; border-bottom-color: " + color + ";"; // IE hack to keep the object transparent after the fade out
      $(this).fadeOut(500, function() {
//        if(jQuery.browser.msie) {
//          $(this).get(0).style.removeAttribute('filter');
//        }
        $(this)
          .removeAttr("style")
          .removeAttr("class")
          //create a <div> inside the <td>
          .wrapInner("<div/>") 
          // override the border color from the css, set it to the background color
          .css("border-bottom-color", color) 
          .children("div")
            //the style is set to the <div>, to have smooth slide
            .attr("style", style)
            .slideUp(800, function() {el.remove();})
      });
    });
  }
});