var time = use('time')
var math = use('math/radians')

def iterations = {
  simple: 10000000,
  medium: 1000000,
  complex: 100000
};

function measureTime(fn) {
  var start = time.number();
  fn();
  var end = time.number();
  return (end - start);
}

function benchPropertyAccess() {
  var obj = {
    a: 1, b: 2, c: 3, d: 4, e: 5,
    nested: { x: 10, y: 20, z: 30 }
  };

  var readTime = measureTime(function() {
    var sum = 0;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      sum += obj.a + obj.b + obj.c + obj.d + obj.e;
      sum += obj.nested.x + obj.nested.y + obj.nested.z;
    }
  });

  var writeTime = measureTime(function() {
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      obj.a = i;
      obj.b = i + 1;
      obj.c = i + 2;
      obj.nested.x = i * 2;
      obj.nested.y = i * 3;
    }
  });

  return { readTime: readTime, writeTime: writeTime };
}

function benchFunctionCalls() {
  function add(a, b) { return a + b; }
  function multiply(a, b) { return a * b; }
  function complexCalc(a, b, c) { return (a + b) * c / 2; }

  var obj = {
    method: function(x) { return x * 2; },
    nested: {
      deepMethod: function(x, y) { return x + y; }
    }
  };

  var simpleCallTime = measureTime(function() {
    var result = 0;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      result = add(i, 1);
      result = multiply(result, 2);
    }
  });

  var methodCallTime = measureTime(function() {
    var result = 0;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      result = obj.method(i);
      result = obj.nested.deepMethod(result, i);
    }
  });

  var complexCallTime = measureTime(function() {
    var result = 0;
    var i = 0
    for (i = 0; i < iterations.medium; i++) {
      result = complexCalc(i, i + 1, i + 2);
    }
  });

  return {
    simpleCallTime: simpleCallTime,
    methodCallTime: methodCallTime,
    complexCallTime: complexCallTime
  };
}

function benchArrayOps() {
  var i = 0

  var pushTime = measureTime(function() {
    var arr = [];
    var j = 0
    for (j = 0; j < iterations.medium; j++) {
      push(arr, j);
    }
  });

  var arr = [];
  for (i = 0; i < 10000; i++) push(arr, i);

  var accessTime = measureTime(function() {
    var sum = 0;
    var j = 0
    for (j = 0; j < iterations.medium; j++) {
      sum += arr[j % 10000];
    }
  });

  var iterateTime = measureTime(function() {
    var sum = 0;
    var j = 0
    var k = 0
    for (j = 0; j < 1000; j++) {
      for (k = 0; k < length(arr); k++) {
        sum += arr[k];
      }
    }
  });

  return {
    pushTime: pushTime,
    accessTime: accessTime,
    iterateTime: iterateTime
  };
}

function benchObjectCreation() {
  var literalTime = measureTime(function() {
    var i = 0
    var obj = null
    for (i = 0; i < iterations.medium; i++) {
      obj = { x: i, y: i * 2, z: i * 3 };
    }
  });

  function Point(x, y) {
    return {x,y}
  }

  var defructorTime = measureTime(function() {
    var i = 0
    var p = null
    for (i = 0; i < iterations.medium; i++) {
      p = Point(i, i * 2);
    }
  });

  var protoObj = {
    x: 0,
    y: 0,
    move: function(dx, dy) {
      this.x += dx;
      this.y += dy;
    }
  };

  var prototypeTime = measureTime(function() {
    var i = 0
    var obj = null
    for (i = 0; i < iterations.medium; i++) {
      obj = meme(protoObj);
      obj.x = i;
      obj.y = i * 2;
    }
  });

  return {
    literalTime: literalTime,
    defructorTime: defructorTime,
    prototypeTime: prototypeTime
  };
}

function benchStringOps() {
  var i = 0
  var strings = [];

  var concatTime = measureTime(function() {
    var str = "";
    var j = 0
    for (j = 0; j < iterations.complex; j++) {
      str = "test" + j + "value";
    }
  });

  for (i = 0; i < 1000; i++) {
    push(strings, "string" + i);
  }

  var joinTime = measureTime(function() {
    var j = 0
    var result = null
    for (j = 0; j < iterations.complex; j++) {
      result = text(strings, ",");
    }
  });

  var splitTime = measureTime(function() {
    var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
    var j = 0
    var parts = null
    for (j = 0; j < iterations.medium; j++) {
      parts = array(str, ",");
    }
  });

  return {
    concatTime: concatTime,
    joinTime: joinTime,
    splitTime: splitTime
  };
}

function benchArithmetic() {
  var intMathTime = measureTime(function() {
    var result = 1;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      result = ((result + i) * 2 - 1) / 3;
      result = result % 1000 + 1;
    }
  });

  var floatMathTime = measureTime(function() {
    var result = 1.5;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      result = math.sine(result) + math.cosine(i * 0.01);
      result = math.sqrt(abs(result)) + 0.1;
    }
  });

  var bitwiseTime = measureTime(function() {
    var result = 0;
    var i = 0
    for (i = 0; i < iterations.simple; i++) {
      result = (result ^ i) & 0xFFFF;
      result = (result << 1) | (result >> 15);
    }
  });

  return {
    intMathTime: intMathTime,
    floatMathTime: floatMathTime,
    bitwiseTime: bitwiseTime
  };
}

function benchClosures() {
  var i = 0

  function makeAdder(x) {
    return function(y) { return x + y; };
  }

  var closureCreateTime = measureTime(function() {
    var funcs = [];
    var j = 0
    for (j = 0; j < iterations.medium; j++) {
      push(funcs, makeAdder(j));
    }
  });

  var adders = [];
  for (i = 0; i < 1000; i++) {
    push(adders, makeAdder(i));
  }

  var closureCallTime = measureTime(function() {
    var sum = 0;
    var j = 0
    for (j = 0; j < iterations.medium; j++) {
      sum += adders[j % 1000](j);
    }
  });

  return {
    closureCreateTime: closureCreateTime,
    closureCallTime: closureCallTime
  };
}

log.console("JavaScript Performance Benchmark");
log.console("======================\n");

log.console("BENCHMARK: Property Access");
var propResults = benchPropertyAccess();
log.console("  Read time:  " + propResults.readTime.toFixed(3) + "s => " +
           (iterations.simple / propResults.readTime).toFixed(1) + " reads/sec [" +
           (propResults.readTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("  Write time: " + propResults.writeTime.toFixed(3) + "s => " +
           (iterations.simple / propResults.writeTime).toFixed(1) + " writes/sec [" +
           (propResults.writeTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("BENCHMARK: Function Calls");
var funcResults = benchFunctionCalls();
log.console("  Simple calls: " + funcResults.simpleCallTime.toFixed(3) + "s => " +
           (iterations.simple / funcResults.simpleCallTime).toFixed(1) + " calls/sec [" +
           (funcResults.simpleCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("  Method calls: " + funcResults.methodCallTime.toFixed(3) + "s => " +
           (iterations.simple / funcResults.methodCallTime).toFixed(1) + " calls/sec [" +
           (funcResults.methodCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("  Complex calls: " + funcResults.complexCallTime.toFixed(3) + "s => " +
           (iterations.medium / funcResults.complexCallTime).toFixed(1) + " calls/sec [" +
           (funcResults.complexCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("BENCHMARK: Array Operations");
var arrayResults = benchArrayOps();
log.console("  Push: " + arrayResults.pushTime.toFixed(3) + "s => " +
           (iterations.medium / arrayResults.pushTime).toFixed(1) + " pushes/sec [" +
           (arrayResults.pushTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("  Access: " + arrayResults.accessTime.toFixed(3) + "s => " +
           (iterations.medium / arrayResults.accessTime).toFixed(1) + " accesses/sec [" +
           (arrayResults.accessTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("  Iterate: " + arrayResults.iterateTime.toFixed(3) + "s => " +
           (1000 / arrayResults.iterateTime).toFixed(1) + " full iterations/sec");
log.console("");

log.console("BENCHMARK: Object Creation");
var objResults = benchObjectCreation();
log.console("  Literal: " + objResults.literalTime.toFixed(3) + "s => " +
           (iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
           (objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("  Constructor: " + objResults.defructorTime.toFixed(3) + "s => " +
           (iterations.medium / objResults.defructorTime).toFixed(1) + " creates/sec [" +
           (objResults.defructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("  Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
           (iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
           (objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("BENCHMARK: String Operations");
var strResults = benchStringOps();
log.console("  Concat: " + strResults.concatTime.toFixed(3) + "s => " +
           (iterations.complex / strResults.concatTime).toFixed(1) + " concats/sec [" +
           (strResults.concatTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console("  Join: " + strResults.joinTime.toFixed(3) + "s => " +
           (iterations.complex / strResults.joinTime).toFixed(1) + " joins/sec [" +
           (strResults.joinTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console("  Split: " + strResults.splitTime.toFixed(3) + "s => " +
           (iterations.medium / strResults.splitTime).toFixed(1) + " splits/sec [" +
           (strResults.splitTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("BENCHMARK: Arithmetic Operations");
var mathResults = benchArithmetic();
log.console("  Integer math: " + mathResults.intMathTime.toFixed(3) + "s => " +
           (iterations.simple / mathResults.intMathTime).toFixed(1) + " ops/sec [" +
           (mathResults.intMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("  Float math: " + mathResults.floatMathTime.toFixed(3) + "s => " +
           (iterations.simple / mathResults.floatMathTime).toFixed(1) + " ops/sec [" +
           (mathResults.floatMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("  Bitwise: " + mathResults.bitwiseTime.toFixed(3) + "s => " +
           (iterations.simple / mathResults.bitwiseTime).toFixed(1) + " ops/sec [" +
           (mathResults.bitwiseTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("BENCHMARK: Closures");
var closureResults = benchClosures();
log.console("  Create: " + closureResults.closureCreateTime.toFixed(3) + "s => " +
           (iterations.medium / closureResults.closureCreateTime).toFixed(1) + " creates/sec [" +
           (closureResults.closureCreateTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("  Call: " + closureResults.closureCallTime.toFixed(3) + "s => " +
           (iterations.medium / closureResults.closureCallTime).toFixed(1) + " calls/sec [" +
           (closureResults.closureCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");

log.console("---------------------------------------------------------");
log.console("Benchmark complete.\n");

$stop()
