﻿// libs used:
// https://github.com/janl/mustache.js
// https://github.com/daniellmb/MinPubSub
/*!
* MinPubSub
* Copyright(c) 2011 Daniel Lamb <daniellmb.com>
* MIT Licensed
*/
(function(d){
    // the topic/subscription hash
    var cache = d.c_ || {}; //check for "c_" cache for unit testing
    d.publish = function(/* String */ topic, /* Array? */ args){
        // summary:
        //        Publish some data on a named topic.
        // topic: String
        //        The channel to publish on
        // args: Array?
        //        The data to publish. Each array item is converted into an ordered
        //        arguments on the subscribed functions.
        //
        // example:
        //        Publish stuff on '/some/topic'. Anything subscribed will be called
        //        with a function signature like: function(a,b,c){ ... }
        //
        //        publish("/some/topic", ["a","b","c"]);
        var subs = cache[topic],
            len = subs ? subs.length : 0;
        //can change loop or reverse array if the order matters
        while(len--){
            subs[len].apply(d, args || []);
        }
    };
    d.subscribe = function(/* String */ topic, /* Function */ callback){
        // summary:
        //        Register a callback on a named topic.
        // topic: String
        //        The channel to subscribe to
        // callback: Function
        //        The handler event. Anytime something is publish'ed on a
        //        subscribed channel, the callback will be called with the
        //        published array as ordered arguments.
        //
        // returns: Array
        //        A handle which can be used to unsubscribe this particular subscription.
        //
        // example:
        //        subscribe("/some/topic", function(a, b, c){ /* handle data */ });
        if(!cache[topic]){
            cache[topic] = [];
        }
        cache[topic].push(callback);
        return [topic, callback]; // Array
    };
    d.unsubscribe = function(/* Array */ handle){
        // summary:
        //        Disconnect a subscribed function for a topic.
        // handle: Array
        //        The return value from a subscribe call.
        // example:
        //        var handle = subscribe("/some/topic", function(){});
        //        unsubscribe(handle);
        var subs = cache[handle[0]],
            callback = handle[1],
            len = subs ? subs.length : 0;
        while(len--){
            if(subs[len] === callback){
                subs.splice(len, 1);
            }
        }
    };
})(this);
/*
  mustache.js — Logic-less templates in JavaScript
  See http://mustache.github.com/ for more info.
*/
var Mustache = function() {
  var Renderer = function() {};
  Renderer.prototype = {
    otag: "{{",
    ctag: "}}",
    pragmas: {},
    buffer: [],
    pragmas_implemented: {
      "IMPLICIT-ITERATOR": true
    },
    context: {},
    render: function(template, context, partials, in_recursion) {
      // reset buffer & set context
      if(!in_recursion) {
        this.context = context;
        this.buffer = []; // TODO: make this non-lazy
      }
      // fail fast
      if(!this.includes("", template)) {
        if(in_recursion) {
          return template;
        } else {
          this.send(template);
          return;
        }
      }
      template = this.render_pragmas(template);
      var html = this.render_section(template, context, partials);
      if(in_recursion) {
        return this.render_tags(html, context, partials, in_recursion);
      }
      this.render_tags(html, context, partials, in_recursion);
    },
    /*
      Sends parsed lines
    */
    send: function(line) {
      if(line != "") {
        this.buffer.push(line);
      }
    },
    /*
      Looks for %PRAGMAS
    */
    render_pragmas: function(template) {
      // no pragmas
      if(!this.includes("%", template)) {
        return template;
      }
      var that = this;
      var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
            this.ctag);
      return template.replace(regex, function(match, pragma, options) {
        if(!that.pragmas_implemented[pragma]) {
          throw({message:
            "This implementation of mustache doesn't understand the '" +
            pragma + "' pragma"});
        }
        that.pragmas[pragma] = {};
        if(options) {
          var opts = options.split("=");
          that.pragmas[pragma][opts[0]] = opts[1];
        }
        return "";
        // ignore unknown pragmas silently
      });
    },
    /*
      Tries to find a partial in the curent scope and render it
    */
    render_partial: function(name, context, partials) {
      name = this.trim(name);
      if(!partials || partials[name] === undefined) {
        throw({message: "unknown_partial '" + name + "'"});
      }
      if(typeof(context[name]) != "object") {
        return this.render(partials[name], context, partials, true);
      }
      return this.render(partials[name], context[name], partials, true);
    },
    /*
      Renders inverted (^) and normal (#) sections
    */
    render_section: function(template, context, partials) {
      if(!this.includes("#", template) && !this.includes("^", template)) {
        return template;
      }
      var that = this;
      // CSW - Added "+?" so it finds the tighest bound, not the widest
      var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
              "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
              "\\s*", "mg");
      // for each {{#foo}}{{/foo}} section do...
      return template.replace(regex, function(match, type, name, content) {
        var value = that.find(name, context);
        if(type == "^") { // inverted section
          if(!value || that.is_array(value) && value.length === 0) {
            // false or empty list, render it
            return that.render(content, context, partials, true);
          } else {
            return "";
          }
        } else if(type == "#") { // normal section
          if(that.is_array(value)) { // Enumerable, Let's loop!
            return that.map(value, function(row) {
              return that.render(content, that.create_context(row),
                partials, true);
            }).join("");
          } else if(that.is_object(value)) { // Object, Use it as subcontext!
            return that.render(content, that.create_context(value),
              partials, true);
          } else if(typeof value === "function") {
            // higher order section
            return value.call(context, content, function(text) {
              return that.render(text, context, partials, true);
            });
          } else if(value) { // boolean section
            return that.render(content, context, partials, true);
          } else {
            return "";
          }
        }
      });
    },
    /*
      Replace {{foo}} and friends with values from our view
    */
    render_tags: function(template, context, partials, in_recursion) {
      // tit for tat
      var that = this;
      var new_regex = function() {
        return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
          that.ctag + "+", "g");
      };
      var regex = new_regex();
      var tag_replace_callback = function(match, operator, name) {
        switch(operator) {
        case "!": // ignore comments
          return "";
        case "=": // set new delimiters, rebuild the replace regexp
          that.set_delimiters(name);
          regex = new_regex();
          return "";
        case ">": // render partial
          return that.render_partial(name, context, partials);
        case "{": // the triple mustache is unescaped
          return that.find(name, context);
        default: // escape the value
          return that.escape(that.find(name, context));
        }
      };
      var lines = template.split("\n");
      for(var i = 0; i < lines.length; i++) {
        lines[i] = lines[i].replace(regex, tag_replace_callback, this);
        if(!in_recursion) {
          this.send(lines[i]);
        }
      }
      if(in_recursion) {
        return lines.join("\n");
      }
    },
    set_delimiters: function(delimiters) {
      var dels = delimiters.split(" ");
      this.otag = this.escape_regex(dels[0]);
      this.ctag = this.escape_regex(dels[1]);
    },
    escape_regex: function(text) {
      // thank you Simon Willison
      if(!arguments.callee.sRE) {
        var specials = [
          '/', '.', '*', '+', '?', '|',
          '(', ')', '[', ']', '{', '}', '\\'
        ];
        arguments.callee.sRE = new RegExp(
          '(\\' + specials.join('|\\') + ')', 'g'
        );
      }
      return text.replace(arguments.callee.sRE, '\\$1');
    },
    /*
      find `name` in current `context`. That is find me a value
      from the view object
    */
    find: function(name, context) {
      name = this.trim(name);
      // Checks whether a value is thruthy or false or 0
      function is_kinda_truthy(bool) {
        return bool === false || bool === 0 || bool;
      }
      var value;
      if(is_kinda_truthy(context[name])) {
        value = context[name];
      } else if(is_kinda_truthy(this.context[name])) {
        value = this.context[name];
      }
      if(typeof value === "function") {
        return value.apply(context);
      }
      if(value !== undefined) {
        return value;
      }
      // silently ignore unkown variables
      return "";
    },
    // Utility methods
    /* includes tag */
    includes: function(needle, haystack) {
      return haystack.indexOf(this.otag + needle) != -1;
    },
    /*
      Does away with nasty characters
    */
    escape: function(s) {
      s = String(s === null ? "" : s);
      return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
        switch(s) {
        case "&": return "&amp;";
        case "\\": return "\\\\";
        case '"': return '&quot;';
        case "'": return '&#39;';
        case "<": return "&lt;";
        case ">": return "&gt;";
        default: return s;
        }
      });
    },
    // by @langalex, support for arrays of strings
    create_context: function(_context) {
      if(this.is_object(_context)) {
        return _context;
      } else {
        var iterator = ".";
        if(this.pragmas["IMPLICIT-ITERATOR"]) {
          iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
        }
        var ctx = {};
        ctx[iterator] = _context;
        return ctx;
      }
    },
    is_object: function(a) {
      return a && typeof a == "object";
    },
    is_array: function(a) {
      return Object.prototype.toString.call(a) === '[object Array]';
    },
    /*
      Gets rid of leading and trailing whitespace
    */
    trim: function(s) {
      return s.replace(/^\s*|\s*$/g, "");
    },
    /*
      Why, why, why? Because IE. Cry, cry cry.
    */
    map: function(array, fn) {
      if (typeof array.map == "function") {
        return array.map(fn);
      } else {
        var r = [];
        var l = array.length;
        for(var i = 0; i < l; i++) {
          r.push(fn(array[i]));
        }
        return r;
      }
    }
  };
  return({
    name: "mustache.js",
    version: "0.3.1-dev",
    /*
      Turns a template and view into HTML
    */
    to_html: function(template, view, partials, send_fun) {
      var renderer = new Renderer();
      if(send_fun) {
        renderer.send = send_fun;
      }
      renderer.render(template, view, partials);
      if(!send_fun) {
        return renderer.buffer.join("\n");
      }
    }
  });
}();
/**
 * Loads & displays FlightPlan Data
 *
 * @author Sebastian Golasch <sebastian.golasch@denkwerk.com>
 */
(function (window, document, $, mustache, undef) {
    var FlightPlan = {
        /**
         * Base table template
         *
         * @type {{String}}
         */
        tableTemplate: '<table id="fp_schedule_template" class="schedule" border="0" cellspacing="0">' +
                            '<colgroup>' +
                            '<col class="route">' +
                            '<col class="week">' +
                            '<col class="from">' +
                            '<col class="to">' +
                            '<col class="flightNumber">' +
                            '<col class="button">' +
                          '</colgroup>' +
                          '<thead>' +
                            '<tr>' +
                                '<th class="paddingleft6px">{{route}}</th>' +
                                '<th>{{week}}</th>' +
                                '<th class="padg">{{from}}</th>' +
                                '<th class="padg">{{to}}</th>' +
                                '<th class="padg">{{flightNumber}}</th>' +
                                '<th>&nbsp;</th>' +
                            '</tr>' +
                        '</thead>' +
                        '<tbody>' +
                            '<tr class="spaceLine">' +
                                '<td>&nbsp;</td>' +
                                '<td class="period">' +
                                    '<a href="#" id="period_down"><img src="{{arrowRight}}" alt="Zurück"></a>' +
                                    '<div><span class="fp_from">{{fromDate}}</span> {{period}} <span class="fp_to">{{toDate}}</span></div>' +
                                    '<a href="#" id="period_up"><img src="{{arrowLeft}}" alt="Vor"></a>' +
                                '</td>' +
                                '<td>&nbsp;</td>' +
                                '<td>&nbsp;</td>' +
                                '<td>&nbsp;</td>' +
                                '<td>&nbsp;</td>' +
                            '</tr>' +
                        '</tbody>' +
                    '</table>',
        /**
         * Template for a single flight plan row
         *
         * @type {{String}}
         */
         rowTemplate: '<tbody class="fp_schedule_flight_template" style="display: table-row-group;">' +
                        '<tr {{#lastRow}}class="spaceLine"{{/lastRow}}>' +
                            '<td class="paddingleft6px fp_origindestination">' +
                                '<a href="#" class="fp_origin">{{#firstRow}}{{origin}}{{/firstRow}}</a>{{#firstRow}}&nbsp;{{/firstRow}}' +
                                '<img {{#notFirstRow}}style="display: none;"{{/notFirstRow}} src="{{arrowDesc}}" alt="Arrow">' +
                                '{{#firstRow}}&nbsp;{{/firstRow}}<a href="#" class="fp_destination">{{#firstRow}}{{destination}}{{/firstRow}}</a>' +
                                '{{#isVia}}&nbsp;{{/isVia}}<span class="fp_via">{{#isVia}}via {{via}}{{/isVia}}</span>' +
                            '</td>' +
                            '<td class="week">' +
                                '<a><img src="{{monday_img}}" class="fp_monday" alt=""></a>' +
                                '<a><img src="{{tuesday_img}}" class="fp_tuesday" alt=""></a>' +
                                '<a><img src="{{wednesday_img}}" class="fp_wednesday" alt=""></a>' +
                                '<a><img src="{{thursday_img}}" class="fp_thursday" alt=""></a>' +
                                '<a><img src="{{friday_img}}" class="fp_friday" alt=""></a>' +
                                '<a><img src="{{saturday_img}}" class="fp_saturday" alt=""></a>' +
                                '<a><img src="{{sunday_img}}" class="fp_sunday" alt=""></a>' +
                            '</td>' +
                            '<td>' +
                                '<div class="fp_departuretime">{{departureTime}}</div>' +
                            '</td>' +
                            '<td>' +
                                '<div class="fp_arrivaltime">{{arrivalTime}}</div>' +
                            '</td>' +
                            '<td>' +
                                '<div class="fp_flightnr">{{flightNo}}</div>' +
                            '</td>' +
                            '<td>' +
                                '<a {{#notFirstRow}}style="display: none;"{{/notFirstRow}} class="floatR fp_lowfarecalendar" href="{{lowfare_link}}"><img height="18" alt="Sparkalender" src="{{lowfare_img}}" /></a></td>' +
                            '</tr>' +
                        '</tbody>',
        /**
         * Current culture
         *
         * @type {{String}}
         */
        culture: null,
        /**
         * Language dependant graphcs
         *
         * @type {{Object}}
         */
        buttons: null,
        /**
         * Language dependant table descriptions
         *
         * @type {{Object}}
         */
        tableDesc: null,
        /**
         * jQueryfied origin select
         *
         * @type {{Object}}
         */
        $originSelect: null,
        /**
         * jQueryfied destination select
         *
         * @type {{Object}}
         */
        $destinationSelect: null,
        /**
         * jQueryfied calendar select
         *
         * @type {{Object}}
         */
        $calendarSelect: null,
        /**
         * jQueryfied shoe flights button
         *
         * @type {{Object}}
         */
        $showFlightsButton: null,
        /**
         * Remote flight plan url
         *
         * @type {{String}}
         */
        flightPlanUrl: '/ajax/FlightPlan.ashx',
        /**
         * Remote station list url
         *
         * @type {{String}}
         */
        flightStationUrl: '/ajax/FlightPlanStations.ashx',
        /**
         * List of all needed selectors
         * Just for convenience, if we need to change smth.,
         * change it here and you´re done!
         *
         * -'#content' The content area
         * -'#fp_form_origin'       Select box for the origin airports
         * -'#fp_form_destination'  Select box for the origin airports
         * -'#fp_form_calendarweek' Select box for the calendar week
         * -'.scheduleSelects a'    Show flights button
         * -'#period_down'          Decrease current date button
         * -'#period_up'            Increase current date button
         * -'#fp_schedule_template' The table thats been used to display the flight data
         * -'#fp_nodata'            The no data layer
         * -'#fp_loading'           The loading layer
         * -'#fp_error'             The error layer
         * -'#fp_station_loading'   The station loading layer
         * -'#fp_container'         The container that´s wrapped around the flights table
         *
         * @type {{Object}}
         */
         selectors: {
             content:               '#content',
             originSelect:          '#fp_form_origin',
             destinationSelect:     '#fp_form_destination',
             calendarWeekSelect:    '#fp_form_calendarweek',
             showFlightsButton:     '.scheduleSelects a',
             decreaseDateButton:    '#period_down',
             increaseDateButton:    '#period_up',
             tableTemplate:         '#fp_schedule_template',
             noDataLayer:           '#fp_nodata',
             loadingLayer:          '#fp_loading',
             errorLayer:            '#fp_error',
             stationLoadingLayer:   '#fp_station_loading',
             tableContainer:        '#fp_container'
         },
        /**
         * Initializes the flightplan
         * Loads the departure airport data
         * Binds event handlers
         *
         * @param {{String}} culture Current culture
         * @param {{Object}} buttons Langauge dependent graphics
         * @param {{Object}} table_desc Table descriptions
         */
        init: function (culture, buttons, table_desc) {
            var selectors = this.selectors,
                $content = $(selectors.content);
            // store culture
            this.culture = this.parseCulture(culture);
            // store buttons
            this.buttons = buttons;
            // store table descriptions
            this.tableDesc = table_desc;
            // store jqueryfied version of the select fields
            this.$originSelect = $(selectors.originSelect);
            this.$destinationSelect = $(selectors.destinationSelect);
            this.$calendarSelect = $(selectors.calendarWeekSelect);
            this.$showFlightsButton = $(selectors.showFlightsButton);
            // load and add the flightplan stations
            this.getAllFlightPlanStationsByCulture(this.culture)
                .success(this.fillDepartureSelectWithXml)
                .error(this.toggleErrorLayer);
           // bind event handler to the origin select change event
           this.$originSelect.bind('change', $.proxy(this.changeOriginSelect, this));
           // bind event handler to the destination select change event
           this.$destinationSelect.bind('change', $.proxy(this.changeDestinationSelect, this));
           // bind event handler to the flight search button click event
           this.$showFlightsButton.bind('click', $.proxy(this.updateFlightPlan, this));
           // bind live event click handlers to the date selctor buttons
           $content.delegate(selectors.decreaseDateButton, 'click', $.proxy(this.periodDown, this));
           $content.delegate(selectors.increaseDateButton, 'click', $.proxy(this.periodUp, this));
        },
        /**
         * Event handler for origin select changes
         *
         */
        changeOriginSelect: function () {
            // load and add the flightplan stations to the departure select
            this.getFlightPlanStationsByTypeStationAndCulture(this.getSelectedOriginAirportTlc(), this.culture)
                .success(this.fillArrivalSelectWithXml)
                .error(this.toggleErrorLayer);
        },
        /**
         * Event handler for destination select changes
         *
         */
        changeDestinationSelect: function () {
            var originSelectVal = this.getSelectedOriginAirportTlc(),
                destinationSelectVal = this.getSelectedOriginAirportTlc();
            // check if origin and destination airport are set correctly
            if (destinationSelectVal.length <= 3) {
                if (originSelectVal.length <= 3) {
                    // enable date picker select
                    this.$calendarSelect[0].disabled = false;
                }
            }
        },
        /**
         * Updates the flight plan html
         * based on the current select settings
         *
         * @param {{Object}} event jQuery event instance
         */
        updateFlightPlan: function (event) {
            // prevent the event from bubbling
            event.preventDefault();
            
            // remove the already existing table
            $(this.selectors.tableTemplate).remove();
            
            // show loading layer
            this.toggleLoadingLayer();
            // load the flight data
            this.getFlightDataByOriginDestinationDateAndCulture(this.getSelectedOriginAirportTlc(), this.getSelectedDepartureAirportTlc(), this.getSelectedDatePeriod(), this.culture)
                .success(this.displayFlights)
                .error(this.toggleErrorLayer);
            return false;
        },
        /**
         * Event handler for date changes
         * based on the flightplan date/week selector field
         * Triggers flightplan to update
         *
         * @param {{Object}} event jQuery event instance
         */
        periodUp: function (event) {
            var dropdown = this.$calendarSelect[0];
            
            // prevent the event from bubbling
            event.preventDefault();
            // increase date select box field
            if (dropdown.selectedIndex < dropdown.length - 1) {
                dropdown.selectedIndex++;
            }
            // trigger content update event
            this.$showFlightsButton.trigger('click');
        },
        /**
         * Event handler for date changes
         * based on the flightplan date/week selector field
         * Triggers flightplan to update
         *
         * @param {{Object}} event jQuery event instance
         */
        periodDown: function (event) {
            var dropdown = this.$calendarSelect[0];
            // prevent the event from bubbling
            event.preventDefault();
            
            // decrease date select box field
            if (dropdown.selectedIndex > 1) {
                dropdown.selectedIndex--;
            }
            // trigger content update event
            this.$showFlightsButton.trigger('click');
        },
        /**
         * Updates the origin select with
         * values from an xml response
         *
         * @param {{Object}} xml Remote loaded xml document
         */
        fillDepartureSelectWithXml: function (xml) {
            var $xml = $(xml),
                $stations = $xml.find("station"),
                $originSelect = this.$originSelect;
            // walk over station and update the origin select
            $.each($stations, function (idx, station) {
                var $station = $(station);
                $originSelect.append(
                    $('<option></option>').val($station.attr('tlc')).html($.trim($station.text()))
                );
            });
        },
        /**
         * Updates the destination select with
         * values from an xml response
         *
         * @param {{Object}} xml Remote loaded xml document
         */
        fillArrivalSelectWithXml: function (xml) {
            var $xml = $(xml),
                $stations = $xml.find("station"),
                $destinationSelect = this.$destinationSelect;
            // remove old options
            $destinationSelect.find('option').each(function (idx, option) {
                if (idx > 0) {
                    $(option).remove();
                }
            });
            // walk over station and update the origin select
            $.each($stations, function (idx, station) {
                var $station = $(station);
                $destinationSelect.append(
                    $('<option></option>').val($station.attr('tlc')).html($.trim($station.text()))
                );
            });
            // enable the select field
            $destinationSelect[0].disabled = false;
            // fire loaded event
            window.publish('/xml/destination/loaded');
        },
        /**
         * Updates the flight plan xml with
         * values from an xml response
         *
         * @param {{Object}} xml Remote loaded xml document
         */
        displayFlights: function (xml) {
            var $xml = $(xml),
                $outward = $xml.find('outward'),
                $return = $xml.find('return'),
                $outwardFlights = $outward.find('flight'),
                $returnFlights =  $return.find('flight'),
                $outwardDestination = $outward.find('destination'),
                $outwardOrigin = $outward.find('origin'),
                $returnDestination = $return.find('destination'),
                $returnOrigin =  $return.find('origin'),
                outward_flights_length = $outwardFlights.length,
                return_flights_length = $returnFlights.length,
                is_outward_flight = (outward_flights_length !== 0),
                is_return_flight = (return_flights_length !== 0),
                template = this.rowTemplate,
                table_desc = this.tableDesc,
                rows = "",
                origin, destination, originTlc, destinationTlc, fromDate, toDate;
            // check if we found flights
            if(!is_return_flight && !is_outward_flight) {
                // hide loading layer
                this.toggleLoadingLayer();
                // show no data found layer
                this.toggleNoDataLayer();
            } else {
                // assign global needed values from te xml response/document
                destination        = !is_outward_flight ? $returnDestination.text()       : $outwardOrigin.text();
                origin    = !is_outward_flight ? $returnOrigin.text()            : $outwardDestination.text();
                destinationTlc = !is_outward_flight ? $returnDestination.attr('tlc')  : $outwardOrigin.attr('tlc');
                originTlc = !is_outward_flight ? $returnOrigin.attr('tlc')       : $outwardDestination.attr('tlc');
                fromDate       = !is_outward_flight ? $return.find('from').text()     : $outward.find('from').text();
                toDate         = !is_outward_flight ? $return.find('to').text()       : $outward.find('to').text();
                // render and add the new table template
                $(mustache.to_html(this.tableTemplate, {
                    fromDate: fromDate,
                    toDate: toDate,
                    route: table_desc.route,
                    week: table_desc.week,
                    from: table_desc.from,
                    to: table_desc.to,
                    flightNumber: table_desc.flightNumber,
                    period: table_desc.period,
                    arrowRight: table_desc.arrowRight,
                    arrowLeft: table_desc.arrowLeft
                })).appendTo(this.selectors.tableContainer);
                // generate outward flight rows
                $outwardFlights.each($.proxy(function (idx, flight) {
                    var $flight = $(flight);
                    // compile the template
                    rows += mustache.to_html(template, this.buildTemplateData('outward', {
                        idx: idx,
                        fromDate: fromDate,
                        origin: origin,
                        destination: destination,
                        originTlc: originTlc,
                        destinationTlc: destinationTlc,
                        outwardFlightsLength: outward_flights_length,
                        returnFlightsLength: return_flights_length,
                        flight: $flight,
                        arrowDesc: table_desc.arrowDesc,
                        lowFareImg: table_desc.lowFareImage
                    }));
                }, this));
                // generate return flight rows
                $returnFlights.each($.proxy(function (idx, flight) {
                    var $flight = $(flight);
                    // compile the template
                    rows += mustache.to_html(template, this.buildTemplateData('return', {
                        idx: idx,
                        fromDate: fromDate,
                        origin: destination,
                        destination: origin,
                        originTlc: destinationTlc,
                        destinationTlc: originTlc,
                        outwardFlightsLength: outward_flights_length,
                        returnFlightsLength: return_flights_length,
                        flight: $flight,
                        arrowDesc: table_desc.arrowDesc,
                        lowFareImg: table_desc.lowFareImage
                    }));
                    
                }, this));
                // insert rows
                $(rows).insertAfter(this.selectors.tableTemplate + ' tbody:last');
                // hide loading layer
                this.toggleLoadingLayer();
            }
        },
        /**
         * Builds the template data for a given row
         *
         * @param {{String}} type The row type ('outward', 'return')
         * @param {{Object}} data The raw data
         * @return {{Object}} tmplData The data in the correct template format
         */
        buildTemplateData: function (type, data) {
            var is_outward = (type === 'outward'),
                buttons = this.buttons,
                idx = data.idx,
                $flight = data.flight,
                $monday = $flight.find('monday'),
                $tuesday = $flight.find('tuesday'),
                $wednesday = $flight.find('wednesday'),
                $thursday = $flight.find('thursday'),
                $friday = $flight.find('friday'),
                $saturday = $flight.find('saturday'),
                $sunday = $flight.find('sunday');
            
            return {
                    firstRow: function() {
                        return idx === 0 ? true : false;
                    },
                    notFirstRow: function() {
                        return idx === 0 ? false : true;
                    },
                    lastRow: function() {
                        return idx === ( is_outward ? (data.outwardFlightsLength -1) : (data.returnFlightsLength -1)) ? true : false;
                    },
                    isVia: function() {
                        return $.trim($flight.attr('via')) !== "" ? true : false;
                    },
                    origin          : data.destination,
                    destination     : data.origin,
                    departureTime   : $flight.attr('departureTime'),
                    arrivalTime     : $flight.attr('arrivalTime'),
                    flightNo        : $flight.attr('flightNr'),
                    via             : $flight.attr('via'),
                    viaTlc          : $flight.attr('viaTlc'),
                    viaFlightNo     : $flight.attr('viaFlightNr'),
                    lowfare_link    : this.getLowfareCalendarLinkByDepartureArrivalDateAndCulture(data.originTlc, data.destinationTlc, data.fromDate, this.culture),
                    arrowDesc       : data.arrowDesc,
                    lowfare_img     : data.lowFareImg,
                    monday_img      : $monday.text() === 'true'      ? buttons.monday   : buttons.monday_gray,
                    monday_date     : $monday.attr('date'),
                    tuesday_img     : $tuesday.text() === 'true'     ? buttons.tuesday  : buttons.tuesday_gray,
                    tuesday_date    : $tuesday.attr('date'),
                    wednesday_img   : $wednesday.text() === 'true'   ? buttons.wednesday : buttons.wednesday_gray,
                    wednesday_date  : $wednesday.attr('date'),
                    thursday_img    : $thursday.text() === 'true'    ? buttons.thursday : buttons.thursday_gray,
                    thursday_date   : $thursday.attr('date'),
                    friday_img      : $friday.text() === 'true'      ? buttons.friday : buttons.friday_gray,
                    friday_date     : $friday.attr('date'),
                    saturday_img    : $saturday.text() === 'true'    ? buttons.saturday : buttons.saturday_gray,
                    saturday_date   : $saturday.attr('date'),
                    sunday_img      : $sunday.text() === 'true'      ? buttons.sunday : buttons.sunday_gray,
                    sunday_date     : $sunday.attr('date')
                };
        },
        /**
         * Toggles the appearance of the 'no data' layer
         *
         */
        toggleNoDataLayer: function () {
            var selectors = this.selectors,
                $layer = $(selectors.noDataLayer);
            // hide other layers
            this.hideLayers([this.selectors.loadingLayer, this.selectors.errorLayer, this.selectors.stationLoadingLayer]);
            // toggle layer visibility
            if ($layer.is(':visible') === true) {
                $layer.hide();
            } else {
                // position layer
                this.positionLayer($layer);
                $layer.show();
            }
        },
        /**
         * Toggles the appearance of the 'loading' layer
         *
         */
        toggleLoadingLayer: function () {
            var selectors = this.selectors,
                $layer = $(selectors.loadingLayer);
            // hide other layers
            this.hideLayers([this.selectors.noDataLayer, this.selectors.errorLayer, this.selectors.stationLoadingLayer]);
            // toggle layer visibility
            if ($layer.is(':visible') === true) {
                $layer.hide();
            } else {
                // position layer
                this.positionLayer($layer);
                $layer.show();
            }
        },
        /**
         * Toggles the appearance of the 'station laoding' layer
         *
         */
        toggleStationLoadingLayer: function () {
            var selectors = this.selectors,
                $layer = $(selectors.stationLoadingLayer);
            // hide other layers
            this.hideLayers([this.selectors.noDataLayer, this.selectors.errorLayer, this.selectors.loadingLayer]);
            // toggle layer visibility
            if ($layer.is(':visible') === true) {
                $layer.hide();
            } else {
                // position layer
                this.positionLayer($layer);
                $layer.show();
            }
        },
        /**
         * Toggles the appearance of the 'error' layer
         *
         */
        toggleErrorLayer: function () {
            var selectors = this.selectors,
                $layer = $(selectors.errorLayer);
            // hide other layers
            this.hideLayers([this.selectors.noDataLayer, this.selectors.stationLoadingLayer, this.selectors.loadingLayer]);
            // toggle layer visibility
            if ($layer.is(':visible') === true) {
                $layer.hide();
            } else {
                // position layer
                this.positionLayer($layer);
                $layer.show();
            }
        },
        /**
         * Helper function for positioning
         * the 'no data', 'loading' and error layers
         *
         * @param {{Object}} $element jQueryfied layer element
         */
        positionLayer: function ($element) {
            var $container = $(this.selectors.tableContainer),
                container_position = $container.position();
            $element.css('left', container_position.left + ( ( $container.width() - $element.width() ) / 2 ) );
            $element.css('top', container_position.top + ( ( $container.height() - $element.height() ) / 2 ) );
        },
        /**
         * Helper function for hiding
         * layers via selectors
         *
         * @param {{Array}} layers The selectors from the layers to be hidden
         * @return {{Object}} FlightPlan Flightplan instance
         */
        hideLayers: function (layers) {
            // hide layers
            $.each(layers, function (idx, layer) {
                $(layer).hide();
            });
            return this;
        },
        /**
         * Loads all flightplan stations
         * 
         * @param {{String}} culture Culture code (format 'de-de')
         * @return {{Object}} jXHR jQuery XHR Object
         */
        getAllFlightPlanStationsByCulture: function (culture) {
            return $.ajax({dataType: 'xml', data: {type: 'origin', culture: culture}, context: this, url: this.flightStationUrl});
        },
        /**
         * Loads all departure stations
         * based on the origin station
         *
         * @param {{String}} station Departure station as three letter code, to load the destinations for
         * @param {{String}} culture Culture code (format 'de-de')
         * @return {{Object}} jXHR jQuery XHR Object
         */
        getFlightPlanStationsByTypeStationAndCulture: function (station, culture) {
            return $.ajax({dataType: 'xml', data: {type: 'destination', affiliatestation: station, culture: culture}, context: this, url: this.flightStationUrl});
        },
        /**
         * Loads all departure stations
         * based on the origin station
         *
         * @param {{String}} origin Departure station as three letter code, to load the flights for
         * @param {{String}} destination Arrival station as three letter code, to load the flights for
         * @param {{String}} date Date range to look for flights (format '06.06.2011,12.06.2011')
         * @param {{String}} culture Culture code (format 'de-de')
         * @return {{Object}} jXHR jQuery XHR Object
         */
        getFlightDataByOriginDestinationDateAndCulture: function (origin, destination, date, culture) {
            return $.ajax({dataType: 'xml', data: {calendarweek: date, origin: origin, destination: destination, culture: culture}, context: this, url: this.flightPlanUrl});
        },
        /**
         * Parses the culture, so it can be used
         * in ajax requests
         *
         * @param {{String}} culture Culture code (format 'de' or 'DE')
         * @return {{String}} culture_code (format 'de-de')
         */
        parseCulture: function (culture) {
            var first = culture.toLowerCase().replace('2', ''),
                last = culture.toLowerCase().replace('2', '');
            // nifty hack for english visitors
            if(first === 'en') {
                last = 'gb';
            };
            return first + '-' + last;
        },
        /**
         * Generates an lowfare calendar link, based on the
         * given arrival station, departure station, date and culture
         * also gets the correct low fare image for the culture
         *
         * @param {{String}} departure Departure station as three letter code, to load the flights for
         * @param {{String}} arrival Arrival station as three letter code, to load the flights for
         * @param {{String}} date Date range to look for flights (format '06.06.2011,12.06.2011')
         * @param {{String}} culture Culture code (format 'de-de')
         * @return {{String}} link Low fare calendar link
         */
        getLowfareCalendarLinkByDepartureArrivalDateAndCulture: function (departure, arrival, date, culture) {
            var monthly_date = date.split('.'),
                arr = departure.toUpperCase(),
                dep = arrival.toUpperCase(),
                link = '',
                query = '?DEPSTA=' + dep + '&amp;ARRVSTA=' + arr + '&amp;FLIGHTDATE=';
            // build monthly date
            query += monthly_date[1] + '-20' + monthly_date[2];
            // switch links based on culture
            switch (culture) {
                case 'en-gb':
                    link = '/en/lowfarecalendar.html';
                    break;
                case 'es-es':
                    link = '/es/calendario-de-ahorro.html';
                    break;
                case 'fr-fr':
                    link = '/fr/calendrier-des-offres.html';
                    break;
                case 'it-it':
                    link = '/it/calendario-delle-offerte.html';
                    break;
                case 'nl-nl':
                    link = '/nl/spaarkalender.html';
                    break;
                case 'ru-ru':
                    link = '/ru/lowfarecalendar-ru.html';
                    break;
                default:
                    link = '/de/sparkalender.html';
                    break;
            }
            return link + query;
        },
        /**
         * Returns the three letter code for the
         * current selected origin airport
         *
         * @return {{String}} origin_tls Selected origin three letter code
         */
        getSelectedOriginAirportTlc: function () {
            return this.$originSelect.val();
        },
        /**
         * Returns the three letter code for the
         * current selected destination airport
         *
         * @return {{String}} destination_tls Selected destination three letter code
         */
        getSelectedDepartureAirportTlc: function () {
            return this.$destinationSelect.val();
        },
        /**
         * Returns the current selected date period
         *
         * @return {{String}} date_period Selected date period
         */
        getSelectedDatePeriod: function () {
            return this.$calendarSelect.val();
        }
    };
    // check for namespace, else create
    if(window.GW === undef) {
        window.GW = {};
    }
    // export module
    window.GW.FlightPlan = FlightPlan;
    
    // init this shizzle
    $(document).ready(function () {
        var FlightPlan = window.GW.FlightPlan,
            $tableTemplate = $(FlightPlan.selectors.tableTemplate),
            $baseTemplate = $tableTemplate.find('thead').first().find('th'),
            $period = $tableTemplate.find('tbody').first().find('.period'),
            $period_anchor = $period.find('a'),
            culture = 'de',
            tableDesc = {};
        
        // load page culture
        $('meta').each(function(idx, element) {
            var $element = $(element);
            if($element.attr('name') == 'language') {
                culture = $element.attr('content');
            }
        });
        // get the table descriptions
        $.each(['route', 'week', 'from', 'to', 'flightNumber'], function (idx, param) {
            tableDesc[param] = $($baseTemplate[idx]).text();
        });
        // add the language based period vocable and arrow images
        tableDesc.period = $.trim($period.find('div').text().replace('dd.mm.yy', '').replace('dd.mm.yy', ''));
        tableDesc.arrowRight = $period_anchor.first().find('img').attr('src');
        tableDesc.arrowLeft = $period_anchor.last().find('img').attr('src');
        tableDesc.arrowDesc = $tableTemplate.find('.fp_origindestination').find('img').first().attr('src');
        tableDesc.lowFareImage = $tableTemplate.find('.fp_lowfarecalendar').find('img').first().attr('src');
        // init the flightplan with the current page culture
        FlightPlan.init(culture, {
            monday:         window.fp_schedule_mon,      
            monday_gray:    window.fp_schedule_mon_gray,
            tuesday:        window.fp_schedule_tue,    
            tuesday_gray:   window.fp_schedule_tue_gray, 
            wednesday:      window.fp_schedule_wed,     
            wednesday_gray: window.fp_schedule_wed_gray, 
            thursday:       window.fp_schedule_thu,    
            thursday_gray:  window.fp_schedule_thu_gray, 
            friday:         window.fp_schedule_fri, 
            friday_gray:    window.fp_schedule_fri_gray,
            saturday:       window.fp_schedule_sat,      
            saturday_gray:  window.fp_schedule_sat_gray, 
            sunday:         window.fp_schedule_sun,   
            sunday_gray:    window.fp_schedule_sun_gray 
        }, tableDesc);
        
    });
    // This function is not useless, we need it for BC
    // Selects data from teasers
    // Triggered inline when a special teaser link is defined wihtin the cms
    window.fp_teaser_update = function (culture, origin, destination, date) {
       // get current origin value
       var selectors = window.GW.FlightPlan.selectors,
           needToWaitForData = $(selectors.originSelect).val() === origin ? false : true,
           isTriggered = false;
       // set the select options
       $(selectors.originSelect).val(origin).trigger('change');
       $(selectors.calendarWeekSelect).val(date);
       // check if we need to wait for the destination data to be loaded
       if(needToWaitForData === true) {
           // subscribe to the data load event
           window.subscribe('/xml/destination/loaded', function () {
               // check if the change has already been triggered, else execute
                if(isTriggered === false) {
                    $(selectors.destinationSelect).val(destination).trigger('change');
                    $(selectors.showFlightsButton).trigger('click');
                }
                // set already triggered flag
                isTriggered = true;
           });
       } else {
            // else:  trigger change directly
            $(selectors.destinationSelect).val(destination).trigger('change');
            $(selectors.showFlightsButton).trigger('click');
            // set already triggered flag
            isTriggered = true;
       }
       return false;
    }
    /**
     * List of useless old functions
     * (We need to override them cause we don´t want a BC break)
     */
    $.each(['fp_position_element', 'station_update', 'stations_update', 'stations_origin_change', 'stations_destination_change', 'fp_update'], function (idx, method) {
        window[method] = $.noop;
    });
}(this, this.document, this.jQuery, this.Mustache));
