// META: global=window,worker
'use strict';

class LipFuzzTransformer {
  constructor(substitutions) {
    this.substitutions = substitutions;
    this.partialChunk = '';
    this.lastIndex = undefined;
  }

  transform(chunk, controller) {
    chunk = this.partialChunk + chunk;
    this.partialChunk = '';
    // lastIndex is the index of the first character after the last substitution.
    this.lastIndex = 0;
    chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
    // Regular expression for an incomplete template at the end of a string.
    const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
    // Avoid looking at any characters that have already been substituted.
    partialAtEndRegexp.lastIndex = this.lastIndex;
    this.lastIndex = undefined;
    const match = partialAtEndRegexp.exec(chunk);
    if (match) {
      this.partialChunk = chunk.substring(match.index);
      chunk = chunk.substring(0, match.index);
    }
    controller.enqueue(chunk);
  }

  flush(controller) {
    if (this.partialChunk.length > 0) {
      controller.enqueue(this.partialChunk);
    }
  }

  replaceTag(match, p1, offset) {
    let replacement = this.substitutions[p1];
    if (replacement === undefined) {
      replacement = '';
    }
    this.lastIndex = offset + replacement.length;
    return replacement;
  }
}

const substitutions = {
  in1: 'out1',
  in2: 'out2',
  quine: '{{quine}}',
  bogusPartial: '{{incompleteResult}'
};

const cases = [
  {
    input: [''],
    output: ['']
  },
  {
    input: [],
    output: []
  },
  {
    input: ['{{in1}}'],
    output: ['out1']
  },
  {
    input: ['z{{in1}}'],
    output: ['zout1']
  },
  {
    input: ['{{in1}}q'],
    output: ['out1q']
  },
  {
    input: ['{{in1}}{{in1}'],
    output: ['out1', '{{in1}']
  },
  {
    input: ['{{in1}}{{in1}', '}'],
    output: ['out1', 'out1']
  },
  {
    input: ['{{in1', '}}'],
    output: ['', 'out1']
  },
  {
    input: ['{{', 'in1}}'],
    output: ['', 'out1']
  },
  {
    input: ['{', '{in1}}'],
    output: ['', 'out1']
  },
  {
    input: ['{{', 'in1}'],
    output: ['', '', '{{in1}']
  },
  {
    input: ['{'],
    output: ['', '{']
  },
  {
    input: ['{', ''],
    output: ['', '', '{']
  },
  {
    input: ['{', '{', 'i', 'n', '1', '}', '}'],
    output: ['', '', '', '', '', '', 'out1']
  },
  {
    input: ['{{in1}}{{in2}}{{in1}}'],
    output: ['out1out2out1']
  },
  {
    input: ['{{wrong}}'],
    output: ['']
  },
  {
    input: ['{{wron', 'g}}'],
    output: ['', '']
  },
  {
    input: ['{{quine}}'],
    output: ['{{quine}}']
  },
  {
    input: ['{{bogusPartial}}'],
    output: ['{{incompleteResult}']
  },
  {
    input: ['{{bogusPartial}}}'],
    output: ['{{incompleteResult}}']
  }
];

for (const testCase of cases) {
  const inputChunks = testCase.input;
  const outputChunks = testCase.output;
  promise_test(() => {
    const lft = new TransformStream(new LipFuzzTransformer(substitutions));
    const writer = lft.writable.getWriter();
    const promises = [];
    for (const inputChunk of inputChunks) {
      promises.push(writer.write(inputChunk));
    }
    promises.push(writer.close());
    const reader = lft.readable.getReader();
    let readerChain = Promise.resolve();
    for (const outputChunk of outputChunks) {
      readerChain = readerChain.then(() => {
        return reader.read().then(({ value, done }) => {
          assert_false(done, `done should be false when reading ${outputChunk}`);
          assert_equals(value, outputChunk, `value should match outputChunk`);
        });
      });
    }
    readerChain = readerChain.then(() => {
      return reader.read().then(({ done }) => assert_true(done, `done should be true`));
    });
    promises.push(readerChain);
    return Promise.all(promises);
  }, `testing "${inputChunks}" (length ${inputChunks.length})`);
}
