const defaultArgs = require('./constants/defaultArgs');
const { setLogging, log } = require('./utils/log');
const resolvePaths = require('./utils/resolvePaths');
const parseProgress = require('./utils/parseProgress');
const stringList2pointer = require('./utils/stringList2pointer');
const {
  defaultOptions,
  getModule,
  fetchFile,
} = require('./node');

const NO_LOAD = Error('FFmpeg.js is not ready, make sure you have completed load().');
const NO_MULTIPLE_RUN = Error('FFmpeg.js can only run one command at a time');
let Module = null;
let ffmpeg = null;

module.exports = (_options = {}) => {
  let runResolve = null;
  let running = false;
  const {
    log: logging,
    logger,
    progress,
    ...options
  } = resolvePaths({
    ...defaultOptions,
    ..._options,
  });
  const detectCompletion = ({ message, type }) => {
    if (type === 'ffmpeg-stdout'
      && message === 'FFMPEG_END'
      && runResolve !== null) {
      runResolve();
      runResolve = null;
      running = false;
    }
  };

  setLogging(logging);

  const load = async () => {
    if (Module === null) {
      log('info', 'load ffmpeg-core');
      Module = await getModule(options);
      Module.setLogger((_log) => {
        detectCompletion(_log);
        parseProgress(_log, progress);
        logger(_log);
        log(_log.type, _log.message);
      });
      if (ffmpeg === null) {
        ffmpeg = Module.cwrap('proxy_main', 'number', ['number', 'number']);
      }
      log('info', 'ffmpeg-core loaded');
    }
  };

  const FS = (method, args) => {
    if (Module === null) {
      throw NO_LOAD;
    } else {
      log('info', `FS.${method} ${args[0]}`);
      return Module.FS[method](...args);
    }
  };

  const write = async (path, data) => (
    FS('writeFile', [path, await fetchFile(data)])
  );

  const writeText = (path, text) => (
    FS('writeFile', [path, text])
  );

  const read = (path) => (
    FS('readFile', [path])
  );

  const remove = (path) => (
    FS('unlink', [path])
  );

  const ls = (path) => (
    FS('readdir', [path])
  );

  const run = (_args) => {
    if (ffmpeg === null) {
      throw NO_LOAD;
    } else if (running) {
      throw NO_MULTIPLE_RUN;
    } else {
      running = true;
      return new Promise((resolve) => {
        const args = [...defaultArgs, ..._args.trim().split(' ')].filter((s) => s.length !== 0);
        log('info', `ffmpeg command: ${args.join(' ')}`);
        runResolve = resolve;
        ffmpeg(args.length, stringList2pointer(Module, args));
      });
    }
  };

  const transcode = (input, output, opts = '') => (
    run(`-i ${input} ${opts} ${output}`)
  );

  const trim = (input, output, from, to, opts = '') => (
    run(`-i ${input} -ss ${from} -to ${to} ${opts} ${output}`)
  );

  const concatDemuxer = (input, output, opts = '') => {
    const text = input.reduce((acc, path) => `${acc}\nfile ${path}`, '');
    writeText('concat_list.txt', text);
    return run(`-f concat -safe 0 -i concat_list.txt ${opts} ${output}`);
  };

  return {
    load,
    FS,
    write,
    writeText,
    read,
    remove,
    ls,
    run,
    transcode,
    trim,
    concatDemuxer,
  };
};
