<template>
  <div>
    <canvas 
      width="600px" height="200px" 
      v-render-waves="{waves:waves, activeWave:activeWave, obstacleSet:obstacleSets[currentObstacleSet]}"
      v-toggle-sound="{on:playSound}"
    >
    </canvas>
    <img src="../assets/barrier-blue.png"id="blue-barrier" class="canvas-asset">
    <img src="../assets/barrier-red.png" id="red-barrier" class="canvas-asset" >
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

window.player = null;
export default {
  props : ['amplitudes', 'size', 'frequency', 'amplitude'],
  directives : {
    renderWave : function(canvas, meh){
      var amplitudes = [];
      amplitudes[meh.value.frequency||0] = (meh.value.amplitude||100)/100;
      
      var plot = new LinePlot({
        canvas : canvas
      });
      var wavgen = new WaveGenerator({width: canvas.width});
      var buffer = wavgen.render(amplitudes);

      plot.plot(buffer, 2, -2);
    }
  },
  computed: {
    ...mapState(['waves', 'activeWave', 'playSound', 'obstacleSets', 'currentObstacleSet'])
  },
  data () {
    return {}
  },
  mounted () {
    // player = new WavePlayer();
  },
  directives : {
    toggleSound (canvas, data){
      player = player || new WavePlayer();
      player.setVolume(data.value.on ? 1 : 0);
    },
    renderWaves (canvas, data, vnode) {
      player = player || new WavePlayer();
      player.setVolume(0);
      
      var waves = data.value.waves;
      var activeWave = data.value.activeWave;
      var state = vnode.context.$store.state;
      //var obstacles = data.value.obstacleSet;

      var stablePlot = new LinePlot({
        canvas : canvas,
        color: '#fff',
        shouldClear: false
      });

      var activePlot = new LinePlot({
        canvas : canvas,
        color: '#fff',
      });
      
      var wavgen = new WaveGenerator({width: canvas.width});

      // var withoutActive = [
      //   ...waves,
      //   {frequency: activeWave.frequency, amplitude: -activeWave.amplitude}
      // ];

      // var buffer = wavgen.render(withoutActive);
      // stablePlot.plot(buffer, 2, -2);

      var scale = wavgen.findMax(waves) * 1.2;
      var detector = new CollisionDetector({width: canvas.width});
      var collisions = detector.findCollisions(waves, data.value.obstacleSet, scale);

      activePlot.color = (collisions.length == 0) ? 'green' : 'red';

      var buffer = wavgen.render(waves);
      activePlot.plot(buffer, scale, -scale, true);

      player.play(waves);


      var obstaclePlotter = new ObstaclePlotter({canvas: canvas, color:'#2bbefc'});
      obstaclePlotter.plot(data.value.obstacleSet);
    }
  }
}

class CollisionDetector {
  constructor(opts){
    this.width = opts.width;
    this.wavegen = new WaveGenerator({width: opts.width});
  }

  findCollisions(waves, obstacles, scale){
    var collisions = [];
    for(let obstacle of obstacles){

      var [xi,y,z] = obstacle;
      for(var jj=0; jj<2; jj++){
        if(jj==0){
          var x = xi - (10 / this.width);
        }else{
          var x = xi + (10 / this.width);
        }
        var waveY = this.wavegen.findPoint(waves, x * this.width, true);
        var waveScreenY = (waveY / scale);
        var obsScreenY = z - Math.sign(z) * y;
        if(waveScreenY > obsScreenY == (z == 1)){
          collisions.push(obstacle);
          obstacle.hit = true;
          break;
        }else{
          obstacle.hit = false;
        }
      }
    }
    return collisions;
  }
}


var plotTimeout = null;
class ObstaclePlotter {
  constructor(opts){
    this.color = opts.color || 'white';
    this.canvas = opts.canvas;
  }

  plot(obstacles){
    for(var i=0; i<obstacles.length; i++){
      var [x,y,z] = obstacles[i];

      var ctx = this.canvas.getContext("2d");

      var imgName =  obstacles[i].hit ? 'red-barrier' : 'blue-barrier'
      var img = document.getElementById(imgName);
      if(plotTimeout){
        clearTimeout(plotTimeout);
      }

      if(img && img.complete){
        ctx.save();
        ctx.translate(0, this.canvas.height / 2);
  
        // flip context horizontally
        ctx.scale(1, z);
        // ctx.translate(0, -this.canvas.height / 2);

        ctx.drawImage(img, this.canvas.width * x - 10, this.canvas.height/2 - (y * this.canvas.height/2));
        ctx.restore();
      }else{
        var t = this;
        plotTimeout = setTimeout(function(){
          t.plot(obstacles);  
        }, 1000);
        var w = 10;
        var rx = x * this.canvas.width - w/2;
        var rh = y * this.canvas.height / 2;
        var ry = 0;
        if(z == -1){
          ry = 0;
        }else if(z == 1){
          ry = 1 * this.canvas.height - rh;
        }
        var rw = 10
        ctx.strokeStyle = obstacles[i].hit ? 'red' : this.color;
        ctx.beginPath();
        ctx.rect(rx,ry,rw,rh);
        ctx.stroke();
      }
    }
  }
}

class WavePlayer {
  constructor(){
    window.context = window.context || new AudioContext();
    this.context = window.context;
    this.oscillators = []
    this.gains = []
    this.compressor = this.context.createGain()
    this.masterVolume = this.context.createGain();

    this.compressor.connect(this.masterVolume);
    this.masterVolume.connect(this.context.destination);
    for(var i=0; i<25; i++){
      var outFreq = (i+1);
      // while(outFreq / 2 >= 1){
      //   outFreq = outFreq / 2;
      // }
      this.oscillators[i] = this.context.createOscillator();
      this.oscillators[i].frequency.value = outFreq * 440;
      this.gains[i] = this.context.createGain();
      this.gains[i].gain.value = 0;
      this.oscillators[i].connect(this.gains[i]);
      this.gains[i].connect(this.compressor);
      this.oscillators[i].start();
    }
  }

  play(waves){
    var totalAmp = 0;
    for(var i=0; i<this.gains.length; i++){
      this.gains[i].gain.value = 0;
    }

    for(var i=0; i<waves.length; i++){
      var wave = waves[i];
      this.gains[wave.frequency-1].gain.value += (wave.amplitude / 100)
      totalAmp += Math.abs(wave.amplitude / 100);
    }
    this.compressor.gain.value = Math.min(1, 0.8 / totalAmp);
  }

  setVolume(vol){
    this.masterVolume.gain.value = vol;
  }
}

class WaveGenerator {
  constructor(opts){
    this.width = opts.width || 400;
    this.buffer = new Float32Array(this.width);
  }

  findMax(waves){
    var maxAmp = 0;
    for(let wave of waves){
      maxAmp += Math.abs(wave.amplitude/ 100);
    }
    return maxAmp;
  }

  render(state){
    this.buffer.fill(0);

    for(var x=0; x<this.buffer.length; x++){
      this.buffer[x] += this.findPoint(x);
    }

    return this.buffer; 
  }

  render(waves){
     for(var x=0; x<this.buffer.length; x++){
       this.buffer[x] += this.findPoint(waves,x);
     }
     return this.buffer;
  }

  findPoint(waves, x, ddd){
    var pos = 0;
    for(var waveIdx=0; waveIdx<waves.length; waveIdx++){
      var wave = waves[waveIdx];
      var amp = wave.amplitude / 100;
      var freq = wave.frequency;
      var phase = wave.phase / 100;
      pos += Math.sin((freq * -(x / this.width) + phase) * (Math.PI * 2)) * amp;
    }
    return pos;
  }
}

function LinePlot(opts){
  this.canvas = typeof(opts.canvas) === "string" 
              ? document.getElementById(opts.canvas) 
              : opts.canvas;
  this.color = opts.color || 'white';
}
LinePlot.prototype = {
  plot : function(buffer, min, max, clear){
    clear = clear === false ? false : true;
    var hscale = buffer.length / this.canvas.width;
    var vscale = this.canvas.height / (max - min);
    var ctx = this.canvas.getContext("2d");
    if(clear){
      ctx.clearRect(0,0,this.canvas.width, this.canvas.height);
    }
    ctx.strokeStyle = this.color;
    ctx.beginPath();
    for(var i=0; i < this.canvas.width; i++){
      var bufferIdx = Math.floor(i * hscale)
      var val = Math.floor((buffer[bufferIdx] - min) * vscale);
      ctx[i==0 ? "moveTo" : "lineTo"](i, this.canvas.height - val, 1, 1);
    }
    ctx.stroke();
  }
}
</script>

<style>
  canvas {
    background-color: #000;
  }
  .canvas-asset {
    display: none;
  }
</style>
