/**
 * Functional object for updating departures, pricing, availability,
 * rooming, and promotional information for a visitor to choose from
 * before starting the booking process.
 */
var booking_form = new function() 
{
    var self = this;
    var is_initialized = false;
    var currency_code, currency_symbol, dossier_code, trip_year, trip_id, logger, date_pricing_tab_loaded;
    var $departure_price, $dates_pricing_tab, $available_spaces, $submit_button, $departure, $pricing, $loading, $calendar;
    var departures_by_date, departures_by_id, selected_departure, selected_start_date, selected_end_date;
    var first_available_date, last_available_date;
    var highlight_departure, highlight_start_date, highlight_end_date = null;
    var spinning_wheel, loading;
    var ISO_FORMAT = 'yyyy-MM-dd';
    // Only one live request is active at any point in time so 
    // we don't get conflicting responses.
    var live_request = null;

    // For trips with multiple rooms on the same departure date, we need
    // to keep track of the user's preferred room, because when they select
    // a date from the calendar the room would normally default to the first
    // in the data.
    var preferred_room_code;

    // Settings may be overridden by passing an object with 
    // the same identifiers into the init function.
    var settings = {
        'url_dates_pricing_html': "/trips/trip/dates_and_pricing_cached/",
        'url_hotels_transfers_html': "/trips/trip/hotels-transfers/",
        'url_departure_json': "/trips/trip/departure-json/",
        'url_live_departure_json': "/trips/trip/live-departure-json/",
        'url_departures_json': "/trips/trip/departures-json/",
        'debug': false,
        'use_calendar': true,
        'img_spinning_wheel': MEDIA_URL + "static/images/small-loading.gif",
        'show_promo_badge': false
    }

    /**
     * Sets up all of the navigation menu behaviour mostly 
     * to support the subnavigation in a fancy way. There
     * is pure css fallback support in case javascript is
     * not available.
     */
    this.init = function(options)
    {
        if (options) {
            $.extend(settings, options);
        }

        logger = typeof(console) == 'undefined' || !settings.debug ? { error: function(){}, info: function(){}, log: function(){}} : console;
        logger.log('booking_form.init()');

        // guard against calling twice
        if(is_initialized) return;
        is_initialized = true;
     
        // These don't change after the page has loaded
        currency_code = $("#pricing_currency_code").val() || 'cad';
        currency_code = currency_code.toLowerCase();
        currency_symbol = $("#pricing_currency_symbol").val();
        dossier_code = $('#id_dossier_code').val();
        trip_id = $("#id_trip_id").val();
        trip_year = $('#id_year').val();
 
        // prepopulate some jQuery objects for quick reference
        $pricing = $("#pricing");
        $departure_price = $("#departure_price");
        $dates_pricing_tab = $("#dates_and_pricing_list");
        $available_spaces = $("#available_spaces");
        $submit_button = $("#book_now_button");
        $departure = $("#id_departure_id");

        spinning_wheel = ['<img src="',settings.img_spinning_wheel,'" />'].join(''); 
        loading = '<div id="loading">'+spinning_wheel+'</div>';
        $(loading).css({
            'position':'absolute', 
            'top':$pricing.height()/2, 
            'width':$pricing.width(), 
            'text-align':'center'}).insertBefore("#pricing");
        $loading = $("#loading");
        $loading.hide();

        if($("#no-departure-message").length == 0) {
            self.load_departures();
            self.bind_handlers();
        }

    };

    /**
     * For indicating that the pricing box is loading new
     * information.
     */
    this.loading = function(loading, doOverlay)
    {

        // At times, we may wish to disable the submit button while not showing any loading
        // visuals. One can pass 'false' to achieve this.
        doOverlay = (typeof doOverlay == "undefined") ? true : doOverlay;

        if(loading) {
            if (doOverlay) {
                $pricing.fadeTo(100, 0.5);
                $loading.show();
            }
            $submit_button.attr('disabled', 'disabled');
            $submit_button.val("Loading..");
        } else {
            if (doOverlay) {
                $pricing.fadeTo(100, 1);
                $loading.hide();
            }
            $submit_button.removeAttr('disabled');
            $submit_button.val("Book Now");
        }
    }

    /**
     * Fetch all departure data for the current trip. This data is
     * used to provide the UI for selecting a departure in the pricing
     * box. 
     */
    this.load_departures = function()
    {
        self.loading(true);
        logger.info('booking_form.load_departures');
        $.ajax({
            url: settings.url_departures_json, 
            dataType: 'json',
            data: { 'trip': trip_id },
            success: function(departures) {
                // Organize the departure data into some nicer
                // data structures.
                departures_by_date = {};
                departures_by_id = {};

                if(departures.length) {
                    first_available_date = departures[0]['start_date'];
                    last_available_date = departures[departures.length-1]['start_date'];
                    logger.info(first_available_date, last_available_date);
                }

                for(var d in departures) {
                    var dep = departures[d];
                    var start_date = dep['start_date'];
                    departures_by_id[dep['id']] = dep;
                    if(!departures_by_date[start_date])
                    {
                        departures_by_date[start_date] = dep;
                    } 
                    // More than one departure on the same date,
                    // so we'll store the departures as an array.
                    // (probably multiple rooms available)
                    else {
                        if(!self.is_array(departures_by_date[start_date])) {
                            departures_by_date[start_date] = [departures_by_date[start_date]]; 
                        }
                        departures_by_date[start_date].push(dep);
                    }
                }
                self.update_selected_departure();
                if(settings.use_calendar) {
                    self.load_calendar();
                }
                self.loading(false);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                logger.error('Error getting /trips/departures');
                logger.error(textStatus);
            }
        });      
    };

    /**
     * Changing input values/selections requrires and updated
     * price and availability.
     */
    this.bind_handlers = function()
    {
        logger.info('booking_form.bind_event_handlers');

        $("#id_flight_city").change(function(){
            var selected = $("#id_flight_city option:selected");
            self.update_departure_display();
        });

        $departure.change(self.departure_dropdown_onchange);

        // dates & pricing tab isn't visible until clicked,
        // so we don't fetch the content until it's needed.
        $("#dates-pricing-tab").click(function(){
            if(!date_pricing_tab_loaded) {  
                self.update_dates_pricing_tab();
                date_pricing_tab_loaded = true;
            }
        });

        $("#hotels_and_transfers").click(function(e){
            e.preventDefault();
            self.view_hotels_and_transfers_info();
        });
    };

    /**
     * The selected departure is updated by the calendar
     * or departure dropdown.
     */
    this.get_selected_start_date = function()
    {
        if(!selected_departure || !selected_start_date) {
            logger.error('No selected departure');
            return null;
        }
        return selected_start_date;
    };

    /**
     * If the departure dropdown changes, the calendar
     * needs to be notified.
     */
    this.departure_dropdown_onchange = function()
    {
        self.update_selected_departure();
    };

    /**
     * Updates selected_departure based on the departure
     * chosen in the departures dropdown.
     */
    this.update_selected_departure = function()
    {
        logger.info('booking_form.update_selected_departure');
        var id = self.get_departure_id();
        var dep = self.get_departure_by_id(id);
        self.set_selected_departure(dep, 'dropdown');
    };

    /**
     * Set the calendar date to the currently selected
     * departure start date.
     */
    this.update_calendar_date = function()
    {
        if(!$calendar.length) {
            logger.info('Calendar not loaded');
            return;
        }
        $calendar.setDate(self.get_selected_start_date());
    };

    /**
     * Get departure information for a given date. If all_departures
     * is true, then return all departures for that date, otherwise
     * just return the first one.
     */
    this.get_departure_by_date = function(date, all_departures)
    {
        var dep = departures_by_date[date.toString(ISO_FORMAT)]; 
        if(all_departures || !self.is_array(dep)) {
            return dep;
        }

        if(preferred_room_code) {
            for(var d in dep) {
                if(dep[d].room && dep[d].room.code == preferred_room_code) {
                    return dep[d];
                }
            }
        }
        return dep[0];
    };
 
    /**
     * Used for getting the departure data of a departure
     * selected in the dropdown.
     */
    this.get_departure_by_id = function(id)
    {
        if(!departures_by_id) {
            logger.error('departures_by_id is not set');
            return null;
        }
        var dep = departures_by_id[id];
        if(!dep) {
            logger.error('No departure found for id', id);
            return null;
        }
        return dep;
    };

    /**
     * Utility for determining if an object is an array. This is used
     * for detecting if there are multiple departures for one date
     * in the departures_by_date object.
     */
    this.is_array = function(obj)
    {
        return obj && !(obj.propertyIsEnumerable('length')) && typeof obj === 'object' && typeof obj.length === 'number';
    };

    /**
     * Load the javascript calendar using the departures data
     * returned in load_departures.
     */
    this.load_calendar = function()
    {

        logger.info('booking_form.load_calendar');
        $calendar = $('#departure-calendar');
        $calendar.DatePicker({
            flat: true,
            date: [], 
            current: selected_start_date,
            calendars: 2,
            mode: 'single',
            starts: 0, // start day of each week 0:Sunday, 1:Monday ...
            onRender: self.calendar_onrender,
            onChange: self.calendar_onchange,
            onMouseEnter: self.calendar_onmouseenter,
            onMouseOut: function(formatted, date) {
                highlight_departure = null;
            }
        });  
        this.$calendar = $calendar;
        // Move the months over by one. By default, when showing
        // multiple months and selecting a date, the first month
        // won't have any departures.
        $(".datepickerGoNext a", $calendar).click();
    };

    /**
     * This callback is called for each date displayed in the calendar, which allows 
     * customization of the display. We use it to define which dates are highlighted
     * when hovering over a departure date, which date is selected, and which dates
     * are disabled (no departure).
    */
    this.calendar_onrender = function(date) 
    {
        var className = [];
        var is_selected = !highlight_departure && date.compareTo(selected_start_date) == 0;
        var departure = self.get_departure_by_date(date);
        if(departure) {
            if(departure.is_discounted || departure.promotion_details) {
                className.push("hasPromo");
            }
            // for availability shading/colouring
            if(departure.availability == 0) {
                className.push("availZero");
            } else if(departure.availability > 0 && departure.availability <= 4) {
                className.push("availLow");
            } else if(departure.availability > 4 && departure.availabilty < 7) {
                className.push("availMed");
            } else if(departure.availability > 7) {
                className.push("availHigh");
            }
        }
        if(highlight_departure && date.compareTo(highlight_start_date) >= 0 && date.compareTo(highlight_end_date) <= 0) {
            className.push("tripDate");
            if(date.compareTo(highlight_start_date) == 0) {
                className.push("startDate");
            } else if(date.compareTo(highlight_end_date) == 0) {
                className.push("endDate");
            }
        } else if(selected_departure && date.compareTo(selected_start_date) >= 0 && date.compareTo(selected_end_date) <= 0) {
            className.push("selTripDate");
            if(date.compareTo(selected_start_date) == 0) {
                className.push("selStartDate");
            } else if(date.compareTo(selected_end_date) == 0) {
                className.push("selEndDate");
            }
        }                            
        return {
            disabled: departure ? false : true,
            className: className.join(' '),
            selected: is_selected
        }     
    };

    /**
     * Need to change the selected_departure whenever a calendar
     * date is clicked, which in turn updates the displayed information.
     */
    this.calendar_onchange = function(formatted, date) 
    {
        var dep = self.get_departure_by_date(date);
        self.set_selected_departure(dep, 'calendar');

        $(document).trigger("booking_form_calendar_change");
    };

    /**
     * Hovering over a calendar date highlights the duration of the trip. The
     * highlighted_* variables are detected in the calendar onRender event
     * handler. Hovering over an active date triggers re-rendering of the calendar.
     */
    this.calendar_onmouseenter = function(formatted, date)
    {
        highlight_departure = self.get_departure_by_date(date);
        highlight_start_date = Date.parse(highlight_departure['start_date']);
        highlight_end_date = Date.parse(highlight_departure['end_date']);
        logger.info(highlight_departure, highlight_start_date, highlight_end_date);
    };

    /*
     * Give an external application the currently selected departure.
     */
    this.get_selected_departure = function()
    {
        return selected_departure;
    };

    /**
     * The selected departure can be updated from either the calendar
     * or the dropdown. To keep them in sync, this method must be called
     * to set the selected departure. The sender param is a string of either 
     * 'calendar' or 'dropdown' so the proper UI element can be synchronized. 
     * We also pre-parse some of the departure information
     * (dates) for the selected departure for faster access elsewhere.
     */
    this.set_selected_departure = function(departure, sender)
    {
        logger.info('booking_form.set_selected_departure');
        selected_departure = departure;
        selected_start_date = Date.parse(departure['start_date']);
        selected_end_date = Date.parse(departure['end_date']);
        if(sender == 'calendar') {
            self.set_dropdown_departure(selected_departure.id); 
        } else if(sender == 'dropdown') {
            if($calendar && $calendar.length) {
                $calendar.DatePickerSetDate(selected_start_date, true);
            }
        }

        self.update_departure_display();
        self.request_live_departure();
    };

    /**
     * Clear existing error messages
     */
    this.clear_messages = function()
    {
        $("#departures dd .error").remove(); 
    };

    /**
     * Used by the calendar onChange handler to set the 
     * selected departure.
     */
    this.set_dropdown_departure = function(departure_id)
    {
        logger.info('booking_form.set_dropdown_departure', departure_id);
        $departure.val(departure_id);
    };

    /**
     * Return an amount with price reduction
     */
    this.get_amount_with_discount = function(amount, discount)
    {
        logger.info('get_amount_with_discount');
        if (discount > 0){
            amount = amount - (amount * discount/100);
        }
        return amount;
    };
         
    /**
     * For making price update requests.
     */
    this.get_departure_id = function()
    {
        if($('input[name=departure_id]:checked').length) {
            return $('input[name=departure_id]:checked').val();
        } else {
            return $('select[name=departure_id]').val();
        }
    }

    /**
     * Generally used when an ajax call returns an error message
     */
    this.display_error = function(message)
    {
        $submit_button.hide();
        var $error = $("<p class='error'>" + message + "</p>").prependTo("#departures dd");
    };

    /**
     * Flight ID is used on the ACV site and must be used when
     * requesting departure information to get the correct pricing.
     */
    this.get_flight_city = function()
    {
        return $('#id_flight_city').val();
    };
        
    /**
     * Attempts to update the departure information from the reservation
     * system's data and returns the corresponding departure json. This
     * data subsequently replaces the client-side cache of the departure
     * which is displayed.
     */
    this.request_live_departure = function()
    {
        var doOverlay = false;
        logger.info('booking_form.request_live_departure');

        // The departure to get the information for
        var departure_id = self.get_departure_id();
        if (departure_id == undefined){ 
            logger.error('No departure id found.');
            return false;
        }

        // Get the departure information through AJAX
        if(live_request) {
            logger.info('Previous live request aborted.');
            live_request.abort();
        }

        this.loading(true, doOverlay);

        live_request = $.ajax({
            url: settings.url_live_departure_json, 
            dataType: 'json',
            data: {
                'id': departure_id,
                'currency':currency_code
            }, 
            success: function(data){
                self.loading(false);

                live_request = null;
                if(data.error) {
                    self.display_error(data.error.message);
                } else {
                    $("#departures dd .error").hide();
                    self.update_departure_data(data);
                    self.update_departure_display();
                }

                $(document).trigger('booking_form_done_request');
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                self.loading(false);
                logger.error('Error getting /trips/departure');
            },
            timeout: 4000
        });
    };

    /**
     * When 'live' data is retrieved, we want to replace the locally stored
     * copies of it in departures_by_id and departures_by_date.
     */
    this.update_departure_data = function(data)
    {
        logger.info('booking_form.update_departure_data');
        departures_by_id[data.id] = data;
        if(self.is_array(departures_by_date[data.start_date])) {
            for(var d in departures_by_date[data.start_date]) {
                if(departures_by_date[data.start_date][d].id == data.id) {
                    departures_by_date[data.start_date][d] = data;
                }
            }
        } else {
            departures_by_date[data.start_date] = data;
        }           

        selected_departure = data;
        selected_start_date = Date.parse(data['start_date']);
        selected_end_date = Date.parse(data['end_date']);
    };

    /**
     * Using the current selected_departure, update the fields and
     * pricing displayed. This happens any time the departure date
     * changes.
     */
    this.update_departure_display = function(override_departure)
    {
        logger.info('booking_form.update_departure_display');
        var d = override_departure || selected_departure;
        var pricing = ""; 
        var flight_html = "";
        var acv_asterik = "";
        var is_discounted = false;

        self.clear_messages();

        $("#start-date").text(d.start_date_label);
        $("#finish-date").text(d.end_date_label);

        $submit_button.val('Book Now');
        if (d.availability_message != 'Sold Out'){
            $submit_button.show();
        }
        if(d.availability_status == 'Wait List') {
            $submit_button.val('Wait List');
        }             

        // Get the discounted info
        if(d.discount_message){
            $("#promotional_message").html(d.discount_message);
            $("#promotional_details").show();
        } else {
            $("#promotional_details").hide();
        }

        // Get the general info for departure
        $available_spaces.html(d.availability_message);
        $("#inca_trail_status").html(d.inca_trail_status);
        $("#departure_id_map").val(d.id);

        // Promotional Message
        if(d.promotion_details)
        {
            $("#promotional_message").html(d.promotion_details.message);
            $("#promotional_details").show();
            if (d.promotion_details.badge_url && settings.show_promo_badge){
                $("#promotional_badge").attr('src', d.promotion_details.badge_url);
                $("#promotional_badge").show();
            }else{
                $("#promotional_badge").hide();
            }
        }


        // My Own Room (e.g. ACHC)
        if(d.my_own_room) {
            $('#my_own_room').show();
            $('#my_own_room_info').show();
            $('#my_own_room_price').html([currency_symbol, d.my_own_room.price, " ", currency_code.toUpperCase()].join(''));
        } else {
            $('#my_own_room').hide();
            $('#my_own_room_info').hide();
        }

        if(d.room) {
            preferred_room_code = d.room.code;
        }
        
        // Build the price display
        for(var i=0; i<d.price_set.length; i++)
        {
            var cssClass = 'pricing-micro';
            var price_label = "";
            var price = d.price_set[i];
            is_discounted = is_discounted || (typeof(price.is_discounted) != 'undefined' && true == price.is_discounted);

            var amount = parseFloat(price.price).toFixed(0);
            var discount_label = is_discounted ? ["<br/>&nbsp;Was ", currency_symbol, parseFloat(price.price_previous).toFixed(0)].join('') : "";

            if(price.is_dominant) {
                cssClass = 'dominant';
            }

            // Kind of lame, but adding the cssClass after the markup is created caused float
            // issues. Thus, we check for it here.
            if (price.has_flights) {
                cssClass += ' has_flights';
            }

            // TODO: calculate total price with flight server side
            // This is for the AirCanada website, and is not related to the milehigh project.
            if (d.flight_prices)
            {
                var flight_price = d.flight_prices[self.get_flight_city()]
                acv_asterik = "*";
                amount = parseFloat(price.price) + parseFloat(flight_price);
                amount = parseFloat(amount).toFixed(2);
                $("#flight-include").show();
                $("#flight-exclude").hide();

                // Checks if the acv_pricing_box functions exists, otherwise fails gracefully
                if (typeof(acv_pricing_box) != "undefined") {
                    flight_html = acv_pricing_box(self.get_flight_city());
                }
            } else {
                price_label = ["&nbsp;<span class='micro break'>(<abbr title='Per Person'>pp</abbr> for ", price['label'], ")", discount_label, "</span>"].join('');
                $("#flight-include").hide();
                $("#flight-exclude").show();
            }

            pricing += ["<dt class='", cssClass, "'>",
                            "<sup class='micro currency'>", currency_symbol, "</sup>",
                            "<span class='price'>", amount, "</span>",
                            "<sup class='micro'>", currency_code.toUpperCase(), acv_asterik, "</sup>",
                            price_label,
                            flight_html,
                        "</dt>"
                       ].join('');
        }

        $pricing.toggleClass('has_promotion', is_discounted);
        $departure_price.html(pricing);

        // Append local payments below the price.
        // (e.g. SEPY)
        if(d.local_payments && d.local_payments.length) {
            var local_payments = '';
            var first_class = ' local-payment-first';
            for(var p in d.local_payments) {
                payment = d.local_payments[p];
                local_payments += ["<dt class='local-payment", first_class, "'>+<span>", Math.ceil(payment['amount']), " ", payment['currency'], "</span> (", payment['label'], ")</dt>"].join('');
                first_class = '';
            }
            $departure_price.append(local_payments);
        }      
    };

    /**
     * This is for the Dates & Pricing tab displayed outside of the
     * booking form, currently below the trip summary in a tab UI.
     */
    this.update_dates_pricing_tab = function()
    {
        logger.info('booking_form.update_dates_pricing_tab');
        $dates_pricing_tab.html(spinning_wheel);
        $dates_pricing_tab.load(
            settings.url_dates_pricing_html, 
            { "trip":trip_id },
            function() {
                $room_links = $("#room_links");
                if($room_links) {
                    $room_links.find('a').each(function(){
                        $(this).click(function(){
                            $('#dates_and_pricing_list form').hide();
                            $('form.'+$(this).attr('rel')).show();
                            return false;
                        });
                    });
                }
            }
        );
    };

    /**
     * Display hotel and arrival transfer pricing in an overlay (facebox)
     */
    this.view_hotels_and_transfers_info = function()
    {
        logger.info('booking_form.view_hotels_and_transfers_info');
        var parameters   = { 
            'id': self.get_departure_id(), 
            'currency': currency_code
        };

        $.get(settings.url_hotels_transfers_html, parameters,
            function(data){
                $.facebox(data);
            }
        );
    };
 

}; // end booking_form
 
 
// Kick off!
$(document).ready(function(){
    booking_form.init();

    if ($("#air-from").length > 0) {
        var airFrom = new AirFromDisplay();
    }

    var flightPricing = new FlightPricing();
});

