/*
*   SoundCloud Custom Player jQuery Plugin
*   Author: Matas Petrikas, matas@soundcloud.com
*   Copyright (c) 2009  SoundCloud Ltd.
*   Licensed under the MIT license:
*   http://www.opensource.org/licenses/mit-license.php
*
*   Usage:
*   <a href="http://soundcloud.com/matas/hobnotropic" class="sc-player">My new dub track</a>
*   The link will be automatically replaced by the HTML based player
*/
var FETCHED_TRACK_TO_PLAY = false;
function getUrlVars()
{
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

(function($) {
    // Convert milliseconds into Hours (h), Minutes (m), and Seconds (s)
    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('.');
    };
    // shuffle the array
    var shuffle = function(arr) {
        arr.sort(function() {
            return Math.round(Math.random());
        } );
        return arr;
    };

    var debug = true,
    useSandBox = false,
    $doc = $(document),
    log = function(args) {
        try {
            if(debug && window.console && window.console.log){
                window.console.log.apply(window.console, arguments);
            }
        } catch (e) {
        // no console available
        }
    },
    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);
                // announce 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 +'&enable_api=true&player_type=engine&auto_play=true&single_active=false&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" />'+
                    '<param name="FlashVars" value="auto_play=true&single_active=false">'+
                    '</object>';
                } else {
                    return '<object height="100%" width="100%" id="' + engineId + '">'+
                    '<embed allowscriptaccess="always" flashvars="autoplay=true&single_active=false" 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]);
    },
    artworkImage = function(track, usePlaceholder) {
        if(usePlaceholder){
            return '<div class="sc-loading-artwork">Loading Artwork</div>';
        }else if (track.artwork_url) {
            return '<img src="' + track.artwork_url.replace('-large', '-t300x300') + '"/>';
        }else{
            return '<div class="sc-no-artwork">No Artwork</div>';
        }
    },
    updateTrackInfo = function($player, track) {
        $('.sc-info', $player).each(function(index) {
            });
        // update the artwork
        $('.sc-artwork-list li', $player).each(function(index) {
            var $item = $(this),
            itemTrack = $item.data('sc-track');

            if (itemTrack === track) {
                // show track artwork
                $item
                .addClass('active')
                .find('.sc-loading-artwork')
                .each(function(index) {
                    // if the image isn't loaded yet, do it now.
                    $(this).removeClass('sc-loading-artwork').html(artworkImage(track, false));
                });
            }else{
                // reset other artworks
                $item.removeClass('active');
            }
        });
        // update the track duration in the progress bar
        $('.sc-duration', $player).html(timecode(track.duration));
        // put the waveform into the progress bar
        $('.sc-waveform-container', $player).html('<img src="' + track.waveform_url.replace('_m', '_s') +'" />');

        $player.trigger('onPlayerTrackSwitch.scPlayer', [track]);
    },
    play = function(track) {
        var url = track.permalink_url;
        if(currentUrl === url){
            // log('will play');
            audioEngine.play();
        }else{
            currentUrl = url;
            // log('will load', url);
            audioEngine.load(track, apiKey);
        }
    },
    getPlayerData = function(node) {
        return players[$(node).data('sc-player').id];
    },
    updatePlayStatus = function(player, status) {
        if(status){
            // reset all other players playing 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);
        // cache the references to most updated DOM nodes in the progress bar
        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-player'),
        $nextItem;
        // update the scrubber width
        updates.$played.css('width', '0%');
        // show the position in the track position counter
        updates.position.innerHTML = timecode(0);
        // reset the player state
        updatePlayStatus($player, false);
        // stop the audio
        audioEngine.stop();
        
        $nextItem = $($player).parent().next().find('.sc-player').find('.sc-trackslist li.active');
        if($($nextItem).length == 0)
        {
            $nextItem = $($player).parent().next().find('.sc-player').find('.sc-trackslist li.active');
            if($($nextItem).length == 0)
            {
                var page = getUrlVars();
                page = page["p"];
                if(undefined == page)
                {
                    page = 2;
                }
                else
                {
                    page++;
                }
                var host = window.location.host;
                if(host.charAt(host.length-1) == '/')
                {
                	host = host.substring(0, host.length-1);
                }
                var path = window.location.pathname;
                if(path.charAt(0) == '/')
                {
                	path = path.substring(1);
                }
                var url = "http://" + host + "/" + path + '?p=' + page + '&play=1';
               	window.location = url;
            }
        }
        $nextItem.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);
        // save the volume in the cookie
        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('');
        // update the volume in the engine
        audioEngine.setVolume(soundVolume);
    },
    positionPoll;

    // listen to audio engine events
    $doc
    .bind('scPlayer:onAudioReady', function(event) {
        log('onPlayerReady: audio engine is ready');
        audioEngine.play();
        // set initial volume
        onVolume(soundVolume);
    })
    // when the loaded track started to play
    .bind('scPlayer:onMediaPlay', function(event) {
        clearInterval(positionPoll);
        positionPoll = setInterval(function() {
            var duration = audioEngine.getDuration(),
            position = audioEngine.getPosition(),
            relative = (position / duration);

            // update the scrubber width
            updates.$played.css('width', (100 * relative) + '%');
            // show the position in the track position counter
            updates.position.innerHTML = timecode(position);
            // announce the track position to the DOM
            $doc.trigger({
                type: 'onMediaTimeUpdate.scPlayer',
                duration: duration,
                position: position,
                relative: relative
            });
        }, 500);
    })
    // when the loaded track is was paused
    .bind('scPlayer:onMediaPause', function(event) {
        clearInterval(positionPoll);
        positionPoll = null;
    })
    // change the volume
    .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 + '%');
    });


    // Generate custom skinnable HTML/CSS/JavaScript based SoundCloud players from links to SoundCloud resources
    $.scPlayer = function(options, node) {
        var opts = $.extend({}, $.scPlayer.defaults, options),
        playerId = players.length,
        $source = node && $(node),
        sourceClasses = $source[0].className.replace('sc-player', ''),
        links = opts.links || $.map($('a', $source).add($source.filter('a')), function(val) {
            return {
                url: val.href,
                title: val.innerHTML
            };
        }),
        $player = $('<div class="sc-player loading"></div>').data('sc-player', {
            id: playerId
        }),
        $artworks = $('<ol class="sc-artwork-list"></ol>').appendTo($player),
        $controls = $('<div class="sc-controls"></div>').appendTo($player),
        $list = $('<ol class="sc-trackslist"></ol>').appendTo($player);

        // add the classes of the source node to the player itself
        // the players can be indvidually styled this way
        if(sourceClasses || opts.customClass){
            $player.addClass(sourceClasses).addClass(opts.customClass);
        }


        // adding controls to the player
        var final_url = links[0]['url'];
        final_url = final_url.replace('http://', '');
        final_url = final_url.split('/');
        final_url = final_url[0]+'/'+final_url[1]+'/'+final_url[2];
        $player
        .find('.sc-controls')
        .append('<a href="#previous" class="sc-previous">Previous</a><a href="#play" class="sc-play">Play</a><a href="#pause" class="sc-pause">Pause</a><a href="#next" class="sc-next">Next</a>')
        .end()
        .append('<div class="sc-scrubber"></div>')
        .find('.sc-scrubber')
        .append('<div class="sc-volume-container"><div class="sc-volume-slider"><span class="sc-volume-status" style="width:' + soundVolume +'%"></span></div></div>')
        .append('<div class="sc-time-span"><div class="sc-waveform-container"></div><div class="sc-buffer"></div><div class="sc-played"></div><div class="sc-background"></div></div>')
        .append('<div class="sc-time-indicators"><span class="sc-position"></span><span class="sc-duration"></span></div>');

        $('<div class="clearfix"></div>').appendTo($player);

        // load and parse the track data from SoundCloud API
        loadTracksData($player, links, opts.apiKey);
        // init the player GUI, when the tracks data was laoded
        $player.bind('onTrackDataLoaded.scPlayer', function(event) {
            // log('onTrackDataLoaded.scPlayer', event.playerObj, playerId, event.target);
            var tracks = event.playerObj.tracks;
            if (opts.randomize) {
                tracks = shuffle(tracks);
            }
            // create the playlist
            $.each(tracks, function(index, track) {
                var active = index === 0;
                // create an item in the playlist
                $('<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);
                // create an item in the artwork list
                $('<li></li>')
                .append(artworkImage(track, index >= opts.loadArtworks))
                .appendTo($artworks)
                .toggleClass('active', active)
                .data('sc-track', track);
            });
            if($list.children().length < 2)
            {
                $list.addClass('hidden');
            }
            $player
            .removeClass('loading')
            .trigger('onPlayerInit.scPlayer').parent().removeClass('loading');

            // update the element before rendering it in the DOM
            $player.each(function() {
                if($.isFunction(opts.beforeRender)){
                    opts.beforeRender.call(this, tracks);
                }
            });
            // set the first track's duration
            $('.sc-duration', $player)[0].innerHTML = timecode(tracks[0].duration);
            $('.sc-position', $player)[0].innerHTML = timecode(0);
            // set up the first track info
            updateTrackInfo($player, tracks[0]);
            // if auto play is enabled and it's the first player, start playing
            if(opts.autoPlay && !didAutoPlay){
                onPlay($player);
                didAutoPlay = true;
            }

            setTimeout(function(){
                if(FETCHED_TRACK_TO_PLAY === false)
                {
                    FETCHED_TRACK_TO_PLAY = true;
                    var play_track = getUrlVars();
                    play_track = play_track['play'];
                    if(undefined != play_track)
                    {
                        play_track = parseInt(play_track);
                        if(play_track == 1)
                        {
                            $pageplayer = $('.sc-player').first().find('ol.sc-trackslist').find('li.active');
                            $pageplayer.click();
                        }
                    }
                }
            }, 1000);
        });


        // replace the DOM source (if there's one)
        $source.each(function(index) {
            $(this).replaceWith($player);
        });

        return $player;
    };

    // stop all players, might be useful, before replacing the player dynamically
    $.scPlayer.stopAll = function() {
        $('.sc-player.playing a.sc-pause').click();
    };

    // plugin wrapper
    $.fn.scPlayer = function(options) {
        // reset the auto play
        didAutoPlay = false;
        // create the players
        this.each(function() {
            $.scPlayer(options, this);
        });
        return this;
    };

    // default plugin options
    $.scPlayer.defaults = $.fn.scPlayer.defaults = {
        customClass: null,
        // do something with the dom object before you render it, add nodes, get more data from the services etc.
        beforeRender  :   function(tracksData) {
            var $player = $(this);
        },
        // initialization, when dom is ready
        onDomReady  : function() {
            $('a.sc-player, div.sc-player').scPlayer();
        },
        autoPlay: false,
        randomize: false,
        loadArtworks: 5,
        // the default Api key should be replaced by your own one
        // get it here http://soundcloud.com/you/apps/new
        apiKey: '8ZF4IkCbUYnc7lw53LmLIg'
    };

    // the GUI event bindings
    //--------------------------------------------------------

    // toggling play/pause
    $('a.sc-play, a.sc-pause').live('click', function(event) {
        var $list = $(this).closest('.sc-player').find('ol.sc-trackslist');
        $list.find('li.active').click();
        return false;
    });

    // selecting tracks in the playlist
    $('.sc-trackslist li').live('click', function(event) {
        var $track = $(this),
        $player = $track.closest('.sc-player'),
        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');
        $('.artworks li', $player).each(function(index) {
            $(this).toggleClass('active', index === trackId);
        });
        return false;
    });

    var scrub = function(node, xPos) {
        var $scrubber = $(node).closest('.sc-time-span'),
        $buffer = $scrubber.find('.sc-buffer'),
        $available = $scrubber.find('.sc-waveform-container img'),
        $player = $scrubber.closest('.sc-player'),
        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();
        }
    };


    // seeking in the loaded track buffer
    $('.sc-time-span')
    .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();
    });

    // changing volume in the player
    var startVolumeTracking = function(node, startEvent) {
        var $node = $(node),
        originX = $node.offset().left,
        originWidth = $node.width(),
        getVolume = function(x) {
            return Math.floor(((x - originX)/originWidth)*100);
        },
        update = function(event) {
            $doc.trigger({
                type: 'scPlayer:onVolumeChange',
                volume: getVolume(event.pageX)
            });
        };
        $node.bind('mousemove.sc-player', update);
        update(startEvent);
    };

    var stopVolumeTracking = function(node, event) {
        $(node).unbind('mousemove.sc-player');
    };

    $('.sc-volume-slider')
    .live('mousedown', function(event) {
        startVolumeTracking(this, event);
    })
    .live('mouseup', function(event) {
        stopVolumeTracking(this, event);
    });

    $doc.bind('scPlayer:onVolumeChange', function(event) {
        $('span.sc-volume-status').css({
            width: event.volume + '%'
        });
    });
    // -------------------------------------------------------------------

    // the default Auto-Initialization
    $(function() {
        if($.isFunction($.scPlayer.defaults.onDomReady)){
            $.scPlayer.defaults.onDomReady();
        }
    });

})(jQuery);
