// Test that constructor for the node with name |nodeName| handles the
// various possible values for channelCount, channelCountMode, and
// channelInterpretation.

// The |should| parameter is the test function from new |Audit|.
function testAudioNodeOptions(should, context, nodeName, expectedNodeOptions) {
  if (expectedNodeOptions === undefined)
    expectedNodeOptions = {};
  let node;

  // Test that we can set channelCount and that errors are thrown for
  // invalid values
  let testChannelCount = 17;
  if (expectedNodeOptions.channelCount) {
    testChannelCount = expectedNodeOptions.channelCount.value;
  }
  should(
      () => {
        node = new window[nodeName](
            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
              channelCount: testChannelCount
            }));
      },
      'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})')
      .notThrow();
  should(node.channelCount, 'node.channelCount').beEqualTo(testChannelCount);

  if (expectedNodeOptions.channelCount &&
      expectedNodeOptions.channelCount.isFixed) {
    // The channel count is fixed.  Verify that we throw an error if
    // we try to change it. Arbitrarily set the count to be one more
    // than the expected value.
    testChannelCount = expectedNodeOptions.channelCount.value + 1;
    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelCount: testChannelCount}));
        },
        'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})')
        .throw(DOMException,
               expectedNodeOptions.channelCount.exceptionType);
    // And test that setting it to the fixed value does not throw.
    testChannelCount = expectedNodeOptions.channelCount.value;
    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelCount: testChannelCount}));
          node.channelCount = testChannelCount;
        },
        '(new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '})).channelCount = ' + testChannelCount)
        .notThrow();
  } else {
    // The channel count is not fixed.  Try to set the count to invalid
    // values and make sure an error is thrown.
    [0, 99].forEach(testValue => {
      should(() => {
        node = new window[nodeName](
            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
              channelCount: testValue
            }));
      }, `new ${nodeName}(c, {channelCount: ${testValue}})`)
          .throw(DOMException, 'NotSupportedError');
    });
  }

  // Test channelCountMode
  let testChannelCountMode = 'max';
  if (expectedNodeOptions.channelCountMode) {
    testChannelCountMode = expectedNodeOptions.channelCountMode.value;
  }
  should(
      () => {
        node = new window[nodeName](
            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
              channelCountMode: testChannelCountMode
            }));
      },
      'new ' + nodeName + '(c, {channelCountMode: "' + testChannelCountMode +
          '"}')
      .notThrow();
  should(node.channelCountMode, 'node.channelCountMode')
      .beEqualTo(testChannelCountMode);

  if (expectedNodeOptions.channelCountMode &&
      expectedNodeOptions.channelCountMode.isFixed) {
    // Channel count mode is fixed.  Test setting to something else throws.
    ['max', 'clamped-max', 'explicit'].forEach(testValue => {
      if (testValue !== expectedNodeOptions.channelCountMode.value) {
        should(
            () => {
              node = new window[nodeName](
                  context,
                  Object.assign(
                      {}, expectedNodeOptions.additionalOptions,
                      {channelCountMode: testValue}));
            },
            `new ${nodeName}(c, {channelCountMode: "${testValue}"})`)
            .throw(DOMException,
                   expectedNodeOptions.channelCountMode.exceptionType);
      } else {
        // Test that explicitly setting the fixed value is allowed.
        should(
            () => {
              node = new window[nodeName](
                  context,
                  Object.assign(
                      {}, expectedNodeOptions.additionalOptions,
                      {channelCountMode: testValue}));
              node.channelCountMode = testValue;
            },
            `(new ${nodeName}(c, {channelCountMode: "${testValue}"})).channelCountMode = "${testValue}"`)
            .notThrow();
      }
    });
  } else {
    // Mode is not fixed. Verify that we can set the mode to all valid
    // values, and that we throw for invalid values.

    let testValues = ['max', 'clamped-max', 'explicit'];

    testValues.forEach(testValue => {
      should(() => {
        node = new window[nodeName](
            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
              channelCountMode: testValue
            }));
      }, `new ${nodeName}(c, {channelCountMode: "${testValue}"})`).notThrow();
      should(
          node.channelCountMode, 'node.channelCountMode after valid setter')
          .beEqualTo(testValue);

    });

    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelCountMode: 'foobar'}));
        },
        'new ' + nodeName + '(c, {channelCountMode: "foobar"}')
        .throw(TypeError);
    should(node.channelCountMode, 'node.channelCountMode after invalid setter')
        .beEqualTo(testValues[testValues.length - 1]);
  }

  // Test channelInterpretation
  if (expectedNodeOptions.channelInterpretation &&
      expectedNodeOptions.channelInterpretation.isFixed) {
    // The channel interpretation is fixed.  Verify that we throw an
    // error if we try to change it.
    ['speakers', 'discrete'].forEach(testValue => {
      if (testValue !== expectedNodeOptions.channelInterpretation.value) {
        should(
            () => {
              node = new window[nodeName](
                  context,
                  Object.assign(
                      {}, expectedNodeOptions.additionOptions,
                      {channelInterpretation: testValue}));
            },
            `new ${nodeName}(c, {channelInterpretation: "${testValue}"})`)
            .throw(DOMException,
                   expectedNodeOptions.channelCountMode.exceptionType);
      } else {
        // Check that assigning the fixed value is OK.
        should(
            () => {
              node = new window[nodeName](
                  context,
                  Object.assign(
                      {}, expectedNodeOptions.additionOptions,
                      {channelInterpretation: testValue}));
              node.channelInterpretation = testValue;
            },
            `(new ${nodeName}(c, {channelInterpretation: "${testValue}"})).channelInterpretation = "${testValue}"`)
            .notThrow();
      }
    });
  } else {
    // Channel interpretation is not fixed. Verify that we can set it
    // to all possible values.
    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelInterpretation: 'speakers'}));
        },
        'new ' + nodeName + '(c, {channelInterpretation: "speakers"})')
        .notThrow();
    should(node.channelInterpretation, 'node.channelInterpretation')
        .beEqualTo('speakers');

    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelInterpretation: 'discrete'}));
        },
        'new ' + nodeName + '(c, {channelInterpretation: "discrete"})')
        .notThrow();
    should(node.channelInterpretation, 'node.channelInterpretation')
        .beEqualTo('discrete');

    should(
        () => {
          node = new window[nodeName](
              context,
              Object.assign(
                  {}, expectedNodeOptions.additionalOptions,
                  {channelInterpretation: 'foobar'}));
        },
        'new ' + nodeName + '(c, {channelInterpretation: "foobar"})')
        .throw(TypeError);
    should(
        node.channelInterpretation,
        'node.channelInterpretation after invalid setter')
        .beEqualTo('discrete');
  }
}

function initializeContext(should) {
  let c;
  should(() => {
    c = new OfflineAudioContext(1, 1, 48000);
  }, 'context = new OfflineAudioContext(...)').notThrow();

  return c;
}

function testInvalidConstructor(should, name, context) {
  should(() => {
    new window[name]();
  }, 'new ' + name + '()').throw(TypeError);
  should(() => {
    new window[name](1);
  }, 'new ' + name + '(1)').throw(TypeError);
  should(() => {
    new window[name](context, 42);
  }, 'new ' + name + '(context, 42)').throw(TypeError);
}

function testDefaultConstructor(should, name, context, options) {
  let node;

  let message = options.prefix + ' = new ' + name + '(context';
  if (options.constructorOptions)
    message += ', ' + JSON.stringify(options.constructorOptions);
  message += ')'

  should(() => {
    node = new window[name](context, options.constructorOptions);
  }, message).notThrow();

  should(node instanceof window[name], options.prefix + ' instanceof ' + name)
      .beEqualTo(true);
  should(node.numberOfInputs, options.prefix + '.numberOfInputs')
      .beEqualTo(options.numberOfInputs);
  should(node.numberOfOutputs, options.prefix + '.numberOfOutputs')
      .beEqualTo(options.numberOfOutputs);
  should(node.channelCount, options.prefix + '.channelCount')
      .beEqualTo(options.channelCount);
  should(node.channelCountMode, options.prefix + '.channelCountMode')
      .beEqualTo(options.channelCountMode);
  should(node.channelInterpretation, options.prefix + '.channelInterpretation')
      .beEqualTo(options.channelInterpretation);

  return node;
}

function testDefaultAttributes(should, node, prefix, items) {
  items.forEach((item) => {
    let attr = node[item.name];
    if (attr instanceof AudioParam) {
      should(attr.value, prefix + '.' + item.name + '.value')
          .beEqualTo(item.value);
    } else {
      should(attr, prefix + '.' + item.name).beEqualTo(item.value);
    }
  });
}
