const fs = require("fs");
const { Transform } = require("stream");
const excel = require("@adncorp/excel-stream");
const request = require("request");
const validUrl = require("valid-url");

function getReadStream(file) {
  if (validUrl.isUri(file)) {
    return request(file);
  }
  return fs.createReadStream(file);
}

function ExcelParser() {}

ExcelParser.prototype.parse = function parse(
  file,
  parsingCallbackFn,
  batchSize,
  transformCallbackFn,
  endCallbackFn,
  headerCallBackFn,
  timeOut,
  destroyStatus,
  sheetIndex,
  skipLine,
  parseInBatch
) {
  const bSize = batchSize && typeof batchSize === "number" ? batchSize : 10000;
  // stream of input file
  const stream = getReadStream(file);
  const tOut = timeOut && typeof timeOut === "number" ? timeOut : 100;
  const sheetId = sheetIndex ? sheetIndex : 0;
  const skip = skipLine ? skipLine : 0;
  // convert excel to object stream
  stream
    .pipe(excel({ sheetIndex: sheetId, fieldSep: ";", delimiter: ";" }))
    .pipe(
      getTransformObject(
        parsingCallbackFn,
        bSize,
        transformCallbackFn,
        endCallbackFn,
        headerCallBackFn,
        tOut,
        destroyStatus ? destroyStatus : { value: false },
        skip,
        parseInBatch
      )
    );
};

// return transform stream which will do processing
function getTransformObject(
  parsingCallbackFn,
  batchSize,
  transformCallbackFn,
  endCallbackFn,
  headerCallBackFn,
  timeOut,
  destroy,
  skip,
  parseInBatch
) {
  const jsonToDb = new Transform({
    writableObjectMode: true,
    readableObjectMode: true,
    transform(chunk, encoding, callback) {
      if (this.index === 0 && typeof headerCallBackFn === "function") {
        headerCallBackFn(Object.keys(chunk));
      }
      this.index++;
      if (destroy.value) {
        callback();
        this.end();
      } else if (this.index <= skip) {
        callback();
      } else {
        this.records.push(Object.values(chunk));
        // batch processing of records
        if (this.records.length === batchSize) {
          applyCustomTransformation(
            this.records,
            transformCallbackFn,
            timeOut
          ).then(data => {
            if (!parseInBatch) {
              data.map(record => parsingCallbackFn(record));
            } else {
              parsingCallbackFn(data);
            }
            this.records = [];
            callback();
          });
        } else {
          callback();
        }
      }
    },
    flush(done) {
      if (this.records.length > 0) {
        applyCustomTransformation(
          this.records,
          transformCallbackFn,
          timeOut
        ).then(data => {
          if (!parseInBatch) {
            data.map(record => parsingCallbackFn(record));
          } else {
            parsingCallbackFn(data);
          }
          this.records = [];
          done();
          if (typeof endCallbackFn === "function") {
            endCallbackFn();
          }
        });
      } else {
        done();
        if (typeof endCallbackFn === "function") {
          endCallbackFn();
        }
      }
    }
  });
  jsonToDb.records = [];
  jsonToDb.index = 0;

  return jsonToDb;
}

// async function to process data
function applyCustomTransformation(elements, transformCallbackFn, timeOut) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(
        elements.map(
          element =>
            typeof transformCallbackFn === "function"
              ? transformCallbackFn(element)
              : element
        )
      );
    }, timeOut);
  });
}

module.exports = ExcelParser;
