/**
 * Copyright (c) 2008-2021 Bird Dog Games, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <https://git.io/fjRmv>.
 */

package com.ardor3d.renderer.queue;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Stack;

import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyVector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.util.SortUtil;

public class AbstractRenderBucket implements RenderBucket {

  protected Comparator<Spatial> _comparator;

  protected Spatial[] _currentList, _tempList;
  protected int _currentListSize;

  protected Stack<Spatial[]> _listStack = new Stack<>();
  protected Stack<Spatial[]> _listStackPool = new Stack<>();
  protected Stack<Integer> _listSizeStack = new Stack<>();

  public AbstractRenderBucket() {
    _currentList = new Spatial[32];
    _tempList = new Spatial[_currentList.length];
  }

  @Override
  public void add(final Spatial spatial) {
    spatial._queueDistance = Double.NEGATIVE_INFINITY;
    if (_currentListSize >= _currentList.length) {
      // grow if necessary
      final Spatial[] temp = new Spatial[_currentListSize * 2];
      System.arraycopy(_currentList, 0, temp, 0, _currentListSize);
      _currentList = temp;
      if (_tempList.length < temp.length) {
        _tempList = new Spatial[temp.length];
      }
    }
    _currentList[_currentListSize++] = spatial;
  }

  @Override
  public void remove(final Spatial spatial) {
    int index = 0;
    for (int i = 0; i < _currentListSize; i++) {
      if (_currentList[index] == spatial) {
        break;
      }
      index++;
    }
    for (int i = index; i < _currentListSize - 1; i++) {
      _currentList[i] = _currentList[i + 1];
    }

    _currentListSize--;
  }

  @Override
  public void clear() {
    if (_currentListSize > 0) {
      Arrays.fill(_currentList, 0, _currentListSize, null);
      _currentListSize = 0;
    }
  }

  @Override
  public void render(final Renderer renderer) {
    for (int i = 0; i < _currentListSize; i++) {
      _currentList[i].draw(renderer);
    }
  }

  @Override
  public void sort() {
    // only sort if we have more than one item in our bucket.
    if (_currentListSize > 1) {
      if (_currentListSize <= SortUtil.SHELL_SORT_THRESHOLD) {
        // shell sort
        SortUtil.shellSort(_currentList, 0, _currentListSize - 1, _comparator);
      } else {
        // copy in our list for use in the merge sort.
        System.arraycopy(_currentList, 0, _tempList, 0, _currentListSize);

        // merge sort
        SortUtil.msort(_tempList, _currentList, 0, _currentListSize - 1, _comparator);

        // null fill to remove references
        Arrays.fill(_tempList, 0, _currentListSize, null);
      }
    }
  }

  @Override
  public void pushBucket() {
    _listStack.push(_currentList);
    if (_listStackPool.isEmpty()) {
      _currentList = new Spatial[32];
    } else {
      _currentList = _listStackPool.pop();
    }

    _listSizeStack.push(_currentListSize);
    _currentListSize = 0;
  }

  @Override
  public void popBucket() {
    if (_currentList != null) {
      _listStackPool.push(_currentList);
    }
    _currentList = _listStack.pop();
    _currentListSize = _listSizeStack.pop();
  }

  /**
   * Calculates the distance from a spatial to the camera. Distance is a squared distance.
   * 
   * @param spat
   *          Spatial to check distance.
   * @return Distance from Spatial to current context's camera.
   */
  protected double distanceToCam(final Spatial spat) {
    if (spat._queueDistance != Double.NEGATIVE_INFINITY) {
      return spat._queueDistance;
    }

    final Camera cam = Camera.getCurrentCamera();

    if (spat.getWorldBound() != null && Vector3.isFinite(spat.getWorldBound().getCenter())) {
      spat._queueDistance = spat.getWorldBound().distanceToEdge(cam.getLocation());
    } else {
      final ReadOnlyVector3 spatPosition = spat.getWorldTranslation();
      if (!Vector3.isFinite(spatPosition)) {
        spat._queueDistance = Double.POSITIVE_INFINITY;
      } else {
        spat._queueDistance = cam.getLocation().distance(spatPosition);
      }
    }

    return spat._queueDistance;
  }

}
