// ----------------------------------------------------------------------------
// Buzz, a Javascript HTML5 Audio library
// v 1.0.4 beta
// Licensed under the MIT license.
// http://buzz.jaysalvat.com/
// ----------------------------------------------------------------------------
// Copyright (C) 2011 Jay Salvat
// http://jaysalvat.com/
// ----------------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files ( the "Software" ), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ----------------------------------------------------------------------------

var buzz = {
    defaults : {
        autoplay : false,
        duration : 5000,
        formats : [],
        loop : false,
        placeholder : '--',
        preload : 'metadata',
        volume : 80
    },
    types : {
        'mp3' : 'audio/mpeg',
        'ogg' : 'audio/ogg',
        'wav' : 'audio/wav',
        'aac' : 'audio/aac',
        'm4a' : 'audio/x-m4a'
    },
    sounds : [],
    el : document.createElement('audio'),

    sound : function(src, options) {
        options = options || {};

        var pid = 0, events = [], eventsOnce = {}, supported = buzz.isSupported();

        // publics
        this.load = function() {
            if(!supported) {
                return this;
            }

            this.sound.load();
            return this;
        };

        this.play = function() {
            if(!supported) {
                return this;
            }

            this.sound.play();
            return this;
        };

        this.togglePlay = function() {
            if(!supported) {
                return this;
            }

            if(this.sound.paused) {
                this.sound.play();
            } else {
                this.sound.pause();
            }
            return this;
        };

        this.pause = function() {
            if(!supported) {
                return this;
            }

            this.sound.pause();
            return this;
        };

        this.isPaused = function() {
            if(!supported) {
                return null;
            }

            return this.sound.paused;
        };

        this.stop = function() {
            if(!supported) {
                return this;
            }

            this.setTime(this.getDuration());
            this.sound.pause();
            return this;
        };

        this.isEnded = function() {
            if(!supported) {
                return null;
            }

            return this.sound.ended;
        };

        this.loop = function() {
            if(!supported) {
                return this;
            }

            this.sound.loop = 'loop';
            this.bind('ended.buzzloop', function() {
                this.currentTime = 0;
                this.play();
            });
            return this;
        };

        this.unloop = function() {
            if(!supported) {
                return this;
            }

            this.sound.removeAttribute('loop');
            this.unbind('ended.buzzloop');
            return this;
        };

        this.mute = function() {
            if(!supported) {
                return this;
            }

            this.sound.muted = true;
            return this;
        };

        this.unmute = function() {
            if(!supported) {
                return this;
            }

            this.sound.muted = false;
            return this;
        };

        this.toggleMute = function() {
            if(!supported) {
                return this;
            }

            this.sound.muted = !this.sound.muted;
            return this;
        };

        this.isMuted = function() {
            if(!supported) {
                return null;
            }

            return this.sound.muted;
        };

        this.setVolume = function(volume) {
            if(!supported) {
                return this;
            }

            if(volume < 0) {
                volume = 0;
            }
            if(volume > 100) {
                volume = 100;
            }

            this.volume = volume;
            this.sound.volume = volume / 100;
            return this;
        };

        this.getVolume = function() {
            if(!supported) {
                return this;
            }

            return this.volume;
        };

        this.increaseVolume = function(value) {
            return this.setVolume(this.volume + (value || 1 ));
        };

        this.decreaseVolume = function(value) {
            return this.setVolume(this.volume - (value || 1 ));
        };

        this.setTime = function(time) {
            if(!supported) {
                return this;
            }

            this.whenReady(function() {
                this.sound.currentTime = time;
            });
            return this;
        };

        this.getTime = function() {
            if(!supported) {
                return null;
            }

            var time = Math.round(this.sound.currentTime * 100) / 100;
            return isNaN(time) ? buzz.defaults.placeholder : time;
        };

        this.setPercent = function(percent) {
            if(!supported) {
                return this;
            }

            return this.setTime(buzz.fromPercent(percent, this.sound.duration));
        };

        this.getPercent = function() {
            if(!supported) {
                return null;
            }

            var percent = Math.round(buzz.toPercent(this.sound.currentTime, this.sound.duration));
            return isNaN(percent) ? buzz.defaults.placeholder : percent;
        };

        this.setSpeed = function(duration) {
            if(!supported) {
                return this;
            }

            this.sound.playbackRate = duration;
        };

        this.getSpeed = function() {
            if(!supported) {
                return null;
            }

            return this.sound.playbackRate;
        };

        this.getDuration = function() {
            if(!supported) {
                return null;
            }

            var duration = Math.round(this.sound.duration * 100) / 100;
            return isNaN(duration) ? buzz.defaults.placeholder : duration;
        };

        this.getPlayed = function() {
            if(!supported) {
                return null;
            }

            return timerangeToArray(this.sound.played);
        };

        this.getBuffered = function() {
            if(!supported) {
                return null;
            }

            return timerangeToArray(this.sound.buffered);
        };

        this.getSeekable = function() {
            if(!supported) {
                return null;
            }

            return timerangeToArray(this.sound.seekable);
        };

        this.getErrorCode = function() {
            if(supported && this.sound.error) {
                return this.sound.error.code;
            }
            return 0;
        };

        this.getErrorMessage = function() {
            if(!supported) {
                return null;
            }

            switch( this.getErrorCode() ) {
                case 1:
                    return 'MEDIA_ERR_ABORTED';
                case 2:
                    return 'MEDIA_ERR_NETWORK';
                case 3:
                    return 'MEDIA_ERR_DECODE';
                case 4:
                    return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
                default:
                    return null;
            }
        };

        this.getStateCode = function() {
            if(!supported) {
                return null;
            }

            return this.sound.readyState;
        };

        this.getStateMessage = function() {
            if(!supported) {
                return null;
            }

            switch( this.getStateCode() ) {
                case 0:
                    return 'HAVE_NOTHING';
                case 1:
                    return 'HAVE_METADATA';
                case 2:
                    return 'HAVE_CURRENT_DATA';
                case 3:
                    return 'HAVE_FUTURE_DATA';
                case 4:
                    return 'HAVE_ENOUGH_DATA';
                default:
                    return null;
            }
        };

        this.getNetworkStateCode = function() {
            if(!supported) {
                return null;
            }

            return this.sound.networkState;
        };

        this.getNetworkStateMessage = function() {
            if(!supported) {
                return null;
            }

            switch( this.getNetworkStateCode() ) {
                case 0:
                    return 'NETWORK_EMPTY';
                case 1:
                    return 'NETWORK_IDLE';
                case 2:
                    return 'NETWORK_LOADING';
                case 3:
                    return 'NETWORK_NO_SOURCE';
                default:
                    return null;
            }
        };

        this.set = function(key, value) {
            if(!supported) {
                return this;
            }

            this.sound[key] = value;
            return this;
        };

        this.get = function(key) {
            if(!supported) {
                return null;
            }

            return key ? this.sound[key] : this.sound;
        };

        this.bind = function(types, func) {
            if(!supported) {
                return this;
            }
            types = types.split(' ');

            var that = this, efunc = function(e) {
                func.call(that, e);
            };
            for(var t = 0; t < types.length; t++) {
                var type = types[t], idx = type;
                type = idx.split( '.' )[0];

                events.push({
                    idx : idx,
                    func : efunc
                });
                this.sound.addEventListener(type, efunc, true);
            }
            return this;
        };

        this.unbind = function(types) {
            if(!supported) {
                return this;
            }
            types = types.split(' ');

            for(var t = 0; t < types.length; t++) {
                var idx = types[t], type = idx.split( '.' )[0];

                for(var i = 0; i < events.length; i++) {
                    var namespace = events[i].idx.split('.');
                    if(events[i].idx == idx || (namespace[1] && namespace[1] == idx.replace('.', '') )) {
                        this.sound.removeEventListener(type, events[i].func, true);
                        delete events[i];
                    }
                }
            }
            return this;
        };

        this.bindOnce = function(type, func) {
            if(!supported) {
                return this;
            }

            var that = this;

            eventsOnce[pid++] = false;
            this.bind(pid + type, function() {
                if(!eventsOnce[pid]) {
                    eventsOnce[pid] = true;
                    func.call(that);
                }
                that.unbind(pid + type);
            });
        };

        this.trigger = function(types) {
            if(!supported) {
                return this;
            }
            types = types.split(' ');

            for(var t = 0; t < types.length; t++) {
                var idx = types[t];

                for(var i = 0; i < events.length; i++) {
                    var eventType = events[i].idx.split('.');
                    if(events[i].idx == idx || (eventType[0] && eventType[0] == idx.replace('.', '') )) {
                        var evt = document.createEvent('HTMLEvents');
                        evt.initEvent(eventType[0], false, true);
                        this.sound.dispatchEvent(evt);
                    }
                }
            }
            return this;
        };

        this.fadeTo = function(to, duration, callback) {
            if(!supported) {
                return this;
            }

            if( duration instanceof Function) {
                callback = duration;
                duration = buzz.defaults.duration;
            } else {
                duration = duration || buzz.defaults.duration;
            }

            var from = this.volume, delay = duration / Math.abs(from - to), that = this;
            this.play();

            function doFade() {
                setTimeout(function() {
                    if(from < to && that.volume < to) {
                        that.setVolume(that.volume += 1);
                        doFade();
                    } else if(from > to && that.volume > to) {
                        that.setVolume(that.volume -= 1);
                        doFade();
                    } else if( callback instanceof Function) {
                        callback.apply(that);
                    }
                }, delay);
            }


            this.whenReady(function() {
                doFade();
            });
            return this;
        };

        this.fadeIn = function(duration, callback) {
            if(!supported) {
                return this;
            }

            return this.setVolume(0).fadeTo(100, duration, callback);
        };

        this.fadeOut = function(duration, callback) {
            if(!supported) {
                return this;
            }

            return this.fadeTo(0, duration, callback);
        };

        this.fadeWith = function(sound, duration) {
            if(!supported) {
                return this;
            }

            this.fadeOut(duration, function() {
                this.stop();
            });

            sound.play().fadeIn(duration);

            return this;
        };

        this.whenReady = function(func) {
            if(!supported) {
                return null;
            }

            var that = this;
            if(this.sound.readyState === 0) {
                this.bind('canplay.buzzwhenready', function() {
                    func.call(that);
                });
            } else {
                func.call(that);
            }
        };
        // privates
        function timerangeToArray(timeRange) {
            var array = [], length = timeRange.length - 1;

            for(var i = 0; i <= length; i++) {
                array.push({
                    start : timeRange.start(length),
                    end : timeRange.end(length)
                });
            }
            return array;
        }

        function getExt(filename) {
            return filename.split('.').pop();
        }

        function addSource(sound, src) {
            var source = document.createElement('source');
            source.src = src;
            if(buzz.types[ getExt(src)]) {
                source.type = buzz.types[ getExt(src)];
            }
            sound.appendChild(source);
        }

        // init
        if(supported) {

            for(var i in buzz.defaults ) {
                if(buzz.defaults.hasOwnProperty(i)) {
                    options[i] = options[i] || buzz.defaults[i];
                }
            }

            this.sound = document.createElement('audio');

            if( src instanceof Array) {
                for(var j in src ) {
                    if(src.hasOwnProperty(j)) {
                        addSource(this.sound, src[j]);
                    }
                }
            } else if(options.formats.length) {
                for(var k in options.formats ) {
                    if(options.formats.hasOwnProperty(k)) {
                        addSource(this.sound, src + '.' + options.formats[k]);
                    }
                }
            } else {
                addSource(this.sound, src);
            }

            if(options.loop) {
                this.loop();
            }

            if(options.autoplay) {
                this.sound.autoplay = 'autoplay';
            }

            if(options.preload === true) {
                this.sound.preload = 'auto';
            } else if(options.preload === false) {
                this.sound.preload = 'none';
            } else {
                this.sound.preload = options.preload;
            }

            this.setVolume(options.volume);

            buzz.sounds.push(this);
        }
    },
    group : function(sounds) {
        sounds = argsToArray(sounds, arguments);

        // publics
        this.getSounds = function() {
            return sounds;
        };

        this.add = function(soundArray) {
            soundArray = argsToArray(soundArray, arguments);
            for(var a = 0; a < soundArray.length; a++) {
                sounds.push(soundArray[a]);
            }
        };

        this.remove = function(soundArray) {
            soundArray = argsToArray(soundArray, arguments);
            for(var a = 0; a < soundArray.length; a++) {
                for(var i = 0; i < sounds.length; i++) {
                    if(sounds[i] == soundArray[a]) {
                        delete sounds[i];
                        break;
                    }
                }
            }
        };

        this.load = function() {
            fn('load');
            return this;
        };

        this.play = function() {
            fn('play');
            return this;
        };

        this.togglePlay = function() {
            fn('togglePlay');
            return this;
        };

        this.pause = function(time) {
            fn('pause', time);
            return this;
        };

        this.stop = function() {
            fn('stop');
            return this;
        };

        this.mute = function() {
            fn('mute');
            return this;
        };

        this.unmute = function() {
            fn('unmute');
            return this;
        };

        this.toggleMute = function() {
            fn('toggleMute');
            return this;
        };

        this.setVolume = function(volume) {
            fn('setVolume', volume);
            return this;
        };

        this.increaseVolume = function(value) {
            fn('increaseVolume', value);
            return this;
        };

        this.decreaseVolume = function(value) {
            fn('decreaseVolume', value);
            return this;
        };

        this.loop = function() {
            fn('loop');
            return this;
        };

        this.unloop = function() {
            fn('unloop');
            return this;
        };

        this.setTime = function(time) {
            fn('setTime', time);
            return this;
        };

        this.setduration = function(duration) {
            fn('setduration', duration);
            return this;
        };

        this.set = function(key, value) {
            fn('set', key, value);
            return this;
        };

        this.bind = function(type, func) {
            fn('bind', type, func);
            return this;
        };

        this.unbind = function(type) {
            fn('unbind', type);
            return this;
        };

        this.bindOnce = function(type, func) {
            fn('bindOnce', type, func);
            return this;
        };

        this.trigger = function(type) {
            fn('trigger', type);
            return this;
        };

        this.fade = function(from, to, duration, callback) {
            fn('fade', from, to, duration, callback);
            return this;
        };

        this.fadeIn = function(duration, callback) {
            fn('fadeIn', duration, callback);
            return this;
        };

        this.fadeOut = function(duration, callback) {
            fn('fadeOut', duration, callback);
            return this;
        };
        // privates
        function fn() {
            var args = argsToArray(null, arguments), func = args.shift();

            for(var i = 0; i < sounds.length; i++) {
                sounds[ i ][func].apply(sounds[i], args);
            }
        }

        function argsToArray(array, args) {
            return ( array instanceof Array ) ? array : Array.prototype.slice.call(args);
        }

    },
    all : function() {
        return new buzz.group(buzz.sounds);
    },
    isSupported : function() {
        return !!buzz.el.canPlayType;
    },
    isOGGSupported : function() {
        return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/ogg; codecs="vorbis"');
    },
    isWAVSupported : function() {
        return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/wav; codecs="1"');
    },
    isMP3Supported : function() {
        return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/mpeg;');
    },
    isAACSupported : function() {
        return !!buzz.el.canPlayType && (buzz.el.canPlayType('audio/x-m4a;') || buzz.el.canPlayType('audio/aac;') );
    },
    toTimer : function(time, withHours) {
        var h, m, s;
        h = Math.floor(time / 3600);
        h = isNaN(h) ? '--' : (h >= 10 ) ? h : '0' + h;
        m = withHours ? Math.floor(time / 60 % 60) : Math.floor(time / 60);
        m = isNaN(m) ? '--' : (m >= 10 ) ? m : '0' + m;
        s = Math.floor(time % 60);
        s = isNaN(s) ? '--' : (s >= 10 ) ? s : '0' + s;
        return withHours ? h + ':' + m + ':' + s : m + ':' + s;
    },
    fromTimer : function(time) {
        var splits = time.toString().split(':');
        if(splits && splits.length == 3) {
            time = (parseInt(splits[0], 10) * 3600 ) + (parseInt(splits[1], 10) * 60 ) + parseInt(splits[2], 10);
        }
        if(splits && splits.length == 2) {
            time = (parseInt(splits[0], 10) * 60 ) + parseInt(splits[1], 10);
        }
        return time;
    },
    toPercent : function(value, total, decimal) {
        var r = Math.pow(10, decimal || 0);

        return Math.round(((value * 100 ) / total ) * r) / r;
    },
    fromPercent : function(percent, total, decimal) {
        var r = Math.pow(10, decimal || 0);

        return Math.round(((total / 100 ) * percent ) * r) / r;
    }
};
