"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.copyData = copyData;
exports.DataSplitter = void 0;

function _builderUtilRuntime() {
  const data = require("builder-util-runtime");

  _builderUtilRuntime = function () {
    return data;
  };

  return data;
}

var _fs = require("fs");

function _stream() {
  const data = require("stream");

  _stream = function () {
    return data;
  };

  return data;
}

function _downloadPlanBuilder() {
  const data = require("./downloadPlanBuilder");

  _downloadPlanBuilder = function () {
    return data;
  };

  return data;
}

const DOUBLE_CRLF = Buffer.from("\r\n\r\n");
var ReadState;

(function (ReadState) {
  ReadState[ReadState["INIT"] = 0] = "INIT";
  ReadState[ReadState["HEADER"] = 1] = "HEADER";
  ReadState[ReadState["BODY"] = 2] = "BODY";
})(ReadState || (ReadState = {}));

function copyData(task, out, oldFileFd, reject, resolve) {
  const readStream = (0, _fs.createReadStream)("", {
    fd: oldFileFd,
    autoClose: false,
    start: task.start,
    // end is inclusive
    end: task.end - 1
  });
  readStream.on("error", reject);
  readStream.once("end", resolve);
  readStream.pipe(out, {
    end: false
  });
}

class DataSplitter extends _stream().Writable {
  constructor(out, options, partIndexToTaskIndex, boundary, partIndexToLength, finishHandler) {
    super();
    this.out = out;
    this.options = options;
    this.partIndexToTaskIndex = partIndexToTaskIndex;
    this.partIndexToLength = partIndexToLength;
    this.finishHandler = finishHandler;
    this.partIndex = -1;
    this.headerListBuffer = null;
    this.readState = ReadState.INIT;
    this.ignoreByteCount = 0;
    this.remainingPartDataCount = 0;
    this.actualPartLength = 0;
    this.boundaryLength = boundary.length + 4;
    /* size of \r\n-- */
    // first chunk doesn't start with \r\n

    this.ignoreByteCount = this.boundaryLength - 2;
  }

  get isFinished() {
    return this.partIndex === this.partIndexToLength.length;
  } // noinspection JSUnusedGlobalSymbols


  _write(data, encoding, callback) {
    if (this.isFinished) {
      console.error(`Trailing ignored data: ${data.length} bytes`);
      return;
    }

    this.handleData(data).then(callback).catch(callback);
  }

  async handleData(chunk) {
    let start = 0;

    if (this.ignoreByteCount !== 0 && this.remainingPartDataCount !== 0) {
      throw (0, _builderUtilRuntime().newError)("Internal error", "ERR_DATA_SPLITTER_BYTE_COUNT_MISMATCH");
    }

    if (this.ignoreByteCount > 0) {
      const toIgnore = Math.min(this.ignoreByteCount, chunk.length);
      this.ignoreByteCount -= toIgnore;
      start = toIgnore;
    } else if (this.remainingPartDataCount > 0) {
      const toRead = Math.min(this.remainingPartDataCount, chunk.length);
      this.remainingPartDataCount -= toRead;
      await this.processPartData(chunk, 0, toRead);
      start = toRead;
    }

    if (start === chunk.length) {
      return;
    }

    if (this.readState === ReadState.HEADER) {
      const headerListEnd = this.searchHeaderListEnd(chunk, start);

      if (headerListEnd === -1) {
        return;
      }

      start = headerListEnd;
      this.readState = ReadState.BODY; // header list is ignored, we don't need it

      this.headerListBuffer = null;
    }

    while (true) {
      if (this.readState === ReadState.BODY) {
        this.readState = ReadState.INIT;
      } else {
        this.partIndex++;
        let taskIndex = this.partIndexToTaskIndex.get(this.partIndex);

        if (taskIndex == null) {
          if (this.isFinished) {
            taskIndex = this.options.end;
          } else {
            throw (0, _builderUtilRuntime().newError)("taskIndex is null", "ERR_DATA_SPLITTER_TASK_INDEX_IS_NULL");
          }
        }

        const prevTaskIndex = this.partIndex === 0 ? this.options.start : this.partIndexToTaskIndex.get(this.partIndex - 1) + 1
        /* prev part is download, next maybe copy */
        ;

        if (prevTaskIndex < taskIndex) {
          await this.copyExistingData(prevTaskIndex, taskIndex);
        } else if (prevTaskIndex > taskIndex) {
          throw (0, _builderUtilRuntime().newError)("prevTaskIndex must be < taskIndex", "ERR_DATA_SPLITTER_TASK_INDEX_ASSERT_FAILED");
        }

        if (this.isFinished) {
          this.onPartEnd();
          this.finishHandler();
          return;
        }

        start = this.searchHeaderListEnd(chunk, start);

        if (start === -1) {
          this.readState = ReadState.HEADER;
          return;
        }
      }

      const partLength = this.partIndexToLength[this.partIndex];
      const end = start + partLength;
      const effectiveEnd = Math.min(end, chunk.length);
      await this.processPartStarted(chunk, start, effectiveEnd);
      this.remainingPartDataCount = partLength - (effectiveEnd - start);

      if (this.remainingPartDataCount > 0) {
        return;
      }

      start = end + this.boundaryLength;

      if (start >= chunk.length) {
        this.ignoreByteCount = this.boundaryLength - (chunk.length - end);
        return;
      }
    }
  }

  copyExistingData(index, end) {
    return new Promise((resolve, reject) => {
      const w = () => {
        if (index === end) {
          resolve();
          return;
        }

        const task = this.options.tasks[index];

        if (task.kind !== _downloadPlanBuilder().OperationKind.COPY) {
          reject(new Error("Task kind must be COPY"));
          return;
        }

        copyData(task, this.out, this.options.oldFileFd, reject, () => {
          index++;
          w();
        });
      };

      w();
    });
  }

  searchHeaderListEnd(chunk, readOffset) {
    const headerListEnd = chunk.indexOf(DOUBLE_CRLF, readOffset);

    if (headerListEnd !== -1) {
      return headerListEnd + DOUBLE_CRLF.length;
    } // not all headers data were received, save to buffer


    const partialChunk = readOffset === 0 ? chunk : chunk.slice(readOffset);

    if (this.headerListBuffer == null) {
      this.headerListBuffer = partialChunk;
    } else {
      this.headerListBuffer = Buffer.concat([this.headerListBuffer, partialChunk]);
    }

    return -1;
  }

  onPartEnd() {
    const expectedLength = this.partIndexToLength[this.partIndex - 1];

    if (this.actualPartLength !== expectedLength) {
      throw (0, _builderUtilRuntime().newError)(`Expected length: ${expectedLength} differs from actual: ${this.actualPartLength}`, "ERR_DATA_SPLITTER_LENGTH_MISMATCH");
    }

    this.actualPartLength = 0;
  }

  processPartStarted(data, start, end) {
    if (this.partIndex !== 0) {
      this.onPartEnd();
    }

    return this.processPartData(data, start, end);
  }

  processPartData(data, start, end) {
    this.actualPartLength += end - start;
    const out = this.out;

    if (out.write(start === 0 && data.length === end ? data : data.slice(start, end))) {
      return Promise.resolve();
    } else {
      return new Promise((resolve, reject) => {
        out.on("error", reject);
        out.once("drain", () => {
          out.removeListener("error", reject);
          resolve();
        });
      });
    }
  }

} exports.DataSplitter = DataSplitter;
// __ts-babel@6.0.4
//# sourceMappingURL=DataSplitter.js.map