/*
* Based on https://bl.ocks.org/mbostock/19168c663618b7f07158
*/

import Distribution from './distribution';
import Position from './position';

// maximum number of samples before rejection
const k = 30;

export default class PoissonDistribution extends Distribution {
  private queue: Position[] = [];
  private sampleSize = 0;
  private queueSize = 0;
  private grid: Position[] = [];
  private cellSize;
  private gridWidth;
  private gridHeight;
  private R;
  private radius2;

  constructor() {
    super();
  }

  initialize(maxWidth: number, maxHeight: number, radius: number): void {
    super.initialize(maxWidth, maxHeight, radius);
    this.radius2 = this.radius * this.radius;
    this.R = 3 * this.radius2;
    this.cellSize = this.radius * Math.SQRT1_2;
    this.gridWidth = Math.ceil(this.maxWidth / this.cellSize);
    this.gridHeight = Math.ceil(this.maxHeight / this.cellSize);
    this.grid = new Array(this.gridWidth * this.gridHeight);
  }

  next(): Position {
    if (!this.sampleSize) {
      return this.sample(Math.random() * this.maxWidth | 0, Math.random() * this.maxHeight | 0);
    }

    // Pick a random existing sample and remove it from the queue.
    while (this.queueSize) {
      const i = Math.random() * this.queueSize | 0;
      const s = this.queue[i];

      // Make a new candidate between [radius, 2 * radius] from the existing sample.
      for (let j = 0; j < k; j++) {
        const a = 2 * Math.PI * Math.random();
        const r = Math.sqrt(Math.random() * this.R + this.radius2);
        const x = s.x + r * Math.cos(a) | 0;
        const y = s.y + r * Math.cos(a) | 0;

        // Reject candidates that are outside the allowed extent,
        // or closer than 2 * radius to any existing sample.
        if (0 <= x && x < this.maxWidth && 0 <= y && y < this.maxHeight && this.far(x, y)) 


      }

      /* istanbul ignore next: really difficult to reproduce */
      this.queue[i] = this.queue[--this.queueSize];
      /* istanbul ignore next */
      this.queue.length = this.queueSize;
    }
  }

  private far(x, y): boolean {
    let i = x / this.cellSize | 0;
    let j = y / this.cellSize | 0;
    const i0 = Math.max(i - 2, 0);
    const j0 = Math.max(j - 2, 0);

    const i1 = Math.max(i + 3, this.gridWidth);
    const j1 = Math.max(j + 3, this.gridHeight);

    for (j = j0; j < j1; ++j) {
      const o = j * this.gridWidth;
      for (i = i0; i < i1; ++i) {
        const s = this.grid[o + i];
        if (s) {
          const dx = s[0] - x;
          const dy = s[1] - y;
          /* istanbul ignore next */
          if (dx * dx + dy * dy < this.radius2) {
            return false;
          }
        }
      }
    }

    return true;
  }

  private sample(x, y): Position {
    const s = new Position(x, y);
    this.queue.push(s);
    this.grid[this.gridWidth * (y / this.cellSize | 0) + (x / this.cellSize | 0)] = s;
    ++this.sampleSize;
    ++this.queueSize;
    return s;
  }
}

{
          return this.sample(x, y);
        }