// Namespace

var brille = {};



// Bootstrap

brille.go = function() {
  // Collections
  brille.bigCartel = new brille.BigCartel();
  brille.songkick = new brille.Songkick();
  brille.soundcloud = new brille.SoundCloud();
  brille.tumblr = new brille.Tumblr();
  brille.vimeo = new brille.Vimeo();

  brille.collections = {
    news   : [brille.tumblr],
    music  : [brille.soundcloud],
    videos : [brille.vimeo],
    shop   : [brille.bigCartel],
    shows  : [brille.songkick]
  }

  // Main View
  brille.displayCollection = new brille.DisplayCollection();
  brille.mainView = new brille.MainView({
    collection: brille.displayCollection
  });

  // General Helpers (need a proper home)
  brille.header.activate();
  brille.captureExternalLinks();
  brille.captureInternalLinks();
  $(window).on('resize orientationchange', function() { brille.scalePlayers() });

  // Router
  brille.router = new brille.Router();

  // Introduce
  brille.introduce().then(function() {
    Backbone.history.start({ pushState: true });
  });
}




// Router

brille.Router = Backbone.Router.extend({

  routes: {
    ''      : 'home',
    '*path' : 'discover'
  },

  reset: function() {
    $('header nav li').removeClass('active');
    brille.displayCollection.empty();
  },

  track: function() {
    _gaq.push(['_trackPageview', window.location.pathname]);
  },

  home: function() {
    this.reset();
    this.track();
  },

  discover: function(path) {
    this.reset();
    this.track();
    brille.showLoadingIndicator();

    var tokens      = path.split('/'),
        collections = [],
        tags        = [];

    _.each(tokens, function(token) {
      $('header nav a[href="/' + token + '"]').closest('li').addClass('active');

      var collectionMap = brille.collections[token];

      if (collectionMap) {
        collections = collections.concat(collectionMap);
      } else {
        tags.push(token);
      }
    });

    brille.displayCollection.addCollections(collections);
    brille.displayCollection.tags = tags;
    brille.displayCollection.fetch();
  }

});




// General Helpers

brille.introduce = function() {
  return $.Deferred(function(dfd) {
    $('#container').delay(333).animate({ opacity: 1 }, dfd.resolve);
  }).promise();
}

brille.refresh = _.debounce(function() {
  $(window).trigger('resize').trigger('scroll');
}, 300);

brille.captureExternalLinks = function() {
  $(document).on('click', 'a', function(e) {
    var href        = $(this).attr('href'),
        isHTTP      = href.indexOf('http') === 0,
        isOtherHost = href.indexOf(window.location.hostname) === -1 ||
                      href.indexOf(window.location.hostname) > 7,
        isExternal  = isHTTP && isOtherHost;

    if (isExternal) {
      e.preventDefault();
      e.stopImmediatePropagation();
      window.open($(this).attr('href'));
    }
  });
}

brille.captureInternalLinks = function() {
  $(document).on('click', 'a', function(e) {
    var href        = $(this).attr('href'),
        isValidPath = href.indexOf('/') === 0;

    if (!isValidPath) return;

    e.preventDefault();
    e.stopImmediatePropagation();

    if (href === '/') {
      brille.router.navigate('', true);
    } else {
      var token        = href.replace('/', ''),
          tokens       = Backbone.history.fragment.split('/'),
          collection   = null,
          tags         = [];

      if (_(tokens).include(token)) {
        tokens = _(tokens).without(token);
      } else {
        tokens.unshift(token);
      }

      _.each(tokens, function(t) {
        if (brille.collections[t] != null) {
          collection = collection || t;
        } else {
          tags.push(t);
        }
      });

      route = _.compact([collection].concat(tags)).join('/');

      brille.router.navigate(route, true);
    }
  });
}

brille.scalePlayers = function(root) {
  $('.player iframe').each(function() {
    var e = $(this),
        w = e.width(),
        h = e.height(),
        a = e.data('aspect') || (h / w),
        t = e.closest('article').innerWidth();

    e.
    data('aspect', a).
    width(t).
    height(t * a);
  });
}

brille.loadingIndicator = function() {
  if (!brille._loadingIndicator) {
    brille._loadingIndicator = new brille.LoadingIndicatorView();
  }
  return brille._loadingIndicator;
}

brille.showLoadingIndicator = function() {
  brille.loadingIndicator().show();
}

brille.hideLoadingIndicator = function() {
  brille.loadingIndicator().hide();
}



// Header Helpers

brille.header = {

  scrollMax: 100,
  shadowMax: 50,

  updateShadow: function() {
    var scrollTop = Math.max(
      0, Math.min(
      $(window).scrollTop(),
      brille.header.scrollMax
    ));

    if (scrollTop >= brille.header.scrollMax) {
      if (!brille.header.beyondMaxScroll) {
        brille.header.beyondMaxScroll = true;
        $('header').css({
          boxShadow: '0 0 ' + brille.header.shadowMax + 'px rgba(0, 0, 0, 0.75)'
        });
      }
    } else {
      brille.header.beyondMaxScroll = false;

      var scrollPortion = scrollTop / brille.header.scrollMax,
          shadowSize    = Math.round(scrollPortion * brille.header.shadowMax);

      $('header').css({
        boxShadow: '0 0 ' + shadowSize + 'px rgba(0, 0, 0, 0.75)'
      });
    }

  },

  onResize: function() {
    var fixed = $('header').css('position') === 'fixed';

    if (fixed && !brille.header.shadowActive) {
      brille.header.shadowActive = true;
      brille.header.updateShadow();
      $(window).on('scroll', brille.header.updateShadow);
    } else if (!fixed && brille.header.shadowActive) {
      brille.header.shadowActive = false;
      $('header').css({ boxShadow: 'none' });
      $(window).off('scroll', brille.header.updateShadow);
    }
  },

  activate: function() {
    $(window).on('resize', brille.header.onResize);
    brille.header.onResize();
  }

};



// Display Collection
// This is used to define the current state of what's on display.
// MainView takes its cues from DisplayCollection.
//
// This is effectively a collection of collections.

brille.DisplayCollection = Backbone.Collection.extend({

  collections: [],

  initialize: function() {
    _.bindAll(this);
  },

  empty: function() {
    this.removeCollections(this.collections.concat());
    this.remove(this.models.concat());
  },

  addCollections: function(collections) {
    _.each(collections, this.addCollection);
  },

  addCollection: function(collection) {
    this.collections.push(collection);
    collection.bind('add', this.add);
    collection.bind('remove', this.remove);
    collection.bind('reset', this.whenCollectionIsReset);
  },

  removeCollections: function(collections) {
    _.each(collections, this.removeCollection);
  },

  removeCollection: function(collection) {
    collection.unbind('add', this.add);
    collection.unbind('remove', this.remove);
    collection.unbind('reset', this.whenCollectionIsReset);
    this.collections = _.without(this.collections, collection);
  },

  whenCollectionIsReset: function(collection) {
    this.remove(collection.models);
    collection.each(this.add);
  },

  fetch: function(options) {
    var promises = _.invoke(this.collections, 'fetchWithOptions');
    $.when.apply($, promises).then(this.whenFetchIsComplete);
  },

  whenFetchIsComplete: function() {
    brille.hideLoadingIndicator();
  },

  add: function(model) {
    if (
      this.tags == null ||
      this.tags.length === 0 ||
      _.intersection(model.tags(), this.tags).length > 0
    ) {
      Backbone.Collection.prototype.add.apply(this, [model]);
    }
  },

  loadMore: function() {
    return _.compact(_.invoke(this.collections, 'loadMore'));
  },

  comparator: function(model) {
    return -model.publishedAt().valueOf();
  }

});




// Specialised Collection base class

brille.Collection = Backbone.Collection.extend({

  fetchDefaults: {
    cache: true,
    dataType: 'jsonp'
  },

  fetchOptions: {}, // override this in subclasses

  state: 'empty',

  initialize: function() {
    _.bindAll(this);
  },

  fetchWithOptions: function(options) {
    var dfd = $.Deferred();

    return this.fetch(_.extend({},
      this.fetchDefaults,
      this.fetchOptions,
      options,
      { success: dfd.resolve }
    ));

    return dfd.promise();
  },

  // This is a no-op, here to be overridden.
  loadMore: function() {}

});




// Tumblr

brille.TumblrPost = Backbone.Model.extend({

  klass: function() {
    return 'Tumblr' + _.string.capitalize(this.get('type')) + 'Post';
  },

  date: function() {
    return moment(this.get('date'), 'YYYY-MM-DD HH:mm:ss');
  },

  publishedAt: function() {
    return this.date();
  },

  tags: function() {
    return this.get('tags');
  }

});

brille.Tumblr = brille.Collection.extend({

  model: brille.TumblrPost,

  url: 'http://api.tumblr.com/v2/blog/brillerecords.tumblr.com/posts',

  fetchOptions: {
    data: { api_key: '89rKnVskbnhs7AkKU5nJRxKEtshVhEIwBqZOL8cHJkTAwa17za' },
    jsonp: 'jsonp'
  },

  parse: function(response) { return response.response.posts }

});

// Big Cartel

brille.BigCartelProduct = Backbone.Model.extend({

  klass: function() {
    return 'BigCartelProduct'
  },

  createdAt: function() {
    return moment(this.get('created_at'), 'YYYY-MM-DDTHH:mm:ssZ');
  },

  publishedAt: function() {
    return this.createdAt();
  },

  tags: function() {
    var categories = _.pluck(this.get('categories'), 'permalink'),
        artists = _.pluck(this.get('artists'), 'permalink');

    return _.union(categories, artists);
  }

});

brille.BigCartel = brille.Collection.extend({

  model: brille.BigCartelProduct,

  url: 'http://api.bigcartel.com/brille/products.js',

  parse: function(response) { return response },

  comparator: function(product) {
    return -product.createdAt().valueOf();
  }

});

// Songkick

brille.SongkickEvent = Backbone.Model.extend({

  klass: function() {
    return 'SongkickEvent';
  },

  date: function() {
    return moment(this.get('start').date, 'YYYY-MM-DD');
  },

  publishedAt: function() {
    return this.date();
  },

  tags: function() {
    return _.map(this.get('performance'), function(artist) {
      return _.string.dasherize(artist.displayName).replace(/^-/, '');
    });
  }

});

brille.Songkick = brille.Collection.extend({

  model: brille.SongkickEvent,

  url: 'http://api.songkick.com/api/3.0/events.json',

  fetchOptions: {
    jsonp: 'jsoncallback',
    data: {
      apikey: 'wyIuXnq069VdJY8b',
      artist_name: 'random impulse'
    }
  },

  parse: function(response) { return response.resultsPage.results.event },

  comparator: function(event) {
    return event.date().valueOf();
  }

});

// SoundCloud

brille.SoundCloudEntity = Backbone.Model.extend({

  createdAt: function() {
    return moment(this.get('created_at'));
  },

  releaseDate: function() {
    return moment(new Date(
      this.get('release_year'),
      this.get('release_month'),
      this.get('release_day')
    ));
  },

  publishedAt: function() {
    return this.releaseDate();
  },

  tags: function() {
    var tags = [],
        inQuotes = false;

    _.each(this.get('tag_list').split(' '), function(piece) {
      piece = piece.toLowerCase();

      switch (piece.indexOf('"')) {
      case -1:
        if (!inQuotes) tags.push([]);
        _.last(tags).push(piece);
        break;
      case 0:
        tags.push([]);
        inQuotes = true;
        _.last(tags).push(piece.replace(/"/g, ''));
        break;
      default:
        inQuotes = false;
        _.last(tags).push(piece.replace(/"/g, ''));
        break;
      }
    });

    tags.push([this.get('user').permalink]);
    tags.push(this.get('user').username.toLowerCase().split(' '));

    tags = _.map(tags, function(tag) {
      return tag.join('-').replace(/[^a-z0-9\-]/g, '');
    });

    return _.without(tags, '');
  }

});

brille.SoundCloudTrack = brille.SoundCloudEntity.extend({

  klass: function() {
    return 'SoundCloudTrack';
  }

});

brille.SoundCloudPlaylist = brille.SoundCloudEntity.extend({

  klass: function() {
    return 'SoundCloudPlaylist';
  }

});

brille.SoundCloud = brille.Collection.extend({

  model: brille.SoundCloudPlaylist,

  url: 'http://api.soundcloud.com/users/brille-records/playlists.json',

  fetchOptions: {
    data: {
      client_id: '0ec9e0816a3bd9b9ee2661363825c69f'
    }
  },

  parse: function(response) { return response },

  comparator: function(track) {
    return -track.createdAt().valueOf();
  }

});

brille.SoundCloudDummy = brille.SoundCloud.extend({

  url: 'http://api.soundcloud.com/users/dummymag-com/playlists.json'

});

// Vimeo

brille.VimeoVideo = Backbone.Model.extend({

  klass: function() {
    return 'VimeoVideo';
  },

  tags: function() {
    return _.map(this.get('tags').split(','), function(tag) {
      return _.string.dasherize(tag).replace(/^-/, '');
    });
  },

  publishedAt: function() {
    return moment(this.get('upload_date'), 'YYYY-MM-DD HH:mm:ss');
  }

});

brille.Vimeo = brille.Collection.extend({

  model: brille.VimeoVideo,

  url: 'http://vimeo.com/api/v2/brillerecords/videos.json',

  parse: function(response) { return response },

  page: 1,

  comparator: function(video) {
    return -video.get('id');
  },

  loadMore: function() {
    if (this.page >= 3) return;
    this.page += 1;
    return this.fetchWithOptions({
      data: { page: this.page },
      add: true
    });
  }

});




// Template Helpers

brille.templates = {};

brille.withTemplate = function(name, callback) {
  var template = brille.templates[name];

  if (template) {
    callback(template);
  } else {
    $.get('/templates/' + name + '.hbs').
    success(function(data) {
      template = Handlebars.compile(data);
      brille.templates[name] = template;
      callback(template);
    }).
    error(function() {
      console.error('Template "' + name + '" not found.');
    });
  }
}

brille.render = function(options) {
  brille.withTemplate(options.template, function(template) {
    $(options.target).html(template(options.context));
    brille.refresh();
  });
}

brille.viewFor = function(model) {
  var klass = brille[model.klass() + 'View'] || brille.ArticleView;
  return new klass({ model: model });
}




// View Mixins

brille.CaptionedPostHelpers = {

  title: function() {
    if (this.paragraphs().length > 1) {
      return this.paragraphs().slice(0, 1)[0].replace(/<.+?>/g, '');
    } else {
      return null;
    }
  },

  text: function() {
    if (this.paragraphs().length > 1) {
      return this.paragraphs().slice(1).join('\n');
    } else {
      return this.paragraphs().join('\n');
    }
  },

  paragraphs: function() {
    return this.model.get('caption').split(/[\n\r]+/);
  }

}





// Handlebars Helpers

Handlebars.registerHelper('shareButtons', function() {
  return new Handlebars.SafeString(
  '<div class="share">' +
    '<a href="https://twitter.com/share' +
      '?url=' + escape(this.permalink()) +
      '&via=' + escape('BrilleRecords') +
    '" class="button share-button tweet">Tweet</a>' +
    '<a href="http://www.facebook.com/sharer.php' +
      '?u=' + escape(this.permalink()) +
      '&t=' + escape(this.title()) +
      '" class="button share-button like">Like</a>' +
  '</div>'
  );
});

Handlebars.registerHelper('textile', function(source) {
  if (_.isFunction(source)) source = source.apply(this);
  return new Handlebars.SafeString(textile(source));
});




// Main View

brille.MainView = Backbone.View.extend({

  el: '#main',

  views: {},

  initialize: function() {
    _.bindAll(this);
    this.collection.bind('reset', this.reset);
    this.collection.bind('add', this.add);
    this.collection.bind('remove', this.remove);
    this.onScroll = _.debounce(this.onScroll, 300);
    $(window).on('scroll', this.onScroll);
  },

  reset: function() {
    this.collection.each(this.add);
  },

  add: function(model) {
    var el = this.renderedElementFor(model),
        position = this.collection.indexOf(model),
        follower = $(this.el).find('nth-child(' + position + ')');

    if (follower.length) {
      el.insertBefore(follower);
    } else {
      el.appendTo(this.el);
    }

    el.
    stop().
    css({ opacity: 0 }).
    animate({ opacity: 1 });
  },

  remove: function(model) {
    this.elementFor(model).
    stop().
    animate({ opacity: 0 }, function() { $(this).detach() });
  },

  viewFor: function(model) {
    return this.views[model.cid] = this.views[model.cid] || brille.viewFor(model);
  },

  elementFor: function(model) {
    return $(this.viewFor(model).el);
  },

  renderedElementFor: function(model) {
    return $(this.viewFor(model).render().el);
  },

  onScroll: _.debounce(function() {
    var body = $('body'),
        topEdge = body.scrollTop(),
        bottomEdge = topEdge + ($(window).height() * 2),
        distanceToBottom = body.height() - bottomEdge;

    this.$('article').each(function() {
      var article = $(this),
          articleTopEdge = article.offset().top,
          articleBottomEdge = articleTopEdge + article.height();

      if (articleTopEdge > bottomEdge) {
        return false;
      } else if (articleBottomEdge < topEdge) {
        return;
      } else {
        article.trigger('article:inView');
      }
    });

    if (distanceToBottom <= 0) this.loadMore();
  }, 300),

  loadMore: function() {
    if (this.loadingMore) return;
    this.loadingMore = true;
    var promises = this.collection.loadMore();
    $.when.apply($, promises).then(this.whenMoreHaveLoaded);
  },

  whenMoreHaveLoaded: function() {
    _.delay(this.allowMoreLoading, 1000);
  },

  allowMoreLoading: function() {
    delete this.loadingMore;
  }

});

// Article View (base for most model views)

brille.ArticleView = Backbone.View.extend({

  tagName: 'article',

  template: 'article',

  render: function() {
    $(this.el).addClass(this.template);
    $(this.el).addClass(this.tags());
    brille.render({ template: this.template, target: this.el, context: this });
    return this;
  },

  tags: function() {
    return this.model.tags().join(' ');
  },

  permalink: function() {
    return window.location.href;
  },

  title: function() {
    return document.title;
  }

});

// Tumblr Views

brille.TumblrPostView = brille.ArticleView.extend({

  template: 'tumblr-post',

  date: function() {
    return this.model.date().format('DD.MM.YY');
  },

  title: function() {
    return this.model.get('title');
  },

  text: function() {
    return this.model.get('body');
  }

});

brille.TumblrTextPostView = brille.TumblrPostView.extend();

brille.TumblrAudioPostView = brille.TumblrPostView.extend({

  template: 'tumblr-player-post',

  player: function() {
    return this.model.get('player');
  },

  title: function() {
    return this.model.get('track_name');
  },

  text: function() {
    return this.model.get('caption');
  }

});

brille.TumblrChatPostView = brille.TumblrPostView.extend({

  title: function() {
    return this.model.get('title');
  },

  text: function() {
    return textile(this.model.get('body'));
  }

});

brille.TumblrLinkPostView = brille.TumblrPostView.extend({

  template: 'tumblr-link-post',

  title: function() {
    return this.model.get('title');
  },

  url: function() {
    return this.model.get('url');
  },

  text: function() {
    return this.model.get('description');
  }

});

brille.TumblrPhotoPostView = brille.TumblrPostView.extend({

  template: 'tumblr-photo-post',

  imageUrl: function() {
    return this.model.get('photos')[0].original_size.url;
  }

});
_.extend(brille.TumblrPhotoPostView.prototype, brille.CaptionedPostHelpers);

brille.TumblrPhotosetPostView = brille.TumblrPhotoPostView.extend();

brille.TumblrQuotePostView = brille.TumblrPostView.extend({

  text: function() {
    return this.model.get('text');
  },

  title: function() {
    return this.model.get('source');
  }

});

brille.TumblrVideoPostView = brille.TumblrPostView.extend({

  template: 'tumblr-player-post',

  player: function() {
    return this.scalePlayer(_.last(this.model.get('player')).embed_code);
  },

  scalePlayer: function(code) {
    return code;
  }

});
_.extend(brille.TumblrVideoPostView.prototype, brille.CaptionedPostHelpers);


// Big Cartel Views

brille.BigCartelProductView = brille.ArticleView.extend({

  template: 'product',

  img: function() {
    return this.model.get('images')[0].url;
  },

  year: function() {
    return this.model.createdAt().format('YYYY');
  },

  name: function() {
    return this.model.get('name');
  },

  description: function() {
    return textile(this.model.get('description'));
  },

  price: function() {
    return _.string.sprintf('£%01.2f', this.model.get('price'));
  },

  optionId: function() {
    return this.model.get('options')[0].id;
  },

  available: function() {
    return this.model.get('status') === 'active';
  }

});


// Songkick Views

brille.SongkickEventView = brille.ArticleView.extend({

  template: 'event',

  displayName: function() {
    return this.model.get('displayName').replace(/ \(.+\)$/, '');
  },

  date: function() {
    return this.model.date().format('DD.MM.YY');
  },

  url: function() {
    return this.model.get('uri');
  }

});

// SoundCloud Views

brille.SoundCloudTrackView = brille.ArticleView.extend({

  template: 'track',

  trackId: function() {
    return this.model.get('id');
  },

  artworkUrl: function() {
    return this.model.get('artwork_url');
  },

  date: function() {
    return this.model.createdAt().format('DD.MM.YY');
  },

  title: function() {
    return this.model.get('title');
  },

  description: function() {
    return textile(this.model.get('description'));
  },

  trackId: function() {
    return this.model.get('id');
  }

});

brille.SoundCloudPlaylistView = brille.ArticleView.extend({

  template: 'playlist',

  events: {
    'article:inView': 'lazyLoadPlayer'
  },

  showArtwork: function() {
    return $(window).width() >= 576 ? 'true' : 'false';
  },

  title: function() {
    return this.model.get('title');
  },

  description: function() {
    return textile(this.model.get('description'));
  },

  uri: function() {
    return this.model.get('uri');
  },

  permalinkUrl: function() {
    return this.model.get('permalink_url');
  },

  playerUrl: function() {
    return 'http://w.soundcloud.com/player/?url=' +
            escape(this.uri()) +
           '&show_artwork=' +
            escape(this.showArtwork());
  },

  artworkUrl: function() {
    var url = this.model.get('artwork_url');
    if (url) return url.replace(/large/, 'crop');
  },

  player: function() {
    return this._player = this._player || $('<iframe>').
      attr('width'       , '100%').
      attr('height'      , '450').
      attr('class'       , 'html5player').
      attr('scrolling'   , 'no').
      attr('frameborder' , 'no').
      attr('src'         , this.playerUrl());
  },

  lazyLoadPlayer: function() {
    var lazyPlayer = this.$('.lazy-player');
    if (lazyPlayer.length === 0) return;

    if (this.loadingLazyPlayer) return;
    this.loadingLazyPlayer = true;

    var realPlayer = this.player();

    lazyPlayer.animate({ opacity: 0 }, _.bind(function() {
      realPlayer.css({ height: lazyPlayer.css('height') });
      lazyPlayer.replaceWith(realPlayer);
      this.loadingLazyPlayer = false;
    }, this));
  }

});

// Vimeo Views

brille.VimeoVideoView = brille.ArticleView.extend({

  template: 'video',

  title: function() {
    return this.model.get('title');
  },

  thumbnailUrl: function() {
    return this.model.get('thumbnail_large');
  },

  videoId: function() {
    return this.model.get('id');
  },

  width: function() {
    return this.model.get('width');
  },

  height: function() {
    return this.model.get('height');
  }

});


// Loading Indicator

brille.LoadingIndicatorView = Backbone.View.extend({

  className: 'loading-indicator',

  initialize: function() {
    _.bindAll(this);
    $(this.el).appendTo('header');
  },

  progress: function() {
    if (!this._progress)
      this._progress = $('<span class="progress">').appendTo(this.el);
    return this._progress;
  },

  show: function() {
    $(this.el).css({ opacity: 1 });
    $(this.el).show();
    this.progress().
      stop().
      css({ width: '0%', opacity: '1' }).
      animate({ width: '100%' }, this.pulse);
  },

  hide: function() {
    this.progress().stop();
    $(this.el).hide();
  },

  pulse: function() {
    var o = this.progress().css('opacity') == 1 ? '0' : '1';
    this.progress().animate({ opacity: o }, 'linear', this.pulse);
  }

});


// Ready

$(brille.go);


