<template>
  <div class="emitter">
    <div
      v-for="p of particles"
      :key="p.id"
      :style="{
        transform: `scale(${p.size}) translate3d(${p.x}px, ${p.y}px, ${p.z}px) rotate(${p.a}rad)`,
        opacity: `${options.opacity * p.l}`,
      }"
      class="particle"
    >
      <slot>{{ p.size.toFixed(0) }}</slot>
    </div>
  </div>
</template>

<script>

const defaults = {
  text: 'z',
  interval: { value: 1100, random: 500 },
  life: { value: 10000, random: 10 },
  animation: 'fade',
  opacity: 1,
  sx: { value: 5, random: 0 },
  sy: { value: -5, random: 0.5 },
  sa: { value: 0, random: 0 },
  dx: { value: 0, random: 0.2 },
  ddx: { value: 0, random: 0 },
  dy: { value: -0.3, random: 0.001 },
  ddy: { value: 0, random: 0.0001 },
  dz: { value: 0, random: 0.01 },
  da: { value: 0, random: 0.01 },
  dda: { value: 0, random: 0.001 },
  size: { value: 1, random: 0.1 },
  dsize: { value: 0, random: 0.001 },
  useAngle: false,
};

export default {
  name: 'Emitter',
  props: {
    text: { type: String, default: 'z' },
    config: { type: Object, default() { return {}; } },
  },
  data() {
    return {
      lastId: 0,
      lastEmitted: Date.now(),
      particles: [],
    };
  },
  computed: {
    options() {
      const retval = {};
      // eslint-disable-next-line guard-for-in
      for (const key in defaults) {
        retval[key] = this.config[key] || defaults[key];
      }
      return retval;
    },
  },
  mounted() {
    this.emit();
  },
  destroyed() {
    cancelAnimationFrame(this.emitter);
  },
  methods: {
    emit() {
      if (this.options) {
        this.emitter = requestAnimationFrame(() => {
          // spawn new
          let count = 0;
          let interval = this.randomize('interval');
          while (count < 10 && Date.now() - this.lastEmitted > interval) {
            this.createParticle();
            this.lastEmitted += interval;
            count += 1;
            interval = this.randomize('interval');
          }

          this.particles.forEach((p) => {
            const particle = p;
            particle.x += p.dx;
            particle.size += p.dsize;
            particle.dx += p.ddx;
            particle.y += p.dy;
            particle.dy += p.ddy;
            particle.z += particle.dz;
            particle.a += p.da;
            particle.da += p.dda;
            particle.life -= Date.now() - this.lastEmitted;
            particle.l = p.life / p.maxLife;
          });

          if (count > 0) {
            this.lastEmitted = Math.max(this.lastEmitted, Date.now() - 100);
          }

          // remove old
          this.particles = this.particles.filter(p => p.life > 0);

          this.emit();
        });
      }
    },
    createParticle() {
      const life = this.randomize('life');
      const a = this.randomize('sa');
      let dx = this.randomize('dx');
      let dy = this.randomize('dy');
      if (this.options.useAngle) {
        const scale = (dx + dy) / 2;
        dx = scale * Math.sin(a);
        dy = scale * -Math.cos(a);
      }

      this.lastId += 1;
      this.particles.push({
        id: this.lastId,
        text: this.options.text,
        x: this.randomize('sx'),
        y: this.randomize('sy'),
        z: 0,
        a,
        size: this.randomize('size'),
        dsize: this.randomize('dsize'),
        life,
        maxLife: life,
        dx,
        dy,
        dz: this.randomize('dz'),
        ddx: this.randomize('ddx'),
        ddy: this.randomize('ddy'),
        da: this.randomize('da'),
        dda: this.randomize('dda'),
      });
    },
    randomize(key) {
      const config = this.options[key];
      if (typeof config === 'object') {
        return config.value + this.nrand(config.random);
      }
      return config || 0;
    },
    nrand(v) {
      const r = Math.random() * (v * 2) - v;
      return r;
    },
  },
};
</script>

<style scoped>
.particle {
  z-index: 10;
  position: absolute;
  transform-origin: center;
  touch-action: none;
  pointer-events: none;
}
</style>
