(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-format'), require('vega-time'), require('vega-util'), require('d3-time-format')) :
  typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-format', 'vega-time', 'vega-util', 'd3-time-format'], factory) :
  (global = global || self, factory(global.vega = {}, global.d3, global.d3, global.vega, global.vega, global.d3));
}(this, (function (exports, d3Array, d3Format, vegaTime, vegaUtil, d3TimeFormat) { 'use strict';

  function memoize(method) {
    const cache = {};
    return spec => cache[spec] || (cache[spec] = method(spec));
  }

  function trimZeroes(numberFormat, decimalChar) {
    return x => {
      var str = numberFormat(x),
          dec = str.indexOf(decimalChar),
          idx, end;

      if (dec < 0) return str;

      idx = rightmostDigit(str, dec);
      end = idx < str.length ? str.slice(idx) : '';
      while (--idx > dec) if (str[idx] !== '0') { ++idx; break; }

      return str.slice(0, idx) + end;
    };
  }

  function rightmostDigit(str, dec) {
    var i = str.lastIndexOf('e'), c;
    if (i > 0) return i;
    for (i=str.length; --i > dec;) {
      c = str.charCodeAt(i);
      if (c >= 48 && c <= 57) return i + 1; // is digit
    }
  }

  function numberLocale(locale) {
    const format = memoize(locale.format),
          formatPrefix = locale.formatPrefix;

    return {
      format,
      formatPrefix,
      formatFloat(spec) {
        var s = d3Format.formatSpecifier(spec || ',');
        if (s.precision == null) {
          s.precision = 12;
          switch (s.type) {
            case '%': s.precision -= 2; break;
            case 'e': s.precision -= 1; break;
          }
          return trimZeroes(
            format(s),          // number format
            format('.1f')(1)[1] // decimal point character
          );
        } else {
          return format(s);
        }
      },
      formatSpan(start, stop, count, specifier) {
        specifier = d3Format.formatSpecifier(specifier == null ? ',f' : specifier);
        const step = d3Array.tickStep(start, stop, count),
              value = Math.max(Math.abs(start), Math.abs(stop));
        let precision;

        if (specifier.precision == null) {
          switch (specifier.type) {
            case 's': {
              if (!isNaN(precision = d3Format.precisionPrefix(step, value))) {
                specifier.precision = precision;
              }
              return formatPrefix(specifier, value);
            }
            case '':
            case 'e':
            case 'g':
            case 'p':
            case 'r': {
              if (!isNaN(precision = d3Format.precisionRound(step, value))) {
                specifier.precision = precision - (specifier.type === 'e');
              }
              break;
            }
            case 'f':
            case '%': {
              if (!isNaN(precision = d3Format.precisionFixed(step))) {
                specifier.precision = precision - (specifier.type === '%') * 2;
              }
              break;
            }
          }
        }
        return format(specifier);
      }
    };
  }

  let defaultNumberLocale;
  resetNumberFormatDefaultLocale();

  function resetNumberFormatDefaultLocale() {
    return defaultNumberLocale = numberLocale({
      format: d3Format.format,
      formatPrefix: d3Format.formatPrefix
    });
  }

  function numberFormatLocale(definition) {
    return numberLocale(d3Format.formatLocale(definition));
  }

  function numberFormatDefaultLocale(definition) {
    return arguments.length
      ? (defaultNumberLocale = numberFormatLocale(definition))
      : defaultNumberLocale;
  }

  function timeMultiFormat(format, interval, spec) {
    spec = spec || {};
    if (!vegaUtil.isObject(spec)) {
      vegaUtil.error(`Invalid time multi-format specifier: ${spec}`);
    }

    const second = interval(vegaTime.SECONDS),
          minute = interval(vegaTime.MINUTES),
          hour = interval(vegaTime.HOURS),
          day = interval(vegaTime.DATE),
          week = interval(vegaTime.WEEK),
          month = interval(vegaTime.MONTH),
          quarter = interval(vegaTime.QUARTER),
          year = interval(vegaTime.YEAR),
          L = format(spec[vegaTime.MILLISECONDS] || '.%L'),
          S = format(spec[vegaTime.SECONDS] || ':%S'),
          M = format(spec[vegaTime.MINUTES] || '%I:%M'),
          H = format(spec[vegaTime.HOURS] || '%I %p'),
          d = format(spec[vegaTime.DATE] || spec[vegaTime.DAY] || '%a %d'),
          w = format(spec[vegaTime.WEEK] || '%b %d'),
          m = format(spec[vegaTime.MONTH] || '%B'),
          q = format(spec[vegaTime.QUARTER] || '%B'),
          y = format(spec[vegaTime.YEAR] || '%Y');

    return date => (
      second(date) < date ? L :
      minute(date) < date ? S :
      hour(date) < date ? M :
      day(date) < date ? H :
      month(date) < date ? (week(date) < date ? d : w) :
      year(date) < date ? (quarter(date) < date ? m : q) :
      y)(date);
  }

  function timeLocale(locale) {
    const timeFormat = memoize(locale.format),
          utcFormat = memoize(locale.utcFormat);

    return {
      timeFormat: spec => vegaUtil.isString(spec)
        ? timeFormat(spec)
        : timeMultiFormat(timeFormat, vegaTime.timeInterval, spec),
      utcFormat: spec => vegaUtil.isString(spec)
        ? utcFormat(spec)
        : timeMultiFormat(utcFormat, vegaTime.utcInterval, spec),
      timeParse: memoize(locale.parse),
      utcParse: memoize(locale.utcParse)
    };
  }

  let defaultTimeLocale;
  resetTimeFormatDefaultLocale();

  function resetTimeFormatDefaultLocale() {
    return defaultTimeLocale = timeLocale({
      format: d3TimeFormat.timeFormat,
      parse: d3TimeFormat.timeParse,
      utcFormat: d3TimeFormat.utcFormat,
      utcParse: d3TimeFormat.utcParse
    });
  }

  function timeFormatLocale(definition) {
    return timeLocale(d3TimeFormat.timeFormatLocale(definition));
  }

  function timeFormatDefaultLocale(definition) {
    return arguments.length
      ? (defaultTimeLocale = timeFormatLocale(definition))
      : defaultTimeLocale;
  }

  const createLocale = (number, time) => vegaUtil.extend({}, number, time);

  function locale(numberSpec, timeSpec) {
    const number = numberSpec
      ? numberFormatLocale(numberSpec)
      : numberFormatDefaultLocale();

    const time = timeSpec
      ? timeFormatLocale(timeSpec)
      : timeFormatDefaultLocale();

    return createLocale(number, time);
  }

  function defaultLocale(numberSpec, timeSpec) {
    const args = arguments.length;
    if (args && args !== 2) {
      vegaUtil.error('defaultLocale expects either zero or two arguments.');
    }

    return args
      ? createLocale(
          numberFormatDefaultLocale(numberSpec),
          timeFormatDefaultLocale(timeSpec)
        )
      : createLocale(
          numberFormatDefaultLocale(),
          timeFormatDefaultLocale()
        );
  }

  function resetDefaultLocale() {
    resetNumberFormatDefaultLocale();
    resetTimeFormatDefaultLocale();
    return defaultLocale();
  }

  exports.defaultLocale = defaultLocale;
  exports.locale = locale;
  exports.numberFormatDefaultLocale = numberFormatDefaultLocale;
  exports.numberFormatLocale = numberFormatLocale;
  exports.resetDefaultLocale = resetDefaultLocale;
  exports.resetNumberFormatDefaultLocale = resetNumberFormatDefaultLocale;
  exports.resetTimeFormatDefaultLocale = resetTimeFormatDefaultLocale;
  exports.timeFormatDefaultLocale = timeFormatDefaultLocale;
  exports.timeFormatLocale = timeFormatLocale;

  Object.defineProperty(exports, '__esModule', { value: true });

})));
