var UUID = require('../lib/uuid');
var assert = require('assert');
var sinon = require('sinon');
var util = require('util');

function isUUID(str) {
  return str.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
}

exports['Check UUID methods'] = function() {
  var methods = [
    'maxFromBits',
    'limitUI04',
    'limitUI06',
    'limitUI08',
    'limitUI12',
    'limitUI14',
    'limitUI16',
    'limitUI32',
    'limitUI40',
    'limitUI48',
    'randomUI04',
    'randomUI06',
    'randomUI08',
    'randomUI12',
    'randomUI14',
    'randomUI16',
    'randomUI32',
    'randomUI40',
    'randomUI48',
    'create',
    '_create1',
    '_create4',
    'paddedString',
    'getTimeFieldValues',
    'fromTime',
    'firstFromTime',
    'lastFromTime',
    'fromURN',
    'fromBytes',
    'fromBinary',
    // Legacy methods:
    'new',
    'newTS'
  ];
  var found = 0;
  for (var key in UUID) {
    if (methods.indexOf(key) !== -1) {
      found++;
      continue;
    }
    assert.ok(false, 'Found unexpected method: ' + key);
  }
  assert.equal(found, methods.length, 'Unexpected number of defined methods: ' + found + ' != ' + methods.length);
};

exports['Check UUID prototypes'] = function() {
  var methods = [
    'fromParts',
    'toString',
    'toURN',
    'toBytes',
    'equals'
  ];
  var found = 0;
  for (var key in UUID.prototype) {
    if (methods.indexOf(key) !== -1) {
      found++;
      continue;
    }
    assert.ok(false, 'Found unexpected prototype: ' + key);
  }
  assert.equal(found, methods.length, 'Unexpected number of defined prototypes: ' + found + ' != ' + methods.length);
};


exports['v4 UUID: uuid = UUID.create(4) -> test properties'] = function() {
  var uuid = UUID.create(4);

  var properties = [
    'version',
    'hex'
  ];
  var found = 0;
  for (var key in uuid) {
    // Filter prototypes
    if (!uuid.hasOwnProperty(key)) {
      continue;
    }
    if (properties.indexOf(key) !== -1) {
      found++;
      continue;
    }
    console.log(key);
    assert.ok(false, 'Found unexpected property in uuid instance: ' + key);
  }
  assert.equal(found, properties.length);

  assert.equal(uuid.version, 4, 'Unexpected version: ' + uuid.version);
  assert.ok(isUUID(uuid.hex), 'UUID semantically incorrect');
};


exports['v4 UUID: uuid.toString()'] = function() {
  var uuid = UUID.create(4);
  assert.equal(uuid.toString(), uuid.hex);
};


exports['v4 UUID: uuid.toURN()'] = function() {
  var uuid = UUID.create(4);
  assert.equal(uuid.toURN(), 'urn:uuid:' + uuid.hex);
};


exports['v4 UUID: uuid.toBytes()'] = function() {
  var uuid = UUID.create(4);
  var bytes = uuid.toBytes();

  // Reassemble the bytes and check if they fit the string representation
  var hex = [];
  for (var i = 0; i < bytes.length; i++) {
    hex.push(bytes[i].toString(16));
  }
  var index = 0;
  var parts = [];
  [4, 2, 2, 2, 6].forEach(function(len) {
    var part = [];
    for (var j = 0; j < len; j++) {
      var val = hex[index]+"";
      if (val.length < 2) {
        val = "0" + val;
      }
      part.push(val);
      index++;
    }
    parts.push(part.join(''));
  });
  assert.equal(uuid.hex, parts.join('-'));
};


exports['v4 UUID: check that they are not time-ordered'] = function() {
  var unsorted = [];
  var sorted = [];
  for (var i = 0; i < 100; i++) {
    var uuid = UUID.create(4).toString();
    unsorted.push(uuid);
    sorted.push(uuid);
  }
  sorted.sort();
  assert.notDeepEqual(sorted, unsorted, 'v4 UUIDs appear to be sorted!');
};


exports['v1 UUID: uuid = UUID.create(1) -> test properties'] = function() {
  var uuid = UUID.create(1);

  var properties = [
    'version',
    'hex'
  ];
  var found = 0;
  for (var key in uuid) {
    // Filter prototypes
    if (!uuid.hasOwnProperty(key)) {
      continue;
    }
    if (properties.indexOf(key) !== -1) {
      found++;
      continue;
    }
    assert.ok(false, 'Found unexpected property in uuid instance: ' + key);
  }
  assert.equal(found, properties.length);

  assert.equal(uuid.version, 1, 'Unexpected version: ' + uuid.version);
  assert.ok(isUUID(uuid.hex), 'UUID semantically incorrect');
};


exports['v1 UUID: uuid.toString()'] = function() {
  var uuid = UUID.create(1);
  assert.equal(uuid.toString(), uuid.hex);
};


exports['v1 UUID: uuid.toURN()'] = function() {
  var uuid = UUID.create(1);
  assert.equal(uuid.toURN(), 'urn:uuid:' + uuid.hex);
};


exports['v1 UUID: uuid.toBytes()'] = function() {
  var uuid = UUID.create(1);
  var bytes = uuid.toBytes();

  // Reassemble the bytes and check if they fit the string representation
  var hex = [];
  for (var i = 0; i < bytes.length; i++) {
    hex.push(bytes[i].toString(16));
  }
  var index = 0;
  var parts = [];
  [4, 2, 2, 2, 6].forEach(function(len) {
    var part = [];
    for (var j = 0; j < len; j++) {
      var val = hex[index]+"";
      if (val.length < 2) {
        val = "0" + val;
      }
      part.push(val);
      index++;
    }
    parts.push(part.join(''));
  });
  assert.equal(uuid.hex, parts.join('-'));
};


exports['v1 UUID: check that they are time-ordered'] = function() {
  var unsorted = [];
  var sorted = [];
  var check = function() {
    sorted.sort();
    assert.deepEqual(sorted, unsorted, 'v1 UUIDs appear not to be sorted!');
  };
  var i = 0;
  // We have to wait a tiny bit between generating two UUIDs to assure time
  // order since times are based on milliseconds and two UUIDs created in
  // the same millisecond need not be different.
  var next = function() {
    var uuid = UUID.create(1).toString();
    unsorted.push(uuid);
    sorted.push(uuid);
    i++;
    if (i < 100) {
      return setTimeout(next, 5);
    }
    check();
  };
  next();
};


exports['firstFromTime()'] = function() {
  var date = new Date();
  date = date.getTime();
  var spy = sinon.spy(UUID, 'fromTime');

  var uuid = UUID.firstFromTime(date);
  assert.ok(spy.calledOnce);
  assert.ok(spy.calledWith(date, false));
  assert.ok(isUUID(uuid.toString()), 'UUID semantically incorrect');

  spy.restore();
};


exports['lastFromTime()'] = function() {
  var date = new Date();
  date = date.getTime();
  var spy = sinon.spy(UUID, 'fromTime');

  var uuid = UUID.lastFromTime(date);
  assert.ok(spy.calledOnce);
  assert.ok(spy.calledWith(date, true));
  assert.ok(isUUID(uuid.toString()), 'UUID semantically incorrect');

  spy.restore();
};


exports['fromTime()'] = function() {
  var date = new Date();
  date = date.getTime();

  var uuidFirst = UUID.fromTime(date, false);
  var uuidLast = UUID.fromTime(date, true);

  assert.strictEqual(uuidFirst.toString().substr(0, 19), uuidLast.toString().substr(0, 19), 'Timestamp part of first and last not equal');
};


exports['create(1) -> _create1()'] = function() {
  var spy = sinon.spy(UUID, '_create1');
  var uuid = UUID.create(1);
  assert.ok(spy.calledOnce);
  spy.restore();
};


exports['create(4) -> _create4()'] = function() {
  var spy = sinon.spy(UUID, '_create4');
  var uuid = UUID.create(4);
  assert.ok(spy.calledOnce);
  spy.restore();
};


exports['create() -> create4()'] = function() {
  var spy = sinon.spy(UUID, '_create4');
  var uuid = UUID.create();
  assert.ok(spy.calledOnce);
  spy.restore();
};


exports['new() alias for create(4)'] = function() {
  var spy = sinon.spy(UUID, 'create');

  var uuid = UUID.new();

  assert.ok(spy.calledOnce);
  assert.ok(spy.calledWith(4));

  spy.restore();
};


exports['newTS() alias for create(1)'] = function() {
  var spy = sinon.spy(UUID, 'create');

  var uuid = UUID.new();

  assert.ok(spy.calledOnce);
  assert.ok(spy.calledWith(4));

  spy.restore();
};

exports['correct random max values'] = function() {

  var stub = sinon.stub(Math, 'random').returns(0.9999999999);

  assert.equal(UUID.randomUI04(), 0x0000000F);
  assert.equal(UUID.randomUI06(), 0x0000003F);
  assert.equal(UUID.randomUI08(), 0x000000FF);
  assert.equal(UUID.randomUI12(), 0x00000FFF);
  assert.equal(UUID.randomUI14(), 0x00003FFF);
  assert.equal(UUID.randomUI16(), 0x0000FFFF);
  assert.equal(UUID.randomUI32(), 0xFFFFFFFF);

  // not really sure about these....
  assert.equal(UUID.randomUI40(), 0xFFFFFFFFFF);
  assert.equal(UUID.randomUI48(), 0xFFFFFFFFFFFF);

  stub.restore();

};

for (var key in exports) {
  exports[key]();
  console.log('✔ ' + key);
}
