define(['jquery', 'date', 'js/utils/change_on_enter', 'jquery.ui', 'jquery.timepicker'],
function($, date, TriggerChangeEventOnEnter) {
    'use strict';
    var setupDatePicker = function(fieldName, view, index) {
        var cacheModel;
        var div;
        if (typeof index !== 'undefined' && view.hasOwnProperty('collection')) {
            cacheModel = view.collection.models[index];
            div = view.$el.find('#' + view.collectionSelector(cacheModel.cid));
        } else {
            cacheModel = view.model;
            div = view.$el.find('#' + view.fieldToSelectorMap[fieldName]);
        }
        var datefield = $(div).find('input.date');
        var timefield = $(div).find('input.time');
        var cacheview = view;
        var setfield = function(event) {
            var newVal = getDate(datefield, timefield);

            // Setting to null clears the time as well, as date and time are linked.
            // Note also that the validation logic prevents us from clearing the start date
            // (start date is required by the back end).
            cacheview.clearValidationErrors();
            cacheview.setAndValidate(fieldName, (newVal || null), event);
        };

        // instrument as date and time pickers
        timefield.timepicker({timeFormat: 'H:i'});
        datefield.datepicker();

        // Using the change event causes setfield to be triggered twice, but it is necessary
        // to pick up when the date is typed directly in the field.
        datefield.change(setfield).keyup(TriggerChangeEventOnEnter);
        timefield.on('changeTime', setfield);
        timefield.on('input', setfield);

        var current_date = null;
        if (cacheModel) {
            current_date = cacheModel.get(fieldName);
        }
        // timepicker doesn't let us set null, so check that we have a time
        if (current_date) {
            setDate(datefield, timefield, current_date);
        } // but reset fields either way
        else {
            timefield.val('');
            datefield.val('');
        }
    };

    var getDate = function(datepickerInput, timepickerInput) {
        // given a pair of inputs (datepicker and timepicker), return a JS Date
        // object that corresponds to the datetime.js that they represent. Assume
        // UTC timezone, NOT the timezone of the user's browser.
        var date = null,
            time = null;
        if (datepickerInput.length > 0) {
            date = $(datepickerInput).datepicker('getDate');
        }
        if (timepickerInput.length > 0) {
            time = $(timepickerInput).timepicker('getTime');
        }
        if (date && time) {
            return new Date(Date.UTC(
                date.getFullYear(), date.getMonth(), date.getDate(),
                time.getHours(), time.getMinutes()
            ));
        } else if (date) {
            return new Date(Date.UTC(
                date.getFullYear(), date.getMonth(), date.getDate()));
        } else {
            return null;
        }
    };

    var setDate = function(datepickerInput, timepickerInput, datetime) {
        // given a pair of inputs (datepicker and timepicker) and the date as an
        // ISO-formatted date string.
        datetime = date.parse(datetime);
        if (datetime) {
            $(datepickerInput).datepicker('setDate', datetime);
            if (timepickerInput.length > 0) {
                $(timepickerInput).timepicker('setTime', datetime);
            }
        }
    };

    var renderDate = function(dateArg) {
        // Render a localized date from an argument that can be passed to
        // the Date constructor (e.g. another Date or an ISO 8601 string)
        var date = new Date(dateArg);
        return date.toLocaleString(
            [],
            {timeZone: 'UTC', timeZoneName: 'short'}
        );
    };

    var parseDateFromString = function(stringDate) {
        if (stringDate && typeof stringDate === 'string') {
            return new Date(stringDate);
        } else {
            return stringDate;
        }
    };

    var convertDateStringsToObjects = function(obj, dateFields) {
        for (var i = 0; i < dateFields.length; i++) {
            if (obj[dateFields[i]]) {
                obj[dateFields[i]] = parseDateFromString(obj[dateFields[i]]);
            }
        }
        return obj;
    };

    return {
        getDate: getDate,
        setDate: setDate,
        renderDate: renderDate,
        convertDateStringsToObjects: convertDateStringsToObjects,
        parseDateFromString: parseDateFromString,
        setupDatePicker: setupDatePicker
    };
});
