import add from './add.js';
import connect from './connect.js';
import events from './events.js';
import {ingest, parse, preload, request} from './load.js';
import on from './on.js';
import {rank, rerank} from './rank.js';
import {enqueue, evaluate, getPulse, run, runAfter, runAsync} from './run.js';
import {pulse, touch, update} from './update.js';
import changeset from '../ChangeSet.js';
import Heap from '../util/Heap.js';
import UniqueList from '../util/UniqueList.js';
import {defaultLocale} from 'vega-format';
import {loader} from 'vega-loader';
import {Error, id, logger} from 'vega-util';

/**
 * A dataflow graph for reactive processing of data streams.
 * @constructor
 */
export default function Dataflow() {
  this.logger(logger());
  this.logLevel(Error);

  this._clock = 0;
  this._rank = 0;
  this._locale = defaultLocale();
  try {
    this._loader = loader();
  } catch (e) {
    // do nothing if loader module is unavailable
  }

  this._touched = UniqueList(id);
  this._input = {};
  this._pulse = null;

  this._heap = Heap((a, b) => a.qrank - b.qrank);
  this._postrun = [];
}

function logMethod(method) {
  return function() {
    return this._log[method].apply(this, arguments);
  };
}

Dataflow.prototype = {

  /**
   * The current timestamp of this dataflow. This value reflects the
   * timestamp of the previous dataflow run. The dataflow is initialized
   * with a stamp value of 0. The initial run of the dataflow will have
   * a timestap of 1, and so on. This value will match the
   * {@link Pulse.stamp} property.
   * @return {number} - The current timestamp value.
   */
  stamp() {
    return this._clock;
  },

  /**
   * Gets or sets the loader instance to use for data file loading. A
   * loader object must provide a "load" method for loading files and a
   * "sanitize" method for checking URL/filename validity. Both methods
   * should accept a URI and options hash as arguments, and return a Promise
   * that resolves to the loaded file contents (load) or a hash containing
   * sanitized URI data with the sanitized url assigned to the "href" property
   * (sanitize).
   * @param {object} _ - The loader instance to use.
   * @return {object|Dataflow} - If no arguments are provided, returns
   *   the current loader instance. Otherwise returns this Dataflow instance.
   */
  loader(_) {
    if (arguments.length) {
      this._loader = _;
      return this;
    } else {
      return this._loader;
    }
  },

  /**
   * Gets or sets the locale instance to use for formatting and parsing
   * string values. The locale object should be provided by the
   * vega-format library, and include methods such as format, timeFormat,
   * utcFormat, timeParse, and utcParse.
   * @param {object} _ - The locale instance to use.
   * @return {object|Dataflow} - If no arguments are provided, returns
   *   the current locale instance. Otherwise returns this Dataflow instance.
   */
  locale(_) {
    if (arguments.length) {
      this._locale = _;
      return this;
    } else {
      return this._locale;
    }
  },

  /**
   * Get or set the logger instance used to log messages. If no arguments are
   * provided, returns the current logger instance. Otherwise, sets the logger
   * and return this Dataflow instance. Provided loggers must support the full
   * API of logger objects generated by the vega-util logger method. Note that
   * by default the log level of the new logger will be used; use the logLevel
   * method to adjust the log level as needed.
   */
  logger(logger) {
    if (arguments.length) {
      this._log = logger;
      return this;
    } else {
      return this._log;
    }
  },

  /**
   * Logs an error message. By default, logged messages are written to console
   * output. The message will only be logged if the current log level is high
   * enough to permit error messages.
   */
  error: logMethod('error'),

  /**
   * Logs a warning message. By default, logged messages are written to console
   * output. The message will only be logged if the current log level is high
   * enough to permit warning messages.
   */
  warn: logMethod('warn'),

  /**
   * Logs a information message. By default, logged messages are written to
   * console output. The message will only be logged if the current log level is
   * high enough to permit information messages.
   */
  info: logMethod('info'),

  /**
   * Logs a debug message. By default, logged messages are written to console
   * output. The message will only be logged if the current log level is high
   * enough to permit debug messages.
   */
  debug: logMethod('debug'),

  /**
   * Get or set the current log level. If an argument is provided, it
   * will be used as the new log level.
   * @param {number} [level] - Should be one of None, Warn, Info
   * @return {number} - The current log level.
   */
  logLevel: logMethod('level'),

  /**
   * Empty entry threshold for garbage cleaning. Map data structures will
   * perform cleaning once the number of empty entries exceeds this value.
   */
  cleanThreshold: 1e4,

  // OPERATOR REGISTRATION
  add,
  connect,
  rank,
  rerank,

  // OPERATOR UPDATES
  pulse,
  touch,
  update,
  changeset,

  // DATA LOADING
  ingest,
  parse,
  preload,
  request,

  // EVENT HANDLING
  events,
  on,

  // PULSE PROPAGATION
  evaluate,
  run,
  runAsync,
  runAfter,
  _enqueue: enqueue,
  _getPulse: getPulse
};
