"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.updateLayeredMaterialNodeImagery = updateLayeredMaterialNodeImagery;
exports.updateLayeredMaterialNodeElevation = updateLayeredMaterialNodeElevation;
exports.removeLayeredMaterialNodeLayer = removeLayeredMaterialNodeLayer;
exports.SIZE_DIAGONAL_TEXTURE = exports.SIZE_TEXTURE_TILE = void 0;

var _LayerUpdateStrategy = require("../Layer/LayerUpdateStrategy");

var _LayerUpdateState = _interopRequireDefault(require("../Layer/LayerUpdateState"));

var _XbilParser = require("../Parser/XbilParser");

var _handlerNodeError = _interopRequireDefault(require("./handlerNodeError"));

var SIZE_TEXTURE_TILE = 256;
exports.SIZE_TEXTURE_TILE = SIZE_TEXTURE_TILE;
var SIZE_DIAGONAL_TEXTURE = Math.pow(2 * (SIZE_TEXTURE_TILE * SIZE_TEXTURE_TILE), 0.5);
exports.SIZE_DIAGONAL_TEXTURE = SIZE_DIAGONAL_TEXTURE;

function materialCommandQueuePriorityFunction(material) {
  // We know that 'node' is visible because commands can only be
  // issued for visible nodes.
  // TODO: need priorization of displayed nodes
  if (material.visible) {
    // Then prefer displayed node over non-displayed one
    return 100;
  } else {
    return 10;
  }
}

function refinementCommandCancellationFn(cmd) {
  if (!cmd.requester.parent || !cmd.requester.material) {
    return true;
  } // Cancel the command if the tile already has a better texture.
  // This is only needed for elevation layers, because we may have several
  // concurrent layers but we can only use one texture.


  if (cmd.layer.isElevationLayer && cmd.requester.material.getElevationLayer() && cmd.targetLevel <= cmd.requester.material.getElevationLayer().level) {
    return true;
  }

  return !cmd.requester.material.visible;
}

function buildCommand(view, layer, extentsSource, extentsDestination, requester, parsedData) {
  return {
    view: view,
    layer: layer,
    extentsSource: extentsSource,
    extentsDestination: extentsDestination,
    requester: requester,
    parsedData: parsedData,
    priority: materialCommandQueuePriorityFunction(requester.material),
    earlyDropFunction: refinementCommandCancellationFn
  };
}

function updateLayeredMaterialNodeImagery(context, layer, node, parent) {
  var material = node.material;

  if (!parent || !material) {
    return;
  }

  var extentsDestination = node.getExtentsByProjection(layer.projection);
  var nodeLayer = material.getLayer(layer.id); // Initialisation

  if (node.layerUpdateState[layer.id] === undefined) {
    node.layerUpdateState[layer.id] = new _LayerUpdateState["default"]();

    if (!layer.source.extentsInsideLimit(extentsDestination)) {
      // we also need to check that tile's parent doesn't have a texture for this layer,
      // because even if this tile is outside of the layer, it could inherit it's
      // parent texture
      if (!(!layer.noTextureParentOutsideLimit && parent.material && parent.material.getLayer && parent.material.getLayer(layer.id))) {
        node.layerUpdateState[layer.id].noMoreUpdatePossible();
        return;
      }
    }

    if (!nodeLayer) {
      // Create new MaterialLayer
      nodeLayer = material.addLayer(layer); // Init the new MaterialLayer by parent

      var parentLayer = parent.material && parent.material.getLayer(layer.id);
      nodeLayer.initFromParent(parentLayer, extentsDestination);
    } // Proposed new process, two separate processes:
    //      * FIRST PASS: initNodeXXXFromParent and get out of the function
    //      * SECOND PASS: Fetch best texture
    // The two-step allows you to filter out unnecessary requests
    // Indeed in the second pass, their state (not visible or not displayed) can block them to fetch


    if (nodeLayer.level >= layer.source.zoom.min) {
      context.view.notifyChange(node, false);
      return;
    }
  } // Node is hidden, no need to update it


  if (!material.visible) {
    return;
  } // TODO: move this to defineLayerProperty() declaration
  // to avoid mixing layer's network updates and layer's params
  // Update material parameters


  if (nodeLayer) {
    nodeLayer.visible = layer.visible;
    nodeLayer.opacity = layer.opacity;
  } // An update is pending / or impossible -> abort


  if (!layer.visible || !node.layerUpdateState[layer.id].canTryUpdate()) {
    return;
  }

  if (nodeLayer.level >= extentsDestination[0].zoom) {
    // default decision method
    node.layerUpdateState[layer.id].noMoreUpdatePossible();
    return;
  } // is fetching data from this layer disabled?


  if (layer.frozen) {
    return;
  }

  var failureParams = node.layerUpdateState[layer.id].failureParams;
  var destinationLevel = extentsDestination[0].zoom || node.level;
  var targetLevel = (0, _LayerUpdateStrategy.chooseNextLevelToFetch)(layer.updateStrategy.type, node, destinationLevel, nodeLayer.level, layer, failureParams);

  if (targetLevel <= nodeLayer.level || targetLevel > destinationLevel) {
    return;
  } // Get equivalent of extent destination in source


  var extentsSource = [];
  var _iteratorNormalCompletion = true;
  var _didIteratorError = false;
  var _iteratorError = undefined;

  try {
    for (var _iterator = extentsDestination[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
      var extentDestination = _step.value;
      var extentSource = extentDestination.tiledExtentParent(targetLevel);

      if (!layer.source.extentInsideLimit(extentSource)) {
        // Retry extentInsideLimit because you must check with the targetLevel
        // if the first test extentInsideLimit returns that it is out of limits
        // and the node inherits from its parent, then it'll still make a command to fetch texture.
        node.layerUpdateState[layer.id].noMoreUpdatePossible();
        return;
      }

      extentsSource.push(extentSource);
    }
  } catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion && _iterator["return"] != null) {
        _iterator["return"]();
      }
    } finally {
      if (_didIteratorError) {
        throw _iteratorError;
      }
    }
  }

  node.layerUpdateState[layer.id].newTry();
  var parsedData = layer.source.isFileSource ? layer.source.parsedData : nodeLayer.textures.map(function (t) {
    return t.parsedData;
  });
  var command = buildCommand(context.view, layer, extentsSource, extentsDestination, node, parsedData);
  return context.scheduler.execute(command).then(function (result) {
    // TODO: Handle error : result is undefined in provider. throw error
    var pitchs = extentsDestination.map(function (ext, i) {
      return ext.offsetToParent(extentsSource[i], nodeLayer.offsetScales[i]);
    });
    nodeLayer.setTextures(result, pitchs);
    node.layerUpdateState[layer.id].success();
  }, function (err) {
    return (0, _handlerNodeError["default"])(err, node, layer, targetLevel, context.view);
  });
}

function updateLayeredMaterialNodeElevation(context, layer, node, parent) {
  var material = node.material;

  if (!parent || !material) {
    return;
  } // TODO: we need either
  //  - compound or exclusive layers
  //  - support for multiple elevation layers
  // Elevation is currently handled differently from color layers.
  // This is caused by a LayeredMaterial limitation: only 1 elevation texture
  // can be used (where a tile can have N textures x M layers)


  var extentsDestination = node.getExtentsByProjection(layer.projection); // Init elevation layer, and inherit from parent if possible

  var nodeLayer = material.getElevationLayer();

  if (!nodeLayer) {
    nodeLayer = material.addLayer(layer);
  }

  if (node.layerUpdateState[layer.id] === undefined) {
    node.layerUpdateState[layer.id] = new _LayerUpdateState["default"]();
    var parentLayer = parent.material && parent.material.getLayer(layer.id);
    nodeLayer.initFromParent(parentLayer, extentsDestination); // If the texture resolution has a poor precision for this node, we don't
    // extract min-max from the texture (too few information), we instead chose
    // to use parent's min-max.

    var useMinMaxFromParent = extentsDestination[0].zoom - nodeLayer.zoom > 6;

    if (nodeLayer.textures[0]) {
      if (!useMinMaxFromParent) {
        var _computeMinMaxElevati = (0, _XbilParser.computeMinMaxElevation)(nodeLayer.textures[0].image.data, SIZE_TEXTURE_TILE, SIZE_TEXTURE_TILE, nodeLayer.offsetScales[0]),
            min = _computeMinMaxElevati.min,
            max = _computeMinMaxElevati.max;

        node.setBBoxZ(min, max, layer.scale);
      }
    }

    if (nodeLayer.level >= layer.source.zoom.min) {
      context.view.notifyChange(node, false);
      return;
    }
  } // Possible conditions to *not* update the elevation texture


  if (layer.frozen || !material.visible || !node.layerUpdateState[layer.id].canTryUpdate()) {
    return;
  }

  var targetLevel = (0, _LayerUpdateStrategy.chooseNextLevelToFetch)(layer.updateStrategy.type, node, extentsDestination[0].zoom, nodeLayer.level, layer);

  if (targetLevel <= nodeLayer.level || targetLevel > extentsDestination[0].zoom) {
    node.layerUpdateState[layer.id].noMoreUpdatePossible();
    return Promise.resolve();
  }

  var extentsSource = [];
  var _iteratorNormalCompletion2 = true;
  var _didIteratorError2 = false;
  var _iteratorError2 = undefined;

  try {
    for (var _iterator2 = extentsDestination[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
      var nodeExtent = _step2.value;
      var extentSource = nodeExtent.tiledExtentParent(targetLevel);

      if (!layer.source.extentInsideLimit(extentSource)) {
        node.layerUpdateState[layer.id].noMoreUpdatePossible();
        return;
      }

      extentsSource.push(extentSource);
    }
  } catch (err) {
    _didIteratorError2 = true;
    _iteratorError2 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
        _iterator2["return"]();
      }
    } finally {
      if (_didIteratorError2) {
        throw _iteratorError2;
      }
    }
  }

  node.layerUpdateState[layer.id].newTry();
  var command = buildCommand(context.view, layer, extentsSource, extentsDestination, node);
  return context.scheduler.execute(command).then(function (textures) {
    // Do not apply the new texture if its level is < than the current
    // one.  This is only needed for elevation layers, because we may
    // have several concurrent layers but we can only use one texture.
    if (targetLevel <= nodeLayer.level) {
      node.layerUpdateState[layer.id].noMoreUpdatePossible();
      return;
    }

    var elevation = {
      texture: textures[0],
      pitch: extentsDestination[0].offsetToParent(textures[0].coords, nodeLayer.offsetScales[0])
    };
    node.layerUpdateState[layer.id].success();

    if (elevation.texture) {
      if (layer.useColorTextureElevation) {
        elevation.min = layer.colorTextureElevationMinZ;
        elevation.max = layer.colorTextureElevationMaxZ;
      } else {
        var _computeMinMaxElevati2 = (0, _XbilParser.computeMinMaxElevation)(elevation.texture.image.data, SIZE_TEXTURE_TILE, SIZE_TEXTURE_TILE, elevation.pitch),
            _min = _computeMinMaxElevati2.min,
            _max = _computeMinMaxElevati2.max;

        elevation.min = !_min ? 0 : _min;
        elevation.max = !_max ? 0 : _max;
      }
    }

    node.setBBoxZ(elevation.min, elevation.max, layer.scale);
    nodeLayer.setTexture(0, elevation.texture, elevation.pitch);
    var nodeParent = parent.material && parent.material.getElevationLayer();
    nodeLayer.replaceNoDataValueFromParent(nodeParent, layer.noDataValue);
  }, function (err) {
    return (0, _handlerNodeError["default"])(err, node, layer, targetLevel, context.view);
  });
}

function removeLayeredMaterialNodeLayer(layerId) {
  return function (node) {
    if (node.material && node.material.removeLayer) {
      node.material.removeLayer(layerId);

      if (node.material.elevationLayerIds[0] == layerId) {
        node.setBBoxZ(0, 0);
      }
    }

    if (node.layerUpdateState && node.layerUpdateState[layerId]) {
      delete node.layerUpdateState[layerId];
    }
  };
}