// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assert} from 'chrome://resources/ash/common/assert.js';

import {ImageCache} from './cache.js';
import {ImageOrientation} from './image_orientation.js';
import {ImageRequestTask} from './image_request_task.js';
import {LoadImageRequest, LoadImageResponse} from './load_image_request.js';
import {Scheduler} from './scheduler.js';

/**
 * Loads and resizes an image.
 * @constructor
 */
export function ImageLoader() {
  /**
   * Persistent cache object.
   * @type {ImageCache}
   * @private
   */
  this.cache_ = new ImageCache();

  /**
   * Manages pending requests and runs them in order of priorities.
   * @type {Scheduler}
   * @private
   */
  this.scheduler_ = new Scheduler();

  // Grant permissions to all volumes, initialize the cache and then start the
  // scheduler.
  chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
    // Listen for mount events, and grant permissions to volumes being mounted.
    chrome.fileManagerPrivate.onMountCompleted.addListener(
        function(event) {
          if (event.eventType === 'mount' && event.status === 'success') {
            chrome.fileSystem.requestFileSystem(
                {volumeId: event.volumeMetadata.volumeId}, function() {});
          }
        });
    const initPromises = volumeMetadataList.map(function(volumeMetadata) {
      const requestPromise = new Promise(function(callback) {
        chrome.fileSystem.requestFileSystem(
            {volumeId: volumeMetadata.volumeId},
            /** @type {function(FileSystem=)} */(callback));
      });
      return requestPromise;
    });
    initPromises.push(new Promise(function(resolve, reject) {
      this.cache_.initialize(resolve);
    }.bind(this)));

    // After all initialization promises are done, start the scheduler.
    Promise.all(initPromises).then(this.scheduler_.start.bind(this.scheduler_));
  }.bind(this));

  // Listen for incoming requests.
  chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
    if (!sender.origin || !msg) {
      return;
    }

    if (ImageLoader.ALLOWED_CLIENT_ORIGINS.indexOf(sender.origin) === -1) {
      return;
    }

    this.onIncomingRequest_(msg, sender.origin, sendResponse);
  });

  chrome.runtime['onConnectNative'].addListener((port) => {
    if (port.sender.nativeApplication != 'com.google.ash_thumbnail_loader') {
      port.disconnect();
      return;
    }

    port.onMessage.addListener((msg) => {
      // Each connection is expected to handle a single request only.
      const started = this.onIncomingRequest_(
          msg, port.sender.nativeApplication, response => {
            port.postMessage(response);
            port.disconnect();
          });

      if (!started) {
        port.disconnect();
      }
    });
  });
}

/**
 * List of extensions allowed to perform image requests.
 *
 * @const
 * @type {Array<string>}
 */
ImageLoader.ALLOWED_CLIENT_ORIGINS = [
  'chrome://file-manager',  // File Manager SWA
];

/**
 * Handler for incoming requests.
 *
 * @param {*} request_data A LoadImageRequest (received untyped).
 * @param {!string} senderOrigin
 * @param {function(*): void} sendResponse
 */
ImageLoader.prototype.onIncomingRequest_ = function(
    request_data, senderOrigin, sendResponse) {
  const request = /** @type {!LoadImageRequest} */ (request_data);

  // Sending a response may fail if the receiver already went offline.
  // This is not an error, but a normal and quite common situation.
  const failSafeSendResponse = function(response) {
    try {
      sendResponse(response);
    } catch (e) {
      // Ignore the error.
    }
  };
  // Incoming requests won't have the full type.
  assert(!(request.orientation instanceof ImageOrientation));
  assert(!(typeof request.orientation === 'number'));

  if (request.orientation) {
    request.orientation =
        ImageOrientation.fromRotationAndScale(request.orientation);
  } else {
    request.orientation = new ImageOrientation(1, 0, 0, 1);
  }
  return this.onMessage_(senderOrigin, request, failSafeSendResponse);
};

/**
 * Handles a request. Depending on type of the request, starts or stops
 * an image task.
 *
 * @param {string} senderOrigin Sender's origin.
 * @param {!LoadImageRequest} request Pre-processed request.
 * @param {function(!LoadImageResponse)} callback Callback to be called to
 *     return response.
 * @return {boolean} True if the message channel should stay alive until the
 *     callback is called.
 * @private
 */
ImageLoader.prototype.onMessage_ = function(senderOrigin, request, callback) {
  const requestId = senderOrigin + ':' + request.taskId;
  if (request.cancel) {
    // Cancel a task.
    this.scheduler_.remove(requestId);
    return false;  // No callback calls.
  } else {
    // Create a request task and add it to the scheduler (queue).
    const requestTask =
        new ImageRequestTask(requestId, this.cache_, request, callback);
    this.scheduler_.add(requestTask);
    return true;  // Request will call the callback.
  }
};

/**
 * Returns the singleton instance.
 * @return {ImageLoader} ImageLoader object.
 */
ImageLoader.getInstance = function() {
  if (!ImageLoader.instance_) {
    ImageLoader.instance_ = new ImageLoader();
  }
  return ImageLoader.instance_;
};