import * as RE from 'rogue-engine'
import * as THREE from 'three'
import { Color } from 'three';

//https://www.youtube.com/watch?v=OFqENgtqRAY

export default class ParticleSystem extends RE.Component  {
	vertexShader = `
	uniform float pointMultiplier;
	varying vec4 vColor;

	void main() {
	  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
	  gl_Position = projectionMatrix * mvPosition;
	  gl_PointSize = pointMultiplier / gl_Position.w;

	  vColor = color;
	}`;
	
	fragmentShader = `
	uniform sampler2D diffuseTexture;
	varying vec4 vColor;

	void main() {
	  gl_FragColor = texture2D(diffuseTexture, gl_PointCoord) * vColor;
	}`;

	@RE.Prop("Texture") texture

	@RE.Prop("Color") color = new Color(255, 255, 255)
	@RE.Prop("Number") alpha = 1

	@RE.Prop("Select") blending = 0
	blendingOptions = ["Additive", "Normal", "Multiply", "None", "Subtractive"]

	//this is the "mesh" that we draw the particle textures into
	//it's a THREE.Points object that I added underneath the GameLogic Object3d
	@RE.Prop("Object3D") globalPointsObject

	@RE.Prop("Number") max = 500
	@RE.Prop("Number") spawnPerTick = 1
	@RE.Prop("Number") lifespan = 2

	//This is the Object3d where we spawn particles from. It uses the worldPosition
	//so things like rotations are accounted for
	@RE.Prop("Object3D") initialLocation
	
	@RE.Prop("Vector3") initialVelocity = new THREE.Vector3(0,0,0)

	@RE.Prop("Number") thrustScalar = 0.01

	@RE.Prop("Vector3") positionRandomBounds = new THREE.Vector3(1,1,1)
	@RE.Prop("Vector3") velocityRandomBounds = new THREE.Vector3(1,1,1)

	start() {
		const bounds = RE.Runtime.rogueDOMContainer.getBoundingClientRect();
		const uniforms = {
			diffuseTexture: {
				value: this.texture
			},
			pointMultiplier: {
				value: bounds.width - bounds.left
			}
		}

		this.material = new THREE.ShaderMaterial({
			uniforms: uniforms,
			vertexShader: this.vertexShader,
			fragmentShader: this.fragmentShader,
			blending: this.convertBlending(this.blending),
			depthTest: true,
			depthWrite: false,
			transparent: true,
			vertexColors: true,
		})

		
		this.geometry = new THREE.BufferGeometry()
		this.geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
		this.geometry.setAttribute('color', new THREE.Float32BufferAttribute([], 4));

		this.globalPointsObject.geometry = this.geometry
		this.globalPointsObject.material = this.material

		this.particles = []

		this.updateGeometry()
	}

	convertBlending(selectIndex) {
		switch(this.blendingOptions[selectIndex]) {
			case "Additive":
				return THREE.AdditiveBlending
			case "Normal":
				return THREE.NormalBlending
			case "Multiply":
				return THREE.MultiplyBlending
			case "None":
				return THREE.NoBlending
			case "Subtractive":
				return THREE.SubtractiveBlending
			default:
				return THREE.AdditiveBlending
		}
	}

	awake() {

	}

	update() {
		this.addParticles()
    	this.updateParticles(RE.Runtime.deltaTime);
    	this.updateGeometry();
	}

	addParticles() {
		if(this.particles.length >= this.max) {
			return
		}
		for (let i = 0; i < this.spawnPerTick; i++) {
		  const life = this.lifespan;
		  const newPosition = new THREE.Vector3()
		  this.initialLocation.getWorldPosition(newPosition)
		
		  newPosition.add(new THREE.Vector3(
			  1 * Math.random() - 0.5,
			  1 * Math.random() - 0.5,
			  1 * Math.random() - 0.5
		  ).multiply(this.positionRandomBounds))

		  const newVelocity = this.initialVelocity.clone()
		  newVelocity.add(new THREE.Vector3(
			  1 * Math.random() - 0.5,
			  1 * Math.random() - 0.5,
			  1 * Math.random() - 0.5
			).multiply(this.velocityRandomBounds)
		  )

		  this.particles.push({
			  position: newPosition,
			  life: life,
			  maxLife: life,
			  velocity: newVelocity,
			  color: this.color,
			  alpha: this.alpha,
		  });
		}
	  }
	
	  updateGeometry() {
		const positions = [];
		const colors = []
	
		for (let p of this.particles) {
		  positions.push(p.position.x, p.position.y, p.position.z);
		  colors.push(p.color.r, p.color.g, p.color.b, p.alpha);
		}
	
		this.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
		this.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4));
	  
		this.geometry.attributes.position.needsUpdate = true;
		this.geometry.attributes.color.needsUpdate = true;
	  }
	
	  updateParticles(deltaTime) {
		for (let p of this.particles) {
		  p.life -= deltaTime;
		}
	
		this.particles = this.particles.filter(p => {
		  return p.life > 0.0;
		});
	
		for (let p of this.particles) {		
		  p.position.add(p.velocity.clone());
		}

		// RE.Debug.log(JSON.stringify(this.particles[0].position))
		let camera = RE.App.currentScene.getObjectByProperty('uuid', RE.App.activeCamera)
	
		this.particles.sort((a, b) => {
		  const d1 = camera.position.distanceTo(a.position);
		  const d2 = camera.position.distanceTo(b.position);
	
		  if (d1 > d2) {
			return -1;
		  }
	
		  if (d1 < d2) {
			return 1;
		  }
	
		  return 0;
		});
	  }

}

RE.registerComponent(ParticleSystem)