<template>
  <div class="player">
    <div v-if="deviceReady">RNBOValues: {{ createdNodes.noiseDevice }}</div>
    <Microphone ref="Microphone" @update:attach="setupMicrophone" />
    <AudioElement
      ref="Noise"
      key="5"
      :src="noise_src"
      title="Noise"
      @update:volume="updateNoiseGain"
      @update:canplay="handleCanPlayNoise"
      @update:playing="handlePlayingUpdate2"
    >
      <template #default="{}">
        <img v-if="!muted" style="width: 25px; height: 25px;" src="~/assets/image/sound.svg" title="Click to mute" @click="toggleMute()">
        <img v-if="muted" style="width: 25px; height: 25px;" src="~/assets/image/sound_muted.svg" title="Click to unmute" @click="toggleMute()">
      </template>
    </AudioElement>
  </div>
</template>
<script lang="ts">
import type { Device } from '@rnbo/js'
import AudioElement from '../AudioElement.vue'
import { useAudioStore } from '../../../stores/audio'
import Microphone from '../tests/Microphone.vue'
import { useDevicesStore } from '../../../stores/device'

export default {
  name: 'RNBODevice',
  components: { AudioElement, Microphone },
  emits: { 'update:control-value': null },
  data () {
    return {
      audioContext: useAudioStore().getContext(),
      createdNodes: {} as any,
      noiseReady: false,
      micReady: false,
      deviceReady: false,
      noise_src: window.location.origin + useRuntimeConfig().public.noise_src as string,
      fading: false,
      connected: false,
      muted: false
    }
  },

  methods: {

    toggleMute () {
      const noiseElement = this.$refs.Noise as typeof AudioElement
      const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
      noiseAudioElement.muted = !noiseAudioElement.muted
      this.muted = noiseAudioElement.muted
    },
    mute () {
      const noiseElement = this.$refs.Noise as typeof AudioElement
      const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
      noiseAudioElement.muted = true
      this.muted = true
    },
    unmute () {
      const noiseElement = this.$refs.Noise as typeof AudioElement
      const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
      noiseAudioElement.muted = false
      this.muted = false
    },
    // This methodd gets a microphone stream from the micorphone component and creates the microphone node
    // need to be called before the noise device is connected to the audio graph
    setupMicrophone (stream:MediaStream) {
      useNuxtApp().$logger.log('setup Microphone')
      try {
        this.createdNodes.microphone ||= this.audioContext.createMediaStreamSource(stream)
        this.micReady = true
      } catch (error: any) {
        this.micReady = false
        throw new Error(error.message)
      }
    },
    // This method setup a RNBO Device, it gets the name of the Patch and add the noise audio node to createdNodes
    async setupDevice () {
      await useAudioStore().ensureAudioContextRunning()
      useNuxtApp().$logger.log('setup Device')
      try {
        const deviceStore = useDevicesStore()
        const device = await deviceStore.createNoiseDevice('adaptive_masking_controller_NoMusic') as Device // ich bekomme keine analyse Werte raus
        this.createdNodes.noiseDevice = deviceStore.getDeviceAudioNode('adaptive_masking_controller_NoMusic')
        this.deviceReady = true
        this.attachDBValueListener(device)
      } catch (error) {
        useNuxtApp().$logger.error('Error setting up device, fall back.', { error })
        this.deviceReady = false
      }
    },

    // This method takes the controll value before ramp and controls the volume of music
    attachDBValueListener (noiseDevice: Device) {
      noiseDevice.messageEvent.subscribe((ev: any) => {
        try {
          // if (ev.tag === 'out4') { // out4 represents controll value before Timeramp
          if (ev.tag === 'out3') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out3= ' + newValue[0])
          }
          if (ev.tag === 'out4') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out4= ' + newValue[0])
          }
          if (ev.tag === 'out5') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out5= ' + newValue[0])
          }
          if (ev.tag === 'ou6') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out6= ' + newValue[0])
          }
          if (ev.tag === 'out7') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out7= ' + newValue[0])
          }
          if (ev.tag === 'out8') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('out8= ' + newValue[0])
          }
          if (ev.tag === 'out9= ') { // out3 represents controll value in dB after Timeramp
            const newValue = ev.payload
            useNuxtApp().$logger.log('Band 1000 = ' + newValue)
            this.$emit('update:control-value', newValue[0])
          }
        } catch (error: any) {
          // this.$logger.warn('Failed to attach a control value listener, music is not gain controlled.')
        }
      })
    },
    // This method helps to get the ressources free when we stop playing the audio
    // without it would be louder each time we start playing
    refreshAudioContext () {
      const newAudioContext = new AudioContext()
      this.audioContext.close()
      useAudioStore().audioContext = newAudioContext
      this.audioContext = useAudioStore().getContext()
    },
    fadeInGains () {
      this.unmute()
      // useNuxtApp().$logger.log('Fade In Gains')
      if (useAudioStore().playing !== true) { return }
      const fadeTime = this.audioContext.currentTime + 3.0

      setTimeout(() => {
        this.fading = true
        const noiseGain = this.createdNodes.noiseGain
        noiseGain.gain.linearRampToValueAtTime(1.0, fadeTime)
      }, 450)
      setTimeout(() => {
        this.fading = false
      }, fadeTime * 1000)
    },
    fadeOutGains () {
      if (this.createdNodes.noiseGain) {
        const noiseGainValue = this.createdNodes.noiseGain.gain.value
        this.createdNodes.noiseGain.gain.linearRampToValueAtTime(noiseGainValue, this.audioContext.currentTime)
        this.createdNodes.noiseGain.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 1.3)
      }
    },

    handleCanPlayNoise (state: boolean) {
      // useNuxtApp().$logger.log('NoiseElement has now playingstate: ' + state)
      this.noiseReady = state
    },
    readyForWebaudio () {
      if (!this.noiseReady) {
        return false
      }
      if (!this.micReady) {
        return false
      }
      if (!this.deviceReady) {
        return false
      }
      return true
    },
    async handlePlayingUpdate2 (state: boolean) {
      useNuxtApp().$logger.log('handling Playing Update2= ' + state, this.audioContext.state)
      if (!state) {
        this.mute()
        return
      }
      if (this.readyForWebaudio()) {
        this.handlePlayingUpdate(true)
      } else {
        if (!this.deviceReady) {
          useNuxtApp().$logger.log('Device is not ready, create it now')
          await this.setupDevice()
        }
        if (!this.micReady) {
          // await this.setupMicrophone(Microphone)
          useNuxtApp().$logger.log('micophone not yet ready attach it!! ')
          // useNuxtApp().$logger.log('microphone attached' + stream)
        }
        if (this.readyForWebaudio()) {
          useNuxtApp().$logger.log('everything is now ready start play')
          this.handlePlayingUpdate(true)
        } else {
          useNuxtApp().$logger.log('Waiting for all devices to be ready')
        }
      }
    },
    handlePlayingUpdate (state: boolean) {
      try {
        // Stop the music again, mute it and set the noiseReady or musicReady to true
        if (state) {
          useNuxtApp().$logger.log('stop playing')
          const noiseElement = this.$refs.Noise as typeof AudioElement
          const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
          const audioContext = this.audioContext
          const destination = this.audioContext.destination
          audioContext.resume()
          this.createdNodes.noiseGain ||= audioContext.createGain()
          this.createdNodes.noiseGain.gain.setValueAtTime(0, audioContext.currentTime)

          this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(noiseAudioElement)

          // HERE THE NOISE PATCH COMES INTO PLAY

          this.createdNodes.micSplitter ||= audioContext.createChannelSplitter(2)
          this.createdNodes.noiseInputChannelSplitter ||= audioContext.createChannelSplitter(2)
          this.createdNodes.microphone.connect(this.createdNodes.micSplitter)
          this.createdNodes.noiseSource.connect(this.createdNodes.noiseInputChannelSplitter)
          this.createdNodes.micSplitter.connect(this.createdNodes.noiseDevice, 0, 0)
          this.createdNodes.noiseInputChannelSplitter.connect(this.createdNodes.noiseDevice, 0, 1)
          this.createdNodes.noiseInputChannelSplitter.connect(this.createdNodes.noiseDevice, 1, 2)

          this.createdNodes.noiseDevice.connect(this.createdNodes.noiseGain)

          this.createdNodes.noiseGain.connect(destination)
          this.createdNodes.noiseGain.gain.cancelScheduledValues(this.audioContext.currentTime)
          this.createdNodes.noiseGain.gain.setValueAtTime(0, this.audioContext.currentTime)
          noiseAudioElement.muted = false
          this.connected = true
          this.unmute()
          this.fadeInGains()
          useAudioStore().playing = true
          this.$logger.info('RNBO Patch successfully connected and playing')
        } else {
          // Music has just stopped react on it.
          this.$logger.info('Stopping audio and disconnecting RNBO Patch')
          this.fadeOutGains()
          this.createdNodes = []
          this.refreshAudioContext()
          this.connected = false
        }
      } catch (error) {
        this.$logger.info('Error in handlePlayingUpdate')
        this.connected = false
        useAudioStore().playing = false
        // You might want to show an error message to the user here
      }
    },
    updateNoiseGain (volume: number) {
      if (this.createdNodes.noiseGain) {
        useNuxtApp().$logger.log('volume= ' + volume)
        this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
      }
    }
  }
}

</script>
