/*
*   SoundCloud Custom Player jQuery Plugin
*   Author: Matas Petrikas, matas@soundcloud.com
*		Modified with little knowledge by: Lee Martin, lee@soundcloud.com
*   Copyright (c) 2009  SoundCloud Ltd.
*   Licensed under the MIT license:
*   http://www.opensource.org/licenses/mit-license.php
*
*/

(function($) {
	
  var timecode = function(ms) {
    var hms = function(ms) {
          return {
            h: Math.floor(ms/(60*60*1000)),
            m: Math.floor((ms/60000) % 60),
            s: Math.floor((ms/1000) % 60)
          };
        }(ms),
        tc = []; // Timecode array to be joined with '.'

    if (hms.h > 0) {
      tc.push(hms.h);
    }

    tc.push((hms.m < 10 && hms.h > 0 ? "0" + hms.m : hms.m));
    tc.push((hms.s < 10  ? "0" + hms.s : hms.s));

    return tc.join('.');
  };

	$.fn.shuffle = function() {
    return this.each(function(){
      var items = $(this).children();
      return (items.length)
        ? $(this).html($.shuffle(items))
        : this;
    });
  }

  $.shuffle = function(arr) {
    for(
      var j, x, i = arr.length; i;
      j = parseInt(Math.random() * i),
      x = arr[--i], arr[i] = arr[j], arr[j] = x
    );
    return arr;
  }

  var debug = true,
      useSandBox = false,
      $doc = $(document),
      log = function(args) {
        if(debug && window.console && window.console.log){
          window.console.log.apply(window.console, arguments);
        }
      },
      domain = useSandBox ? 'sandbox-soundcloud.com' : 'soundcloud.com',
      scApiUrl = function(url, apiKey) {
        return (/api\./.test(url) ? url + '?' : 'http://api.' + domain +'/resolve?url=' + url + '&') + 'format=json&consumer_key=' + apiKey +'&callback=?';
      };

  var audioEngine = function() {
	
    var html5AudioAvailable = function() {
        var state = false;
        try{
          var a = new Audio();
          state = a.canPlayType && (/maybe|probably/).test(a.canPlayType('audio/mpeg'));
          // let's enable the html5 audio on selected mobile devices first, unlikely to support Flash
          // the desktop browsers are still better with Flash, e.g. see the Safari 10.6 bug
          // comment the following line out, if you want to force the html5 mode
          state = state && (/iPad|iphone|mobile|pre\//i).test(navigator.userAgent);
        }catch(e){
          // there's no audio support here sadly
        }
        return state;
    }(),

    callbacks = {
      onReady: function() {
        $doc.trigger('scPlayer:onAudioReady');
      },
      onPlay: function() {
        $doc.trigger('scPlayer:onMediaPlay');
      },
      onPause: function() {
        $doc.trigger('scPlayer:onMediaPause');
      },
      onEnd: function() {
        $doc.trigger('scPlayer:onMediaEnd');
      },
      onBuffer: function(percent) {
        $doc.trigger({type: 'scPlayer:onMediaBuffering', percent: percent});
      }
    };

    var html5Driver = function() {
      var player = new Audio(),
          onTimeUpdate = function(event){
            var obj = event.target,
                buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
            // ipad has no progress events implemented yet
            callbacks.onBuffer(buffer);
            // anounce if it's finished for the clients without 'ended' events implementation
            if (obj.currentTime === obj.duration) { callbacks.onEnd(); }
          },
          onProgress = function(event) {
            var obj = event.target,
                buffer = ((obj.buffered.length && obj.buffered.end(0)) / obj.duration) * 100;
            callbacks.onBuffer(buffer);
          };

      $('<div class="sc-player-engine-container"></div>').appendTo(document.body).append(player);

      // prepare the listeners
      player.addEventListener('play', callbacks.onPlay, false);
      player.addEventListener('pause', callbacks.onPause, false);
      player.addEventListener('ended', callbacks.onEnd, false);
      player.addEventListener('timeupdate', onTimeUpdate, false);
      player.addEventListener('progress', onProgress, false);

      return {
        load: function(track, apiKey) {
          player.pause();
          player.src = track.stream_url + '?consumer_key=' + apiKey;
          player.load();
          player.play();
        },
        play: function() {
          player.play();
        },
        pause: function() {
          player.pause();
        },
        stop: function(){
          player.currentTime = 0;
          player.pause();
        },
        seek: function(relative){
          player.currentTime = player.duration * relative;
          player.play();
        },
        getDuration: function() {
          return player.duration;
        },
        getPosition: function() {
          return player.currentTime;
        },
        setVolume: function(val) {
          //if(a){
          //  a.volume = val / 100;
          //}
        }
      };

    };

    var flashDriver = function() {
      var engineId = 'scPlayerEngine',
          player,
          flashHtml = function(url) {
            var swf = 'http://player.' + domain +'/player.swf?url=' + url +'&amp;enable_api=true&amp;player_type=engine&amp;object_id=' + engineId;
            if ($.browser.msie) {
              return '<object height="100%" width="100%" id="' + engineId + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" data="' + swf + '">'+
                '<param name="movie" value="' + swf + '" />'+
                '<param name="allowscriptaccess" value="always" />'+
                '</object>';
            } else {
              return '<object height="100%" width="100%" id="' + engineId + '">'+
                '<embed allowscriptaccess="always" height="100%" width="100%" src="' + swf + '" type="application/x-shockwave-flash" name="' + engineId + '" />'+
                '</object>';
            }
          };



      // listen to audio engine events
      // when the loaded track is ready to play
      soundcloud.addEventListener('onPlayerReady', function(flashId, data) {
        player = soundcloud.getPlayer(engineId);
        callbacks.onReady();
      });

      // when the loaded track finished playing
      soundcloud.addEventListener('onMediaEnd', callbacks.onEnd);

      // when the loaded track is still buffering
      soundcloud.addEventListener('onMediaBuffering', function(flashId, data) {
        callbacks.onBuffer(data.percent);
      });

      // when the loaded track started to play
      soundcloud.addEventListener('onMediaPlay', callbacks.onPlay);

      // when the loaded track is was paused
      soundcloud.addEventListener('onMediaPause', callbacks.onPause);

      return {
        load: function(track) {
          var url = track.permalink_url;
          if(player){
            player.api_load(url);
          }else{
            // create a container for the flash engine (IE needs this to operate properly)
            $('<div class="sc-player-engine-container"></div>').appendTo(document.body).html(flashHtml(url));
          }
        },
        play: function() {
          player && player.api_play();
        },
        pause: function() {
          player && player.api_pause();
        },
        stop: function(){
          player && player.api_stop();
        },
        seek: function(relative){
          player && player.api_seekTo((player.api_getTrackDuration() * relative));
        },
        getDuration: function() {
          return player && player.api_getTrackDuration && player.api_getTrackDuration() * 1000;
        },
        getPosition: function() {
          return player && player.api_getTrackPosition && player.api_getTrackPosition() * 1000;
        },
        setVolume: function(val) {
          if(player && player.api_setVolume){
            player.api_setVolume(val);
          }
        }

      };
    };

    return html5AudioAvailable? html5Driver() : flashDriver();

  }();

  var apiKey,
      didAutoPlay = false,
      players = [],
      updates = {},
      currentUrl,
      loadTracksData = function($player, links, key) {
        var index = 0,
            playerObj = {node: $player, tracks: []},
            loadUrl = function(link) {
              $.getJSON(scApiUrl(link.url, apiKey), function(data) {
                // log('data loaded', link.url, data);
                index += 1;
                if(data.tracks){
                  // log('data.tracks', data.tracks);
                  playerObj.tracks = playerObj.tracks.concat(data.tracks);
                }else if(data.duration){
                  // a secret link fix, till the SC API returns permalink with secret on secret response
                  data.permalink_url = link.url;
                  // if track, add to player
                  playerObj.tracks.push(data);
                }else if(data.username){
                  // if user, get his tracks or favorites
                  if(/favorites/.test(link.url)){
                    links.push({url:data.uri + '/favorites'});
                  }else{
                    links.push({url:data.uri + '/tracks'});
                  }
                }else if($.isArray(data)){
                  playerObj.tracks = playerObj.tracks.concat(data);
                }
                if(links[index]){
                  // if there are more track to load, get them from the api
                  loadUrl(links[index]);
                }else{
                  // if loading finishes, anounce it to the GUI
                  playerObj.node.trigger({type:'onTrackDataLoaded.scPlayer', playerObj: playerObj});
                }
             });
           };
        // update current API key
        apiKey = key;
        // update the players queue
        players.push(playerObj);
        // load first tracks
        loadUrl(links[index]);
      },
      updateTrackInfo = function($player, track) {
		
				opts = $player.data("sc-player").opts;
				
				// Track Duration
				
				$('.sc-duration', $player).html(timecode(track.duration));
				
				// Add Wave
				
				if($.browser.webkit){
					$('.sc-wave').css({'-webkit-mask-box-image' : 'url(' + track.waveform_url + ') 0  stretch', 'background-color' : $player.css('background-color')});
				} else {
					$('.sc-wave').html('<img src="' + track.waveform_url +'" />');
				}
				
				// Caption
				
				caption = '<a href="' + track.permalink_url + '" target="_blank">' + track.title + '</a>';
				
				if (opts.show_user) { caption = caption + ' by <a href="' + track.user.permalink_url + '" target="_blank">' + track.user.username + '</a>'; }
				
				if (opts.show_playcount) { caption = caption + ' ' + track.playback_count + ' plays'; }
				
				$('.sc-caption', $player).html(caption);
				
				// Color Links
				
				$('.sc-caption a').css('color', '#' + opts.color);
				
				// Info Link
				
				$('.sc-info', $player).attr('href', track.permalink_url);
				
				// Downloadable?
				
				if (track.downloadable) {					
					$('.sc-download', $player).removeClass('disabled').attr('href', track.download_url + '?consumer_key=ybtyKcnlhP3RKXpJ58fg').unbind('click');					
				} else {					
					$('.sc-download', $player).addClass('disabled').removeAttr('href').click(function(){return false;});					
				}
				
				// Buyable?
				
				if (track.purchase_url) {					
					$('.sc-buy', $player).removeClass('disabled').attr('href', track.purchase_url).unbind('click');					
				} else {					
					$('.sc-buy', $player).addClass('disabled').removeAttr('href').click(function(){return false;});					
				}

        $player.trigger('onPlayerTrackSwitch.scPlayer', [track]);

      },
      play = function(track) {
        var url = track.permalink_url;
        if(currentUrl === url){
          audioEngine.play();
        }else{
          currentUrl = url;
          audioEngine.load(track, apiKey);
        }
      },
      getPlayerData = function(node) {
        return players[$(node).data('sc-player').id];
      },
      updatePlayStatus = function(player, status) {
        if(status){
          $('div.sc-player.playing').removeClass('playing');
        }
        $(player)
          .toggleClass('playing', status)
          .trigger((status ? 'onPlayerPlay' : 'onPlayerPause') + '.scPlayer');
      },
      onPlay = function(player, id) {
        var track = getPlayerData(player).tracks[id || 0];
        updateTrackInfo(player, track);
        updates = {
          $buffer: $('.sc-buffer', player),
          $played: $('.sc-played', player),
          position:  $('.sc-position', player)[0]
        };
        updatePlayStatus(player, true);
        play(track);
      },
      onPause = function(player) {
        updatePlayStatus(player, false);
        audioEngine.pause();
      },
      onFinish = function() {
        var $player = updates.$played.closest('.sc-stratus'), $nextItem;        
        updates.$played.css('width', '0%');
        updates.position.innerHTML = timecode(0);
        updatePlayStatus($player, false);
        audioEngine.stop();
				onNext($player);
      },
			onNext = function(player) {

				if ( $('.sc-trackslist li.active', player).next().click().length == 0 ) { 
					$('.sc-trackslist li:first', player).click();
				}

			},
			onPrev = function(player) {
				
				if ( $('.sc-trackslist li.active', player).prev().click().length == 0 ) {
					$('.sc-trackslist li:last', player).click();
				}
				
			},
      onSeek = function(player, relative) {
        audioEngine.seek(relative);
      },
      soundVolume = function() {
        var vol = 80,
            cooks = document.cookie.split(';'),
            volRx = new RegExp('scPlayer_volume=(\\d+)');
        for(var i in cooks){
          if(volRx.test(cooks[i])){
            vol = parseInt(cooks[i].match(volRx)[1], 10);
            break;
          }
        }
        return vol;
      }(),
      onVolume = function(volume) {
        var vol = Math.floor(volume);
        var date = new Date();
        date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
        soundVolume = vol;
        document.cookie = ['scPlayer_volume=', vol, '; expires=', date.toUTCString(), '; path="/"'].join('');
        audioEngine.setVolume(soundVolume);

				$('.sc-volume').removeClass('min max high low');

				if(soundVolume == 0){
					$('.sc-volume').addClass('min');
				} else if (soundVolume == 100){
					$('.sc-volume').addClass('max');
				} else if (soundVolume > 50){
					$('.sc-volume').addClass('high');
				} else {
					$('.sc-volume').addClass('low');
				}

      },
      positionPoll;

    $doc
      .bind('scPlayer:onAudioReady', function(event) {
        audioEngine.play();
        onVolume(soundVolume);
      })
      .bind('scPlayer:onMediaPlay', function(event) {
	
        clearInterval(positionPoll);

        positionPoll = setInterval(function() {
	
          var duration = audioEngine.getDuration(),
              position = audioEngine.getPosition(),
              relative = (position / duration);

          updates.$played.css('width', (100 * relative) + '%');
          updates.position.innerHTML = timecode(position);

          $doc.trigger({
            type: 'onMediaTimeUpdate.scPlayer',
            duration: duration,
            position: position,
            relative: relative
          });
        }, 500);
      })
      .bind('scPlayer:onMediaPause', function(event) {
        clearInterval(positionPoll);
        positionPoll = null;
      })
      .bind('scPlayer:onVolumeChange', function(event) {
        onVolume(event.volume);
      })
      .bind('scPlayer:onMediaEnd', function(event) {
        onFinish();
      })
      .bind('scPlayer:onMediaBuffering', function(event) {
        updates.$buffer.css('width', event.percent + '%');
      });

  $.scPlayer = function(options, node) {
	
    var opts = $.extend({}, $.scPlayer.defaults, options),

        playerId = players.length,
				$source = $('<a class="sc-stratus"></a>').appendTo('body'),
				links = opts.links,
				
        $player = $('<div class="sc-stratus loading"></div>').data('sc-player', {id: playerId, opts: opts}),
				$logo = $('<a href="http://bit.ly/stratus-stats" target="_blank" class="sc-logo"></a>').appendTo($player),
				$volume = $('<a class="sc-volume"></a>').appendTo($player),
				$info = $('<a target="_blank" class="sc-info"></a>').appendTo($player),
				$download = $('<a target="_blank" class="sc-download"></a>').appendTo($player),
				$buy = $('<a target="_blank" class="sc-buy"></a>').appendTo($player),
				$duration = $('<div class="sc-time-indicator"><span class="sc-position"></span>&nbsp;/&nbsp;<span class="sc-duration"></span></div>').appendTo($player),
				$controls = $('<div class="sc-controls"><div class="sc-prev"></div><div class="sc-circle"><div class="sc-play"></div><div class="sc-pause"></div></div><div class="sc-next"></div></div>').appendTo($player),
				$time = $('<div class="sc-time"><div class="sc-wave"></div><div class="sc-played"></div><div class="sc-buffer"></div></div>').appendTo($player),
				$caption = $('<div class="sc-caption"></div>').appendTo($player),
        $list = $('<ol class="sc-trackslist"></ol>').appendTo($player);

				$logo.after('<div class="sc-border"></div>');
				$volume.after('<div class="sc-border"></div>');
				$info.after('<div class="sc-border"></div>');
				$download.after('<div class="sc-border"></div>');
				$buy.after('<div class="sc-border"></div>');
				
				// Ping Bit.ly ..?

				ping = new Image();
				
				ping.src="http://bit.ly/stratus-stats";
				
				// load and parse the track data from SoundCloud API

				loadTracksData($player, links, opts.apiKey);
        
        $player.bind('onTrackDataLoaded.scPlayer', function(event) { // init the player GUI, when the tracks data was laoded
	
					$player.after('<div class="sc-volume-bar"><div class="sc-slider"></div></div>');
	
					// Align
					
					$player.addClass("stratus-" + opts.align);
					
					$('.sc-volume-bar').addClass("stratus-" + opts.align);
					
					// Offset Bar
					
					if (opts.offset != 0) {
					
						$player.css(opts.align, opts.offset);
						$('.sc-volume-bar').css(opts.align, (30 + opts.offset));
						
					}
					
					// Position
					
					$player.css("position", opts.position);
					
					$('.sc-volume-bar').css("position", opts.position);
	
					// Color play button and waveform
					
					if (opts.color != false) {					
						$('.sc-circle, .sc-played').css('background-color', '#' + opts.color);						
					}
					
					// Show/Hide
					
					if (opts.download == false) {						
						$download.hide();
						$buy.css('right', '105px');
					}
					
					if (opts.buying == false) {						
						$buy.hide();
					}
					
					if (opts.download == false && opts.buying == false) {
						$duration.css('right', '115px');
						$('.sc-buy, .sc-download').next(".sc-border").hide();
					} else if (opts.download == false || opts.buying == false) {
						$duration.css('right', '145px');
						$buy.next(".sc-border").hide();
					}
	
					$(".sc-slider").slider({ // create slider

						value: opts.volume, 
						animate: true, 
						orientation: 'vertical',

						slide: function(event, ui) { onVolume(ui.value); }

					});
					
					onVolume(opts.volume);

          var tracks = event.playerObj.tracks;

          if (opts.randomize) { $.shuffle(tracks); } // shuffle 

          $.each(tracks, function(index, track) {	// create tracklist
            var active = index === 0;
            $('<li><a href="' + track.permalink_url +'">' + track.title + '</a><span class="sc-track-duration">' + timecode(track.duration) + '</span></li>').data('sc-track', {id:index}).toggleClass('active', active).appendTo($list);
          });

          $player.removeClass('loading').trigger('onPlayerInit.scPlayer');

          $('.sc-position', $player)[0].innerHTML = timecode(0);

          updateTrackInfo($player, tracks[0]);

      		if(opts.animate == "slide"){ // show player
						$player.slideToggle();
					} else if(opts.animate == "fade"){
						$player.fadeIn();
					} else {
						$player.show();
					}

          if(opts.auto_play && !didAutoPlay){ onPlay($player); didAutoPlay = true; } // autoPlay

        });

    $source.each(function(index) { $(this).replaceWith($player); });

    return $player;

  };

  $.fn.scPlayer = function(options) {
    didAutoPlay = false;
    this.each(function() { $.scPlayer(options, this); });
    return this;
  };
		
	$.stratus = function(settings) {
		
		var defaultSettings = {
			align: 'bottom',
			animate: 'slide',
			auto_play: false,
			apiKey: 'ybtyKcnlhP3RKXpJ58fg',
			buying: true,
			color: false, //'ff7700',
			download: true,
			offset: 0,
			position: 'fixed',
			randomize: false,
			show_playcount: true,
			show_user: true,
			volume: 50			
		}
		
		settings = $.extend(defaultSettings, settings);
		
		$.scPlayer(settings);
	
	}

  // GUI events

  $('.sc-play, .sc-pause').live('click', function(event) {

		$(this).closest('.sc-stratus').find('ol.sc-trackslist').find('li.active').click();

    return false;

  });

  $('.sc-trackslist li').live('click', function(event) {
	
    var $track = $(this),
        $player = $track.closest('.sc-stratus'),
        trackId = $track.data('sc-track').id,
        play = $player.is(':not(.playing)') || $track.is(':not(.active)');

    if (play) { onPlay($player, trackId); }else{ onPause($player); }

    $track.addClass('active').siblings('li').removeClass('active');

    return false;

  });

	$('.sc-next').live('click', function(event) {
		$player = $(this).closest('.sc-stratus');
		onNext($player);
	});
	
	$('.sc-prev').live('click', function(event) {
		$player = $(this).closest('.sc-stratus');
		onPrev($player);
	});

	$('.sc-volume').live('click', function(event) { $('.sc-volume-bar').slideToggle('fast', 'linear'); });

  var scrub = function(node, xPos) {
	
    var $scrubber = $(node).closest('.sc-time'),
        $buffer = $scrubber.find('.sc-buffer'),
        $available = $scrubber.find('.sc-wave'),
        $player = $scrubber.closest('.sc-stratus'),
        relative = Math.min($buffer.width(), (xPos  - $available.offset().left)) / $available.width();

    onSeek($player, relative);

  };

  var onTouchMove = function(ev) {
    if (ev.targetTouches.length === 1) {
      scrub(ev.target, ev.targetTouches && ev.targetTouches.length && ev.targetTouches[0].clientX);
      ev.preventDefault();
    }
  };

  $('.sc-time')
    .live('click', function(event) {
      scrub(this, event.pageX);
      return false;
    })
    .live('touchstart', function(event) {
      this.addEventListener('touchmove', onTouchMove, false);
      event.originalEvent.preventDefault();
    })
    .live('touchend', function(event) {
      this.removeEventListener('touchmove', onTouchMove, false);
      event.originalEvent.preventDefault();
    });

})(jQuery);
