"use strict";

require("should");

var async = require("async"),
  fs = require("fs"),
  path = require("path"),
  zlib = require("zlib"),
  streams = require("stream"),
  RollingFileStream = require("../lib").RollingFileStream;

function remove(filename, cb) {
  fs.unlink(filename, function() {
    cb();
  });
}

function create(filename, cb) {
  fs.writeFile(filename, "test file", cb);
}

describe("RollingFileStream", function() {
  describe("arguments", function() {
    var stream;

    before(function(done) {
      remove(__dirname + "/test-rolling-file-stream", function() {
        stream = new RollingFileStream(
          __dirname + "/test-rolling-file-stream",
          1024,
          5
        );
        done();
      });
    });

    after(function(done) {
      remove(__dirname + "/test-rolling-file-stream", done);
    });

    it("should take a filename, file size (bytes), no. backups, return Writable", function() {
      stream.should.be.an.instanceOf(streams.Writable);
      stream.filename.should.eql(__dirname + "/test-rolling-file-stream");
      stream.size.should.eql(1024);
      stream.backups.should.eql(5);
    });

    it("should apply default settings to the underlying stream", function() {
      stream.theStream.mode.should.eql(420);
      stream.theStream.flags.should.eql("a");
    });
  });

  describe("with stream arguments", function() {
    it("should pass them to the underlying stream", function() {
      var stream = new RollingFileStream(
        __dirname + "/test-rolling-file-stream",
        1024,
        5,
        { mode: parseInt("0666", 8) }
      );
      stream.theStream.mode.should.eql(parseInt("0666", 8));
    });

    after(function(done) {
      remove(__dirname + "/test-rolling-file-stream", done);
    });
  });

  describe("without size", function() {
    it("should default to max int size", function() {
      var stream = new RollingFileStream(
        __dirname + "/test-rolling-file-stream"
      );
      stream.size.should.eql(Number.MAX_SAFE_INTEGER);
    });

    after(function(done) {
      remove(__dirname + "/test-rolling-file-stream", done);
    });
  });

  describe("without number of backups", function() {
    it("should default to 1 backup", function() {
      var stream = new RollingFileStream(
        __dirname + "/test-rolling-file-stream",
        1024
      );
      stream.backups.should.eql(1);
    });

    after(function(done) {
      remove(__dirname + "/test-rolling-file-stream", done);
    });
  });

  describe("writing less than the file size", function() {
    before(function(done) {
      remove(__dirname + "/test-rolling-file-stream-write-less", function() {
        var stream = new RollingFileStream(
          __dirname + "/test-rolling-file-stream-write-less",
          100
        );
        stream.write("cheese", "utf8", function() {
          stream.end(done);
        });
      });
    });

    after(function(done) {
      remove(__dirname + "/test-rolling-file-stream-write-less", done);
    });

    it("should write to the file", function(done) {
      fs.readFile(
        __dirname + "/test-rolling-file-stream-write-less",
        "utf8",
        function(err, contents) {
          contents.should.eql("cheese");
          done(err);
        }
      );
    });

    it("should write one file", function(done) {
      fs.readdir(__dirname, function(err, files) {
        files
          .filter(function(file) {
            return file.indexOf("test-rolling-file-stream-write-less") > -1;
          })
          .should.have.length(1);
        done(err);
      });
    });
  });

  describe("writing more than the file size", function() {
    before(function(done) {
      async.forEach(
        [
          __dirname + "/test-rolling-file-stream-write-more",
          __dirname + "/test-rolling-file-stream-write-more.1"
        ],
        remove,
        function() {
          var stream = new RollingFileStream(
            __dirname + "/test-rolling-file-stream-write-more",
            45
          );
          async.forEachSeries(
            [0, 1, 2, 3, 4, 5, 6],
            function(i, cb) {
              stream.write(i + ".cheese\n", "utf8", cb);
            },
            function() {
              stream.end(done);
            }
          );
        }
      );
    });

    after(function(done) {
      async.forEach(
        [
          __dirname + "/test-rolling-file-stream-write-more",
          __dirname + "/test-rolling-file-stream-write-more.1"
        ],
        remove,
        done
      );
    });

    it("should write two files", function(done) {
      fs.readdir(__dirname, function(err, files) {
        files
          .filter(function(file) {
            return file.indexOf("test-rolling-file-stream-write-more") > -1;
          })
          .should.have.length(2);
        done(err);
      });
    });

    it("should write the last two log messages to the first file", function(done) {
      fs.readFile(
        __dirname + "/test-rolling-file-stream-write-more",
        "utf8",
        function(err, contents) {
          contents.should.eql("5.cheese\n6.cheese\n");
          done(err);
        }
      );
    });

    it("should write the first five log messages to the second file", function(done) {
      fs.readFile(
        __dirname + "/test-rolling-file-stream-write-more.1",
        "utf8",
        function(err, contents) {
          contents.should.eql(
            "0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n"
          );
          done(err);
        }
      );
    });
  });

  describe("with options.compress = true", function() {
    before(function(done) {
      var stream = new RollingFileStream(
        path.join(__dirname, "compressed-backups.log"),
        30, //30 bytes max size
        2, //two backup files to keep
        { compress: true }
      );
      async.forEachSeries(
        [
          "This is the first log message.",
          "This is the second log message.",
          "This is the third log message.",
          "This is the fourth log message."
        ],
        function(i, cb) {
          stream.write(i + "\n", "utf8", cb);
        },
        function() {
          stream.end(done);
        }
      );
    });

    it("should produce three files, with the backups compressed", function(done) {
      fs.readdir(__dirname, function(err, files) {
        var testFiles = files
          .filter(function(f) {
            return f.indexOf("compressed-backups.log") > -1;
          })
          .sort();

        testFiles.length.should.eql(3);
        testFiles.should.eql([
          "compressed-backups.log",
          "compressed-backups.log.1.gz",
          "compressed-backups.log.2.gz"
        ]);

        fs.readFile(path.join(__dirname, testFiles[0]), "utf8", function(
          err,
          contents
        ) {
          contents.should.eql("This is the fourth log message.\n");

          zlib.gunzip(
            fs.readFileSync(path.join(__dirname, testFiles[1])),
            function(err, contents) {
              contents
                .toString("utf8")
                .should.eql("This is the third log message.\n");
              zlib.gunzip(
                fs.readFileSync(path.join(__dirname, testFiles[2])),
                function(err, contents) {
                  contents
                    .toString("utf8")
                    .should.eql("This is the second log message.\n");
                  done(err);
                }
              );
            }
          );
        });
      });
    });

    after(function(done) {
      async.forEach(
        [
          path.join(__dirname, "compressed-backups.log"),
          path.join(__dirname, "compressed-backups.log.1.gz"),
          path.join(__dirname, "compressed-backups.log.2.gz")
        ],
        remove,
        done
      );
    });
  });

  describe("with options.keepFileExt = true", function() {
    before(function(done) {
      var stream = new RollingFileStream(
        path.join(__dirname, "extKept-backups.log"),
        30, //30 bytes max size
        2, //two backup files to keep
        { keepFileExt: true }
      );
      async.forEachSeries(
        [
          "This is the first log message.",
          "This is the second log message.",
          "This is the third log message.",
          "This is the fourth log message."
        ],
        function(i, cb) {
          stream.write(i + "\n", "utf8", cb);
        },
        function() {
          stream.end(done);
        }
      );
    });

    it("should produce three files, with the file-extension kept", function(done) {
      fs.readdir(__dirname, function(err, files) {
        var testFiles = files
          .filter(function(f) {
            return f.indexOf("extKept-backups") > -1;
          })
          .sort();

        testFiles.length.should.eql(3);
        testFiles.should.eql([
          "extKept-backups.1.log",
          "extKept-backups.2.log",
          "extKept-backups.log"
        ]);

        fs.readFile(path.join(__dirname, testFiles[0]), "utf8", function(
          err,
          contents
        ) {
          contents.should.eql("This is the third log message.\n");

          fs.readFile(path.join(__dirname, testFiles[1]), "utf8", function(
            err,
            contents
          ) {
            contents
              .toString("utf8")
              .should.eql("This is the second log message.\n");
            fs.readFile(path.join(__dirname, testFiles[2]), "utf8", function(
              err,
              contents
            ) {
              contents
                .toString("utf8")
                .should.eql("This is the fourth log message.\n");
              done(err);
            });
          });
        });
      });
    });

    after(function(done) {
      async.forEach(
        [
          path.join(__dirname, "extKept-backups.log"),
          path.join(__dirname, "extKept-backups.1.log"),
          path.join(__dirname, "extKept-backups.2.log")
        ],
        remove,
        done
      );
    });
  });

  describe("with options.compress = true and keepFileExt = true", function() {
    before(function(done) {
      var stream = new RollingFileStream(
        path.join(__dirname, "compressed-backups.log"),
        30, //30 bytes max size
        2, //two backup files to keep
        { compress: true, keepFileExt: true }
      );
      async.forEachSeries(
        [
          "This is the first log message.",
          "This is the second log message.",
          "This is the third log message.",
          "This is the fourth log message."
        ],
        function(i, cb) {
          stream.write(i + "\n", "utf8", cb);
        },
        function() {
          stream.end(done);
        }
      );
    });

    it("should produce three files, with the backups compressed", function(done) {
      fs.readdir(__dirname, function(err, files) {
        var testFiles = files
          .filter(function(f) {
            return f.indexOf("compressed-backups") > -1;
          })
          .sort();

        testFiles.length.should.eql(3);
        testFiles.should.eql([
          "compressed-backups.1.log.gz",
          "compressed-backups.2.log.gz",
          "compressed-backups.log"
        ]);

        fs.readFile(path.join(__dirname, testFiles[2]), "utf8", function(
          err,
          contents
        ) {
          contents.should.eql("This is the fourth log message.\n");

          zlib.gunzip(
            fs.readFileSync(path.join(__dirname, testFiles[1])),
            function(err, contents) {
              contents
                .toString("utf8")
                .should.eql("This is the second log message.\n");
              zlib.gunzip(
                fs.readFileSync(path.join(__dirname, testFiles[0])),
                function(err, contents) {
                  contents
                    .toString("utf8")
                    .should.eql("This is the third log message.\n");
                  done(err);
                }
              );
            }
          );
        });
      });
    });

    after(function(done) {
      async.forEach(
        [
          path.join(__dirname, "compressed-backups.log"),
          path.join(__dirname, "compressed-backups.1.log.gz"),
          path.join(__dirname, "compressed-backups.2.log.gz")
        ],
        remove,
        done
      );
    });
  });

  describe("when many files already exist", function() {
    before(function(done) {
      async.forEach(
        [
          __dirname + "/test-rolling-stream-with-existing-files.11",
          __dirname + "/test-rolling-stream-with-existing-files.20",
          __dirname + "/test-rolling-stream-with-existing-files.-1",
          __dirname + "/test-rolling-stream-with-existing-files.1.1",
          __dirname + "/test-rolling-stream-with-existing-files.1"
        ],
        remove,
        function(err) {
          if (err) done(err);

          async.forEach(
            [
              __dirname + "/test-rolling-stream-with-existing-files.11",
              __dirname + "/test-rolling-stream-with-existing-files.20",
              __dirname + "/test-rolling-stream-with-existing-files.-1",
              __dirname + "/test-rolling-stream-with-existing-files.1.1",
              __dirname + "/test-rolling-stream-with-existing-files.1"
            ],
            create,
            function(err) {
              if (err) done(err);

              var stream = new RollingFileStream(
                __dirname + "/test-rolling-stream-with-existing-files",
                18,
                5
              );

              async.forEachSeries(
                [0, 1, 2, 3, 4, 5, 6],
                function(i, cb) {
                  stream.write(i + ".cheese\n", "utf8", cb);
                },
                function() {
                  stream.end(done);
                }
              );
            }
          );
        }
      );
    });

    after(function(done) {
      async.forEach(
        [
          __dirname + "/test-rolling-stream-with-existing-files.-1",
          __dirname + "/test-rolling-stream-with-existing-files",
          __dirname + "/test-rolling-stream-with-existing-files.1.1",
          __dirname + "/test-rolling-stream-with-existing-files.0",
          __dirname + "/test-rolling-stream-with-existing-files.1",
          __dirname + "/test-rolling-stream-with-existing-files.2",
          __dirname + "/test-rolling-stream-with-existing-files.3",
          __dirname + "/test-rolling-stream-with-existing-files.4",
          __dirname + "/test-rolling-stream-with-existing-files.5",
          __dirname + "/test-rolling-stream-with-existing-files.6",
          __dirname + "/test-rolling-stream-with-existing-files.11",
          __dirname + "/test-rolling-stream-with-existing-files.20"
        ],
        remove,
        done
      );
    });

    it("should roll the files, removing the highest indices", function(done) {
      fs.readdir(__dirname, function(err, files) {
        files.should.containEql("test-rolling-stream-with-existing-files");
        files.should.containEql("test-rolling-stream-with-existing-files.1");
        files.should.containEql("test-rolling-stream-with-existing-files.2");
        files.should.containEql("test-rolling-stream-with-existing-files.3");
        files.should.containEql("test-rolling-stream-with-existing-files.4");
        done(err);
      });
    });
  });

  describe("when the directory gets deleted", function() {
    var stream;
    before(function(done) {
      stream = new RollingFileStream(
        path.join("subdir", "test-rolling-file-stream"),
        5,
        5
      );
      stream.write("initial", "utf8", done);
    });

    after(function() {
      fs.unlinkSync(path.join("subdir", "test-rolling-file-stream"));
      fs.rmdirSync("subdir");
    });

    it("handles directory deletion gracefully", function(done) {
      stream.theStream.on("error", done);

      remove(path.join("subdir", "test-rolling-file-stream"), function() {
        fs.rmdir("subdir", function() {
          stream.write("rollover", "utf8", function() {
            fs.readFileSync(
              path.join("subdir", "test-rolling-file-stream"),
              "utf8"
            ).should.eql("rollover");
            done();
          });
        });
      });
    });
  });
});
