'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var index = require('./utils/index.js');
var readableStream = require('readable-stream');
var all = require('it-all');
var last = require('it-last');
var drain = require('it-drain');
var supports_js = require('ipfs-utils/src/supports.js');
var globSource = require('ipfs-utils/src/files/glob-source.js');
var env_js = require('ipfs-utils/src/env.js');
var chai_js = require('aegir/utils/chai.js');
var mocha = require('./utils/mocha.js');
var fromString = require('uint8arrays/from-string');
var bufferStream = require('it-buffer-stream');
var raw = require('multiformats/codecs/raw');
var dagPB = require('@ipld/dag-pb');
var resolve = require('aegir/utils/resolve.js');
var sha2 = require('multiformats/hashes/sha2');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n["default"] = e;
  return Object.freeze(n);
}

var all__default = /*#__PURE__*/_interopDefaultLegacy(all);
var last__default = /*#__PURE__*/_interopDefaultLegacy(last);
var drain__default = /*#__PURE__*/_interopDefaultLegacy(drain);
var globSource__default = /*#__PURE__*/_interopDefaultLegacy(globSource);
var bufferStream__default = /*#__PURE__*/_interopDefaultLegacy(bufferStream);
var raw__namespace = /*#__PURE__*/_interopNamespace(raw);
var dagPB__namespace = /*#__PURE__*/_interopNamespace(dagPB);
var resolve__default = /*#__PURE__*/_interopDefaultLegacy(resolve);

function testAddAll(factory, options) {
  const describe = mocha.getDescribe(options);
  const it = mocha.getIt(options);
  describe('.addAll', function () {
    this.timeout(120 * 1000);
    let ipfs;
    async function testMode(mode, expectedMode) {
      const content = String(Math.random() + Date.now());
      const files = await all__default["default"](ipfs.addAll([{
          content: fromString.fromString(content),
          mode
        }]));
      chai_js.expect(files).to.have.length(1);
      chai_js.expect(files).to.have.nested.property('[0].mode', expectedMode);
      const stats = await ipfs.files.stat(`/ipfs/${ files[0].cid }`);
      chai_js.expect(stats).to.have.property('mode', expectedMode);
    }
    async function testMtime(mtime, expectedMtime) {
      const content = String(Math.random() + Date.now());
      const files = await all__default["default"](ipfs.addAll([{
          content: fromString.fromString(content),
          mtime
        }]));
      chai_js.expect(files).to.have.length(1);
      chai_js.expect(files).to.have.deep.nested.property('[0].mtime', expectedMtime);
      const stats = await ipfs.files.stat(`/ipfs/${ files[0].cid }`);
      chai_js.expect(stats).to.have.deep.property('mtime', expectedMtime);
    }
    before(async () => {
      ipfs = (await factory.spawn()).api;
    });
    after(() => factory.clean());
    it('should add a File as array of tuples', async function () {
      if (!supports_js.supportsFileReader) {
        return this.skip('skip in node');
      }
      const tuple = {
        path: 'filename.txt',
        content: new self.File(['should add a File'], 'filename.txt', { type: 'text/plain' })
      };
      const filesAdded = await all__default["default"](ipfs.addAll([tuple]));
      chai_js.expect(filesAdded[0].cid.toString()).to.be.eq('QmTVfLxf3qXiJgr4KwG6UBckcNvTqBp93Rwy5f7h3mHsVC');
    });
    it('should add a Uint8Array as array of tuples', async () => {
      const tuple = {
        path: 'testfile.txt',
        content: index.fixtures.smallFile.data
      };
      const filesAdded = await all__default["default"](ipfs.addAll([tuple]));
      chai_js.expect(filesAdded).to.have.length(1);
      const file = filesAdded[0];
      chai_js.expect(file.cid.toString()).to.equal(index.fixtures.smallFile.cid.toString());
      chai_js.expect(file.path).to.equal('testfile.txt');
    });
    it('should add array of objects with readable stream content', async function () {
      if (!env_js.isNode) {
        this.skip('Only node supports readable streams');
      }
      const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS';
      const rs = new readableStream.Readable();
      rs.push(fromString.fromString('some data'));
      rs.push(null);
      const tuple = {
        path: 'data.txt',
        content: rs
      };
      const filesAdded = await all__default["default"](ipfs.addAll([tuple]));
      chai_js.expect(filesAdded).to.be.length(1);
      const file = filesAdded[0];
      chai_js.expect(file.path).to.equal('data.txt');
      chai_js.expect(file.size).to.equal(17);
      chai_js.expect(file.cid.toString()).to.equal(expectedCid);
    });
    it('should add a nested directory as array of tupples', async function () {
      const content = name => ({
        path: `test-folder/${ name }`,
        content: index.fixtures.directory.files[name]
      });
      const emptyDir = name => ({ path: `test-folder/${ name }` });
      const dirs = [
        content('pp.txt'),
        content('holmes.txt'),
        content('jungle.txt'),
        content('alice.txt'),
        emptyDir('empty-folder'),
        content('files/hello.txt'),
        content('files/ipfs.txt'),
        emptyDir('files/empty')
      ];
      const root = await last__default["default"](ipfs.addAll(dirs));
      if (!root) {
        throw new Error('Dirs were not loaded');
      }
      chai_js.expect(root.path).to.equal('test-folder');
      chai_js.expect(root.cid.toString()).to.equal(index.fixtures.directory.cid.toString());
    });
    it('should add a nested directory as array of tupples with progress', async function () {
      const content = name => ({
        path: `test-folder/${ name }`,
        content: index.fixtures.directory.files[name]
      });
      const emptyDir = name => ({
        path: `test-folder/${ name }`,
        content: undefined
      });
      const progressSizes = {};
      const dirs = [
        content('pp.txt'),
        content('holmes.txt'),
        content('jungle.txt'),
        content('alice.txt'),
        emptyDir('empty-folder'),
        content('files/hello.txt'),
        content('files/ipfs.txt'),
        emptyDir('files/empty')
      ];
      const total = dirs.reduce((acc, curr) => {
        if (curr.content) {
          acc[curr.path] = curr.content.length;
        }
        return acc;
      }, {});
      const handler = (bytes, path) => {
        if (path) {
          progressSizes[path] = bytes;
        }
      };
      const root = await last__default["default"](ipfs.addAll(dirs, { progress: handler }));
      chai_js.expect(progressSizes).to.deep.equal(total);
      chai_js.expect(root).to.have.property('path', 'test-folder');
      chai_js.expect(root).to.have.deep.property('cid', index.fixtures.directory.cid);
    });
    it('should receive progress path as empty string when adding content without paths', async function () {
      const content = name => index.fixtures.directory.files[name];
      const progressSizes = {};
      const dirs = [
        content('pp.txt'),
        content('holmes.txt'),
        content('jungle.txt')
      ];
      const total = { '': dirs.reduce((acc, curr) => acc + curr.length, 0) };
      const handler = (bytes, path) => {
        progressSizes[`${ path }`] = bytes;
      };
      await drain__default["default"](ipfs.addAll(dirs, { progress: handler }));
      chai_js.expect(progressSizes).to.deep.equal(total);
    });
    it('should receive file name from progress event', async () => {
      const receivedNames = [];
      function handler(p, name) {
        if (name) {
          receivedNames.push(name);
        }
      }
      await drain__default["default"](ipfs.addAll([
        {
          content: 'hello',
          path: 'foo.txt'
        },
        {
          content: 'world',
          path: 'bar.txt'
        }
      ], {
        progress: handler,
        wrapWithDirectory: true
      }));
      chai_js.expect(receivedNames).to.deep.equal([
        'foo.txt',
        'bar.txt'
      ]);
    });
    it('should add files to a directory non sequentially', async function () {
      const content = path => ({
        path: `test-dir/${ path }`,
        content: index.fixtures.directory.files[path.split('/').pop() || '']
      });
      const input = [
        content('a/pp.txt'),
        content('a/holmes.txt'),
        content('b/jungle.txt'),
        content('a/alice.txt')
      ];
      const filesAdded = await all__default["default"](ipfs.addAll(input));
      const toPath = ({path}) => path;
      const nonSeqDirFilePaths = input.map(toPath).filter(p => p.includes('/a/'));
      const filesAddedPaths = filesAdded.map(toPath);
      chai_js.expect(nonSeqDirFilePaths.every(p => filesAddedPaths.includes(p))).to.be.true();
    });
    it('should fail when passed invalid input', async () => {
      const nonValid = 138;
      await chai_js.expect(all__default["default"](ipfs.addAll(nonValid))).to.eventually.be.rejected();
    });
    it('should fail when passed single file objects', async () => {
      const nonValid = { content: 'hello world' };
      await chai_js.expect(all__default["default"](ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/);
    });
    it('should fail when passed single strings', async () => {
      const nonValid = 'hello world';
      await chai_js.expect(all__default["default"](ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/);
    });
    it('should fail when passed single buffers', async () => {
      const nonValid = fromString.fromString('hello world');
      await chai_js.expect(all__default["default"](ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/);
    });
    it('should wrap content in a directory', async () => {
      const data = {
        path: 'testfile.txt',
        content: index.fixtures.smallFile.data
      };
      const filesAdded = await all__default["default"](ipfs.addAll([data], { wrapWithDirectory: true }));
      chai_js.expect(filesAdded).to.have.length(2);
      const file = filesAdded[0];
      const wrapped = filesAdded[1];
      chai_js.expect(file.cid.toString()).to.equal(index.fixtures.smallFile.cid.toString());
      chai_js.expect(file.path).to.equal('testfile.txt');
      chai_js.expect(wrapped.path).to.equal('');
    });
    it('should add a directory with only-hash=true', async function () {
      this.slow(10 * 1000);
      const content = String(Math.random() + Date.now());
      const files = await all__default["default"](ipfs.addAll([
        {
          path: '/foo/bar.txt',
          content: fromString.fromString(content)
        },
        {
          path: '/foo/baz.txt',
          content: fromString.fromString(content)
        }
      ], { onlyHash: true }));
      chai_js.expect(files).to.have.length(3);
      await Promise.all(files.map(file => chai_js.expect(ipfs.object.get(file.cid, { timeout: 4000 })).to.eventually.be.rejected().and.to.have.property('name').that.equals('TimeoutError')));
    });
    it('should add with mode as string', async function () {
      this.slow(10 * 1000);
      const mode = '0777';
      await testMode(mode, parseInt(mode, 8));
    });
    it('should add with mode as number', async function () {
      this.slow(10 * 1000);
      const mode = parseInt('0777', 8);
      await testMode(mode, mode);
    });
    it('should add with mtime as Date', async function () {
      this.slow(10 * 1000);
      const mtime = new Date(5000);
      await testMtime(mtime, {
        secs: 5,
        nsecs: 0
      });
    });
    it('should add with mtime as { nsecs, secs }', async function () {
      this.slow(10 * 1000);
      const mtime = {
        secs: 5,
        nsecs: 0
      };
      await testMtime(mtime, mtime);
    });
    it('should add with mtime as timespec', async function () {
      this.slow(10 * 1000);
      await testMtime({
        Seconds: 5,
        FractionalNanoseconds: 0
      }, {
        secs: 5,
        nsecs: 0
      });
    });
    it('should add with mtime as hrtime', async function () {
      this.slow(10 * 1000);
      const mtime = process.hrtime();
      await testMtime(mtime, {
        secs: mtime[0],
        nsecs: mtime[1]
      });
    });
    it('should add a directory from the file system', async function () {
      if (!env_js.isNode)
        this.skip();
      const filesPath = resolve__default["default"]('test/fixtures/test-folder', 'interface-ipfs-core');
      const result = await all__default["default"](ipfs.addAll(globSource__default["default"](filesPath, '**/*')));
      chai_js.expect(result.length).to.be.above(8);
    });
    it('should add a directory from the file system with an odd name', async function () {
      if (!env_js.isNode)
        this.skip();
      const filesPath = resolve__default["default"]('test/fixtures/weird name folder [v0]', 'interface-ipfs-core');
      const result = await all__default["default"](ipfs.addAll(globSource__default["default"](filesPath, '**/*')));
      chai_js.expect(result.length).to.be.above(8);
    });
    it('should ignore a directory from the file system', async function () {
      if (!env_js.isNode)
        this.skip();
      const filesPath = resolve__default["default"]('test/fixtures/test-folder', 'interface-ipfs-core');
      const result = await all__default["default"](ipfs.addAll(globSource__default["default"](filesPath, '@(!(files*))')));
      chai_js.expect(result.length).to.equal(6);
    });
    it('should add a file from the file system', async function () {
      if (!env_js.isNode)
        this.skip();
      const filePath = resolve__default["default"]('test/fixtures/test-folder', 'interface-ipfs-core');
      const result = await all__default["default"](ipfs.addAll(globSource__default["default"](filePath, 'ipfs-add.js')));
      chai_js.expect(result.length).to.equal(1);
      chai_js.expect(result[0].path).to.equal('ipfs-add.js');
    });
    it('should add a hidden file in a directory from the file system', async function () {
      if (!env_js.isNode)
        this.skip();
      const filesPath = resolve__default["default"]('test/fixtures', 'interface-ipfs-core');
      const result = await all__default["default"](ipfs.addAll(globSource__default["default"](filesPath, 'hidden-files-folder/**/*', { hidden: true })));
      chai_js.expect(result.map(object => object.path)).to.include('hidden-files-folder/.hiddenTest.txt');
      chai_js.expect(result.map(object => object.cid.toString())).to.include('QmdbAjVmLRdpFyi8FFvjPfhTGB2cVXvWLuK7Sbt38HXrtt');
    });
    it('should add a file with only-hash=true', async function () {
      if (!env_js.isNode)
        this.skip();
      this.slow(10 * 1000);
      const out = await all__default["default"](ipfs.addAll([{ content: fromString.fromString('hello world') }], { onlyHash: true }));
      await chai_js.expect(ipfs.object.get(out[0].cid, { timeout: 500 })).to.eventually.be.rejected().and.to.have.property('name').that.equals('TimeoutError');
    });
    it('should add all with sha2-256 by default', async function () {
      const content = String(Math.random() + Date.now());
      const files = await all__default["default"](ipfs.addAll([content]));
      chai_js.expect(files).to.have.nested.property('[0].cid.multihash.code', sha2.sha256.code);
    });
    it('should add all with a different hashing algorithm', async function () {
      const content = String(Math.random() + Date.now());
      const files = await all__default["default"](ipfs.addAll([content], { hashAlg: 'sha2-512' }));
      chai_js.expect(files).to.have.nested.property('[0].cid.multihash.code', sha2.sha512.code);
    });
    it('should respect raw leaves when file is smaller than one block and no metadata is present', async () => {
      const files = await all__default["default"](ipfs.addAll([Uint8Array.from([
          0,
          1,
          2
        ])], {
        cidVersion: 1,
        rawLeaves: true
      }));
      chai_js.expect(files.length).to.equal(1);
      chai_js.expect(files[0].cid.toString()).to.equal('bafkreifojmzibzlof6xyh5auu3r5vpu5l67brf3fitaf73isdlglqw2t7q');
      chai_js.expect(files[0].cid.code).to.equal(raw__namespace.code);
      chai_js.expect(files[0].size).to.equal(3);
    });
    it('should override raw leaves when file is smaller than one block and metadata is present', async () => {
      const files = await all__default["default"](ipfs.addAll([{
          content: Uint8Array.from([
            0,
            1,
            2
          ]),
          mode: 83,
          mtime: {
            secs: 1000,
            nsecs: 0
          }
        }], {
        cidVersion: 1,
        rawLeaves: true
      }));
      chai_js.expect(files.length).to.equal(1);
      chai_js.expect(files[0].cid.toString()).to.equal('bafybeifmayxiu375ftlgydntjtffy5cssptjvxqw6vyuvtymntm37mpvua');
      chai_js.expect(files[0].cid.code).to.equal(dagPB__namespace.code);
      chai_js.expect(files[0].size).to.equal(18);
    });
    it('should add directories with metadata', async () => {
      const files = await all__default["default"](ipfs.addAll([{
          path: '/foo',
          mode: 83,
          mtime: {
            secs: 1000,
            nsecs: 0
          }
        }]));
      chai_js.expect(files.length).to.equal(1);
      chai_js.expect(files[0].cid.toString()).to.equal('QmaZTosBmPwo9LQ48ESPCEcNuX2kFxkpXYy8i3rxqBdzRG');
      chai_js.expect(files[0].cid.code).to.equal(dagPB__namespace.code);
      chai_js.expect(files[0].size).to.equal(11);
    });
    it('should support bidirectional streaming', async function () {
      let progressInvoked = false;
      const handler = (bytes, path) => {
        progressInvoked = true;
      };
      const source = async function* () {
        yield {
          content: 'hello',
          path: '/file'
        };
        await new Promise(resolve => {
          const interval = setInterval(() => {
            if (progressInvoked) {
              clearInterval(interval);
              resolve(null);
            }
          }, 10);
        });
      };
      await drain__default["default"](ipfs.addAll(source(), {
        progress: handler,
        fileImportConcurrency: 1
      }));
      chai_js.expect(progressInvoked).to.be.true();
    });
    it('should error during add-all stream', async function () {
      const source = async function* () {
        yield {
          content: 'hello',
          path: '/file'
        };
        yield {
          content: 'hello',
          path: '/file'
        };
      };
      await chai_js.expect(drain__default["default"](ipfs.addAll(source(), {
        fileImportConcurrency: 1,
        chunker: 'rabin-2048--50'
      }))).to.eventually.be.rejectedWith(/Chunker parameter avg must be an integer/);
    });
    it('should add big files', async function () {
      const totalSize = 1024 * 1024 * 200;
      const chunkSize = 1024 * 1024 * 99;
      const source = async function* () {
        yield {
          path: '/dir/file-200mb-1',
          content: bufferStream__default["default"](totalSize, { chunkSize })
        };
        yield {
          path: '/dir/file-200mb-2',
          content: bufferStream__default["default"](totalSize, { chunkSize })
        };
      };
      const results = await all__default["default"](ipfs.addAll(source()));
      chai_js.expect(await ipfs.files.stat(`/ipfs/${ results[0].cid }`)).to.have.property('size', totalSize);
      chai_js.expect(await ipfs.files.stat(`/ipfs/${ results[1].cid }`)).to.have.property('size', totalSize);
    });
  });
}

exports.testAddAll = testAddAll;
