import { Timeline } from '@luma.gl/engine';
import { getPipelineFactory, getShaderAssembler } from '../shaderlib';
import { LIFECYCLE } from '../lifecycle/constants';
import log from '../utils/log';
import debug from '../debug';
import { flatten } from '../utils/flatten';
import { Stats } from '@probe.gl/stats';
import ResourceManager from './resource/resource-manager';
import Viewport from '../viewports/viewport';
const TRACE_SET_LAYERS = 'layerManager.setLayers';
const TRACE_ACTIVATE_VIEWPORT = 'layerManager.activateViewport';
export default class LayerManager {
  constructor(device, props) {
    this.layers = void 0;
    this.context = void 0;
    this.resourceManager = void 0;
    this._lastRenderedLayers = [];
    this._needsRedraw = false;
    this._needsUpdate = false;
    this._nextLayers = null;
    this._debug = false;

    this.activateViewport = viewport => {
      debug(TRACE_ACTIVATE_VIEWPORT, this, viewport);

      if (viewport) {
        this.context.viewport = viewport;
      }
    };

    const {
      deck,
      stats,
      viewport: _viewport,
      timeline
    } = props || {};
    this.layers = [];
    this.resourceManager = new ResourceManager({
      device,
      protocol: 'deck://'
    });
    this.context = {
      mousePosition: null,
      userData: {},
      layerManager: this,
      device,
      gl: device === null || device === void 0 ? void 0 : device.gl,
      deck,
      shaderAssembler: getShaderAssembler(),
      pipelineFactory: device && getPipelineFactory(device),
      renderPass: undefined,
      stats: stats || new Stats({
        id: 'deck.gl'
      }),
      viewport: _viewport || new Viewport({
        id: 'DEFAULT-INITIAL-VIEWPORT'
      }),
      timeline: timeline || new Timeline(),
      resourceManager: this.resourceManager,
      onError: undefined
    };
    Object.seal(this);
  }

  finalize() {
    this.resourceManager.finalize();

    for (const layer of this.layers) {
      this._finalizeLayer(layer);
    }
  }

  needsRedraw(opts = {
    clearRedrawFlags: false
  }) {
    let redraw = this._needsRedraw;

    if (opts.clearRedrawFlags) {
      this._needsRedraw = false;
    }

    for (const layer of this.layers) {
      const layerNeedsRedraw = layer.getNeedsRedraw(opts);
      redraw = redraw || layerNeedsRedraw;
    }

    return redraw;
  }

  needsUpdate() {
    if (this._nextLayers && this._nextLayers !== this._lastRenderedLayers) {
      return 'layers changed';
    }

    return this._needsUpdate;
  }

  setNeedsRedraw(reason) {
    this._needsRedraw = this._needsRedraw || reason;
  }

  setNeedsUpdate(reason) {
    this._needsUpdate = this._needsUpdate || reason;
  }

  getLayers({
    layerIds
  } = {}) {
    return layerIds ? this.layers.filter(layer => layerIds.find(layerId => layer.id.indexOf(layerId) === 0)) : this.layers;
  }

  setProps(props) {
    if ('debug' in props) {
      this._debug = props.debug;
    }

    if ('userData' in props) {
      this.context.userData = props.userData;
    }

    if ('layers' in props) {
      this._nextLayers = props.layers;
    }

    if ('onError' in props) {
      this.context.onError = props.onError;
    }
  }

  setLayers(newLayers, reason) {
    debug(TRACE_SET_LAYERS, this, reason, newLayers);
    this._lastRenderedLayers = newLayers;
    const flatLayers = flatten(newLayers, Boolean);

    for (const layer of flatLayers) {
      layer.context = this.context;
    }

    this._updateLayers(this.layers, flatLayers);
  }

  updateLayers() {
    const reason = this.needsUpdate();

    if (reason) {
      this.setNeedsRedraw(`updating layers: ${reason}`);
      this.setLayers(this._nextLayers || this._lastRenderedLayers, reason);
    }

    this._nextLayers = null;
  }

  _handleError(stage, error, layer) {
    layer.raiseError(error, `${stage} of ${layer}`);
  }

  _updateLayers(oldLayers, newLayers) {
    const oldLayerMap = {};

    for (const oldLayer of oldLayers) {
      if (oldLayerMap[oldLayer.id]) {
        log.warn(`Multiple old layers with same id ${oldLayer.id}`)();
      } else {
        oldLayerMap[oldLayer.id] = oldLayer;
      }
    }

    const generatedLayers = [];

    this._updateSublayersRecursively(newLayers, oldLayerMap, generatedLayers);

    this._finalizeOldLayers(oldLayerMap);

    let needsUpdate = false;

    for (const layer of generatedLayers) {
      if (layer.hasUniformTransition()) {
        needsUpdate = `Uniform transition in ${layer}`;
        break;
      }
    }

    this._needsUpdate = needsUpdate;
    this.layers = generatedLayers;
  }

  _updateSublayersRecursively(newLayers, oldLayerMap, generatedLayers) {
    for (const newLayer of newLayers) {
      newLayer.context = this.context;
      const oldLayer = oldLayerMap[newLayer.id];

      if (oldLayer === null) {
        log.warn(`Multiple new layers with same id ${newLayer.id}`)();
      }

      oldLayerMap[newLayer.id] = null;
      let sublayers = null;

      try {
        if (this._debug && oldLayer !== newLayer) {
          newLayer.validateProps();
        }

        if (!oldLayer) {
          this._initializeLayer(newLayer);
        } else {
          this._transferLayerState(oldLayer, newLayer);

          this._updateLayer(newLayer);
        }

        generatedLayers.push(newLayer);
        sublayers = newLayer.isComposite ? newLayer.getSubLayers() : null;
      } catch (err) {
        this._handleError('matching', err, newLayer);
      }

      if (sublayers) {
        this._updateSublayersRecursively(sublayers, oldLayerMap, generatedLayers);
      }
    }
  }

  _finalizeOldLayers(oldLayerMap) {
    for (const layerId in oldLayerMap) {
      const layer = oldLayerMap[layerId];

      if (layer) {
        this._finalizeLayer(layer);
      }
    }
  }

  _initializeLayer(layer) {
    try {
      layer._initialize();

      layer.lifecycle = LIFECYCLE.INITIALIZED;
    } catch (err) {
      this._handleError('initialization', err, layer);
    }
  }

  _transferLayerState(oldLayer, newLayer) {
    newLayer._transferState(oldLayer);

    newLayer.lifecycle = LIFECYCLE.MATCHED;

    if (newLayer !== oldLayer) {
      oldLayer.lifecycle = LIFECYCLE.AWAITING_GC;
    }
  }

  _updateLayer(layer) {
    try {
      layer._update();
    } catch (err) {
      this._handleError('update', err, layer);
    }
  }

  _finalizeLayer(layer) {
    this._needsRedraw = this._needsRedraw || `finalized ${layer}`;
    layer.lifecycle = LIFECYCLE.AWAITING_FINALIZATION;

    try {
      layer._finalize();

      layer.lifecycle = LIFECYCLE.FINALIZED;
    } catch (err) {
      this._handleError('finalization', err, layer);
    }
  }

}
//# sourceMappingURL=layer-manager.js.map