/*
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

var eventEmitter = require('minimal-event-emitter');
var NetworkError = require('../NetworkError');
var WorkPool = require('../collections/WorkPool');
var chain = require('../util/chain');
var delay = require('../util/delay');
var clock = require('../util/clock');


// Map template properties to their corresponding tile properties.
var templateProperties = {
  x: 'x',
  y: 'y',
  z: 'z',
  f: 'face'
};

// Default face order for cube maps.
var defaultCubeMapFaceOrder = 'bdflru';

// Default maximum number of concurrent requests.
var defaultConcurrency = 4;

// Default milliseconds to wait before retrying failed requests.
var defaultRetryDelay = 10000;


/**
 * @class
 * @classdesc Source that loads images given a URL and a crop rectangle.
 * @implements Source
 * @param {Function} sourceFromTile Function that receives a tile and returns
 * a `{ url, rect }` object, where `url` is an image URL and `rect`, when
 * present, is an `{ x, y, width, height }` object in normalized coordinates
 * denoting the portion of the image to use.
 * @param {Object} opts
 * @param {number} [opts.concurrency=4] Maximum number of tiles to request at
 *     the same time. The limit is per {@link ImageSourceUrl} instance.
 * @param {number} [opts.retryDelay=10000] Time in milliseconds to wait before
 *     retrying a failed request.
 */
function ImageUrlSource(sourceFromTile, opts) {

  opts = opts ? opts : {};

  this._loadPool = new WorkPool({
    concurrency: opts.concurrency || defaultConcurrency
  });

  this._retryDelay = opts.retryDelay || defaultRetryDelay;
  this._retryMap = {};

  this._sourceFromTile = sourceFromTile;
}


ImageUrlSource.prototype.loadAsset = function(stage, tile, done) {

  var self = this;

  var retryDelay = this._retryDelay;
  var retryMap = this._retryMap;

  var tileSource = this._sourceFromTile(tile);
  var url = tileSource.url;
  var rect = tileSource.rect;

  var loadImage = stage.loadImage.bind(stage, url, rect);

  var loadFn = function(done) {
    // TODO: Deduplicate load requests for the same URL. Although the browser
    // might be smart enough to avoid duplicate requests, they are still unduly
    // impacted by the concurrency parameter.
    return self._loadPool.push(loadImage, function(err, asset) {
      if (err) {
        if (err instanceof NetworkError) {
          // If a network error occurred, wait before retrying.
          retryMap[url] = clock();
          self.emit('networkError', asset, err);
        }
        done(err, tile);
      } else {
        // On a successful fetch, forget the previous timeout.
        delete retryMap[url];
        done(null, tile, asset);
      }
    });
  };

  // Check whether we are retrying a failed request.
  var delayAmount;
  var lastTime = retryMap[url];
  if (lastTime != null) {
    var now = clock();
    var elapsed = now - lastTime;
    if (elapsed < retryDelay) {
      // Wait before retrying.
      delayAmount = retryDelay - elapsed;
    } else {
      // Retry timeout expired; perform the request at once.
      delayAmount = 0;
      delete retryMap[url];
    }
  }

  var delayFn = delay.bind(null, delayAmount);

  return chain(delayFn, loadFn)(done);
};


/**
 * Creates an ImageUrlSource from a string template.
 *
 * @param {String} url Tile URL template, which may contain the following
 *    placeholders:
 *    - `{f}` : tile face (one of `b`, `d`, `f`, `l`, `r`, `u`)
 *    - `{z}` : tile level index (0 is the smallest level)
 *    - `{x}` : tile horizontal index
 *    - `{y}` : tile vertical index
 * @param {Object} opts In addition to the options already supported by the
 *     {@link ImageUrlSource} constructor.
 * @param {String} opts.cubeMapPreviewUrl URL to use as the preview level.
 *     This must be a single image containing six cube faces laid out
 *     vertically according to the face order parameter.
 * @param {String} [opts.cubeMapPreviewFaceOrder='bdflru'] Face order within
 *     the preview image.
 */
ImageUrlSource.fromString = function(url, opts) {
  opts = opts || {};

  var faceOrder = opts && opts.cubeMapPreviewFaceOrder || defaultCubeMapFaceOrder;

  var urlFn = opts.cubeMapPreviewUrl ? withPreview : withoutPreview;

  return new ImageUrlSource(urlFn, opts);

  function withoutPreview(tile) {
    var tileUrl = url;

    for (var property in templateProperties) {
      var templateProperty = templateProperties[property];
      var regExp = propertyRegExp(property);
      var valueFromTile = tile.hasOwnProperty(templateProperty) ? tile[templateProperty] : '';
      tileUrl = tileUrl.replace(regExp, valueFromTile);
    }

    return { url: tileUrl };
  }

  function withPreview(tile) {
    if (tile.z === 0) {
      return cubeMapUrl(tile);
    }
    else {
      return withoutPreview(tile);
    }
  }

  function cubeMapUrl(tile) {
    var y = faceOrder.indexOf(tile.face) / 6;
    return {
      url: opts.cubeMapPreviewUrl,
      rect: { x: 0, y: y, width: 1, height: 1/6 }
    };
  }
};


ImageUrlSource.stringHasFace = function(url) {
  return ImageUrlSource.stringHasProperty(url, 'f');
};


ImageUrlSource.stringHasX = function(url) {
  return ImageUrlSource.stringHasProperty(url, 'x');
};


ImageUrlSource.stringHasY = function(url) {
  return ImageUrlSource.stringHasProperty(url, 'y');
};


ImageUrlSource.stringHasLevel = function(url) {
  return ImageUrlSource.stringHasProperty(url, 'z');
};


ImageUrlSource.stringHasProperty = function(url, property) {
  return !!url.match(propertyRegExp(property));
};


function propertyRegExp(property) {
  var regExpStr = '\\{(' + property + ')\\}';
  return new RegExp(regExpStr, 'g');
}

eventEmitter(ImageUrlSource);

module.exports = ImageUrlSource;

@@ -2259,16 +2259,47 @@
 ile;%0A%7D%0A%0A
+eventEmitter(ImageUrlSource);%0A%0A
 %0AImageUr
@@ -5633,538 +5633,8 @@
 %7D;%0A%0A
-%0AImageUrlSource.stringHasFace = function(url) %7B%0A  return ImageUrlSource.stringHasProperty(url, 'f');%0A%7D;%0A%0A%0AImageUrlSource.stringHasX = function(url) %7B%0A  return ImageUrlSource.stringHasProperty(url, 'x');%0A%7D;%0A%0A%0AImageUrlSource.stringHasY = function(url) %7B%0A  return ImageUrlSource.stringHasProperty(url, 'y');%0A%7D;%0A%0A%0AImageUrlSource.stringHasLevel = function(url) %7B%0A  return ImageUrlSource.stringHasProperty(url, 'z');%0A%7D;%0A%0A%0AImageUrlSource.stringHasProperty = function(url, property) %7B%0A  return !!url.match(propertyRegExp(property));%0A%7D;%0A%0A%0A
 func
@@ -5755,39 +5755,8 @@
 %0A%7D%0A%0A
-eventEmitter(ImageUrlSource);%0A%0A
 modu

Remove utility methods to query for the presence of parameters in the ImageUrlSource template string.