const { assert, test, module: describe, skip } = require('qunit');
const { GPU, CPUKernel, WebGLKernel, WebGL2Kernel, HeadlessGLKernel, Kernel } = require('../../src');

describe('internal: kernel');

/**
 *
 * @param {Kernel} Kernel
 */
function argumentTypesTest(Kernel) {
  const kernel = new Kernel(`function(value) { return value[this.thread.x]; }`, {
    output: [1],
    functionBuilder: {
      addKernel: function() {},
      addFunctions: function() {},
      getPrototypes: function() { return []; },
      addNativeFunctions: function() {}
    },
  });
  kernel.build([1]);
  assert.equal(kernel.argumentTypes.length, 1);
  assert.equal(kernel.argumentTypes[0], 'Array');
  kernel.destroy();
}

test('CPUKernel argumentTypes', () => {
  argumentTypesTest(CPUKernel);
});

(GPU.isWebGLSupported ? test : skip)('WebGLKernel argumentTypes', () => {
  argumentTypesTest(WebGLKernel);
});

(GPU.isWebGL2Supported ? test : skip)('WebGL2Kernel argumentTypes', () => {
  argumentTypesTest(WebGL2Kernel);
});

(GPU.isHeadlessGLSupported ? test : skip)('HeadlessGLKernel argumentTypes', () => {
  argumentTypesTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform1fTest(Kernel) {
  const canvas = {};
  const context = {
    uniform1f: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform1f('test', 1);
  assert.equal(kernel.uniform1fCache['test'], 1);

  throws = true;
  kernel.setUniform1f('test', 1);
  assert.equal(kernel.uniform1fCache['test'], 1);

  throws = false;
  kernel.setUniform1f('test', 2);
  assert.equal(kernel.uniform1fCache['test'], 2);
  kernel.destroy();
}

test('WebGLKernel.setUniform1f only calls context when values change', () => {
  setUniform1fTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform1f only calls context when values change', () => {
  setUniform1fTest(WebGL2Kernel);
});
(GPU.isHeadlessGLSupported ? test : skip)('HeadlessGLKernel.setUniform1f only calls context when values change', () => {
  setUniform1fTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform1iTest(Kernel) {
  const canvas = {};
  const context = {
    uniform1i: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform1i('test', 1);
  assert.equal(kernel.uniform1iCache['test'], 1);

  throws = true;
  kernel.setUniform1i('test', 1);
  assert.equal(kernel.uniform1iCache['test'], 1);

  throws = false;
  kernel.setUniform1i('test', 2);
  assert.equal(kernel.uniform1iCache['test'], 2);
  kernel.destroy();
}

test('WebGLKernel.setUniform1i only calls context when values change', () => {
  setUniform1iTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform1i only calls context when values change', () => {
  setUniform1iTest(WebGL2Kernel);
});
(GPU.isHeadlessGLSupported ? test : skip)('HeadlessGLKernel.setUniform1i only calls context when values change', () => {
  setUniform1iTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform2fTest(Kernel) {
  const canvas = {};
  const context = {
    uniform2f: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform2f('test', 1, 2);
  assert.deepEqual(kernel.uniform2fCache['test'], [1, 2]);

  throws = true;
  kernel.setUniform2f('test', 1, 2);
  assert.deepEqual(kernel.uniform2fCache['test'], [1, 2]);

  throws = false;
  kernel.setUniform2f('test', 3, 4);
  assert.deepEqual(kernel.uniform2fCache['test'], [3, 4]);
  kernel.destroy();
}
test('WebGLKernel.setUniform2f only calls context when values change', () => {
  setUniform2fTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform2f only calls context when values change', () => {
  setUniform2fTest(WebGL2Kernel);
});
(GPU.isHeadlessGLSupported ? test : skip)('HeadlessGLKernel.setUniform2f only calls context when values change', () => {
  setUniform2fTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform2fvTest(Kernel) {
  const canvas = {};
  const context = {
    uniform2fv: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform2fv('test', [1, 2]);
  assert.deepEqual(kernel.uniform2fvCache['test'], [1, 2]);

  throws = true;
  kernel.setUniform2fv('test', [1, 2]);
  assert.deepEqual(kernel.uniform2fvCache['test'], [1, 2]);

  throws = false;
  kernel.setUniform2fv('test', [2, 3]);
  assert.deepEqual(kernel.uniform2fvCache['test'], [2, 3]);
  kernel.destroy();
}
test('WebGLKernel.setUniform2fv only calls context when values change', () => {
  setUniform2fvTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform2fv only calls context when values change', () => {
  setUniform2fvTest(WebGL2Kernel);
});
test('HeadlessGLKernel.setUniform2fv only calls context when values change', () => {
  setUniform2fvTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform3fvTest(Kernel) {
  const canvas = {};
  const context = {
    uniform3fv: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform3fv('test', [1, 2, 3]);
  assert.deepEqual(kernel.uniform3fvCache['test'], [1, 2, 3]);

  throws = true;
  kernel.setUniform3fv('test', [1, 2, 3]);
  assert.deepEqual(kernel.uniform3fvCache['test'], [1, 2, 3]);

  throws = false;
  kernel.setUniform3fv('test', [2, 3, 4]);
  assert.deepEqual(kernel.uniform3fvCache['test'], [2, 3, 4]);
  kernel.destroy();
}
test('WebGLKernel.setUniform3fv only calls context when values change', () => {
  setUniform3fvTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform3fv only calls context when values change', () => {
  setUniform3fvTest(WebGL2Kernel);
});
test('HeadlessGLKernel.setUniform3fv only calls context when values change', () => {
  setUniform3fvTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform4ivTest(Kernel) {
  const canvas = {};
  const context = {
    uniform4iv: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform4iv('test', [1, 2, 3, 4]);
  assert.deepEqual(kernel.uniform4ivCache['test'], [1, 2, 3, 4]);

  throws = true;
  kernel.setUniform4iv('test', [1, 2, 3, 4]);
  assert.deepEqual(kernel.uniform4ivCache['test'], [1, 2, 3, 4]);

  throws = false;
  kernel.setUniform4iv('test', [2, 3, 4, 5]);
  assert.deepEqual(kernel.uniform4ivCache['test'], [2, 3, 4, 5]);
  kernel.destroy();
}
test('WebGLKernel.setUniform4iv only calls context when values change', () => {
  setUniform4ivTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform4iv only calls context when values change', () => {
  setUniform4ivTest(WebGL2Kernel);
});
test('HeadlessGLKernel.setUniform4iv only calls context when values change', () => {
  setUniform4ivTest(HeadlessGLKernel);
});

/**
 *
 * @param {Kernel} Kernel
 */
function setUniform4fvTest(Kernel) {
  const canvas = {};
  const context = {
    uniform4fv: () => {
      if (throws) new Error('This should not get called');
    },
    getUniformLocation: (name) => {
      return name;
    }
  };
  const kernel = new Kernel('function() {}', { canvas, context, output: [1] });
  let throws = false;
  kernel.setUniform4fv('test', [1, 2, 3, 4]);
  assert.deepEqual(kernel.uniform4fvCache['test'], [1, 2, 3, 4]);

  throws = true;
  kernel.setUniform4fv('test', [1, 2, 3, 4]);
  assert.deepEqual(kernel.uniform4fvCache['test'], [1, 2, 3, 4]);

  throws = false;
  kernel.setUniform4fv('test', [2, 3, 4, 5]);
  assert.deepEqual(kernel.uniform4fvCache['test'], [2, 3, 4, 5]);
  kernel.destroy();
}
test('WebGLKernel.setUniform4fv only calls context when values change', () => {
  setUniform4fvTest(WebGLKernel);
});
test('WebGL2Kernel.setUniform4fv only calls context when values change', () => {
  setUniform4fvTest(WebGL2Kernel);
});
test('HeadlessGLKernel.setUniform4fv only calls context when values change', () => {
  setUniform4fvTest(HeadlessGLKernel);
});

test('functionToIFunction with function', () => {
  const fn = function name() {};
  const result = Kernel.prototype.functionToIGPUFunction(fn);
  assert.deepEqual(result, {
    name: 'name',
    source: fn.toString(),
    argumentTypes: [],
    returnType: null
  });
});

test('functionToIFunction with function and argumentTypes array', () => {
  const fn = function name(a, b) {};
  const argumentTypes = ['number','string'];
  const result = Kernel.prototype.functionToIGPUFunction(fn, { argumentTypes });
  assert.deepEqual(result, {
    name: 'name',
    source: fn.toString(),
    argumentTypes: ['number', 'string'],
    returnType: null,
  });
});

test('functionToIFunction with function and argumentTypes object', () => {
  const fn = function name(a, b) {};
  const argumentTypes = { a: 'number', b: 'string' };
  const result = Kernel.prototype.functionToIGPUFunction(fn, { argumentTypes });
  assert.deepEqual(result, {
    name: 'name',
    source: fn.toString(),
    argumentTypes: ['number', 'string'],
    returnType: null,
  });
});

test('functionToIGPUFunction with function and returnType', () => {
  const fn = function name(a, b) {};
  const result = Kernel.prototype.functionToIGPUFunction(fn, { returnType: 'string' });
  assert.deepEqual(result, {
    name: 'name',
    source: fn.toString(),
    argumentTypes: [],
    returnType: 'string',
  });
});
