/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineLazyGetter(this, "ZipWriter", function () {
  return Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter");
});

const OPEN_FLAGS = {
  RDONLY: parseInt("0x01", 16),
  WRONLY: parseInt("0x02", 16),
  CREATE_FILE: parseInt("0x08", 16),
  APPEND: parseInt("0x10", 16),
  TRUNCATE: parseInt("0x20", 16),
  EXCL: parseInt("0x80", 16),
};

function formatDate(date) {
  const year = String(date.getFullYear() % 100).padStart(2, "0");
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hour = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");

  return `${year}-${month}-${day} ${hour}-${minutes}-${seconds}`;
}

/**
 * Helper API for HAR export features.
 */
var HarUtils = {
  getHarFileName(defaultFileName, jsonp, compress, hostname, pathname = "") {
    const extension = jsonp ? ".harp" : ".har";

    const now = new Date();
    let name = defaultFileName.replace(/%date/g, formatDate(now));
    name = name.replace(/%hostname/g, hostname + pathname);
    name = name.replace(/\:/gm, "-", "");
    name = name.replace(/\//gm, "_", "");

    let fileName = name + extension;

    // Default file extension is zip if compressing is on.
    if (compress) {
      fileName += ".zip";
    }

    return fileName;
  },

  /**
   * Save HAR string into a given file. The file might be compressed
   * if specified in the options.
   *
   * @param {File} file Target file where the HAR string (JSON)
   * should be stored.
   * @param {string} jsonString HAR data (JSON or JSONP)
   * @param {boolean} compress The result file is zipped if set to true.
   */
  saveToFile(file, jsonString, compress) {
    const openFlags =
      OPEN_FLAGS.WRONLY | OPEN_FLAGS.CREATE_FILE | OPEN_FLAGS.TRUNCATE;

    try {
      const foStream = Cc[
        "@mozilla.org/network/file-output-stream;1"
      ].createInstance(Ci.nsIFileOutputStream);

      const permFlags = parseInt("0666", 8);
      foStream.init(file, openFlags, permFlags, 0);

      const convertor = Cc[
        "@mozilla.org/intl/converter-output-stream;1"
      ].createInstance(Ci.nsIConverterOutputStream);
      convertor.init(foStream, "UTF-8");

      // The entire jsonString can be huge so, write the data in chunks.
      const chunkLength = 1024 * 1024;
      for (let i = 0; i <= jsonString.length; i++) {
        const data = jsonString.substr(i, chunkLength + 1);
        if (data) {
          convertor.writeString(data);
        }

        i = i + chunkLength;
      }

      // this closes foStream
      convertor.close();
    } catch (err) {
      console.error(err);
      return false;
    }

    // If no compressing then bail out.
    if (!compress) {
      return true;
    }

    // Remember name of the original file, it'll be replaced by a zip file.
    const originalFilePath = file.path;
    const originalFileName = file.leafName;

    try {
      // Rename using unique name (the file is going to be removed).
      file.moveTo(null, "temp" + new Date().getTime() + "temphar");

      // Create compressed file with the original file path name.
      const zipFile = Cc["@mozilla.org/file/local;1"].createInstance(
        Ci.nsIFile
      );
      zipFile.initWithPath(originalFilePath);

      // The file within the zipped file doesn't use .zip extension.
      let fileName = originalFileName;
      if (fileName.indexOf(".zip") == fileName.length - 4) {
        fileName = fileName.substr(0, fileName.indexOf(".zip"));
      }

      const zip = new ZipWriter();
      zip.open(zipFile, openFlags);
      zip.addEntryFile(
        fileName,
        Ci.nsIZipWriter.COMPRESSION_DEFAULT,
        file,
        false
      );
      zip.close();

      // Remove the original file (now zipped).
      file.remove(true);
      return true;
    } catch (err) {
      console.error(err);

      // Something went wrong (disk space?) rename the original file back.
      file.moveTo(null, originalFileName);
    }

    return false;
  },

  getLocalDirectory(path) {
    let dir;

    if (!path) {
      dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
      dir.append("har");
      dir.append("logs");
    } else {
      dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      dir.initWithPath(path);
    }

    return dir;
  },
};

// Exports from this module
exports.HarUtils = HarUtils;
