$(function() {

    /**
     * Ws is a wrapper of the WebSocket API
     * @class Ws
     */
    var Ws = function(options, managerOptions) {
        options = options || {};
        managerOptions = managerOptions || {};

        if (!options.url)
            throw "Cannot open a socket without a url";

        //@TODO: add try catch & retry
        this.onMessageCallbacks = [];
        this.socket = new WebSocket(options.url);
        this.url = options.url;
        this.maxTry = 4;
        this.recoTry = 0;
    };

    /**
     * @method init
     */
    Ws.prototype.init = function(fn) {
        var self = this;
        this.init = true;
        this.socket.onopen = function() {
            self._onOpen(this);
            if (self.init && typeof fn === 'function') {
                //Should be called only once
                fn(this);
            }
            self.init = false;
        };

        this.socket.onmessage = function(e) {
            self.onMessage(e);
        };

        this.socket.onclose = function() {
            self._onClose();
        };

        this.socket.onerror = function(error) {
            self._onError(error);
        };
    };

    /**
     * @method _onOpen
     * @param {Object} e
     * @private
     */
    Ws.prototype._onOpen = function(e) {
        console.log(e);
        this.recoTry = 0;
    };

    /**
     * On message call message callbacks
     * @method onMessage
     * @param {Object} e
     */
    Ws.prototype.onMessage = function(e) {
        //@TODO: is json?
        var message = $.parseJSON(e.data);
        for (var i = 0, length = this.onMessageCallbacks.length; i < length; i++) {
            var cb = this.onMessageCallbacks[i];
            if (typeof cb === 'function') {
                cb(message);
            }
        }
    };

    /**
     * @method _onError
     * @param {Object} e
     * @private
     */
    Ws.prototype._onError = function(e) {
        console.log(e);
    };

    /**
     * @method _onClose
     * @param {Object} e
     * @private
     */
    Ws.prototype._onClose = function(e) {
        console.log(e);
        //try to reco?
        if (this.maxTry > this.recoTry) {
            return;
        }
        this.socket = new WebSocket(this.url);
        this.init();
        this.recoTry++;
    };

    /**
     * Send stringify & send message to the socket
     * @method sendMessage
     * @param message
     */
    Ws.prototype.sendMessage = function(message) {
        message = JSON.stringify(message);
        this.socket.send(message);
    };

    /**
     * @class PlayerControl
     */
    var PlayerControl = function(options) {
        options = options || {};
        if (!options.socket instanceof Ws)
            throw "You need to provide a socket instance";

        if (!options.element || !options.element.length)
            throw "Element not found";

        this._playing = false;
        this._vClicked = false;
        this._progDragged = false;
        this._progClicked = false;
        this.element = options.element;
        this.socket = options.socket;
        this.duration = 0;
        this.currentTime = 0;
        this.volume = 0.5;
        this._previousVolume = 1;
        this.timeInterval = null;
        this.enableInterval = false;
        this.volumeStyleMap = {
            0: 'mute',
            1: 'very-low',
            2: 'very-low',
            3: 'low',
            4: 'low',
            5: 'medium',
            6: 'medium',
            7: 'high',
            8: 'high',
            9: 'very-high',
            10: 'very-high'
        };
    };

    /**
     * @method init
     */
    PlayerControl.prototype.init = function() {
        //Add event listener
        var self = this;
        //Update time
        self.updateTime();
        var buttonHeight = self.element.find('.volume-button').height();
        var y = buttonHeight * this.volume * 10;
        self.updateVolume(y);
        var progWidth = this.element.find('.progress').width();
        var x = self.duration ? (progWidth / self.duration) * self.currentTime : 0;
        self.updateProgress(x);

        $(window).bind('mouseup', function(e) {
            self._progClicked = false;
            self._progDragged = false;
            self._vClicked = false;
            self._dragVolume = false;
        });

        // Shortcuts
        $(window).bind('keydown', function(e) {
            switch (e.which) {
                case 32:
                    e.preventDefault();
                    self.playPause();
                    break;
                case 37: // left
                    var time = self.currentTime - (self.duration / 100);
                    self.seekTo({
                        send: true,
                        currentTime: time
                    });
                    break;

                case 39: // right
                    var time = self.currentTime + (self.duration / 100);
                    self.seekTo({
                        send: true,
                        currentTime: time
                    });
                    break;

                case 38: // up
                    e.preventDefault();
                    var buttonHeight = self.element.find('.volume-button').height();
                    var y = buttonHeight * (self.volume + 0.1) * 10;
                    self.updateVolume(y);
                    break;

                case 40: // down
                    e.preventDefault();
                    var buttonHeight = self.element.find('.volume-button').height();
                    var y = buttonHeight * (self.volume - 0.1) * 10;
                    self.updateVolume(y);
                    break;

                default:
                    return; // exit this handler for other keys
            }

        });

        this.element.find('.play-pause').bind('click', function() {
            self.playPause();
        });

        this.element.find('.progress').bind('mousedown', function(e) {
            self.mousedownProgress(e);
        });

        this.element.bind('mousemove', function(e) {
            if (self._progClicked) {
                self.dragProgress(e);
            }
            if (self._vClicked) {
                self.dragVolume(e);
            }
        });

        this.element.find('.volume-bar-holder').bind('mousedown', function(e) {
            self._vClicked = true;
            var y = self.element.find('.volume-bar-holder').height() - (e.pageY - self.element.find('.volume-bar-holder').offset().top);
            self.updateVolume(y);
        });

        this.element.find('.volume-icon').bind('mousedown', function() {
            if (self.volume) {
                self._previousVolume = self.volume;
                self.volume = 0;
            } else {
                self.volume = self._previousVolume;
            }
            var buttonHeight = self.element.find('.volume-button').height();
            var y = buttonHeight * self.volume * 10;
            self.updateVolume(y);
        });

    };

    /**
     * @method updateVolume
     * @param {number} y
     */
    PlayerControl.prototype.updateVolume = function(y) {
        var buttonHeight = this.element.find('.volume-button').height();
        var barHolderHeight = this.element.find('.volume-bar-holder').height();
        if (y > barHolderHeight) {
            y = barHolderHeight;
        }
        this.element.find('.volume-bar').css({
            height: y + 'px'
        });
        //between 1 and 0
        this.volume = this.element.find('.volume-bar').height() / barHolderHeight;
        this.animateVolume();
    };

    /**
     * @method playPause
     */
    PlayerControl.prototype.playPause = function() {
        if (this.isEnded()) {
            return this.pause();
        }

        if (this._playing) {
            this.pause({
                send: true
            });
        }
        else {
            this.play({
                send: true
            });
        }
    };

    /**
     * @method animateVolume
     */
    PlayerControl.prototype.animateVolume = function() {
        var volumeIcon = this.element.find('.volume-icon');
        volumeIcon.removeClass().addClass('volume-icon v-' + this.volumeStyleMap[parseInt(this.volume * 10)]);
    };

    /**
     * @method getMouseProgressPosition
     * @param {Object} e - DOM event
     * @returns {number}
     */
    PlayerControl.prototype.getMouseProgressPosition = function(e) {
        return e.pageX - this.element.find('.progress').offset().left;
    };

    /**
     * @method mousedownProgress
     * @param {Object} e - DOM event
     */
    PlayerControl.prototype.mousedownProgress = function(e) {
        var progWidth = this.element.find('.progress').width();
        this._progClicked = true;
        var x = this.getMouseProgressPosition(e);

        this.currentTime = Math.round((x / progWidth) * this.duration);
        this.seekTo({
            send: true,
            currentTime: this.currentTime
        });
    };

    /**
     * @method dragProgress
     * @param {Object} e - DOM event
     */
    PlayerControl.prototype.dragProgress = function(e) {
        this._progDragged = true;
        var progMove = 0;
        var x = this.getMouseProgressPosition(e);
        var progWidth = this.element.find('.progress').width();
        if (this._playing && (this.currentTime < this.duration)) {
            this.play();
        }
        if (x <= 0) {
            progMove = 0;
            this.currentTime = 0;
        }
        else if (x > progWidth) {
            this.currentTime = this.duration;
            progMove = progWidth;
        }
        else {
            progMove = x;
            this.currentTime = Math.round((x / progWidth) * this.duration);
        }
        this.seekTo({
            send: true,
            currentTime: this.currentTime
        });
    };

    /**
     * @method dragVolume
     * @param {Object} e - DOM event
     */
    PlayerControl.prototype.dragVolume = function(e) {
        this._dragVolume = true;
        var volHeight = this.element.find('.volume-bar-holder').height();
        var y = volHeight - (e.pageY - this.element.find('.volume-bar-holder').offset().top);
        var volMove = 0;
        if (y <= 0) {
            volMove = 0;
        } else if (y > this.element.find('.volume-bar-holder').height() ||
            (y / volHeight) === 1) {
            volMove = volHeight;
        } else {
            volMove = y;
        }
        this.updateVolume(volMove);
    };

    PlayerControl.prototype.formatTime = function(ms) {
        var timeMatch = new Date(ms).toUTCString().match(/(\d\d:\d\d:\d\d)/);
        return timeMatch.length ? timeMatch[0].replace('00:', '') : '00:00';
    };

    /**
     * @method updateTime
     * @param {number} currentTime
     */
    PlayerControl.prototype.updateTime = function(currentTime) {
        var progWidth, cTime, tTime;
        progWidth = this.element.find('.progress').width();
        currentTime = currentTime ||
        Math.round(($('.progress-bar').width() / progWidth) * this.duration);
        cTime = this.formatTime(currentTime);
        tTime = this.formatTime(this.duration);
        this.element.find('.ctime').text(cTime);
        this.element.find('.ttime').text(tTime);
    };

    /**
     * @method updateProgress
     * @param {number} x
     */
    PlayerControl.prototype.updateProgress = function(x) {
        var buttonWidth = this.element.find('.progress-button').width();

        this.element.find('.progress-bar').css({
            width: x
        });

        this.element.find('.progress-button').css({
            left: x - (2 * buttonWidth) - (buttonWidth / 2) + 'px'
        });
    };

    /**
     * @method isEnded
     * @returns {boolean}
     */
    PlayerControl.prototype.isEnded = function() {
        return this.currentTime >= this.duration;
    };

    /**
     * @method play
     * @param {boolean} send
     */
    PlayerControl.prototype.play = function(options) {
        options = options || {};
        var self = this,
            send = options.send,
            currentTime = options.currentTime,
            delay = 1000,
            progWidth = this.element.find('.progress').width();

        //update currentTime
        this.currentTime = typeof currentTime !== 'undefined' ? currentTime : this.currentTime;

        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }
        //@TODO: use requestFrame instead

        this._playing = true;
        this.element
            .find('.play-pause')
            .addClass('pause')
            .removeClass('play');

        if (send) {
            this.socket.sendMessage({
                type: 'pause'
            });
        }

        if (!this.enableInterval) {
            return;
        }
        this.timeInterval = setInterval(function() {
            if (self.isEnded()) {
                self.currentTime = self.duration;
                self.pause();
                return clearInterval(this.timeInterval);
            }
            self.currentTime += 1;
            self.updateTime(self.currentTime);
            var x = (progWidth / self.duration) * self.currentTime;
            self.updateProgress(x);
        }, delay);
    };

    /**
     * @method pause
     * @param {boolean} send
     */
    PlayerControl.prototype.pause = function(options) {
        options = options || {};
        var send = options.send,
            currentTime = options.currentTime;
        //update currentTime
        this.currentTime = typeof currentTime !== 'undefined' ? currentTime : this.currentTime;

        if (this.timeInterval) {
            clearInterval(this.timeInterval);
        }

        this._playing = false;
        this.element
            .find('.play-pause')
            .addClass('play')
            .removeClass('pause');

        if (send) {
            this.socket.sendMessage({
                type: 'pause',
                currentTime: this.currentTime,
                media: {
                    id: this.id
                }
            });
        }
    };

    /**
     * @method seekTo
     * @param {boolean} send
     * @param {number} time
     */
    PlayerControl.prototype.seekTo = function(options) {
        options = options || {};
        var currentTime = options.currentTime,
            send = options.send;

        //@TODO: make a debounce when dragging & key
        var progWidth = this.element.find('.progress').width();

        if (currentTime < 0) {
            currentTime = 0;
        }
        if (currentTime > this.duration) {
            currentTime = this.duration;
        }

        this.currentTime = currentTime;

        if (this._playing) {
            this.play();
        }

        var x = (progWidth / this.duration) * this.currentTime;
        this.updateProgress(x);
        this.updateTime(this.currentTime);
        if (send) {
            this.socket.sendMessage({
                type: 'seekTo',
                currentTime: currentTime
            });
        }
    };

    /**
     * @method getPlaying
     */
    PlayerControl.prototype.getPlaying = function() {
        //Trigger playing event (from server)
        this.socket.sendMessage({
            type: 'playing'
        });
    };

    /**
     * @method playing
     * @param {Object} message
     */
    PlayerControl.prototype.playing = function(options) {
        if (!options) {
            return;
        }
        options = options || {};

        this.currentTime = options.currentTime;
        this.duration = options.duration;
        this.title = options.title;
        this.id = options.id;

        var titleElement = this.element.find('.title');
        titleElement.text(this.title);
        this.play();
    }

    PlayerControl.prototype.openURL = function(options) {
        options = options || {};
        this.socket.sendMessage({
            type: 'openURL',
            url: options.url
        });
    };

    /**
     * Instanciation of the Ws class
     */
    var URL = 'ws://' + location.host;

    var socket = new Ws({
        url: URL
    });

    var playerControl = new PlayerControl({
        socket: socket,
        element: $('.player-control')
    });

    playerControl.init();
    socket.init(function() {
        playerControl.getPlaying();
    });

    /**
     * Map which method is allowed by event type
     */
    var TYPE_MAP = {
        play: function(message) {
            playerControl.play({
                currentTime: message.currentTime
            });
        },
        pause: function(message) {
            playerControl.pause({
                currentTime: message.currentTime
            });
        },
        playing: function(message) {
            playerControl.playing({
                currentTime: message.currentTime,
                duration: message.media.duration,
                title: message.media.title,
                id: message.media.id
            });
        },
        seekTo: function(message) {
            playerControl.seekTo({
                currentTime: message.currentTime
            });
        }
    };

    /**
     * Manage incoming messages
     */
    socket.onMessageCallbacks.push(function(message) {
        var key = 'type';
        var type = message[key];
        if (!type || typeof TYPE_MAP[type] !== 'function')
            return;
        TYPE_MAP[type](message);
    });

    $('form.open-url').on('submit', function(e) {
        e.preventDefault();
        var url = $(this).find('input').val();
        var localesURL = LOCALES ? (LOCALES.PLAYER_CONTROL ? LOCALES.PLAYER_CONTROL.URL : {}) : {};
        if (!url) {
            return displayMessage(localesURL.EMPTY);
        } else if (!isURL(url)) {
            return displayMessage(localesURL.NOT_VALID);
        }
        displayMessage(localesURL.SENT_SUCCESSFULLY);
        playerControl.openURL({
            url: url
        });
        //clear the form
        $(this).find('input').val('');
    });

    /**
     * Check if a given string is a URL
     * Regex from https://gist.github.com/searls/1033143
     * @param {string} str
     * @returns {boolean}
     */
    function isURL(str) {
        var p = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
        return p.test(str);
    }
    //Display message to the user
    var TIMEOUT = null;
    var DELAY = 5000;
    function displayMessage(message) {
        if (!message) {
            return;
        }
        $('.display-message').addClass('show');
        $('.display-message').text(message);
        if (TIMEOUT) {
            clearTimeout(TIMEOUT);
        }
        TIMEOUT = setTimeout(function() {
            clearMessage();
        }, DELAY);
    }

    function clearMessage() {
        if (TIMEOUT) {
            clearTimeout(TIMEOUT);
        }
        $('.display-message').removeClass('show');
    }

});
