///
/// Created by @Haoyi on 2021/7/16
///
///
part of '../visual_feast_engine.dart';

class Layer {
  final String name;
  final int priority;

  final _SortingLayers _layers;

  static final Layer invalid = Layer._("", -1, _SortingLayers());

  Layer._(this.name, this.priority, this._layers);

  @override
  int get hashCode => hash2(name, priority);

  void _frameUpdate(double t) {
    _layers._frameUpdate(t);
  }

  void _render(Canvas canvas) {
    _layers._render(canvas);
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Layer &&
          runtimeType == other.runtimeType &&
          name == other.name &&
          priority == other.priority;

  bool get isEmpty => _layers.isEmpty;

  void attachRenderElement(RenderElement renderElement) {
    _layers.attachRender(renderElement);
  }

  void detachRenderElement(RenderElement renderElement) {
    _layers.detachRender(renderElement);
  }

  void detachAll() {
    _layers.detachAll();
  }
}

class LayerManager {
  final List<Layer> layers;
  final Map<int, Layer> _layerMap = <int, Layer>{};

  Layer findOrCreate(String name, int priority) {
    final hashCode = hash2(name, priority);
    final layerKey = _layerMap[hashCode];
    if (layerKey != null) {
      return layerKey;
    }
    final newLayer = Layer._(name, priority, _SortingLayers());
    _layerMap[hashCode] = newLayer;
    return newLayer;
  }

  // Use Binary Search
  int _nearest(int low, int high, Layer layer) {
    if (low > high) {
      return -low;
    }
    while (low <= high) {
      final int mid = (low + high) >> 1;
      if (layer.priority == layers[mid].priority) {
        return mid;
      } else if (layer.priority < layers[mid].priority) {
        return _nearest(low, mid - 1, layer);
      } else {
        return _nearest(mid + 1, high, layer);
      }
    }
    return -low;
  }

  LayerManager() : layers = <Layer>[Layer.invalid];

  void attachRenderElement(RenderElement renderElement) {
    final layer = findOrCreate(renderElement.layerName, renderElement.layerPriority);
    final index = _nearest(1, layers.length - 1, layer);
    if (index < 0) {
      layers.insert(-index, layer);
    }
    layer.attachRenderElement(renderElement);
  }

  void detachRenderElement(RenderElement renderElement) {
    final layer = findOrCreate(renderElement.layerName, renderElement.layerPriority);
    layer.detachRenderElement(renderElement);
    if (layer.isEmpty) {
      final index = _nearest(1, layers.length - 1, layer);
      if (index > 0) {
        layers.removeAt(index);
      }
    }
  }

  void detachAll() {
    for (var layer in layers) {
      layer.detachAll();
    }
  }

  void frameUpdate(double t) {
    for (var layer in layers) {
      layer._frameUpdate(t);
    }
  }

  void render(Canvas canvas) {
    for (var layer in layers) {
      layer._render(canvas);
    }
  }
}
