(function (context, $) {

  "use strict";

  context.JK = context.JK || {};
  context.JK.Feed = function (app) {

    var logger = context.JK.logger;
    var rest = new context.JK.Rest();
    var EVENTS = context.JK.EVENTS;
    var ui = new context.JK.UIHelper(JK.app);
    var recordingUtils = context.JK.RecordingUtils;
    var userId = null;
    var bandId = null;
    var currentFeedPage = 0;
    var feedBatchSize = 20;
    var $screen = null;
    var $scroller = null;
    var $content = null;
    var $noMoreFeeds = null;
    var $refresh = null;
    var $sortFeedBy = null;
    var $includeDate = null;
    var nextPage = 1;
    var $includeType = null;
    var didLoadAllFeeds = false, isLoading = false;
    var $templateRecordingDiscardedSoon = null;
    var defaults;

    function defaultQuery() {
      var query = { limit: feedBatchSize };

      if(nextPage) {
        query.next_page = nextPage;
      }
      if(userId) {
        query.user = userId;
      }

      if(bandId) {
        query.band = bandId;
      }

      return query;
    }

    function buildQuery() {
      var currentQuery = defaultQuery();

      // specify search criteria based on form
      currentQuery.sort = $sortFeedBy.length == 0 ? defaults.sort : $sortFeedBy.val();
      currentQuery.time_range = $includeDate.length == 0 ? defaults.time_range : $includeDate.val();
      currentQuery.type = $includeType.length == 0 ? defaults.type : $includeType.val();

      return currentQuery;
    }

    function clearResults() {
      didLoadAllFeeds = false;
      currentFeedPage = 0;
      $content.empty(); // TODO: do we need to delete audio elements?
      $noMoreFeeds.hide();
      nextPage = 1;
    }

    function handleFeedResponse(response) {
      nextPage = response.next_page;
      renderFeeds(response);

      if(nextPage == null) {
        didLoadAllFeeds = true;

        // if we got less results than asked for, end searching
        logger.debug("end of feeds")

        if(currentFeedPage == 0 && response.entries.length == 0) {
          $content.append("<div class='no-feed-msg'>This user has no history.</div>")  ;
        }

        if(currentFeedPage > 0) {
          $noMoreFeeds.show();
        }
      } else {
        currentFeedPage++;
      }
    }

    function setUser(_userId) {
      userId = _userId;
    }

    function setBand(_bandId) {
      bandId = _bandId;
    }

    function refresh() {
      clearResults();
      populate();
    }

    function setLoading(yn) {
        isLoading = yn;
        if (yn) {
            $('#'+screenID()+'-loading-feeds').show();
        } else {
            $('#'+screenID()+'-loading-feeds').hide();
        }
    }

    function populate() {
      if (isLoading || didLoadAllFeeds) return;

      setLoading(true);

      rest.getFeeds(buildQuery())
        .done(function(response) {
          handleFeedResponse(response);
          setLoading(false);
        })
        .fail(function(jqXHR) {
          setLoading(false);
          app.notifyServerError(jqXHR, 'Feed Unavailable')
        });
    }

    function toggleSessionDetails() {
      var $detailsLink = $(this);
      var $feedItem = $detailsLink.closest('.feed-entry');
      var $musicians = $feedItem.find('.musician-detail');
      var $description = $feedItem.find('.description');
      var toggledOpen = $detailsLink.data('toggledOpen');

      if(toggledOpen) {
        $feedItem.css('height', $feedItem.height() + 'px')
        $feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
          $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));

          $musicians.hide();
          $description.css('height', $description.data('original-height'));
          $description.dotdotdot();
        });
      }
      else {
        $description.trigger('destroy.dot');
        $description.data('original-height', $description.css('height')).css('height', 'auto');
        $musicians.show();
        $feedItem.animate({'max-height': '1000px'});
      }

      toggledOpen = !toggledOpen;
      $detailsLink.data('toggledOpen', toggledOpen);
      return false;
    }

    function startSessionPlay($feedItem) {
      var img = $('.play-icon', $feedItem);
      var $controls = $feedItem.find('.session-controls');
      img.attr('src', '/assets/content/icon_pausebutton.png');
      $controls.trigger('play.listenBroadcast');
      $feedItem.data('playing', true);
    }

    function stopSessionPlay($feedItem) {
      var img = $('.play-icon', $feedItem);
      var $controls = $feedItem.find('.session-controls');
      img.attr('src', '/assets/content/icon_playbutton.png');
      $controls.trigger('pause.listenBroadcast');
      $feedItem.data('playing', false);
    }

    function toggleSessionPlay() {
      var $playLink = $(this);
      var $feedItem = $playLink.closest('.feed-entry');

      var $status = $feedItem.find('.session-status')
      var playing = $feedItem.data('playing');

      if(playing) {
        $status.text('SESSION IN PROGRESS');
        stopSessionPlay($feedItem);
      }
      else {
        startSessionPlay($feedItem);
      }
      return false;
    }

    function stateChangeSession(e, data) {
      var $controls = data.element;
      var $feedItem = $controls.closest('.feed-entry');
      var $status = $feedItem.find('.session-status');

      if(data.displayText) $status.text(data.displayText);

      if(data.isEnd) stopSessionPlay($feedItem);

      if(data.isSessionOver) {
        $controls.removeClass('inprogress').addClass('ended')
      }
    }

    function startRecordingPlay($feedItem) {
      var img = $('.play-icon', $feedItem);
      var $controls = $feedItem.find('.recording-controls');
      img.attr('src', '/assets/content/icon_pausebutton.png');
      $controls.trigger('play.listenRecording');
      $feedItem.data('playing', true);
    }

    function stopRecordingPlay($feedItem) {
      var img = $('.play-icon', $feedItem);
      var $controls = $feedItem.find('.recording-controls');
      img.attr('src', '/assets/content/icon_playbutton.png');
      $controls.trigger('pause.listenRecording');
      $feedItem.data('playing', false);
    }

    function toggleRecordingPlay() {

      var $playLink = $(this);
      var $feedItem = $playLink.closest('.feed-entry');
      var playing = $feedItem.data('playing');

      if(playing) {
        stopRecordingPlay($feedItem);
      }
      else {
        startRecordingPlay($feedItem);
      }
      return false;
    }

    function isOwner() {
      return userId == context.JK.currentUserId;
    }

    function screenID() {
      return $screen.attr('id');
    }

    function obtainCandidate(recording) {
      if(isOwner()) {
        var candidate = null;
        context._.each(recording.claimed_recordings, function(claimedRecording) {
          if(claimedRecording.user_id == context.JK.currentUserId) {
            candidate = claimedRecording;
            return false;
          }
        })

        if(!candidate) throw "unable to find candidate claimed recording, yet we can see this recording.  server error..."
        return candidate;
      }
      else {
        return recording.claimed_recordings[0];
      }
    }

    function toggleOpen($feedItem, $name, $description, $musicians) {
      $description.trigger('destroy.dot');
      $description.data('original-height', $description.css('height')).css('height', 'auto');
      $name.trigger('destroy.dot');
      $name.data('original-height', $name.css('height')).css('height', 'auto');
      $musicians.show();
      $feedItem.animate({'max-height': '1000px'});
    }

    function toggleClose($feedItem, $name, $description, $musicians, immediate) {
      $feedItem.css('height', $feedItem.height() + 'px')
      $feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).promise().done(function() {
        $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));

        $musicians.hide();
        $description.css('height', $description.data('original-height'));
        $description.dotdotdot();
        $name.css('height', $name.data('original-height'));
        $name.dotdotdot();
      });
    }

    function stateChangeRecording(e, data) {
      var $controls = data.element;
      var $feedItem = $controls.closest('.feed-entry');

      var $sliderBar      = $('.recording-position', $feedItem);
      var $statusBar      = $('.recording-status', $feedItem);
      var $currentTime    = $('.recording-current', $feedItem);
      var $status         = $('.status-text', $feedItem);
      var $playButton     = $('.play-button', $feedItem);

      if(data.isEnd) stopRecordingPlay($feedItem);
      if(data.isError) {
        $sliderBar.hide();
        $playButton.hide();
        $currentTime.hide();
        $statusBar.show();
        $status.text(data.displayText);
      }
    }

    function toggleRecordingDetails() {
      var $detailsLink = $(this);
      var $feedItem = $detailsLink.closest('.feed-entry');
      var $musicians = $feedItem.find('.musician-detail');
      var $description = $feedItem.find('.description');
      var $name = $feedItem.find('.name');
      var toggledOpen = $detailsLink.data('toggledOpen');

      if(toggledOpen) {
        toggleClose($feedItem, $name, $description, $musicians)
      }
      else {
        toggleOpen($feedItem, $name, $description, $musicians)
      }

      toggledOpen = !toggledOpen;
      $detailsLink.data('toggledOpen', toggledOpen);

      return false;
    }

    function updateRecordingName($feedEntry, name) {
      $feedEntry.find('.name-text').text(name);
    }

    function updateRecordingDescription($feedEntry, description) {
      $feedEntry.find('.description').text(description);
    }

    function updateIsPublic($feedEntry, isPublic) {
      var $isPrivate = $feedEntry.find('.is_private')
      if(isPublic) {
        $isPrivate.removeClass('enabled')
      }
      else {
        if(!isOwner())
        {
          $feedEntry.find('.play-button').hide(); // still show play button if private && isOwner()
        }
        $isPrivate.addClass('enabled')
      }
    }

    function updateComments($feedEntry, comments) {
      $feedEntry.find('.comments').html(comments)
    }

    function updatePlays($feedEntry, plays) {
      $feedEntry.find('.plays').html(plays);
    }

    function updateLikes($feedEntry, likes) {
      $feedEntry.find('.likes').html(likes);
    }

    function updateStats($feedEntry) {
      if($feedEntry.is('.recording-entry')) {
        var id = $feedEntry.attr('data-claimed-recording-id');
        rest.getClaimedRecording(id)
          .done(function(claimedRecording) {
            updateComments($feedEntry, claimedRecording.recording.comment_count);
            updateLikes($feedEntry, claimedRecording.recording.like_count);
            updatePlays($feedEntry, claimedRecording.recording.play_count);

          })
          .fail(app.ajaxError)
      }
      else {
        var id = $feedEntry.attr('data-music-session');
        rest.getSessionHistory(id)
          .done(function(music_session) {
            updateComments($feedEntry, music_session.comment_count);
            updateLikes($feedEntry, music_session.like_count);
            updatePlays($feedEntry, music_session.play_count);
          })
          .fail(app.ajaxError)
      }
    }

    function updateGenre($feedEntry, genre) {
      $feedEntry.find('.genre').text(context.JK.GenreSelectorHelper.getNameForId(genre));
    }

    function renderFeeds(feeds) {

      $.each(feeds.entries, function(i, feed) {
        if(feed.type == 'music_session') {
          var options = {
            feed_item: feed,
            status_class: feed['is_over?'] ? 'ended' : 'inprogress',
            mount_class: feed['has_mount?'] ? 'has-mount' : 'no-mount'
          }
          var $feedItem = $(context._.template($('#template-feed-music-session').html(), options, {variable: 'data'}));
          var $controls = $feedItem.find('.session-controls');

          // do everything we can before we attach the item to the page
          $('.timeago', $feedItem).timeago();
          context.JK.prettyPrintElements($('.duration', $feedItem).show());
          context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
          $('.details', $feedItem).click(toggleSessionDetails);
          $('.details-arrow', $feedItem).click(toggleSessionDetails);
          $('.play-button', $feedItem).click(toggleSessionPlay);

          if (!feed.session_removed_at)
          {
            $('.btn-share', $feedItem).click(function() {
              ui.launchShareDialog(feed.id, 'session');
            });
          }
          else {
            $('.btn-share', $feedItem).hide();
          }

          $('.btn-comment', $feedItem).click(function() {
            var result = ui.launchCommentDialog({
              session_id: feed.id,
              entity_type: 'session'
            }).one(EVENTS.DIALOG_CLOSED, function() {
              updateStats($feedItem);
            })
          });

          $('.btn-like', $feedItem).click(function() {
            ui.addSessionLike(feed.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem))
          });

          // should we show the select checkbox?
          if(!defaults.show_checkbox) {
            $feedItem.find('.select-box').hide();
          }

          // put the feed item on the page
          renderFeed($feedItem);

          // these routines need the item to have height to work (must be after renderFeed)
          $controls.listenBroadcast();
          $controls.bind('statechange.listenBroadcast', stateChangeSession);
          $('.dotdotdot', $feedItem).dotdotdot();
          $feedItem.data('original-max-height', $feedItem.css('height'));
          context.JK.bindHoverEvents($feedItem);
          context.JK.bindProfileClickEvents($feedItem);
        }
        else if(feed.type == 'recording') {
          if(feed.claimed_recordings.length == 0) {
            logger.error("a recording in the feed should always have one claimed_recording")
            return;
          }
          // pump some useful data about mixing into the feed item
          feed.mix_info = recordingUtils.createMixInfo({state: feed.mix_state})

          var options = {
            feed_item: feed,
            candidate_claimed_recording: obtainCandidate(feed),
            mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
          }

          var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
          var $controls = $feedItem.find('.recording-controls');

          var $titleText = $feedItem.find('.title .title-text');

          // if this item will be discarded, tack on a * to the RECORDING NAME
          var discardTime = feed['when_will_be_discarded?'];
          if(discardTime) {
            context.JK.helpBubble($titleText, 'recording-discarded-soon', {discardTime: discardTime}, {});
            $titleText.text($titleText.text() + '*');
          }

          $controls.data('mix-state', feed.mix_info) // for recordingUtils helper methods
          $controls.data('server-info', feed.mix) // for recordingUtils helper methods
          $controls.data('view-context', 'feed')

          // tack on video if available
          if(feed.external_video_id)  {

            var $videoWrapper = $feedItem.find('.video-wrapper')
            var $videoContainer = $feedItem.find('.video-container')
            if(gon.isNativeClient) {
              var $embed = $('<a class="video-thumbnail" href="https://www.youtube.com/watch?v=' + feed.external_video_id + '" rel="external">' +
                '<img class="video" src="//img.youtube.com/vi/' + feed.external_video_id + '/hqdefault.jpg" />' +
                '<img class="play" src="/assets/content/icon_youtube_play.png" /></a>')
              $videoContainer.append($embed).addClass('no-embed')
              $videoWrapper.removeClass('hidden')
              $embed.click(function() {
                context.JK.popExternalLink($(this).attr('href'))
                return false;
              })
            }
            else {
              var $embed = $('<iframe src="//www.youtube.com/embed/' + feed.external_video_id + '" frameborder="0" allowfullscreen="allowfullscreen" />')
              $videoContainer.append($embed)
              $videoWrapper.removeClass('hidden')
            }

            if(feed.owner_id == context.JK.currentUserId) {
              var $deleteVideoLink = $('<a href="#" class="delete-video">delete video</a>')
              $deleteVideoLink.click(function() {
                app.layout.showDialog('delete-video-confirm-dialog', { d1: feed.id }).one(EVENTS.DIALOG_CLOSED, function(e, data) {
                  if(!data.canceled && data.result) {
                    $deleteVideoLink.remove();
                    $videoWrapper.slideUp(1000);
                  }
                })
                return false;
              })
              $videoContainer.append($deleteVideoLink);
            }
          }

          $('.timeago', $feedItem).timeago();
          context.JK.prettyPrintElements($('.duration', $feedItem));
          context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
          $('.details', $feedItem).click(toggleRecordingDetails);
          $('.details-arrow', $feedItem).click(toggleRecordingDetails);
          $('.play-button', $feedItem).click(toggleRecordingPlay);
          updateIsPublic($feedItem, options.candidate_claimed_recording.is_public);

          $('.btn-share', $feedItem).click(function() {
            ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording');
          });

          $('.btn-comment', $feedItem).click(function() {
            ui.launchCommentDialog({
              recording_id: feed.id,
              claimed_recording_id: options.candidate_claimed_recording.id,
              entity_type: 'recording'
            })
            .one(EVENTS.DIALOG_CLOSED, function() {
              updateStats($feedItem);
            });
          });

          $('.btn-like', $feedItem).click(function() {
            ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
          });

          if(isOwner()) {
            $('.edit-recording-dialog', $feedItem).data('claimed_recording_id', options.candidate_claimed_recording.id).click(function() {
              app.layout.showDialog('edit-recording', {d1: $(this).data('claimed_recording_id')})
                .one(EVENTS.DIALOG_CLOSED, function() {
                  $(this).unbind('recording_updated').unbind('recording_deleted');
                })
                .one('recording_updated', function(e, data) {
                  // find recording by claimed recording id
                  var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
                  var $musicians = $feedEntry.find('.musician-detail');
                  var $description = $feedEntry.find('.description');
                  var $name = $feedEntry.find('.name');
                  var $detailsLink = $feedEntry.find('.details');
                  var toggledOpen = $detailsLink.data('toggledOpen');

                  if(toggledOpen) {
                    toggleClose($feedEntry, $name, $description, $musicians, true);
                  }

                  $description.trigger('destroy.dot');
                  $name.trigger('destroy.dot');

                  updateRecordingName($feedEntry, data.name);
                  updateRecordingDescription($feedEntry, data.description);
                  updateIsPublic($feedEntry, data.is_public);
                  updateGenre($feedEntry, data.genre);

                  $name.dotdotdot();
                  $description.dotdotdot();
                  $feedItem.data('original-max-height', $feedEntry.css('height'));

                  $detailsLink.data('toggledOpen', false);
                })
                .one('recording_deleted', function(e, data) {
                  var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
                  $feedEntry.remove();
                })
              return false;
            }).show();
          }

          context.JK.helpBubble($feedItem.find('.help-launcher'), recordingUtils.onMixHover, {}, {width:'450px', closeWhenOthersOpen: true, positions:['top', 'left', 'bottom', 'right'], offsetParent: $screen.parent()})

          // should we show the select checkbox?
          if(!defaults.show_checkbox) {
            $feedItem.find('.select-box').hide();
          }
          else {
            context.JK.checkbox($feedItem.find('.select-box'))
          }
          if(defaults.hide_avatar) {
            $feedItem.find('.avatar-small.ib').hide();
          }

          // put the feed item on the page
          renderFeed($feedItem);

          if(feed.external_video_id) {
            toggleRecordingDetails.call($('.details', $feedItem).get(0))
          }
          // these routines need the item to have height to work (must be after renderFeed)
          $controls.listenRecording({recordingId: feed.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
          $controls.bind('statechange.listenRecording', stateChangeRecording);
          $('.dotdotdot', $feedItem).dotdotdot();
          $feedItem.data('original-max-height', $feedItem.css('height'));
          context.JK.bindHoverEvents($feedItem);
          context.JK.bindProfileClickEvents($feedItem);
          context.JK.popExternalLinks($feedItem)
        }
        else {
          logger.warn("skipping feed type: " + feed.type);
        }

        context.JK.bindProfileClickEvents();
      });
    }

    function renderFeed(feed) {
      $content.append(feed);
    }

    function search() {
      logger.debug("Searching for feeds...");
      refresh();
      return false;
    }

    function events() {
      $refresh.on("click", search);
      $sortFeedBy.on('change', search);
      $includeDate.on('change', search);
      $includeType.on('change', search);

      var scrollerID = '#'+screenID()+'-feed-scroller';
      $(scrollerID).scroll(function() {
          var wintop = $(scrollerID).scrollTop();
          var winheight = $(scrollerID).height();
          var docheight = $('#'+screenID()+'-feed-entry-list').height();
          var scrollTrigger = 0.90;
          if ((wintop / (docheight - winheight)) > scrollTrigger) {
              populate();
          }
      });

    }

    function initialize(_$parent, _$scroller, _$content, _$noMorefeeds, _$refresh, _$sortFeedBy, _$includeDate, _$includeType, _defaults) {
      $screen = _$parent;
      $scroller = _$scroller;
      $content = _$content;
      $noMoreFeeds = _$noMorefeeds;
      $refresh = _$refresh;
      $sortFeedBy = _$sortFeedBy;
      $includeDate = _$includeDate;
      $includeType = _$includeType;

      if($screen.length == 0) throw "$screen must be specified";
      if($scroller.length == 0) throw "$scroller must be specified";
      if($content.length == 0) throw "$content must be specified";
      if($noMoreFeeds.length == 0) throw "$noMoreFeeds must be specified";

      // show_checkbox will show a checkbox in the upper left
      defaults = $.extend({}, {sort: 'date', time_range: 'month', type: 'all', show_checkbox: false, hide_avatar: true}, _defaults)

      // set default search criteria
      $sortFeedBy.val(defaults.date)
      $includeDate.val(defaults.time_range)
      $includeType.val(defaults.type)

      $templateRecordingDiscardedSoon = $('#template-help-recording-discarded-soon');
      events();
    }

    this.initialize = initialize;
    this.refresh = refresh;
    this.setUser = setUser;
    this.setBand = setBand;

    return this;
  }
})(window, jQuery)