function padDatePart(date_part) {
    return (date_part < 0 || date_part > 9 ? '' : '0') + date_part;
}



if (typeof(Function.prototype.bind) == 'undefined') {
    Function.prototype.bind = function(obj) {
        var applied_method = this, applied_args = arguments, apply_fn = function() { return applied_method.apply(obj, applied_args); };
        return apply_fn;
    }
}



function Dropdown_Datepicker(id, calendar_location_id, date_range, long_day_names) {

    this.id                    = id;
    this.value_element         = document.getElementById(id);
    this.day_string_element    = document.getElementById(id + '_day_string');
    this.year_element          = document.getElementById(id + '_year');
    this.month_element         = document.getElementById(id + '_month');
    this.day_element           = document.getElementById(id + '_day');
    this.hour_element          = false;
    this.minute_element        = false;
    this.meridian_element      = false;
    this.days_diff_element     = document.getElementById(id + '_days_diff');
    this.calendar              = null;
    this.current_date          = null;
    this.calendar_location_id  = calendar_location_id;
    this.date_range            = date_range;
    this.long_day_names        = long_day_names ? true : false;
    this.time_component        = (arguments.length > 4 ? arguments[4] : 0);

    this.year_element.ddp_id  = id;
    this.month_element.ddp_id = id;
    this.day_element.ddp_id   = id;

    this.onupdate = null;

    if (typeof this.date_range == 'undefined' || this.date_range == null) {
        this.date_range = { from: { y: 1900, m: 1, d: 1, h: 1, i: 0, s: 0 }, to: { y: 2099, m: 12, d: 31, h: 23, i: 23, s: 59 } }
    }

    var update_function = function() { Dropdown_Datepicker.updateFromDropdownChangeObject(this); };

    this.year_element.onchange  = update_function;
    this.month_element.onchange = update_function;
    this.day_element.onchange   = update_function;

    if (this.time_component > 0) {
        this.hour_element   = document.getElementById(id + '_hour');
        this.minute_element = document.getElementById(id + '_minute');

        this.hour_element.ddp_id   = id;
        this.minute_element.ddp_id = id;

        this.hour_element.onchange   = update_function;
        this.minute_element.onchange = update_function;

        if (this.time_component == 1 && document.getElementById(id + '_meridian')) {
            this.meridian_element = document.getElementById(id + '_meridian');
            this.meridian_element.ddp_id   = id;
            this.meridian_element.onchange = update_function;
        }
    }

    // :TODO: support (initialise) limited ranges on years/months/days

    this.updateFromDropdownChange();
}



Dropdown_Datepicker.initOnLoad = function(ddp_id, date_range, long_day_string, show_time) {
    var load_init_fn = function() { window[ddp_id + '_dropdown_datepicker'] = new Dropdown_Datepicker(ddp_id, ddp_id + '_image', date_range, long_day_string, show_time); };
    //event_handler.addMethod(ONLOAD, load_init_fn); //no longer required, as loop calling this checks if datepicker is loaded
    load_init_fn();
}



Dropdown_Datepicker.updateFromDropdownChangeObject = function(obj) {
    if (typeof(obj.ddp_id) == 'string' && obj.ddp_id != '' && typeof(window[obj.ddp_id + '_dropdown_datepicker']) == 'object' && window[obj.ddp_id + '_dropdown_datepicker'].updateFromDropdownChange) {
        window[obj.ddp_id + '_dropdown_datepicker'].updateFromDropdownChange();
    }
}



Dropdown_Datepicker.prototype.updateFromDropdownChange = function(no_call_to_update_available_days) {
    var year = parseInt(this.year_element.options[this.year_element.selectedIndex].value);
    var month = parseInt(this.month_element.options[this.month_element.selectedIndex].value);
    var day = parseInt(this.day_element.options[this.day_element.selectedIndex].value);
    var hour = (this.time_component > 0 ? parseInt(this.hour_element.options[this.hour_element.selectedIndex].value) : 0);
    var minute = (this.time_component > 0 ? parseInt(this.minute_element.options[this.minute_element.selectedIndex].value) : 0);
    var meridian = (this.time_component == 1 && this.meridian_element ? parseInt(this.meridian_element.options[this.meridian_element.selectedIndex].value) : 0);

    if (year == -1 || month == -1 || day == -1 || hour == -1 || minute == -1 || meridian == -1) {
        var d = 'Never';
    } else {
        if (this.time_component == 1 && this.meridian_element) {
            // Convert 12-hour time parts to 24-hour (12Nm = 0, then Npm = N + 12)
            hour = (hour == 12 ? 0 : hour);
            hour += (meridian == 2 ? 12 : 0);
        }

        var d = new Date(year, month - 1, day, hour, minute, 0, 0);
    }

    this.updateDate(d, true, no_call_to_update_available_days);
}



Dropdown_Datepicker.prototype.updateAvailableDays = function() {

    var selected_index = this.day_element.selectedIndex;
    var day   = parseInt(this.day_element.options[this.day_element.selectedIndex].value, 10);
    var month = parseInt(this.month_element.options[this.month_element.selectedIndex].value, 10);
    var year  = parseInt(this.year_element.options[this.year_element.selectedIndex].value, 10);
    var first_day = 1;
    var last_day = 31;
    var first_month = 1;
    var last_month = 12;

    // Restrict available months & days to the date range if within a year
    if (this.date_range.from.y == this.date_range.to.y) {
        first_month = this.date_range.from.m;
        last_month = this.date_range.to.m;
        if (this.date_range.from.m == this.date_range.to.m) {
            first_day = this.date_range.from.d;
            last_day = this.date_range.to.d;
        }
    }

    // Make adjustments for the date range for the selected year
    if (year == this.date_range.from.y) {
        first_month = this.date_range.from.m;
    }
    if (year == this.date_range.to.y) {
        last_month = this.date_range.to.m;
    }
    // Shift the currently selected month to within the available range
    if (month > 0) {
        month = Math.max(Math.min(month, last_month), first_month);
    }

    // Make adjustments for the date range for the selected year & month
    if (year == this.date_range.from.y && month == this.date_range.from.m) {
        first_day = this.date_range.from.d;
    }
    if (year == this.date_range.to.y && month == this.date_range.to.m) {
        last_day = this.date_range.to.d;
    }
    // Restirct the available days further, according to the days in the month
    var days_in_month = 31;
    if (month == 2) {
        days_in_month = (new Date(year, 1, 29).getDate() == 29 ? 29 : 28);
    } else if (month == 4 || month == 6 || month == 9 || month == 11) {
        days_in_month = 30;
    }
    if (last_day > days_in_month) {
        last_day = days_in_month;
    }
    // Shift the currently selected day to within the available range
    if (day > 0) {
        day = Math.max(Math.min(day, last_day), first_day);
    }

    // Build up the new day & month options
    this.day_element.options.length = (this.day_element.options[0].value == '-1') ? 1 : 0;
    for (var d = first_day; d <= last_day; d++) {
        this.day_element.options[this.day_element.options.length] = new Option(d, d);
        if (day == d) {
            this.day_element.options[this.day_element.options.length - 1].selected = true;
        }
    }

    var month_names = { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' };
    this.month_element.options.length = (this.month_element.options[0].value == '-1') ? 1 : 0;
    for (var m = first_month; m <= last_month; m++) {
        this.month_element.options[this.month_element.options.length] = new Option(month_names[m], m);
        if (month == m) {
            this.month_element.options[this.month_element.options.length - 1].selected = true;
        }
    }

    // update value which may have changed if we set
    if (year == -1 || month == -1 || day == -1) {
        this.value_element.value = 'Never';
    } else {
        this.value_element.value = year + '-' + padDatePart(month) + '-' + padDatePart(this.day_element.options[this.day_element.selectedIndex].value);

        if (this.time_component > 0) {
            var hour   = parseInt(this.hour_element.options[this.hour_element.selectedIndex].value);
            var minute = parseInt(this.minute_element.options[this.minute_element.selectedIndex].value);

            if (this.time_component == 1 && this.meridian_element) {
                var meridian = parseInt(this.meridian_element.options[this.meridian_element.selectedIndex].value);
                // Convert 12-hour time parts to 24-hour (12Nm = 0, then Npm = N + 12)
                hour = (hour == 12 ? 0 : hour);
                hour += (meridian == 2 ? 12 : 0);
            }

            if (hour == -1 || minute == -1) {
                this.value_element.value = 'Never';
            } else {
                this.value_element.value += ' ' + padDatePart(hour) + ':' + padDatePart(minute);
            }
        }
    }

    // Updating the available dates for selection can change the date selected, so we need to re-do the date
    this.updateFromDropdownChange(true);
}



Dropdown_Datepicker.prototype.updateDate = function(date, from_dropdowns, no_call_to_update_available_days) {
    this.current_date = date;
    var date_string = date.toString();
    var diff_string = 'No date selected';

    if (date_string == 'Never') {
        this.value_element.value = date_string;
    } else {
        var tmp = date_string.split(/ /);
        var day_string = tmp[0];

        if (this.long_day_names) {
            var days = { 'Mon': 'Monday', 'Tue': 'Tuesday', 'Wed': 'Wednesday', 'Thu': 'Thursday', 'Fri': 'Friday', 'Sat': 'Saturday', 'Sun': 'Sunday' };
            day_string = days[day_string];
        }

        // Calculate difference between today and the supplied date, and generate delta string
        var year  = date.getFullYear();
        var month = date.getMonth() + 1;
        var day   = date.getDate();
        var today = new Date();
        today.setHours(0, 0, 0, 0);
        var diff = Math.floor((date - today) / (1000 * 60 * 60 * 24));

        if (diff == 0) {
            diff_string = 'Today';
        } else if (diff == -1) {
            diff_string = 'Yesterday';
        } else if (diff == 1) {
            diff_string = 'Tomorrow';
        } else if (diff > 0) {
            diff_string = 'In ' + diff + ' days';
        } else {
            diff_string = '' + Math.abs(diff) + ' days ago';
        }

        var available_days_updated = false;

        // no need to update select lists if date has been changed using them
        if (!from_dropdowns) {
            for (var i = 0; i < this.year_element.options.length; i++) {
                this.year_element.options[i].selected = (this.year_element.options[i].value == year);
            }

            for (var i = 0; i < this.month_element.options.length; i++) {
                this.month_element.options[i].selected = (this.month_element.options[i].value == month);
            }

            if (!no_call_to_update_available_days) {
                this.updateAvailableDays();
                available_days_updated = true;
            }

            for (var i = 0; i < this.day_element.options.length; i++) {
                this.day_element.options[i].selected = (this.day_element.options[i].value == day);
            }
        }

        this.day_string_element.innerHTML = day_string;

        this.value_element.value = year + '-' + padDatePart(month) + '-' + padDatePart(day);

        if (this.time_component > 0) {
            var hour   = date.getHours();
            var minute = date.getMinutes();
            var compare_hour = hour;

            if (this.time_component != 2) {
                compare_hour = (compare_hour > 11 ? compare_hour - 12 : compare_hour);
                compare_hour = compare_hour || 12;
            }

            for (var i = 0; i < this.hour_element.options.length; i++) {
                this.hour_element.options[i].selected = (this.hour_element.options[i].value == compare_hour);
            }
            for (var i = 0; i < this.minute_element.options.length; i++) {
                this.minute_element.options[i].selected = (this.minute_element.options[i].value == minute);
            }
            this.value_element.value += ' ' + padDatePart(hour) + ':' + padDatePart(minute);
        }
    }

    if (this.days_diff_element) {
        this.days_diff_element.innerHTML = '( ' + diff_string + ' )';
    };

    if (!available_days_updated && !no_call_to_update_available_days) {
        this.updateAvailableDays();
    }

    if (this.onupdate instanceof Function) {
        this.onupdate(this.value_element.value, this);
    }
}



Dropdown_Datepicker.prototype.showCalendar = function() {
    // :TODO: Not fully implemented
    return false;
    document.getElementById(this.calendar_location_id).value = this.value_element.value;
    // :TODO: Never
//    Calendar.noNever = (this.year_element.options[i].value > -1);
    var r = showCalendar(this.calendar_location_id, 'DD, M d y');
    if (calendar) {
        calendar.onSelected = this.calendarCallback.bind(this);
        calendar.setDisabledHandler(this.calendarDisableDateCallback.bind(this));
    }
    this.clendar = calendar;
    return r;
}



Dropdown_Datepicker.prototype.calendarCallback = function(calendar, date_string) {
    this.updateDate(new Date(date_string));
    calendar.hide();
}



Dropdown_Datepicker.prototype.calendarDisableDateCallback = function(d) {
/*
    if (this.allowed_years) {
        return !(this.allowed_years.indexOf(d.getFullYear()) != -1);
    }
*/
    return false;
}
