import LocalPlayer from './LocalPlayer'
import CastPlayer from './CastPlayer'
import AudioTrack from './AudioTrack'

export default class PlayerHandler {
  constructor(ctx) {
    this.ctx = ctx
    this.audiobook = null
    this.playWhenReady = false
    this.player = null
    this.playerState = 'IDLE'
    this.currentStreamId = null
    this.startTime = 0

    this.lastSyncTime = 0
    this.lastSyncedAt = 0
    this.listeningTimeSinceSync = 0

    this.playInterval = null
  }

  get isCasting() {
    return this.ctx.$store.state.globals.isCasting
  }
  get isPlayingCastedAudiobook() {
    return this.audiobook && (this.player instanceof CastPlayer)
  }
  get isPlayingLocalAudiobook() {
    return this.audiobook && (this.player instanceof LocalPlayer)
  }
  get userToken() {
    return this.ctx.$store.getters['user/getToken']
  }
  get playerPlaying() {
    return this.playerState === 'PLAYING'
  }

  load(audiobook, playWhenReady, startTime = 0) {
    if (!this.player) this.switchPlayer()

    console.log('Load audiobook', audiobook)
    this.audiobook = audiobook
    this.startTime = startTime
    this.playWhenReady = playWhenReady
    this.prepare()
  }

  switchPlayer() {
    if (this.isCasting && !(this.player instanceof CastPlayer)) {
      console.log('[PlayerHandler] Switching to cast player')

      this.stopPlayInterval()
      this.playerStateChange('LOADING')

      this.startTime = this.player ? this.player.getCurrentTime() : this.startTime
      if (this.player) {
        this.player.destroy()
      }
      this.player = new CastPlayer(this.ctx)
      this.setPlayerListeners()

      if (this.audiobook) {
        // Audiobook was already loaded - prepare for cast
        this.playWhenReady = false
        this.prepare()
      }
    } else if (!this.isCasting && !(this.player instanceof LocalPlayer)) {
      console.log('[PlayerHandler] Switching to local player')

      this.stopPlayInterval()
      this.playerStateChange('LOADING')

      if (this.player) {
        this.player.destroy()
      }
      this.player = new LocalPlayer(this.ctx)
      this.setPlayerListeners()

      if (this.audiobook) {
        // Audiobook was already loaded - prepare for local play
        this.playWhenReady = false
        this.prepare()
      }
    }
  }

  setPlayerListeners() {
    this.player.on('stateChange', this.playerStateChange.bind(this))
    this.player.on('timeupdate', this.playerTimeupdate.bind(this))
    this.player.on('buffertimeUpdate', this.playerBufferTimeUpdate.bind(this))
  }

  playerStateChange(state) {
    console.log('[PlayerHandler] Player state change', state)
    this.playerState = state
    if (this.playerState === 'PLAYING') {
      this.startPlayInterval()
    } else {
      this.stopPlayInterval()
    }
    if (this.playerState === 'LOADED' || this.playerState === 'PLAYING') {
      this.ctx.setDuration(this.player.getDuration())
    }
    if (this.playerState !== 'LOADING') {
      this.ctx.setCurrentTime(this.player.getCurrentTime())
    }

    this.ctx.isPlaying = this.playerState === 'PLAYING'
    this.ctx.playerLoading = this.playerState === 'LOADING'
  }

  playerTimeupdate(time) {
    this.ctx.setCurrentTime(time)
  }

  playerBufferTimeUpdate(buffertime) {
    this.ctx.setBufferTime(buffertime)
  }

  async prepare() {
    var useHls = !this.isCasting
    if (useHls) {
      var stream = await this.ctx.$axios.$get(`/api/books/${this.audiobook.id}/stream`).catch((error) => {
        console.error('Failed to start stream', error)
      })
      if (stream) {
        console.log(`[PlayerHandler] prepare hls stream`, stream)
        this.setHlsStream(stream)
      }
    } else {
      // Setup tracks
      var runningTotal = 0
      var audioTracks = (this.audiobook.tracks || []).map((track) => {
        var audioTrack = new AudioTrack(track)
        audioTrack.startOffset = runningTotal
        audioTrack.contentUrl = `/lib/${this.audiobook.libraryId}/${this.audiobook.folderId}/${track.path}?token=${this.userToken}`
        audioTrack.mimeType = (track.codec === 'm4b' || track.codec === 'm4a') ? 'audio/mp4' : `audio/${track.codec}`

        runningTotal += audioTrack.duration
        return audioTrack
      })
      this.setDirectPlay(audioTracks)
    }
  }

  closePlayer() {
    console.log('[PlayerHandler] CLose Player')
    if (this.player) {
      this.player.destroy()
    }
    this.player = null
    this.playerState = 'IDLE'
    this.audiobook = null
    this.currentStreamId = null
    this.startTime = 0
    this.stopPlayInterval()
  }

  prepareStream(stream) {
    if (!this.player) this.switchPlayer()
    this.audiobook = stream.audiobook
    this.setHlsStream({
      streamId: stream.id,
      streamUrl: stream.clientPlaylistUri,
      startTime: stream.clientCurrentTime
    })
  }

  setHlsStream(stream) {
    this.currentStreamId = stream.streamId
    var audioTrack = new AudioTrack({
      duration: this.audiobook.duration,
      contentUrl: stream.streamUrl + '?token=' + this.userToken,
      mimeType: 'application/vnd.apple.mpegurl'
    })
    this.startTime = stream.startTime
    this.ctx.playerLoading = true
    this.player.set(this.audiobook, [audioTrack], this.currentStreamId, stream.startTime, this.playWhenReady)
  }

  setDirectPlay(audioTracks) {
    this.currentStreamId = null
    this.ctx.playerLoading = true
    this.player.set(this.audiobook, audioTracks, null, this.startTime, this.playWhenReady)
  }

  resetStream(startTime, streamId) {
    if (this.currentStreamId === streamId) {
      this.player.resetStream(startTime)
    } else {
      console.warn('resetStream mismatch streamId', this.currentStreamId, streamId)
    }
  }

  startPlayInterval() {
    clearInterval(this.playInterval)
    var lastTick = Date.now()
    this.playInterval = setInterval(() => {
      // Update UI
      if (!this.player) return
      var currentTime = this.player.getCurrentTime()
      this.ctx.setCurrentTime(currentTime)

      var exactTimeElapsed = ((Date.now() - lastTick) / 1000)
      lastTick = Date.now()
      this.listeningTimeSinceSync += exactTimeElapsed
      if (this.listeningTimeSinceSync >= 5) {
        this.sendProgressSync(currentTime)
        this.listeningTimeSinceSync = 0
      }
    }, 1000)
  }

  sendProgressSync(currentTime) {
    var diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime)
    if (diffSinceLastSync < 1) return

    this.lastSyncTime = currentTime
    if (this.currentStreamId) { // Updating stream progress (HLS stream)
      var listeningTimeToAdd = Math.max(0, Math.floor(this.listeningTimeSinceSync))
      var syncData = {
        timeListened: listeningTimeToAdd,
        currentTime,
        streamId: this.currentStreamId,
        audiobookId: this.audiobook.id
      }
      this.ctx.$axios.$post('/api/syncStream', syncData, { timeout: 1000 }).catch((error) => {
        console.error('Failed to update stream progress', error)
      })
    } else {
      // Direct play via chromecast does not yet have backend stream session model
      //   so the progress update for the audiobook is updated this way (instead of through the stream)
      var duration = this.getDuration()
      var syncData = {
        totalDuration: duration,
        currentTime,
        progress: duration > 0 ? currentTime / duration : 0,
        isRead: false,
        audiobookId: this.audiobook.id,
        lastUpdate: Date.now()
      }
      this.ctx.$axios.$post('/api/syncLocal', syncData, { timeout: 1000 }).catch((error) => {
        console.error('Failed to update local progress', error)
      })
    }
  }

  stopPlayInterval() {
    clearInterval(this.playInterval)
    this.playInterval = null
  }

  playPause() {
    if (this.player) this.player.playPause()
  }

  play() {
    if (!this.player) return
    this.player.play()
  }

  pause() {
    if (this.player) this.player.pause()
  }

  getCurrentTime() {
    return this.player ? this.player.getCurrentTime() : 0
  }

  getDuration() {
    return this.player ? this.player.getDuration() : 0
  }

  jumpBackward() {
    if (!this.player) return
    var currentTime = this.getCurrentTime()
    this.seek(Math.max(0, currentTime - 10))
  }

  jumpForward() {
    if (!this.player) return
    var currentTime = this.getCurrentTime()
    this.seek(Math.min(currentTime + 10, this.getDuration()))
  }

  setVolume(volume) {
    if (!this.player) return
    this.player.setVolume(volume)
  }

  setPlaybackRate(playbackRate) {
    if (!this.player) return
    this.player.setPlaybackRate(playbackRate)
  }

  seek(time) {
    if (!this.player) return
    this.player.seek(time, this.playerPlaying)
    this.ctx.setCurrentTime(time)

    // Update progress if paused
    if (!this.playerPlaying) {
      this.sendProgressSync(time)
    }
  }
}