(function(context, $) {

  "use strict";

  context.JK = context.JK || {};

  // the purpose of this code is to simply the interaction for recording widgets
  context.JK.ListenRecording = function($parentElement, options){

    var WAIT_FOR_BUFFER_TIMEOUT = 5000;

    var logger                  = context.JK.logger;
    var rest                    = context.JK.Rest();
    var $parent                 = $parentElement;
    var recordingId             = null;
    var claimedRecordingId      = null;
    var $currentTime            = null;
    var $slider                 = null;
    var $sliderBar              = null;
    var $audio                  = null;
    var audioDomElement         = null;
    var self                    = this;
    var waitForBufferingTimeout = null;
    var destroyed               = false;
    var lastTime                = -1;
    var dragging                = false;
    var loaded                  = null;
    var isPlaying               = false;

    var PlayStateNone               = 'none';
    var PlayStateInitializing       = 'initializing'; // user clicked play--nothing has happened yet
    var PlayStateBuffering          = 'buffering'; // user clicked play--the stream is being read, buffer is being built
    var PlayStatePlaying            = 'playing'; // we are playing
    var PlayStateStalled            = 'stalled'; // we were playing, but the stream is stalled
    var PlayStateEnded              = 'ended'; // we were playing, but the stream ended
    var PlayStateFailedStart        = 'failed_start'; // we could not start the stream.  no more events are coming
    var PlayStateFailedPlaying      = 'failed_playing'; // failed while playing.
    var PlayStateNetworkError       = 'network_error'; // network error

    var playState = PlayStateNone; // tracks if the stream is actually playing

    var canPlay = false;

    function waitForLoaded() {

        if(needsReloadOnEveryPlay() || !loaded) {
            loaded = $.Deferred();

            if(supportsLoadBeforePlay()) {
              audioDomElement.load();
            }
            else {
              audioDomElement.play();
              audioDomElement.pause();
              //loaded.resolve(); // will be resolved by 'canplay' even
            }
        }

        return loaded;
    }

    function play(e) {
      if(e) {
        e.preventDefault();
        e.stopPropagation();
      }

      if(destroyed) return;

      if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play"

      transition(PlayStateInitializing);
      // keep this after transition, because any transition clears this timer
      waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT);

      waitForLoaded().done(function() {
          if(lastTime > -1) {audioDomElement.currentTime = lastTime;}
          audioDomElement.play();
          isPlaying = true;
          rest.addPlayablePlay(recordingId, 'JamRuby::Recording', claimedRecordingId, context.JK.currentUserId);
          rest.getRecording({id: recordingId})
              .done(function(recording) {
                context.JK.GA.trackRecordingPlay(recording);
              })            
      })

    }

    function pause(e) {
      if(e) {
        e.preventDefault();
        e.stopPropagation();
      }

      if(destroyed) return;

      if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause"

      transition(PlayStateNone);

      audioDomElement.pause();
      isPlaying = false;
    }

    function destroy() {
      if(!destroyed) {
        $audio.remove();
        $audio = null;
        audioDomElement = null;
        destroyed = true;
      }
    }


    function noBuffer() {
      logger.log("never received indication of buffering or playing");
      transition(PlayStateFailedStart);
    }

    // this is the only way to make audio stop buffering after the user hits pause
    function recreateAudioElement() {
      // jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599
      var originalSource = $audio.html()
      audioDomElement.pause();
      audioDomElement.src = '';
      audioDomElement.load();
      var $parent = $audio.parent();
      $audio.remove();
      $parent.append('<audio preload="none"></audio>');
      $audio = $('audio', $parent);
      $audio.append(originalSource);
      audioDomElement = $audio.get(0);
      audioBind();
    }

    function clearBufferTimeout() {
      if(waitForBufferingTimeout) {
        clearTimeout (waitForBufferingTimeout);
        waitForBufferingTimeout = null;
      }
    }

    function transition(newState) {
      logger.log("transitioning from " + playState + " to " + newState);

      clearBufferTimeout();

      playState = newState;


      // sometimes browsers don't quite make it to 100% if we end at 99%, reset it
      var percentComplete = (audioDomElement.currentTime / audioDomElement.duration) * 100;
      if(playState == PlayStateEnded && percentComplete > 99) {
        updateSliderPosition(0);
        $currentTime.html("0:00");
        lastTime = 0
      }

      if( playState == PlayStateNone ||
        playState == PlayStateEnded ||
        playState == PlayStateFailedStart ||
        playState == PlayStateFailedPlaying ||
        playState == PlayStateNetworkError) {
        context.JK.ListenBroadcastCurrentlyPlaying = null;
        isPlaying = false;
      }

      triggerStateChange();
    }


    function isNoisyEvent(eventName) {
      if(playState == PlayStateNone) {
        logger.log("ignoring: " + eventName)
        return true;
      }

      return false;
    }

    function onPlay() {
      // this just confirms that the user tried to play
    }

    function onPlaying() {
      if(isNoisyEvent('playing')) return;
      logger.log("playing", arguments);

      transition(PlayStatePlaying);
    }

    function onPause() {
      if(isNoisyEvent('pause')) return;
      logger.log("pause", arguments);

      if(treatPauseLikeEnd()) {
        transition(PlayStateEnded);
        return;
      };

      transition(PlayStateStalled);
    }

    function onError() {
      if(isNoisyEvent('error')) return;
      logger.log("error", arguments);

      if(playState == PlayStatePlaying || playState == PlayStateStalled) {
        transition(PlayStateFailedPlaying);
      }
      else {
        transition(PlayStateFailedStart);
      }
    }

    function onEnded() {
      if(isNoisyEvent('ended')) return;
      logger.log("ended", arguments);

      transition(PlayStateEnded);
    }

    function onEmptied() {
      if(isNoisyEvent('emptied')) return;
      logger.log("emptied", arguments);
    }

    function onAbort() {
      if(isNoisyEvent('abort')) return;
      logger.log("abort", arguments);

      //transition(PlayStateFailedStart);
    }

    function onStalled() {
      if(isNoisyEvent('stalled')) return;
      logger.log("stalled", arguments);

      // fires in Chrome on page load

      if(playState == PlayStateBuffering || playState == PlayStatePlaying) {
        transition(PlayStateStalled);
      }
    }

    function onSuspend() {
      if(isNoisyEvent('suspend')) return;
      logger.log("onsuspend", arguments);

      transition(PlayStateStalled);
    }

    function onTimeUpdate() {

      if(dragging) {
        return;
      }

      var percentComplete = (audioDomElement.currentTime / audioDomElement.duration) * 100;
      updateSliderPosition(percentComplete);
      $currentTime.html(context.JK.prettyPrintSeconds(parseInt(audioDomElement.currentTime)));

      // reset icon to play and slider to far left when done
      if (percentComplete === 100) {
        updateSliderPosition(0);
        $currentTime.html("0:00");
        lastTime = 0
      }
      else {
        lastTime = audioDomElement.currentTime;
      }
    }

    function updateSliderPosition(percent) {
      //logger.log("updateSliderPosition", percent)
      $slider.css({'left': percent + '%'});
    }

    function onProgress() {
      if(isNoisyEvent('progress')) return;

      if(playState == PlayStateInitializing)  {
        transition(PlayStateBuffering);
      }
    }

    function startDrag(e, ui) {
      dragging = true;
    }

    function stopDrag(e, ui) {
      dragging = false;

      var percent = ui.position.left / $sliderBar.width() * 100;
      updateSliderPosition(percent);

      waitForLoaded()
        .done(function() {
          audioDomElement.currentTime = percent * audioDomElement.duration / 100;
        });
    }

    function onDrag(e, ui) {
      updateSliderPosition(ui.position.left / $sliderBar.width() * 100);
    }

    function triggerStateChange() {

      var isEnd = false;
      var displayText = null;
      var refresh = false;
      var isError = false;

      if(playState == 'none')  {
        //$status.text('SESSION IN PROGRESS');
      }
      else if(playState == 'initializing') {
        displayText = 'PREPARING AUDIO';
      }
      else if(playState == 'buffering') {

      }
      else if(playState == 'playing') {
        displayText = 'PLAYING';
      }
      else if(playState == 'stalled') {
        displayText = 'RECONNECTING';
      }
      else if(playState == 'ended') {
        displayText = 'DISCONNECTED';
        isEnd = true;
      }
      else if(playState == 'failed_start') {
        displayText = 'AUDIO DID NOT START';
        isEnd = true;
        isError = true;
      }
      else if(playState == 'failed_playing') {
        displayText = 'STREAM DISCONNECTED';
        isEnd = true;
        isError = true;
      }
      else if(playState == 'network_error') {
        displayText = 'NO NETWORK';
        isEnd = true;
        isError = true;
      }
      else {
        logger.error("unknown state: " + playState)
      }

      $parent.triggerHandler('statechange.listenRecording',
      {
        element: $parent,
        state: playState,
        displayText: displayText,
        isEnd: isEnd,
        isError: isError
      })
    }

    function supportsLoadBeforePlay() {
      // firefox does not need you to call .load to mess with currentTime
      return !(navigator.userAgent.toLowerCase().indexOf('firefox') > -1);
    }
    function needsReloadOnEveryPlay() {
      return navigator.userAgent.toLowerCase().indexOf('jamkazam') > -1;
    }

    function treatPauseLikeEnd() {
      // never see a pause event until the session is over, in the rich client
      return playState == PlayStatePlaying && navigator.userAgent.toLowerCase().indexOf('jamkazam') > -1;
    }

    function audioBind() {
      $audio.bind('play', onPlay);
      $audio.bind('playing', onPlaying);
      $audio.bind('error', onError);
      $audio.bind('emptied', onEmptied);
      $audio.bind('abort', onAbort);
      $audio.bind('ended', onEnded);
      $audio.bind('pause', onPause);
      $audio.bind('suspend', onSuspend);
      $audio.bind('stalled', onStalled);
      $audio.bind('timeupdate', onTimeUpdate);
      $audio.bind('progress', onProgress);
      $audio.bind('canplay', function() { loaded.resolve();})
    }

    function updateCurrentTime(timeMs) {

    }
    function initialize() {

      $slider = $(options.sliderSelector, $parent);
      $sliderBar = $(options.sliderBarSelector, $parent);
      $currentTime = $(options.currentTimeSelector, $parent);
      claimedRecordingId = options.claimedRecordingId;
      recordingId = options.recordingId;
      $audio = $('audio', $parent);

      if($audio.length == 0) {
        return;
      }
      if($audio.length > 1) {
        throw "more than one <audio> element found";
      }

      audioDomElement = $audio.get(0);
      audioBind();

      $parent.bind('play.listenRecording', play);
      $parent.bind('pause.listenRecording', pause);
      $parent.bind('destroy.listenRecording', destroy);

      $sliderBar.bind('click', function(e) {
        var offset = e.pageX - $(this).offset().left;
        var percent = offset / $sliderBar.width() * 100;
        updateSliderPosition(percent);
        waitForLoaded()
          .done(function() {
            audioDomElement.currentTime = percent / 100 * audioDomElement.duration;
            if(isPlaying) audioDomElement.play();
          })
        return false;
      });

      $slider.draggable({
        axis: 'x',
        containment: $sliderBar,
        start: startDrag,
        stop: stopDrag,
        drag: onDrag
      });

    }

    initialize();

    this.play = play;
    this.pause = pause;

    return this;
  }

  $.fn.listenRecording = function(options) {
    new context.JK.ListenRecording(this, options);
  }
})(window, jQuery);
