import {
  kMaximumLimitBaseParams,
  MaximumLimitValueTest,
  MaximumTestValue,
  makeLimitTestGroup,
  LimitTestsImpl,
} from './limit_utils.js';

/**
 * Given a 3 dimensional size, and a limit, compute
 * the smallest volume with more then limit units.
 */
function getClosestSizeOverLimit(size: number[], limit: number) {
  let closest = Number.MAX_SAFE_INTEGER;
  let closestSize: number[] = [];
  const depthLimit = Math.min(limit, size[2]);
  for (let depth = 1; depth <= depthLimit; ++depth) {
    for (let height = 1; height <= size[1]; ++height) {
      const planeSize = depth * height;
      if (planeSize <= limit) {
        const width = Math.min(size[0], Math.ceil(limit / planeSize));
        const num = width * planeSize;
        const dist = num - limit;
        if (dist > 0 && dist < closest) {
          closest = dist;
          closestSize = [width, height, depth];
        }
      }
    }
  }
  return closestSize;
}

/**
 * Given a 3 dimensional size, and a limit, compute
 * the largest volume with limit or less units.
 */
function getClosestSizeUnderOrAtLimit(size: number[], limit: number) {
  let closest = Number.MAX_SAFE_INTEGER;
  let closestSize: number[] = [];
  const depthLimit = Math.min(limit, size[2]);
  for (let depth = 1; depth <= depthLimit; ++depth) {
    for (let height = 1; height <= size[1]; ++height) {
      const planeSize = depth * height;
      if (planeSize <= limit) {
        const width = Math.min(size[0], Math.floor(limit / planeSize));
        const num = width * planeSize;
        const dist = limit - num;
        if (dist < closest) {
          closest = dist;
          closestSize = [width, height, depth];
        }
      }
    }
  }
  return closestSize;
}

function getDeviceLimitToRequest(
  limitValueTest: MaximumLimitValueTest,
  defaultLimit: number,
  maximumLimit: number
) {
  switch (limitValueTest) {
    case 'atDefault':
      return defaultLimit;
    case 'underDefault':
      return defaultLimit - 1;
    case 'betweenDefaultAndMaximum':
      return Math.floor((defaultLimit + maximumLimit) / 2);
    case 'atMaximum':
      return maximumLimit;
    case 'overMaximum':
      return maximumLimit + 1;
  }
}

function getTestWorkgroupSize(
  t: LimitTestsImpl,
  testValueName: MaximumTestValue,
  requestedLimit: number
) {
  const maxDimensions = [
    t.getDefaultLimit('maxComputeWorkgroupSizeX'),
    t.getDefaultLimit('maxComputeWorkgroupSizeY'),
    t.getDefaultLimit('maxComputeWorkgroupSizeZ'),
  ];

  switch (testValueName) {
    case 'atLimit':
      return getClosestSizeUnderOrAtLimit(maxDimensions, requestedLimit);
    case 'overLimit':
      return getClosestSizeOverLimit(maxDimensions, requestedLimit);
  }
}

function getDeviceLimitToRequestAndValueToTest(
  t: LimitTestsImpl,
  limitValueTest: MaximumLimitValueTest,
  testValueName: MaximumTestValue,
  defaultLimit: number,
  maximumLimit: number
) {
  const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
  const workgroupSize = getTestWorkgroupSize(t, testValueName, requestedLimit);
  return {
    requestedLimit,
    workgroupSize,
  };
}

const limit = 'maxComputeInvocationsPerWorkgroup';
export const { g, description } = makeLimitTestGroup(limit);

g.test('createComputePipeline,at_over')
  .desc(`Test using createComputePipeline(Async) at and over ${limit} limit`)
  .params(kMaximumLimitBaseParams.combine('async', [false, true] as const))
  .fn(async t => {
    const { limitTest, testValueName, async } = t.params;
    const { defaultLimit, adapterLimit: maximumLimit } = t;

    const { requestedLimit, workgroupSize } = getDeviceLimitToRequestAndValueToTest(
      t,
      limitTest,
      testValueName,
      defaultLimit,
      maximumLimit
    );
    const testValue = workgroupSize.reduce((a, b) => a * b, 1);

    await t.testDeviceWithSpecificLimits(
      requestedLimit,
      testValue,
      async ({ testValue, actualLimit, shouldError }) => {
        const { module, code } = t.getModuleForWorkgroupSize(workgroupSize);

        await t.testCreatePipeline(
          'createComputePipeline',
          async,
          module,
          shouldError,
          `workgroupSize: [${workgroupSize}], size: ${testValue}, limit: ${actualLimit}\n${code}`
        );
      }
    );
  });
