import {
  BufferGeometry,
  Float32BufferAttribute,
  InstancedMesh,
  Material,
  Matrix4,
  Quaternion,
  Vector2,
  Vector3,
} from 'three';
import { rand } from '../../../common/helper';

let instance: Grass;
export class Grass {
  private geom!: BufferGeometry;
  initialize() {
    const grassindices = [0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7];
    const grassuvs = [0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0];
    const grassverts = [
      -0.25, 0.5, 0, -0.25, 0, 0, 0.25, 0.5, 0, 0.25, 0, 0, 0, 0.5, -0.25, 0, 0,
      -0.25, 0, 0.5, 0.25, 0, 0, 0.25,
    ];

    const grassnormals = Array.from({ length: grassverts.length }, (_, k) => {
      return k % 2 === 0 ? 1 : 0;
    });

    this.geom = new BufferGeometry();
    this.geom.setIndex(grassindices);
    this.geom.setAttribute(
      'position',
      new Float32BufferAttribute(grassverts, 3),
    );
    this.geom.setAttribute(
      'normal',
      new Float32BufferAttribute(grassnormals, 3),
    );
    this.geom.setAttribute('uv', new Float32BufferAttribute(grassuvs, 2));
  }

  public static getInstance(): Grass {
    if (!instance) {
      instance = new Grass();
      instance.initialize();
    }
    return instance;
  }

  public createInstance(
    positions: Vector3[],
    material: Material,
  ): InstancedMesh {
    const mesh = new InstancedMesh(this.geom, material, positions.length);
    positions.forEach((pos, index) => {
      const mat = new Matrix4();
      const quat = new Quaternion();
      quat.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI * Math.random());
      mat.compose(pos, quat, new Vector3(1, 1, 1));
      mesh.setMatrixAt(index, mat);
    });
    return mesh;
  }

  public createGrassPatch(
    origin: Vector3,
    radius: number,
    density: number,
    pluck: number,
    getHeight: (x: number, y: number) => number,
  ): Vector3[] {
    const positions: Vector3[] = [];
    const posc = new Vector2(origin.x, origin.z);
    const grassPerUnit = 1 / density;
    const actualRadius = radius / density;
    // Create patch circle
    for (let x = -actualRadius; x < actualRadius; x++) {
      for (let y = -actualRadius; y < actualRadius; y++) {
        const posi = new Vector2(origin.x + x, origin.z + y);
        if (posc.distanceTo(posi) <= actualRadius) {
          for (let u = 0; u < grassPerUnit; u++) {
            if (pluck > 0 && rand(Math.random, 0, pluck) === 0) continue;
            const pos = new Vector3(
              origin.x + x * density,
              0,
              origin.z + y * density,
            );
            pos.x += Math.random() / 2 - 1;
            pos.z += Math.random() / 2 - 1;
            pos.y = getHeight(pos.x, pos.z);
            positions.push(pos);
          }
        }
      }
    }
    return positions;
  }
}
