define(['../number/pad', '../string/lpad', './i18n_', './dayOfTheYear', './timezoneOffset', './timezoneAbbr', './weekOfTheYear'], function (pad, lpad, i18n, dayOfTheYear, timezoneOffset, timezoneAbbr, weekOfTheYear) {

    var _combinations = {
        'D': '%m/%d/%y',
        'F': '%Y-%m-%d',
        'r': '%I:%M:%S %p',
        'R': '%H:%M',
        'T': '%H:%M:%S',
        'x': 'locale',
        'X': 'locale',
        'c': 'locale'
    };


    /**
     * format date based on strftime format
     */
    function strftime(date, format, localeData){
        localeData = localeData  || i18n;
        var reToken = /%([a-z%])/gi;

        function makeIterator(fn) {
            return function(match, token){
                return fn(date, token, localeData);
            };
        }

        return format
            .replace(reToken, makeIterator(expandCombinations))
            .replace(reToken, makeIterator(convertToken));
    }


    function expandCombinations(date, token, l10n){
        if (token in _combinations) {
            var expanded = _combinations[token];
            return expanded === 'locale'? l10n[token] : expanded;
        } else {
            return '%'+ token;
        }
    }


    function convertToken(date, token, l10n){
        switch (token){
            case 'a':
                return l10n.days_abbr[date.getDay()];
            case 'A':
                return l10n.days[date.getDay()];
            case 'h':
            case 'b':
                return l10n.months_abbr[date.getMonth()];
            case 'B':
                return l10n.months[date.getMonth()];
            case 'C':
                return pad(Math.floor(date.getFullYear() / 100), 2);
            case 'd':
                return pad(date.getDate(), 2);
            case 'e':
                return pad(date.getDate(), 2, ' ');
            case 'H':
                return pad(date.getHours(), 2);
            case 'I':
                return pad(date.getHours() % 12, 2);
            case 'j':
                return pad(dayOfTheYear(date), 3);
            case 'l':
                return lpad(date.getHours() % 12, 2);
            case 'L':
                return pad(date.getMilliseconds(), 3);
            case 'm':
                return pad(date.getMonth() + 1, 2);
            case 'M':
                return pad(date.getMinutes(), 2);
            case 'n':
                return '\n';
            case 'p':
                return date.getHours() >= 12? l10n.pm : l10n.am;
            case 'P':
                return convertToken(date, 'p', l10n).toLowerCase();
            case 's':
                return date.getTime() / 1000;
            case 'S':
                return pad(date.getSeconds(), 2);
            case 't':
                return '\t';
            case 'u':
                var day = date.getDay();
                return day === 0? 7 : day;
            case 'U':
                return pad(weekOfTheYear(date), 2);
            case 'w':
                return date.getDay();
            case 'W':
                return pad(weekOfTheYear(date, 1), 2);
            case 'y':
                return pad(date.getFullYear() % 100, 2);
            case 'Y':
                return pad(date.getFullYear(), 4);
            case 'z':
                return timezoneOffset(date);
            case 'Z':
                return timezoneAbbr(date);
            case '%':
                return '%';
            default:
                // keep unrecognized tokens
                return '%'+ token;
        }
    }


    return strftime;

});
