define('jls/util/Formatter', [
  'jls/lang/Class',
  'jls/lang/Exception',
  'jls/lang/Logger',
  'jls/util/Locale'
], function (
  Class,
  Exception,
  Logger,
  Locale
) {

var Formatter;
//Late binding
var Resource;
require(['jls/util/Resource'], function(m) {
    Resource = m;
});

/**
 * @class This class provides a printf like String formatter.
 * @name jls.util.Formatter
 */
Formatter = Class.create({});

/*
 * %[argument_index$][flags][width][.precision]conversion
 */
Object.extend(Formatter, /** @lends jls.util.Formatter */
{
    _lineSeparator : _native.core.properties['line.separator'],
    _resources : {},
    getResource : function(language) {
        if (language in Formatter._resources) {
            return Formatter._resources[language];
        }
        var res = Resource.load('jls/util/formatter', language);
        var resource = {
                dayFullNames: res.get('dayFullNames').split(','),
                monthFullNames: res.get('monthFullNames').split(',')
        };
        return Formatter._resources[language] = resource;
    },
    /**
     * Returns a padded string.
     *
     * @param {String} s The string to pad.
     * @param {String} l The padding length.
     * @param {String} c The padding character.
     * @param {String} d The direction, true for left padding.
     * @returns {String} The padded string.
     */
    pad : function(s, l, c, d) {
        if ((! l) || (l < 1)) {
            return '';
        }
        s = s.toString();
        if (s.length >= l) {
            return s;
        }
        c = c || ' ';
        d = d || false;
        var p = '';
        for (var i = s.length; i < l; i++) {
            p += c;
        }
        if (d) {
            s = p + s;
        } else {
            s += p;
        }
        return s;
    },
    /**
     * Returns a formatted string.
     *
     * @param {String} [locale] The locale to use to format.
     * @param {String} format The format string.
     * @returns {String} The formatted string.
     */
    format : function() {
        var args = Array.from(arguments);
        var locale;
        if (args[0] instanceof Locale) {
            locale = args.shift();
        } else {
            locale = Locale.getDefault();
        }
        var format = args.shift();
        if (Logger.getInstance().isTraceEnabled()) {
            Logger.getInstance().trace('format(' + locale + ', "' + format + '", ' + args.toJSON() + ')');
        }
        var argi = 0;
        return format.replace(/%(<|[0-9]+\$){0,1}([+\- 0#]+){0,1}([0-9]+){0,1}(?:\.([0-9]+)){0,1}([dinsuxb%]|[tT][yYmedHIklMSLAB])/g,
        function(str, index, flags, width, precision, specifier) {
            switch (specifier) {
            case 'n': return Formatter._lineSeparator;
            case '%': return specifier;
            }
            var padLeft = true;
            var padChar = ' ';
            var ai = index == '<' ? argi : (index ? parseInt(index, 10) : ++argi);
            if ((ai < 1) || (ai > args.length)) {
                throw new Exception('Missing argument ' + ai);
            }
            for (var i = 0; flags && (i < flags.length); i++) {
                var flag = flags.charAt(i);
                switch (flag) {
                case '+':
                    padLeft = true;
                    break;
                case '-':
                    padLeft = false;
                    break;
                case '0':
                    padChar = flag;
                    break;
                }
            }
            width = width ? parseInt(width, 10) : 0;
            precision = precision ? parseInt(precision, 10) : 0;
            var arg = args[ai - 1];
            switch (specifier.charAt(0)) {
            case 'b':
                arg = (typeof arg == 'boolean' ? arg : (arg ? true : false));
                break;
            }
            if ((typeof arg == 'undefined') || (arg == null)) {
                throw new Exception('Missing argument ' + ai);
            }
            // format argument
            switch (specifier.charAt(0)) {
            case 'd':
            case 'i':
            case 's':
            case 'u':
            case 'b':
                break;
            case 'x':
                arg = arg.toString(16);
                break;
            case 't':
            case 'T':
                var p = 2;
                switch (specifier.charAt(1)) {
                case 'y':
                    arg = arg.getYear();
                    break;
                case 'Y':
                    p = 0;
                    arg = arg.getFullYear();
                    break;
                case 'm':
                    arg = arg.getMonth() + 1;
                    break;
                case 'e':
                    p = 0;
                case 'd':
                    arg = arg.getDate();
                    break;
                case 'k':
                    p = 0;
                case 'H':
                    arg = arg.getHours();
                    break;
                case 'l':
                    p = 0;
                case 'I':
                    arg = ((arg.getHours() + 11) % 12 + 1);
                    break;
                case 'M':
                    arg = arg.getMinutes();
                    break;
                case 'S':
                    arg = arg.getSeconds();
                    break;
                case 'L':
                    p = 3;
                    arg = arg.getMilliseconds();
                    break;
                case 'A':
                    p = 0;
                    arg = Formatter.getResource(locale.language).dayFullNames[arg.getDay()];
                    break;
                case 'B':
                    p = 0;
                    arg = Formatter.getResource(locale.language).monthFullNames[arg.getMonth()];
                    break;
                default:
                    throw new Exception('Invalid date specifier: ' + specifier.charAt(1));
                }
                if (p > 0) {
                    arg = Formatter.pad(arg, p, '0', true);
                }
                break;
            default:
                throw new Exception('Invalid specifier: ' + specifier.charAt(0));
            }
            arg = arg.toString();
            if (width && (arg.length < width)) {
                arg = Formatter.pad(arg, width, padChar, padLeft);
            }
            if (precision && (arg.length > precision)) {
                if (padLeft) {
                    arg = arg.substr(-precision, precision);
                } else {
                    arg = arg.substr(0, precision);
                }
            }
            return arg;
        });
    }
});

return Formatter;
});
