import config from 'web-client/config/environment';

const POLL_TIME = config.delay.videoUploader.pollStatusInterval;

/**
 * This class manages status polling for video updates.
 */
export default class VideoProcessor {
  /**
   * Create a VideoProcessor.
   *
   * @param {function} enqueueTask - A callback that the VideoProcessor will call when it
   * has work to do.
   */
  constructor(enqueueTask) {
    this._enqueueTask = enqueueTask;
    this.videos = new IndexedList();
    this._tickCount = 0;
  }

  /**
   * Add a video to the internal list of videos to be updated.
   *
   * @param {...WorkItem} videos - A video WorkItem to keep up-to-date
   * @return {undefined}
   */
  push(...videos) {
    let wasEmpty = this.videos.isEmpty;

    videos.forEach((video) => {
      if (this.videos.has(video)) {
        return;
      }

      this.videos.push(video);
    });

    if (wasEmpty && !this.videos.isEmpty) {
      this._tickLater();
    }
  }

  /**
   * Clear the list of videos. This will also no-op the next _tick() polling
   * request and prevent future polling requests.
   *
   * @return {undefined}
   */
  clear() {
    this.videos = new IndexedList();
  }

  /**
   * Tell the VideoProcessor that the requested pollTime has ellapsed so that it
   * can perform work on its videos.
   *
   * Returning null means that there is no more work to be done. Tick should
   * not be called again until after the `start` callback is called.
   *
   * @return {undefined} - The amount of time to wait before calling _tick()
   */
  _tick() {
    this._tickCount++;

    let nextVideos = new IndexedList();
    let videoToPoll;

    this.videos.forEach(function(video) {
      if (video.isStatusSettled || video.isDeleted || video.isUploading) {
        return;
      }

      if (video.isInFlight) {
        nextVideos.push(video);
        return;
      }

      if (!videoToPoll || videoToPoll.lastReload > video.lastReload) {
        videoToPoll = video;
      }

      nextVideos.push(video);
    });

    if (videoToPoll) {
      videoToPoll.reload(this._tickCount);
    }

    this.videos = nextVideos;

    if (!this.videos.isEmpty) {
      this._tickLater();
    }
  }

  _tickLater() {
    this._enqueueTask(() => this._tick(), POLL_TIME);
  }
}


/**
 * A list that tracks items with both an array and an object indexed by id.
 */
class IndexedList {
  constructor() {
    this.array = [];
    this.map = {};
  }

  get isEmpty() {
    return !this.array.length;
  }

  has(item) {
    return Boolean(this.map[item.id]);
  }

  push(item) {
    this.array.push(item);
    this.map[item.id] = item;
  }

  forEach(...args) {
    this.array.forEach(...args);
  }
}


/**
 * A WorkItem wraps an Ember Data model to insulate VideoProcessor from ED.Model APIs.
 */
export class WorkItem {
  constructor(video) {
    this.video = video;
    this.lastReload = 0;
  }

  /**
   * From the perspective of the server, is the video in a state where the status is stable
   * and will not change without intervention from the client?
   *
   * If the server will change the status, then we need to poll for updates.
   *
   * @returns {Boolean}
   */
  get isStatusSettled() {
    let status = this.video.get('status');
    return status === 'failed' ||
           status === 'recorded' ||
           status === 'created' && !this.video.get('videoUpload');
  }

  get isUploading() {
    return this.video.get('videoUpload.isSaving');
  }

  get isInFlight() {
    return this.video.get('isLoading') || this.video.get('isSaving');
  }

  get isDeleted() {
    return this.video.get('isDeleted');
  }

  get id() {
    return this.video.get('id');
  }

  reload(tickCount) {
    this.lastReload = tickCount;
    this.video.reload();
  }
}
