/*
 * 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.
 *
 */
var mobileImageString = "<img id=\"image-90-opacity\" src=\"/images/netc_logo_150.gif\"/>"

/**
 * The Main class is the starting point of the application.
 */
Main = {
  imagesString: "http://netcetera.biz/tramdroid/logo",
  station: null,
  display: null,
  config: null,   
  
  /**
   * Initializes the configuration and starts the urlObserver.
   */
  init: function() {
  
    ErrorMessage.init();
    PerturbationMessage.init();
    
    Main.config = Config.load();
    
    if (Main.config && Main.config.opts && Main.config.opts.m) {
      Mobile.init();
      $("#logo").html(mobileImageString);
    } else {
      $("#logo").html($("html>head>link[rel='" + this.imagesString + "']").attr('href'));
    }
    
    ConfigUI.currentConfig(this.config);
    ConfigUI.setup();
    
    $(window).resize($.bind(this.resizeHandler, this));

    // initialize datetime, the callback function is called
    // after the initialize() method is finished.
    DateTime.initialize(Main.config, new Date(), function() {
      Main.urlObserver.start();
      
      if (Main.config && Main.config.isValid()) {
        Main.start();
      } else {
        Main.configure();
      }
    });
    
  },
  
  /**
   * This method is also called every time the URL changes.
   * New instances of the station and display objects are created 
   * and actions are added to the minuteTicker that are being executed periodically. 
   */
  start: function() {
    
    ConfigUI.currentConfig(Main.config);
    
    this.station = new Station(this.config);
    this.display = new Display(this.config, this.station);
    
    // add the actions that need to be performed on each minute tick
    this.minuteTicker.add(
      $.bind(this.station.update, this.station),
      $.bind(this.display.update, this.display),
      this.updateClock
    );
    // start the minute ticker
    this.minuteTicker.start();
    
    // add the action that will blink the departure times
    this.blinkTicker.reset();
    this.blinkTicker.add(
      $.bind(this.display.blinkDepartureTime, this.display)
    );
    // start the minute ticker
    this.blinkTicker.start();
  },
  
  /**
   * The reset method is called when the url changes.
   * The minuteTicker is reset and the config is reloaded so the new parameters can be read. 
   */
  restart: function() {
    this.minuteTicker.reset();
    ErrorMessage.hide();
    if (this.display) {
      this.display.scrap();
    }
    this.display = null;
    this.station = null;

    var c = Config.load();
    if (c && c.isValid()) {
      this.config = c;
      this.setAdditionalStylesheet(this.config.opts.css);

      DateTime.initialize(Main.config, new Date(), function() {      
        Main.start();
      });
    }
  },
  
  /**
   * Set a config object in ConfigUI.
   */
  configure: function() {
    ConfigUI.show(Main.config);
  },
  
  /**
   * Object responsible for executing predefined actions at the start of every minute.
   */ 
  minuteTicker: {
    actions: [],

    /**
     * Start the minute ticker.
     */
    start: function() {
      var now = DateTime.now();
      var nextMin = now.clone().setToNextMinute();

      if (nextMin > now) {
        $(document).oneTime(nextMin.getTime() - now.getTime() + 10, "firstMinute", $.bind(function() {
          this.startTicker();
          this.execute(true);
        }, this));
      } else {
        this.startTicker();
      }

      this.execute();
    },

    /**
     * Reset the ticker.
     * Remove the actions and stop the ticker.
     */
    reset: function() {
      $(document)
        .stopTime("firstMinute")
        .stopTime("minuteTicker");
      this.actions = [];
    },
    
    /**
     * Start the ticker.
     * Every minute an internal method is executed which in turn executes the registered action.
     */
    startTicker: function() {
      var me = this;
      $(document).everyTime(60000, "minuteTicker", $.bind(this.execute, this));
    },

    /**
     * Execute the registered actions.
     */
    execute: function(skipTickerLagCheck) {
      var now = DateTime.now();
      Main.log.info("");
      Main.log.info("Minute ticker: Time = " + now + "; System Time = " + new Date());
      
      if (this.actions.length == 0) {
        Main.log.info("         minute ticker have 0 actions, reset main");
        Main.restart();
      }
      
      $.each(this.actions, function(i, action) {
        Main.log.info("         action: " + i);
        this(); // calls the action
      });
      Main.log.info("   after minute ticker actions " + this.actions.length);
      if (skipTickerLagCheck) {
        this.maybeFixTickerLag(now);
      }
    },

    /**
     * Add new actions that should be executed every minute.
     */
    add: function() {
      var aArray = $.makeArray(arguments);
      this.actions = this.actions.concat(aArray);
    },
    
    /**
     * On some browsers the timer takes more and more time to tick. Reset if the delay is more than 2 sec
     */
    maybeFixTickerLag: function(now) {
      var minuteStart = now.clone().setToMinuteStart();
      if (now.getTime() - minuteStart.getTime() > 2000) {
        Main.log.info("         minute ticker lag, reset");
        this.reset();
        this.start();
      }
    }
  },
  
  /**
   * Object responsible for changing the visibility of the departure time.
   * This is needed for making the departure time blink, since the "text-decoration: blink"
   * doesn't work in IE, Chrome nor Safari. 
   */ 
  blinkTicker: {
    actions: [],

    /**
     * Start the ticker.
     */
    start: function() {
      this.execute();
      this.startTicker();
    },

    /**
     * Reset the ticker.
     * Remove the actions and stop the ticker.
     */
    reset: function() {
      $(document)
        .stopTime("ticker");
      this.actions = [];
    },
    
    /**
     * Start the ticker.
     * Every 10 seconds an internal method is executed which in turn executes the registered action.
     */
    startTicker: function() {
      var me = this;
      $(document).everyTime(500, "ticker", $.bind(this.execute, this));
    },

    /**
     * Execute the registered actions.
     */
    execute: function() {
      $.each(this.actions, function(i, action) {
        this(); // calls the action
      });
    },

    /**
     * Add new action that should be executed.
     */
    add: function() {
      var aArray = $.makeArray(arguments);
      this.actions = this.actions.concat(aArray);
    }
  },
  
  /**
   * Object responsible for periodically checking the browser's URL and restart the application if there is a change.
   */
  urlObserver: {
    lastHash: undefined,
     
    /**
     * Start the observer.
     * Adds an action to be executed every 200 milliseconds.
     */
    start: function() {
      this.lastHash = window.location.hash;
      $(document).everyTime(200, "urlObserver", $.bind(this.observe, this));
    },
  
    /**
     * Stop the observer.
     */
    stop: function() {
      $(document).stopTime("urlObserver");
    },
    
    /**
     * Check the current hash in the URL with the last known hash.
     * If there is a change, restart the Main class.
     */
    observe: function() {
      var h = window.location.hash;
      
      if (!h) {
        return;
      }
      
      if (this.lastHash && this.lastHash == h) {
        return;
      }
      
      Main.restart();
      
      this.lastHash = h;
    }
  }, 
  
  log: {
    _log: function(method, args) {
      if (typeof console != "undefined") {
        if (typeof console[method] == "function") {
          console[method].apply(console, args);
        } else if (console.log) {
          console.log(args[0]);
        }
      }
    },
    
    debug: function() {
      this._log("debug", arguments);
    },
    
    info: function() {
      this._log("info", arguments);
    },
      
    error: function() {
      this._log("error", arguments);
    },
    
    warn: function() {
      this._log("warn", arguments);
    }
  },
  
  /**
   * setAdditionalStylesheet.
   */
  setAdditionalStylesheet: function(stylesheet) {
    if (stylesheet) {
      $('#additional-stylesheet').attr('href', stylesheet);
    }
  },
  
  /**
   * Updates the clock in the lower right corner of the application.
   */
  updateClock: function() {
    var now = DateTime.now();
    $('#datetime').text(sprintf("%s, %02d.%02d.%4d, %02d:%02d",
      now.getWeekDayString(), 
      now.getDate(), now.getMonth() + 1, now.getFullYear(), 
      now.getHours(), now.getMinutes()));
  },
  
  /**
   * This function is called when a resise is detected.
   * We handle this here because the display instance changes on reconfguration
   */
  resizeHandler: function() {
    if (this.display) {
      this.display.updateSizes();
    }
    ErrorMessage.adjust();
    ConfigUI.adjust();
  }
};

function hideAddressBar() { 
  window.scrollTo(0, 1); 
};

/**
 * jQuery idiom for "execute when the document is ready"
 */
$(function() {
  // initialization
  Main.init();
});


