text: "use strict";
  
  /*global require*/
  var clone = require("terriajs-cesium/Source/Core/clone").default;
  var defaultValue = require("terriajs-cesium/Source/Core/defaultValue").default;
  var defined = require("terriajs-cesium/Source/Core/defined").default;
  
  var DeveloperError = require("terriajs-cesium/Source/Core/DeveloperError")
    .default;
  
  var Resource = require("terriajs-cesium/Source/Core/Resource").default;
  var when = require("terriajs-cesium/Source/ThirdParty/when").default;
  
  var inherit = require("../Core/inherit");
  var Metadata = require("./Metadata");
  var TableCatalogItem = require("./TableCatalogItem");
  var TerriaError = require("../Core/TerriaError");
  var proxyCatalogItemUrl = require("./proxyCatalogItemUrl");
  var readText = require("../Core/readText");
  var TableStructure = require("../Map/TableStructure");
  var i18next = require("i18next").default;
  
  /**
   * A {@link CatalogItem} representing CSV data.
   *
   * @alias CsvCatalogItem
   * @constructor
   * @extends TableCatalogItem
   *
   * @param {Terria} terria The Terria instance.
   * @param {String} [url] The URL from which to retrieve the CSV data.
   * @param {Object} [options] Initial values.
   * @param {Boolean} [options.isCsvForCharting] Whether this is solely for charting
   * @param {TableStyle} [options.tableStyle] An initial table style can be supplied if desired.
   */
  var CsvCatalogItem = function(terria, url, options) {
    TableCatalogItem.call(this, terria, url, options);
  
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  
    /**
     * Gets or sets the character set of the data, which overrides the file information if present. Default is undefined.
     * This property is observable.
     * @type {String}
     */
    this.charSet = undefined;
  
    /**
     * Some catalog items are created from other catalog items.
     * Record here so that the user (eg. via "About this Dataset") can reference the source item.
     * @type {CatalogItem}
     */
    this.sourceCatalogItem = undefined;
    this.sourceCatalogItemId = undefined;
    this.regenerationOptions = {};
  
    /**
     * Options for the value of the animation timeline at start. Valid options in config file are:
     *     initialTimeSource: "present"                            // closest to today's date
     *     initialTimeSource: "start"                              // start of time range of animation
     *     initialTimeSource: "end"                                // end of time range of animation
     *     initialTimeSource: An ISO8601 date e.g. "2015-08-08"    // specified date or nearest if date is outside range
     * @type {String}
     */
    this.initialTimeSource = undefined;
  
    /**
     * Flag to be used for charting, whether this item is generated for the purposes of drawing a chart
     * @type {Boolean}
     */
    this.isCsvForCharting = defaultValue(options.isCsvForCharting, false);
  
    /**
     * A HTML string to show above the chart as a disclaimer
     * @type {String}
     * @default null
     */
    this.chartDisclaimer = defaultValue(options.chartDisclaimer, null);
  };
  
  inherit(TableCatalogItem, CsvCatalogItem);
  
  Object.defineProperties(CsvCatalogItem.prototype, {
    /**
     * Gets the type of data member represented by this instance.
     * @memberOf CsvCatalogItem.prototype
     * @type {String}
     */
    type: {
      get: function() {
        return "csv";
      }
    },
  
    /**
     * Gets a human-readable name for this type of data source, 'CSV'.
     * @memberOf CsvCatalogItem.prototype
     * @type {String}
     */
    typeName: {
      get: function() {
        return i18next.t("models.csv.name");
      }
    },
  
    /**
     * Gets the metadata associated with this data source and the server that provided it, if applicable.
     * @memberOf CsvCatalogItem.prototype
     * @type {Metadata}
     */
    metadata: {
      //TODO: return metadata if tableDataSource defined
      get: function() {
        var result = new Metadata();
        result.isLoading = false;
        result.dataSourceErrorMessage = i18next.t(
          "models.csv.dataSourceErrorMessage"
        );
        result.serviceErrorMessage = i18next.t("models.csv.serviceErrorMessage");
        return result;
      }
    },
  
    /**
     * Gets the data source associated with this catalog item.
     * @memberOf CsvCatalogItem.prototype
     * @type {DataSource}
     */
    dataSource: {
      get: function() {
        return this._dataSource;
      }
    },
  
    /**
     * Gets the set of names of the properties to be serialized for this object when {@link CatalogMember#serializeToJson} is called
     * for a share link.
     * @memberOf ImageryLayerCatalogItem.prototype
     * @type {String[]}
     */
    propertiesForSharing: {
      get: function() {
        return CsvCatalogItem.defaultPropertiesForSharing;
      }
    }
  });
  
  CsvCatalogItem.defaultUpdaters = clone(TableCatalogItem.defaultUpdaters);
  
  CsvCatalogItem.defaultUpdaters.sourceCatalogItem = function() {
    // TODO: For now, don't update from JSON. Better to do it via an id?
  };
  
  Object.freeze(CsvCatalogItem.defaultUpdaters);
  
  CsvCatalogItem.defaultSerializers = clone(TableCatalogItem.defaultSerializers);
  
  CsvCatalogItem.defaultSerializers.sourceCatalogItem = function() {
    // TODO: For now, don't serialize. Can we do it via an id?
  };
  
  Object.freeze(CsvCatalogItem.defaultSerializers);
  
  CsvCatalogItem.defaultPropertiesForSharing = clone(
    TableCatalogItem.defaultPropertiesForSharing
  );
  CsvCatalogItem.defaultPropertiesForSharing.push("isCsvForCharting");
  CsvCatalogItem.defaultPropertiesForSharing.push("dataUrl");
  CsvCatalogItem.defaultPropertiesForSharing.push("sourceCatalogItemId");
  CsvCatalogItem.defaultPropertiesForSharing.push("regenerationOptions");
  
  Object.freeze(CsvCatalogItem.defaultPropertiesForSharing);
  
  /**
   * Loads the TableStructure from a csv file.
   *
   * @param {CsvCatalogItem} item Item that tableDataSource is created for
   * @param {String} csvString String in csv format.
   * @return {Promise} A promise that resolves to true if it is a recognised format.
   * @private
   */
  function loadTableFromCsv(item, csvString) {
    var tableStyle = item._tableStyle;
    var options = {
      idColumnNames: item.idColumns,
      isSampled: item.isSampled,
      initialTimeSource: item.initialTimeSource,
      displayDuration: tableStyle.displayDuration,
      replaceWithNullValues: tableStyle.replaceWithNullValues,
      replaceWithZeroValues: tableStyle.replaceWithZeroValues,
      columnOptions: tableStyle.columns // may contain per-column replacements for these
    };
    var tableStructure = new TableStructure(undefined, options);
    tableStructure.loadFromCsv(csvString);
    return item.initializeFromTableStructure(tableStructure);
  }
  
  /**
   * Regenerates a chart from a given itemJson
   * @param {Object} itemsJson The items as simple JSON data. The JSON should be in the form of an object literal, not a
   *                 string
   * @return {Promise} A promise which resolves to the newly created CsvCatalogItem
   */
  CsvCatalogItem.regenerateChartItem = function(itemJson, terria) {
    // we have a csv with url so regenerate, reimplements some of `makeNewCatalogItem()` in
    // lib/ReactViews/Custom/Chart/ChartExpandAndDownloadButtons.jsx
    const newItem = new CsvCatalogItem(terria, itemJson.url, {
      tableStyle: itemJson.tableStyle,
      isCsvForCharting: true
    });
    const group = terria.catalog.chartDataGroup;
    newItem.name = itemJson.name;
    newItem.id = group.uniqueId + "/" + itemJson.name;
    group.isOpen = true;
    group.add(newItem);
    newItem.isLoading = true;
    newItem.isMappable = false;
    terria.catalog.addChartableItem(newItem); // Notify the chart panel so it shows "loading".
  
    // if we have sourceCatalogItemId and it's a SOS item, use the tablestructure from that to load
    if (itemJson.sourceCatalogItemId) {
      const sourceCatalogItem =
        terria.catalog.shareKeyIndex[itemJson.sourceCatalogItemId];
      newItem.sourceCatalogItem = sourceCatalogItem;
      if (
        defined(sourceCatalogItem) &&
        sourceCatalogItem.type === "sos" &&
        defined(itemJson.regenerationOptions) &&
        defined(sourceCatalogItem.load)
      ) {
        return newItem
          .updateFromJson(itemJson)
          .then(sourceCatalogItem.load.bind(sourceCatalogItem))
          .then(() => {
            newItem.data = sourceCatalogItem.loadIntoTableStructure(
              itemJson.url,
              itemJson.regenerationOptions
            );
            newItem.isEnabled = true;
          })
          .then(newItem.load.bind(newItem))
          .then(() =>
            newItem.applyTableStyleColumnsToStructure(
              itemJson.tableStyle,
              newItem.tableStructure
            )
          );
      } else {
        console.error(
          "Csv regeneration referenced a sourceCatalogItemId that we could not look up"
        );
      }
    }
  
    newItem.isEnabled = true; // This loads it as well.
  
    return newItem
      .updateFromJson(itemJson)
      .then(newItem.load.bind(newItem))
      .then(function() {
        return newItem.applyTableStyleColumnsToStructure(
          itemJson.tableStyle,
          newItem.tableStructure
        );
      });
  };
  /**
   * Loads csv data from a URL into a (usually temporary) table structure.
   * This is required by Chart.jsx for any non-csv format.
   * @param  {String} url The URL.
   * @return {Promise} A promise which resolves to a table structure.
   */
  CsvCatalogItem.prototype.loadIntoTableStructure = function(url) {
    const item = this;
    const tableStructure = new TableStructure();
    // Note item is only used for its 'terria', 'forceProxy' and 'cacheDuration' properties
    // (which are all defined on CatalogMember, the base class of CatalogItem).
    return loadTextWithMime(proxyCatalogItemUrl(item, url, "0d")).then(
      tableStructure.loadFromCsv.bind(tableStructure)
    );
  };
  
  /**
   * Every <polling.seconds> seconds, if the csvItem is enabled,
   * request data from the polling.url || url, and update/replace this._tableStructure.
   */
  CsvCatalogItem.prototype.startPolling = function() {
    const polling = this.polling;
  
    if (defined(polling.seconds) && polling.seconds > 0) {
      var item = this;
  
      // Initialise polling and timer variables, because they might not be set yet
      polling.isPolling = item.isEnabled;
  
      if (!defined(polling.nextScheduledUpdateTime)) {
        const tempDate = new Date();
        tempDate.setSeconds(new Date().getSeconds() + polling.seconds);
        polling.nextScheduledUpdateTime = tempDate;
      }
  
      this._pollTimeout = setTimeout(function() {
        if (item.isEnabled) {
          polling.isPolling = true;
          item
            .loadIntoTableStructure(polling.url || item.url)
            .then(function(newTable) {
              // Update timestamp
              const tempDate = new Date();
              tempDate.setSeconds(new Date().getSeconds() + polling.seconds);
              polling.nextScheduledUpdateTime = tempDate;
  
              if (
                item._tableStructure.hasLatitudeAndLongitude !==
                  newTable.hasLatitudeAndLongitude ||
                item._tableStructure.columns.length !== newTable.columns.length
              ) {
                console.log(
                  "The newly polled data is incompatible with the old data."
                );
                throw new DeveloperError(
                  "The newly polled data is incompatible with the old data."
                );
              }
              // Maintain active item and colors.  Assume same column ordering for now.
              item._tableStructure.columns.forEach(function(column, i) {
                newTable.columns[i].isActive = column.isActive;
                newTable.columns[i].color = column.color;
              });
              if (polling.replace) {
                item._tableStructure.columns = newTable.columns;
              } else {
                if (defined(item.idColumns) && item.idColumns.length > 0) {
                  item._tableStructure.merge(newTable);
                } else {
                  item._tableStructure.append(newTable);
                }
              }
            });
        }
        // update isPolling - if the item is disabled then we are not polling
        polling.isPolling = false;
  
        // Note this means the timer keeps going even when you remove (disable) the item,
        // but it doesn't actually request new data any more.
        // If the item is re-enabled, the same timer just starts picking it up again.
        item.startPolling();
      }, polling.seconds * 1000);
    }
  };
  
  /**
   * @returns {Promise} A promise that resolves when the load is complete, or undefined if the function is already loaded.
   */
  CsvCatalogItem.prototype._load = function() {
    var that = this;
    const sourceCatalogItem = this.terria.catalog.shareKeyIndex[
      this.sourceCatalogItemId
    ];
  
    const sourceIsSos = sourceCatalogItem && sourceCatalogItem.type === "sos";
    const urlToUse = this.url || this.dataUrl;
    // For sos sourced charts, we need it to be ready first so we can assign the promise from
    // SensorObservationServiceCatalogItem's `loadIntoTableStructure` to data and load it that way
    // Otherwise we don't know how to parse the result via loadTableFromCsv
    if (!defined(this.data) && sourceIsSos) {
      return;
    }
    if (defined(this.data)) {
      return when(that.data, function(data) {
        if (typeof Blob !== "undefined" && data instanceof Blob) {
          return readText(data).then(function(text) {
            return loadTableFromCsv(that, text);
          });
        } else if (typeof data === "string") {
          return loadTableFromCsv(that, data);
        } else if (data instanceof TableStructure) {
          that.applyTableStyleColumnsToStructure(that._tableStyle, data);
          return that.initializeFromTableStructure(data);
        } else {
          throw new TerriaError({
            sender: that,
            title: i18next.t("models.csv.unexpectedTypeTitle"),
            message:
              i18next.t("models.csv.unexpectedTypeMessage", {
                appName: that.terria.appName
              }) +
              '<a href="mailto:' +
              that.terria.supportEmail +
              '">' +
              that.terria.supportEmail +
              "</a>."
          });
        }
      });
    } else if (defined(urlToUse)) {
      var overrideMimeType;
      if (defined(that.charSet)) {
        overrideMimeType = "text/csv; charset=" + that.charSet;
      }
      return loadTextWithMime(
        proxyCatalogItemUrl(that, urlToUse, "1d"),
        undefined,
        overrideMimeType
      )
        .then(function(text) {
          return loadTableFromCsv(that, text);
        })
        .otherwise(function(e) {
          throw new TerriaError({
            sender: that,
            title: i18next.t("models.csv.unableToLoadTitle"),
            message: i18next.t("models.csv.unableToLoadMessage", {
              message: e.message || e.response
            })
          });
        });
    } else {
      throw new TerriaError({
        sender: that,
        title: i18next.t("models.csv.unableToLoadItemTitle"),
        message: i18next.t("models.csv.unableToLoadItemMessage")
      });
    }
  };
  
  /**
   * The same as terriajs-cesium/Source/Core/loadText, but with the ability to pass overrideMimeType through to loadWithXhr.
   * @private
   */
  function loadTextWithMime(url, headers, overrideMimeType) {
    return Resource.fetch({
      url: url,
      headers: headers,
      overrideMimeType: overrideMimeType
    });
  }
  
  module.exports = CsvCatalogItem;
  