$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS


JamTrackActions = @JamTrackActions

@JamTrackStore = Reflux.createStore(
  {
    listenables: [JamTrackActions, JamTrackMixdownActions]
    jamTrack: null
    previous: null
    requestedSearch: null
    requestedFilter: null
    subscriptions: {}
    enqueuedMixdowns: {}

    init: ->
      # Register with the app store to get @app
      this.listenTo(context.AppStore, this.onAppInit)

    onAppInit: (app) ->
      @app = app

    getState: () ->
      @state

    pickMyPackage: () ->

      return unless @jamTrack?


      for mixdown in @jamTrack.mixdowns

        myPackage = null
        for mixdown_package in mixdown.packages
          if mixdown_package.file_type == 'ogg' && mixdown_package.encrypt_type == 'jkz' && mixdown_package.sample_rate == @sampleRate
            myPackage = mixdown_package
            break

        mixdown.myPackage = myPackage

    subscriptionKey: (mixdown_package) ->
      "mixdown-#{mixdown_package.id}"

    subscribe: (mixdown_package) ->
      key = @subscriptionKey(mixdown_package)

      if !@watchedMixdowns[key]?
        # we need to register
        context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onMixdownSubscriptionEvent)
        @watchedMixdowns[key] = {type:'mixdown', id: mixdown_package.id}

    unsubscribe: (mixdown_package) ->
      key = @subscriptionKey(mixdown_package)
      if @watchedMixdowns[key]?
        context.JK.SubscriptionUtils.unsubscribe('mixdown', mixdown_package.id)
        delete @watchedMixdowns[key]

    manageWatchedMixdowns: () ->

      if @jamTrack?
        for mixdown in @jamTrack.mixdowns
          if mixdown.myPackage
            if mixdown.myPackage.signing_state == 'SIGNED'
              @unsubscribe(mixdown.myPackage)
            else
              @subscribe(mixdown.myPackage)

      else
        for key, subscription of @watchedMixdowns
          logger.debug("unsubscribing bulk", key, subscription)
          context.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id)

        # we cleared them all out; clear out storage
        @watchedMixdowns = {}

    onMixdownSubscriptionEvent: (e, data) ->
      logger.debug("JamTrackStore: subscription notification received: type:" + data.type, data)

      return unless @jamTrack?

      mixdown_package_id = data.id

      for mixdown in @jamTrack.mixdowns
        for mixdown_package in mixdown.packages
          if mixdown_package.id == mixdown_package_id
            mixdown_package.signing_state = data.body.signing_state
            mixdown_package.packaging_steps = data.body.packaging_steps
            mixdown_package.current_packaging_step = data.body.current_packaging_step
            logger.debug("updated package with subscription notification event")

            if mixdown_package.signing_state == 'SIGNING_TIMEOUT' || mixdown_package.signing_state == 'QUEUED_TIMEOUT' || mixdown_package.signing_state == 'QUIET_TIMEOUT' || mixdown_package.signing_state == 'ERROR'
              @reportError(mixdown)

            @changed()
            break

    # this drives the state engine required to get a Mixdown from 'available on the server' to
    manageMixdownSynchronization: () ->

      @jamTrack.activeMixdown = null if @jamTrack

      # let's see if we have a mixdown active?

      if !@jamTrack?.last_mixdown_id?
        logger.debug("JamTrackStore: no mixdown active")
        @clearMixdownTimers()
        return

      for mixdown in @jamTrack.mixdowns
        if mixdown.id == @jamTrack.last_mixdown_id
          @jamTrack.activeMixdown = mixdown
          logger.debug("JamTrackStore: mixdown active:", mixdown)
          break

      if @jamTrack.activeMixdown?

        # if we don't have this on the server yet, don't engage the rest of this logic...
        return if @jamTrack.activeMixdown?.myPackage?.signing_state != 'SIGNED'

        fqId = "#{@jamTrack.id}_#{@jamTrack.activeMixdown.id}-#{@sampleRate}"
        @trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId)

        logger.debug("JamTrackStore: JamTrackGetTrackDetail(#{fqId}).key_state: " + @trackDetail.key_state, @trackDetail)

        # first check if the version is not the same; if so, invalidate.

        if @trackDetail.version? && @jamTrack.activeMixdown.myPackage?
          if @jamTrack.activeMixdown.myPackage.version != @trackDetail.version
            logger.info("JamTrackStore: JamTrack Mixdown on disk is different version (stored: #{@trackDetail.version}, server: #{@jamTrack.activeMixdown.myPackage.version}. Invalidating")
            context.jamClient.InvalidateJamTrack(fqId)
            @trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId)

            if @trackDetail.version?
              logger.error("after invalidating package, the version is still wrong!", @trackDetail.version)
              throw "after invalidating package, the version is still wrong!"

        if @jamTrack.activeMixdown.client_state == 'cant_open'
          logger.debug(" skipping state check because of earlier 'cant_open'. user should hit retry. ")
          return

        if @jamTrack.activeMixdown.client_state == 'download_fail'
          logger.debug("skipping state check because of  earlier 'download_fail'. user should hit retry. ")
          return

        if @jamTrack.activeMixdown.client_state == 'downloading'
          logger.debug("skipping state check because we are downloading")

        switch @trackDetail.key_state
          when 'pending'
            @attemptKeying()
          when 'not authorized'
            # TODO: if not authorized, do we need to re-initiate a keying attempt?
            @attemptKeying()
          when 'ready'
            if @jamTrack.activeMixdown.client_state != 'ready'

              @clearMixdownTimers()
              @jamTrack.activeMixdown.client_state = 'ready'

              # now load it:
              # JamTrackPlay means 'load'
              logger.debug("JamTrackStore: loading mixdown")
              context.jamClient.JamTrackStopPlay();

              if @jamTrack.jmep

                if @jamTrack.activeMixdown.settings.speed?
                  @jamTrack.jmep.speed = @jamTrack.activeMixdown.settings.speed
                else
                  @jamTrack.jmep.speed = 0

                logger.debug("setting jmep data. speed:" + @jamTrack.jmep.speed)

                context.jamClient.JamTrackLoadJmep(fqId, @jamTrack.jmep)
              else
                logger.debug("no jmep data for jamtrack")

              result = context.jamClient.JamTrackPlay(fqId);
              if !result
                @jamTrack.activeMixdown.client_state = 'cant_open'
                @reportError(@jamTrack.activeMixdown)
                @app.notify(
                  {
                    title: "Mixdown Can Not Open",
                    text: "Unable to open your JamTrack Mixdown.  Please contact support@jamkazam.com"
                  }
                , null, true)

          when 'unknown'
            # we need to check if @keyCheckTimeout exists; because if it does, we don't want to download while keying.
            # 'unknown' is tricky here because the file probably is actually on disk, but the bridge API can say unknown until you've tried to key at least once
            if @jamTrack.activeMixdown.client_state != 'downloading' && !@keyCheckTimeout?
              @jamTrack.activeMixdown.client_state = 'downloading'
              logger.debug("JamTrackStore: initiating download of mixdown")
              context.jamClient.JamTrackDownload(@jamTrack.id, @jamTrack.activeMixdown.id, context.JK.currentUserId,
                this.makeDownloadProgressCallback(),
                this.makeDownloadSuccessCallback(),
                this.makeDownloadFailureCallback())
            else
              logger.debug("JamTrackStore: already downloading")

    attemptKeying: () ->
      if @keyCheckTimeout?
        logger.debug("JamTrackStore: attemptKeying: skipping because already keying")
        return
      else if @jamTrack.activeMixdown.client_state == 'keying_timeout'
        # if we have timed out keying, we shouldn't automatically retry
        logger.debug("JamTrackStore: attempKeying: skipping because we have timed out before and user hasn't requested RETRY")
        return
      else
        @keyCheckTimeout = setTimeout(@onKeyCheckTimeout, 10000)
        @keyCheckoutInterval = setInterval(@checkOnKeying, 1000)
        @jamTrack.activeMixdown.client_state = 'keying'
        logger.debug("JamTrackStore: initiating keying requested")
        context.jamClient.JamTrackKeysRequest()

    onKeyCheckTimeout: () ->
      @keyCheckTimeout = null
      clearInterval(@keyCheckoutInterval)
      @keyCheckoutInterval = null

      if @jamTrack?.activeMixdown?
        @jamTrack.activeMixdown.client_state = 'keying_timeout'
        @reportError(@jamTrack.activeMixdown)

      @changed()

    checkOnKeying: () ->
      @manageMixdownSynchronization()

      # if we exit keying state, we can clear our timers and poke state
      if @jamTrack.activeMixdown.client_state != 'keying'
        @clearMixdownTimers()
        @changed()


    # clear out any timer/watcher stuff
    clearMixdownTimers: () ->
      logger.debug("JamTrackStore: clearing mixdown timers", @keyCheckTimeout, @keyCheckoutInterval)
      clearTimeout(@keyCheckTimeout) if @keyCheckTimeout?
      clearInterval(@keyCheckoutInterval) if @keyCheckoutInterval?
      @keyCheckTimeout = null
      @keyCheckoutInterval = null

    changed: () ->

      @pickMyPackage()
      @manageWatchedMixdowns()
      @manageMixdownSynchronization()

      @state = {
        jamTrack: @jamTrack,
        opened: @previous == null && @jamTrack != null,
        closed: @previous != null && @jamTrack == null,
        fullTrackActivated: @previousMixdown != null && @jamTrack?.activeMixdown == null}
      @previous = @jamTrack
      @previousMixdown = @jamTrack?.activeMixdown
      this.trigger(@state)


    onOpen: (jamTrack) ->
      if @jamTrack?
        @app.notify({text: 'Unable to open JamTrack because another one is already open.'})
        return

      @enqueuedMixdowns = {}
      @jamTrack = jamTrack

      # we can cache this because you can't switch gear while in a session (and possible change sample rate!)
      sampleRate = context.jamClient.GetSampleRate()
      @sampleRate = if sampleRate == 48 then 48 else 44

      @changed()

    onClose: () ->
      @jamTrack = null
      @changed()

    onRequestSearch:(searchType, searchData) ->
      @requestedSearch = {searchType: searchType, searchData: searchData}
      window.location.href = '/client#/jamtrack/search'

    # needed by the JamTrackSearchScreen
    checkRequestedSearch:() ->
      requested = @requestedSearch
      @requestedSearch = null
      requested

    onRequestFilter:(genre, instrument) ->
      @requestedFilter = {genre: genre, instrument:instrument}
      window.location.href = '/client#/jamtrack/filter'

    # needed  by the JamTrackSearchScreen
    checkRequestedFilter:() ->
      requested = @requestedFilter
      @requestedFilter = null
      requested

    onCreateMixdown: (mixdown, done, fail) ->

      volumeSettings = context.jamClient.GetJamTrackSettings();

      track_settings = []

      for track in volumeSettings.tracks
        track_settings.push({id: track.id, pan: track.pan, vol: track.vol_l, mute: track.mute})

      mixdown.settings.tracks = track_settings

      logger.debug("creating mixdown", mixdown)

      rest.createMixdown(mixdown)
      .done((created) =>

        @addMixdown(created)

        logger.debug("created mixdown", created)

        @onEnqueueMixdown({id: created.id}, done, fail)
      )
      .fail((jqxhr) =>
        fail(jqxhr)
      )


    onEditMixdown: (mixdown) ->
      logger.debug("editing mixdown", mixdown)

      rest.editMixdown(mixdown)
      .done((updatedMixdown) =>
        logger.debug("edited mixdown")
        @updateMixdown(updatedMixdown)
      ).fail((jqxhr) =>
        @app.layout.notify({title:'Unable to Edit Custom Mix', text: 'The server was unable to edit this mix.'})
      )

    onDeleteMixdown: (mixdown) ->
      logger.debug("deleting mixdown", mixdown)

      rest.deleteMixdown(mixdown)
      .done(() =>
        logger.debug("deleted mixdown")

        @deleteMixdown(mixdown)
      )
      .fail((jqxhr) =>
        @app.layout.notify({title:'Unable to Deleted Custom Mix', text: 'The server was unable to delete this mix.'})
      )

    onOpenMixdown: (mixdown) ->
      logger.debug("opening mixdown", mixdown)

      # check if it's already available in the backend or not
      rest.markMixdownActive({id: @jamTrack.id, mixdown_id: mixdown.id})
      .done((edited) =>
        logger.debug("marked mixdown as active")
        @jamTrack = edited

        # unload any currently loaded JamTrack
        context.jamClient.JamTrackStopPlay();

        @changed()

        SessionActions.mixdownActive(mixdown)
      )
      .fail((jqxhr) =>
        @app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
      )

    onActivateNoMixdown: (jamTrack) ->
      logger.debug("activating no mixdown")

      rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null})
      .done((edited) =>
        logger.debug("marked JamTrack as active")

        @jamTrack = edited
        @changed()

        SessionActions.mixdownActive({id:null})
      )
      .fail((jqxhr) =>
        @app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
      )


    onCloseMixdown: (mixdown) ->
      logger.debug("closing mixdown", mixdown)

    onEnqueueMixdown: (mixdown, done, fail) ->
      logger.debug("enqueuing mixdown", mixdown)

      package_settings = {file_type: 'ogg', encrypt_type: 'jkz', sample_rate: @sampleRate}
      package_settings.id = mixdown.id

      rest.enqueueMixdown(package_settings)
      .done((enqueued) =>

        @enqueuedMixdowns[mixdown.id] = {}

        logger.debug("enqueued mixdown package", package_settings)
        @addOrUpdatePackage(enqueued)
        done(enqueued) if done
      )
      .fail((jqxhr) =>
        @app.layout.notify({title:'Unable to Create Custom Mix', text: 'Click the error icon to retry.'})
        fail(jqxhr) if fail?
      )

    onDownloadMixdown: (mixdown) ->
      logger.debug("download mixdown", mixdown)

    onOpenDownloader: (mixdown) ->
      logger.debug("open mixdown dowloader", mixdown)

      window.open("/popups/jamtrack/download/#{mixdown.jam_track_id}/mixdowns/#{mixdown.id}", 'Mixdown Downloader', 'scrollbars=yes,toolbar=no,status=no,height=171,width=340')

    onRefreshMixdown: (mixdown) ->
      logger.debug("refresh mixdown", mixdown)

    addMixdown: (mixdown) ->
      if @jamTrack?
        logger.debug("adding mixdown to JamTrackStore", mixdown)
        @jamTrack.mixdowns.splice(0, 0, mixdown)
        @changed()
      else
        logger.warn("no jamtrack to add mixdown to in JamTrackStore", mixdown)

    deleteMixdown: (mixdown) ->
      if @jamTrack?
        logger.debug("deleting mixdown from JamTrackStore", mixdown)
        index = null
        for matchMixdown, i in @jamTrack.mixdowns
          if mixdown.id == matchMixdown.id
            index = i
        if index?
          @jamTrack.mixdowns.splice(index, 1)

          if @jamTrack.activeMixdown?.id == mixdown.id
            @onActivateNoMixdown(@jamTrack)

          @changed()
        else
          logger.warn("unable to find mixdown to delete in JamTrackStore", mixdown)
      else
        logger.warn("no jamtrack to delete mixdown for in JamTrackStore", mixdown)

    updateMixdown: (mixdown) ->
      if @jamTrack?
        logger.debug("editing mixdown from JamTrackStore", mixdown)
        index = null
        for matchMixdown, i in @jamTrack.mixdowns
          if mixdown.id == matchMixdown.id
            index = i
        if index?
          @jamTrack.mixdowns[index] = mixdown

          @changed()
        else
          logger.warn("unable to find mixdown to edit in JamTrackStore", mixdown)
      else
        logger.warn("no jamtrack to edit mixdown for in JamTrackStore", mixdown)

    addOrUpdatePackage: (mixdown_package) ->
      if @jamTrack?
        added = false
        index = null
        for mixdown in @jamTrack.mixdowns
          existing = false
          if mixdown_package.jam_track_mixdown_id == mixdown.id
            for possiblePackage, i in mixdown.packages
              if possiblePackage.id == mixdown_package.id
                existing = true
                index = i
                break

            if existing
              mixdown.packages[index] = mixdown_package
              logger.debug("replacing mixdown package in JamTrackStore", mixdown_package)
            else
              mixdown.packages.splice(0, 0, mixdown_package)
              logger.debug("adding mixdown package in JamTrackStore")

            added = true
            @changed()
            break

        if !added
          logger.debug("couldn't find the mixdown associated with package in JamTrackStore", mixdown_package)
      else
        logger.warn("no mixdown to add package to in JamTrackStore", mixdown_package)


    updateDownloadProgress: () ->

      if @bytesReceived? and @bytesTotal?
        progress = "#{Math.round(@bytesReceived/@bytesTotal * 100)}%"
      else
        progress = '0%'

      #@root.find('.state-downloading .progress').text(progress)

    downloadProgressCallback: (bytesReceived, bytesTotal) ->
      logger.debug("download #{bytesReceived}/#{bytesTotal}")

      @bytesReceived = Number(bytesReceived)
      @bytesTotal = Number(bytesTotal)

      # the reason this timeout is set is because, without it,
      # we observe that the client will hang.   So, if you remove this timeout, make sure to test with real client
      setTimeout(this.updateDownloadProgress, 100)

    downloadSuccessCallback: (updateLocation) ->
      # is the package loadable yet?
      logger.debug("JamTrackStore:  download complete - on to keying")
      @attemptKeying()
      @changed()

    downloadFailureCallback: (errorMsg) ->

      logger.debug("mixdown download failed", errorMsg);

      if @jamTrack?.activeMixdown?
        @jamTrack.activeMixdown.client_state = 'download_fail'
        @reportError(@jamTrack.activeMixdown)
        @changed()

    # makes a function name for the backend
    makeDownloadProgressCallback: () ->
      "JamTrackStore.downloadProgressCallback"

    # makes a function name for the backend
    makeDownloadSuccessCallback: () ->
      "JamTrackStore.downloadSuccessCallback"

    # makes a function name for the backend
    makeDownloadFailureCallback: () ->
      "JamTrackStore.downloadFailureCallback"


    reportError: (mixdown) ->

      enqueued = @enqueuedMixdowns[mixdown?.id]

      # don't double-report
      if !enqueued? || enqueued.marked
        return

      enqueued.marked = true
      data = {
        value: 1,
        user_id: context.JK.currentUserId,
        user_name: context.JK.currentUserName,
        result: "signing state: #{mixdown.myPackage?.signing_state}, client state: #{mixdown.client_state}",
        mixdown: mixdown.id,
        package: mixdown.myPackage?.id
        detail: mixdown.myPackage?.error_reason
      }
      rest.createAlert("Mixdown Sync failed for #{context.JK.currentUserName}", data)

      context.stats.write('web.mixdown.error', data)
  }
)