import "../format/format";
import "linear";
import "nice";
import "scale";

d3.scale.log = function() {
  return d3_scale_log(d3.scale.linear().domain([0, 1]), 10, true, [1, 10]);
};

function d3_scale_log(linear, base, positive, domain) {

  function log(x) {
    return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
  }

  function pow(x) {
    return positive ? Math.pow(base, x) : -Math.pow(base, -x);
  }

  function scale(x) {
    return linear(log(x));
  }

  scale.invert = function(x) {
    return pow(linear.invert(x));
  };

  scale.domain = function(x) {
    if (!arguments.length) return domain;
    positive = x[0] >= 0;
    linear.domain((domain = x.map(Number)).map(log));
    return scale;
  };

  scale.base = function(_) {
    if (!arguments.length) return base;
    base = +_;
    linear.domain(domain.map(log));
    return scale;
  };

  scale.nice = function() {
    var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
    linear.domain(niced); // do not modify the linear scale’s domain in-place!
    domain = niced.map(pow);
    return scale;
  };

  scale.ticks = function() {
    var extent = d3_scaleExtent(domain),
        ticks = [],
        u = extent[0],
        v = extent[1],
        i = Math.floor(log(u)),
        j = Math.ceil(log(v)),
        n = base % 1 ? 2 : base;
    if (isFinite(j - i)) {
      if (positive) {
        for (; i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
        ticks.push(pow(i));
      } else {
        ticks.push(pow(i));
        for (; i++ < j;) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
      }
      for (i = 0; ticks[i] < u; i++) {} // strip small values
      for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values
      ticks = ticks.slice(i, j);
    }
    return ticks;
  };

  scale.tickFormat = function(n, format) {
    if (!arguments.length) return d3_scale_logFormat;
    if (arguments.length < 2) format = d3_scale_logFormat;
    else if (typeof format !== "function") format = d3.format(format);
    var k = Math.max(1, base * n / scale.ticks().length);
    return function(d) {
      var i = d / pow(Math.round(log(d)));
      if (i * base < base - 0.5) i *= base;
      return i <= k ? format(d) : "";
    };
  };

  scale.copy = function() {
    return d3_scale_log(linear.copy(), base, positive, domain);
  };

  return d3_scale_linearRebind(scale, linear);
}

var d3_scale_logFormat = d3.format(".0e"),
    d3_scale_logNiceNegative = {floor: function(x) { return -Math.ceil(-x); }, ceil: function(x) { return -Math.floor(-x); }};
