<template>
  <div v-bind="{ ...$props, ...$attrs }" v-on="$listeners" class="messageIn">
    <div class="load-earlier clickable" @click="loadPrevious">
      <v-icon color="white" size="28">expand_less</v-icon>
    </div>

    <!-- Currently recording users -->
    <div class="typing-users">
      <transition-group name="list" tag="div">
        <v-avatar v-for="(member) in recordingMembersExceptMe" :key="member.userId" class="typing-user" size="32" color="grey">
          <img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
          <span v-else class="white--text headline">{{
            member.name.substring(0, 1).toUpperCase()
          }}</span>
        </v-avatar>
      </transition-group>
    </div>

    <div class="sound-wave-view">
      <div class="volume-container">
        <div ref="volume"></div>
      </div>
      <v-avatar v-if="currentAudioEvent" class="avatar" ref="avatar" size="32" color="#ededed"
        @click.stop="otherAvatarClicked($refs.avatar.$el)">
        <img v-if="messageEventAvatar(currentAudioEvent)" :src="messageEventAvatar(currentAudioEvent)" />
        <span v-else class="white--text headline">{{
          eventSenderDisplayName(currentAudioEvent).substring(0, 1).toUpperCase()
        }}</span>
      </v-avatar>
    </div>

    <!-- Current emoji reactions -->
    <div class="typing-users">
      <transition-group name="list" tag="div">
        <v-avatar v-for="reaction in reactions" :key="reaction.member.userId" class="typing-user" size="32" color="grey">
          <img v-if="memberAvatar(reaction.member)" :src="memberAvatar(reaction.member)" />
          <span v-else class="white--text headline">{{
            reaction.member.name.substring(0, 1).toUpperCase()
          }}</span>
          <div class="reaction-emoji">{{ reaction.emoji }}</div>
        </v-avatar>
      </transition-group>
    </div>

    <div v-if="currentAudioEvent" class="senderAndTime">
      <div class="sender">{{ eventSenderDisplayName(currentAudioEvent) }}</div>
      <div class="time">
        {{ formatTime(currentAudioEvent.event.origin_server_ts) }}
      </div>
    </div>
    <div class="play-time">
      {{ currentTime }} / {{ totalTime }}
    </div>
    <div v-if="currentAudioEvent" class="auto-audio-player">
      <v-btn id="btn-rewind" :disabled="!info || info.loading" @click.stop="rewind" icon>
        <v-icon size="28">$vuetify.icons.rewind</v-icon>
      </v-btn>
      <v-progress-circular v-if="info && info.loading" :value="info.loadPercent" @click.stop="pause" size="36" width="2" style="margin:26px"></v-progress-circular>
      <v-btn v-else-if="info && info.playing" id="btn-pause" @click.stop="pause" icon>
        <v-icon size="56">$vuetify.icons.pause_circle</v-icon>
      </v-btn>
      <v-btn v-else id="btn-play" @click.stop="play" icon>
        <v-icon size="56">$vuetify.icons.play_circle</v-icon>
      </v-btn>
      <v-btn id="btn-forward" :disabled="!info || info.loading" @click.stop="forward" icon>
        <v-icon size="28">$vuetify.icons.forward</v-icon>
      </v-btn>
    </div>

    <div class="load-later">
      <div style="align-self: flex-end;">
        <v-btn class="clap-button clickable" text elevation="0" v-blur @click.stop="clapButtonClicked()">👏</v-btn>
        <v-btn :class="{'mic-button': true, 'dimmed': !canRecordAudio}" ref="mic_button" fab small elevation="0" v-blur
          @click.stop="micButtonClicked()">
          <v-icon color="white">mic</v-icon>
        </v-btn>
      </div>
      <v-icon class="clickable" @click="loadNext" color="white" size="28">expand_more</v-icon>
    </div>

    <div v-if="showReadOnlyToast" class="toast-read-only">{{ $t("message.not_allowed_to_send") }}</div>
  </div>
</template>

<script>
import messageMixin from "./messages/messageMixin";
import util from "../plugins/utils";

export default {
  mixins: [messageMixin],
  components: {},
  props: {
    autoplay: {
      type: Boolean,
      default: function () {
        return true
      }
    },
    events: {
      type: Array,
      default: function () {
        return []
      }
    },
    readMarker: {
      type: String,
      default: function () {
        return null;
      }
    },
    recordingMembers: {
      type: Array,
      default: function () {
        return []
      }
    },
  },
  data() {
    return {
      REACTION_ANIMATION_TIME: 2500,
      info: null,
      currentAudioEvent: null,
      autoPlayNextEvent: false,
      analyzer: null,
      analyzerDataArray: null,
      showReadOnlyToast: false,
      reactions: [],
      updateReactionsTimer: null,
    };
  },
  mounted() {
    this.$root.$on('audio-playback-started', this.audioPlaybackStarted);
    this.$root.$on('audio-playback-paused', this.audioPlaybackPaused);
    this.$root.$on('audio-playback-ended', this.audioPlaybackEnded);
    this.$root.$on('audio-playback-reaction', this.audioPlaybackReaction);
    document.body.classList.add("dark");
    this.$audioPlayer.setAutoplay(false);
  },
  beforeDestroy() {
    this.$root.$off('audio-playback-started', this.audioPlaybackStarted);
    this.$root.$off('audio-playback-paused', this.audioPlaybackPaused);
    this.$root.$off('audio-playback-ended', this.audioPlaybackEnded);
    this.$root.$off('audio-playback-reaction', this.audioPlaybackReaction);
    document.body.classList.remove("dark");
    this.$audioPlayer.removeListener(this._uid);
    this.currentAudioEvent = null;
  },
  computed: {
    canRecordAudio() {
      return !this.$matrix.currentRoomIsReadOnlyForUser && util.browserCanRecordAudio();
    },
    currentTime() {
      return util.formatDuration(this.info ? this.info.currentTime : 0);
    },
    currentTimeMs() {
      return this.info ? this.info.currentTime : 0;
    },
    totalTime() {
      return util.formatDuration(this.info ? this.info.duration : 0);
    },
    recordingMembersExceptMe() {
      return this.recordingMembers.filter((member) => {
        return member.userId !== this.$matrix.currentUserId;
      });
    },
  },
  watch: {
    autoplay: {
      immediate: true,
      handler(autoplay, ignoredOldValue) {
        if (!autoplay) {
          this.pause();
        }
      }
    },
    events: {
      immediate: true,
      handler(events, ignoredOldValue) {
        if (!this.currentAudioEvent || this.autoPlayNextEvent) {
          // Make sure all events are decrypted!
          const eventsBeingDecrypted = events.filter((e) => e.isBeingDecrypted());
          if (eventsBeingDecrypted.length > 0) {
            Promise.allSettled(eventsBeingDecrypted.map((e) => e.getDecryptionPromise())).then(() => {
              this.loadNext(this.autoPlayNextEvent && this.autoplay);
            });
          } else {
            this.loadNext(this.autoPlayNextEvent && this.autoplay);
          }
        }
      }
    },
    currentAudioEvent: {
      immediate: true,
      handler(value, oldValue) {
        if (value && oldValue && value.getId && oldValue.getId && value.getId() === oldValue.getId()) {
          return;
        }
        if (!value || !value.getId) {
          return;
        }

        this.clearReactions();

        this.info = this.$audioPlayer.addListener(this._uid, value);

        const autoPlayWasSet = this.autoPlayNextEvent;
        this.autoPlayNextEvent = false;

        if (value.getSender() == this.$matrix.currentUserId) {
          // Sent by us. Don't autoplay if we just sent this (i.e. it is ahead of our read marker)
          if (this.room && !this.room.getReceiptsForEvent(value).includes(value.getSender())) {
            this.$audioPlayer.setAutoplay(false);
            this.autoPlayNextEvent = autoPlayWasSet;
          }
        }

        this.$audioPlayer.load(value, this.timelineSet);
      }
    },
  },
  methods: {
    play() {
      if (this.currentAudioEvent) {
        this.$audioPlayer.setAutoplay(false);
        this.$audioPlayer.play(this.currentAudioEvent, this.timelineSet);
      }
    },
    pause() {
      this.$audioPlayer.setAutoplay(false);
      if (this.currentAudioEvent) {
        this.$audioPlayer.pause(this.currentAudioEvent);
      }
    },
    rewind() {
      if (this.currentAudioEvent) {
        this.$audioPlayer.seekRelative(this.currentAudioEvent, -15000);
      }
    },
    forward() {
      if (this.currentAudioEvent) {
        this.$audioPlayer.seekRelative(this.currentAudioEvent, 15000);
      }
    },
    audioPlaybackStarted() {
      if (!this.analyser) {
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        let audioSource = null;
        if (audioCtx) {
          audioSource = audioCtx.createMediaElementSource(this.$audioPlayer.getPlayerElement());
          this.analyser = audioCtx.createAnalyser();
          audioSource.connect(this.analyser);
          this.analyser.connect(audioCtx.destination);

          this.analyser.fftSize = 128;
          const bufferLength = this.analyser.frequencyBinCount;
          this.analyzerDataArray = new Uint8Array(bufferLength);
        }
      }
      this.updateVisualization();
      if (this.currentAudioEvent) {
        this.$emit("mark-read", this.currentAudioEvent.getId(), this.currentAudioEvent.getId());
      }
    },
    audioPlaybackPaused() {
      this.clearVisualization();
    },
    audioPlaybackEnded() {
      this.clearVisualization();
      this.loadNext(true && this.autoplay);
    },
    audioPlaybackReaction(reaction) {
      // Play sound!
      const audio = new Audio(require("@/assets/sounds/clapping.mp3"));
      audio.volume = 0.6;
      audio.play();

      const member = this.room.getMember(reaction.sender);
      if (member) {
        this.reactions.push(Object.assign({ addedAt: Date.now(), member: member}, reaction));
      if (!this.updateReactionsTimer) {
        this.updateReactionsTimer = setInterval(this.updateReactions, 300);
      }
      }
    },
    loadPrevious() {
      const audioMessages = this.events.filter((e) => e.getContent().msgtype === "m.audio");
      for (let i = 0; i < audioMessages.length; i++) {
        const e = audioMessages[i];
        if (this.currentAudioEvent && e.getId() === this.currentAudioEvent.getId()) {
          if (i > 0) {
            this.pause();
            this.currentAudioEvent = audioMessages[i - 1];
            return;
          }
          break;
        }
      }
      this.$emit("loadprevious");
    },
    loadNext(autoplay = false) {
      const audioMessages = this.events.filter((e) => e.getContent().msgtype === "m.audio");
      if (audioMessages.length == 0) {
        // Try to load earlier
        this.$emit("loadprevious");
        return;
      }
      if (!this.currentAudioEvent) {
        // Figure out which audio event to start with, i.e. our "read marker"
        for (let i = 0; i < audioMessages.length; i++) {
          const e = audioMessages[i];
          if (e.getId() === this.readMarker) {
            if (i < (audioMessages.length - 1)) {
              this.pause();
              this.$audioPlayer.setAutoplay(autoplay);
              this.currentAudioEvent = audioMessages[i + 1];
            } else {
              this.autoPlayNextEvent = true;
              this.$audioPlayer.setAutoplay(autoplay);
              this.currentAudioEvent = e;
              this.$emit("loadnext");
            }
            return;
          }
        }

        // No read marker found. Just use the first event here...
        if (audioMessages.length > 0) {
          this.pause();
          this.$audioPlayer.setAutoplay(autoplay);
          this.currentAudioEvent = audioMessages[0];
        }
        return;
      }

      for (let i = 0; i < audioMessages.length; i++) {
        const e = audioMessages[i];
        if (e.getId() === this.currentAudioEvent.getId()) {
          if (i < (audioMessages.length - 1)) {
            this.pause();
            this.$audioPlayer.setAutoplay(autoplay);
            this.currentAudioEvent = audioMessages[i + 1];
          } else {
            this.autoPlayNextEvent = true;
            this.$audioPlayer.setAutoplay(autoplay);
            this.$emit("loadnext");
          }
          break;
        }
      }
    },
    updateVisualization() {

      const volume = this.$refs.volume;
      if (volume && this.analyser) {
        const volumeContainer = volume.parentElement;

        const bufferLength = this.analyser.frequencyBinCount;

        this.analyser.getByteFrequencyData(this.analyzerDataArray);
        var value = 0;
        for (let i = 0; i < bufferLength; i++) {
          value += this.analyzerDataArray[i];
        }
        value = value / bufferLength;
        const avatarWidth = 1.1 * this.$refs.avatar ? this.$refs.avatar.clientWidth : 104;
        const range = Math.max(0, (volumeContainer.clientWidth - avatarWidth));
        const w = avatarWidth + (value * range) / 256;
        volume.style.width = "" + w + "px";
        volume.style.height = "" + w + "px";
        const color = 80 + (value * (256 - 80)) / 256;
        volume.style.backgroundColor = `rgb(${color},${color},${color})`;
        if (this.info && this.info.playing) {
          requestAnimationFrame(this.updateVisualization);
        } else {
          this.clearVisualization();
        }
      }
    },
    clearVisualization() {
      const volume = this.$refs.volume;
      volume.style.width = "0px";
      volume.style.height = "0px";
      volume.style.backgroundColor = "transparent";
    },
    updateReactions() {
      const now = Date.now();
      this.reactions = this.reactions.filter(r => {
        return (r.addedAt + this.REACTION_ANIMATION_TIME > now);
      });
      if (this.reactions.length == 0) {
        this.clearReactions();
      }
    },

    clearReactions() {
      if (this.updateReactionsTimer) {
        clearInterval(this.updateReactionsTimer);
        this.updateReactionsTimer = null;
      }
      this.reactions = [];
    },
    
    memberAvatar(member) {
      if (member) {
        return member.getAvatarUrl(
          this.$matrix.matrixClient.getHomeserverUrl(),
          40,
          40,
          "scale",
          true
        );
      }
      return null;
    },

    micButtonClicked() {
      if (this.$matrix.currentRoomIsReadOnlyForUser) {
        this.showReadOnlyToast = true;
        setTimeout(() => {
          this.showReadOnlyToast = false;
        }, 3000);
      } else {
        this.$emit('start-recording');
      }
    },

    clapButtonClicked() {
      if (this.currentAudioEvent) {
        this.$emit("sendclap", { event: this.currentAudioEvent, timeOffset: this.currentTimeMs })

        // Also, play locally
        this.audioPlaybackReaction({
          sender: this.$matrix.currentUserId,
          emoji: "👏"
        });
      }
    }
  }
};
</script>

<style lang="scss">
@import "@/assets/css/chat.scss";
</style>