// vim: et:ts=4:sw=4:sts=4:ft=javascript

'use strict';

/*@
Ox.VideoPlayer <f> Generic Video Player
    (options, self) -> <o> Video Player
    options <o> Options
        annotations <[]> Array of annotations
            id <s> Optional id
            in <n> In point (sec)
            out <n> Out point (sec)
            text <s> Text
        censored <a|[]> Array of censored ranges
        controlsBottom <[s]|[]> Bottom controls, from left to right
            Can be 'close', fullscreen', 'scale', 'title', 'find', 'open',
            'play', 'playInToOut', 'previous', 'next', 'mute', 'volume', 'size',
            'timeline', 'space', 'position', 'settings'. The 'space' control is
            empty space that separates left-aligned from right-aligned controls.
        controlsTop <[s]|[]> Top controls, from left to right
        duration <n|-1> Duration (sec)
        enableDownload <b|false> If true, enable download
        enableFind <b|false> If true, enable find
        enableFullscreen <b|false> If true, enable fullscreen
        enableKeyboard <b|false> If true, enable keyboard controls
        enableMouse <b|false> If true, click toggles paused and drag changes position
        externalControls <b|false> If true, controls are outside the video
        find <s|''> Query string
        focus <s|'click'> focus on 'click', 'load' or 'mouseover'
        format <s|''> video format (like 'webm')
        fps <n|25> Frames per second
        fullscreen <b|false> If true, video is in fullscreen
        height <n|144> Height in px (excluding external controls)
        in <n> In point (sec)
        keepIconVisible <b|false> If true, play icon stays visible after mouseleave
        keepLargeTimelineVisible <b|false> If true, large timeline stays visible after mouseleave
        keepLogoVisible <b|false> If true, logo stays visible after mouseleave
        logo <s|''> Logo image URL
        logoLink <s|''> Logo link URL
        logoTitle <s|''> Text for Logo tooltip // fixme: shouldn't this be logoTooltip then?s
        muted <b|false> If true, video is muted
        paused <b|false> If true, video is paused
        playInToOut <b|false> If true, video plays only from in to out
        position <n|0> Initial position (sec)
        poster <s|''> Poster URL
        posterFrame <n|-1> Position of poster frame (sec)
        preload <s|'auto'> 'auto', 'metadata' or 'none'
        out <n> Out point (sec)
        resolution <n|0> resolution
        rewind <b|false> If true, video will rewind when ended
        scaleToFill <b|false> If true, scale to fill (otherwise, scale to fit)
        showControlsOnLoad <b|false> If true, show controls on load
        showFind <b|false> If true, show find input
        showHours <b|false> If true, don't show hours for videos shorter than one hour
        showIcon <b|false> If true, show play icon
        showIconOnLoad <b|false> If true, show icon on load
        showLargeTimeline <b|false> If true, show large timeline
        showMilliseconds <n|0> Number of decimals to show
        showMarkers <b|false> If true, show in/out/poster markers
        showProgress <|false> If true, show buffering progress
        sizeIsLarge <b|false> If true, initial state of the size control is large
        subtitles <s|[o]|[]> URL or SRT or array of subtitles
            id <s> Optional id
            in <n> In point (sec)
            out <n> Out point (sec)
            text <s> Text
        timeline <s> Timeline image URL
        title <s|''> Video title
        type <s|'play'> 'play', 'in' or 'out'
        video <s|[s]|o|''> Video URL
            String or array of strings ([part1, part2, ...]) or object
            ({resolution: url, ...} or {resolution: [part1, part2, ...], ...})
        volume <n|1> Volume (0-1)
        width <n|256> Width in px
    self <o> shared private variable
@*/

Ox.VideoPlayer = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            annotations: [],
            brightness: 1,
            censored: [],
            controlsBottom: [],
            controlsTop: [],
            duration: 0,
            enableDownload: false,
            enableFind: false,
            enableFullscreen: false,
            enableKeyboard: false,
            enableMouse: false,
            enablePosition: false,
            enableSubtitles: false,
            enableTimeline: false,
            externalControls: false,
            find: '',
            focus: 'click',
            format: '',
            fps: 25,
            fullscreen: false,
            height: 144,
            'in': 0,
            keepIconVisible: false,
            keepLargeTimelineVisible: false,
            keepLogoVisible: false,
            logo: '',
            logoLink: '',
            logoTitle: '',
            muted: false,
            paused: false,
            playInToOut: false,
            position: 0,
            poster: '',
            posterFrame: -1,
            preload: 'auto',
            out: 0,
            resolution: 0,
            rewind: false,
            scaleToFill: false,
            showControlsOnLoad: false,
            showFind: false,
            showHours: false,
            showIcon: false,
            showIconOnLoad: false,
            showLargeTimeline: false,
            showMarkers: false,
            showMilliseconds: 0,
            showProgress: false,
            sizeIsLarge: false,
            subtitles: [],
            timeline: '',
            title: '',
            type: 'play',
            video: '',
            volume: 1,
            width: 256
        })
        .options(options || {})
        .addClass('OxVideoPlayer');

    Ox.Log('VIDEO', 'VIDEO PLAYER OPTIONS', self.options)

    Ox.UI.$window.bind({
        resize: function() {
            self.options.fullscreen && setSizes();
        }
    });

    if (Ox.isEmpty(self.options.annotations)) {
        self.options.annotations = self.options.subtitles;
    }

    if (Ox.isObject(self.options.video)) {
        self.resolutions = Ox.sort(Object.keys(self.options.video));
        if (!(self.options.resolution in self.options.video)) {
            self.options.resolution = self.resolutions[0];
        }
        self.video = self.options.video[self.options.resolution];
    } else {
        self.isPlaylist = Ox.isFunction(self.options.video);
        self.video = self.options.video;
    }

    if (self.options.playInToOut) {
        self['in'] = self.options['in'];
        self.out = self.options.out;
        self.options.duration = self.out - self['in'];
    } else {
        self['in'] = 0;
        self.out = self.options.duration || 86399; // fixme: ugly
    }
    self.options.position = Ox.limit(self.options.position, self['in'], self.out);

    self.millisecondsPerFrame = 1000 / self.options.fps;
    self.secondsPerFrame = 1 / self.options.fps;
    self.barHeight = 16;
    self.width = self.options.fullscreen ? window.innerWidth : self.options.width;
    self.height = self.options.fullscreen ? window.innerHeight : self.options.height;
    self.videoWidth = self.options.width;
    self.videoHeight = self.options.height;
    self.results = [];

    /* 
    ----------------------------------------------------------------------------
    Keyboard
    ----------------------------------------------------------------------------
    */

    if (self.options.enableKeyboard) {
        that.bindEvent({
            key_0: toggleMuted,
            key_1: toggleScale,
            key_equal: function() {
                changeVolume(0.1);
            },
            key_escape: hideControlMenus,
            key_f: focusFind,
            key_g: function() {
                goToNextResult(1);
            },
            key_left: function() {
                setPosition(self.options.position - self.secondsPerFrame);
            },
            key_minus: function() {
                changeVolume(-0.1);
            },
            key_p: function() {
                playInToOut();
            },
            key_right: function() {
                setPosition(self.options.position + self.secondsPerFrame);
            },
            key_shift_f: function() {
                self.options.enableFullscreen && toggleFullscreen();
            },
            key_shift_g: function() {
                goToNextResult(-1);
            },
            key_shift_left: function() {
                setPosition(self.options.position - 1);
            },
            key_shift_right: function() {
                setPosition(self.options.position + 1);
            },
            key_space: togglePaused
        });
        if (self.options.focus == 'mouseenter') {
            that.bind({
                mouseenter: function() {
                    if (!self.inputHasFocus) {
                        that.gainFocus();
                    }
                },
                mouseleave: function() {
                    that.loseFocus();
                }
            });
        } else {
            that.bind({
                click: function() {
                    var focused = Ox.Focus.focused();
                    if (
                        !focused
                        || !Ox.UI.elements[focused].is('.OxInput')
                    ) {
                        that.gainFocus();
                    }
                }
            });
        }
    }

    /* 
    ----------------------------------------------------------------------------
    Mouse
    ----------------------------------------------------------------------------
    */

    if (
        (!self.options.externalControls &&
        (self.options.controlsTop.length || self.options.controlsBottom.length)) ||
        self.options.showIcon
    ) {
        that.bind({
            mouseenter: function() {
                showControls();
                self.mouseHasLeft = false;
                //Ox.Log('Video', 'MOUSE HAS ENTERED')
            },
            mouseleave: function() {
                hideControls();
                self.mouseHasLeft = true;
                //Ox.Log('Video', 'MOUSE HAS LEFT')
            }
        });
    }

    /* 
    ----------------------------------------------------------------------------
    Video
    ----------------------------------------------------------------------------
    */

    self.$videoContainer = Ox.Element()
        .addClass('OxVideoContainer')
        .css({
            top: self.options.externalControls && self.options.controlsTop.length ? '16px' : 0,
        })
        .appendTo(that.$element)

    if (self.options.type == 'play') {

        self.options.enableMouse && self.$videoContainer.bindEvent({
            anyclick: function(e) {
                if (!$(e.target).is('.OxLogo')) {
                    togglePaused();
                }
            },
            dragstart: dragstart,
            drag: drag,
            dragend: dragend
        });

        self.$video = Ox.VideoElement(
                // autoplay seems to always play from the beginning,
                // and poster doesn't seem to work at all
                Ox.extend({
                    preload: self.options.preload,
                    src: self.video,
                }, !self.options.paused && !self.options.playInToOut ? {
                    /*autoplay: 'autoplay'*/
                } : {}/*, self.options.poster ? {
                    poster: self.options.poster
                } : {}*/)
            )
            .bindEvent(Ox.extend({
                ended: ended,
                loadedmetadata: loadedmetadata,
                pointschange: pointschange,
                seeked: seeked,
                seeking: seeking,
                sizechange: sizechange
            }, self.options.progress ? {
                progress: progress
            } : {}))
            .appendTo(self.$videoContainer);

        self.$video.$element.css({position: 'absolute'});

    } else {

        self.options.enableMouse && self.$videoContainer.bind({
            click: function(e) {
                if (!$(e.target).is('.OxLogo')) {
                    goToPoint();
                }
            }
        });

        self.$video = $('<div>')
            .appendTo(self.$videoContainer);
        self.$image = $('<img>')
            .attr({
                src: Ox.UI.PATH + 'png/transparent.png'
            })
            .css({
                position: 'absolute',
                width: '100%',
                height: '100%'
            })
            .appendTo(self.$video)
        self.$brightness = $('<div>')
            .css({
                position: 'absolute',
                width: '100%',
                height: '100%',
                background: 'rgb(0, 0, 0)',
                opacity: 1 - self.options.brightness
            })
            .appendTo(self.$video);

    }

    /* 
    ----------------------------------------------------------------------------
    Poster
    ----------------------------------------------------------------------------
    */

    if (self.options.poster) {
        self.$poster = $('<img>')
            .addClass('OxPoster')
            .attr({
                src: self.options.poster
            })
            .appendTo(self.$videoContainer);
        self.posterIsVisible = true;
    }

    /* 
    ----------------------------------------------------------------------------
    Logo
    ----------------------------------------------------------------------------
    */

    if (self.options.logo) {
        self.$logo = $('<img>')
            .addClass('OxLogo')
            .attr({
                src: self.options.logo
            })
            .css({
                cursor: self.options.logoLink ? 'pointer' : 'default'
            })
            .appendTo(self.$videoContainer);
        if (self.options.logoTitle) {
            self.$logoTooltip = Ox.Tooltip({
                title: self.options.logoTitle
            });
        }
    }

    /* 
    ----------------------------------------------------------------------------
    Icons
    ----------------------------------------------------------------------------
    */

    self.$loadingIcon = $('<img>')
        .addClass('OxLoadingIcon OxVideo')
        .attr({
            src: Ox.UI.getImageURL('symbolLoadingAnimated', 'modern')
        })
        .appendTo(self.$videoContainer);

    if (self.options.showIcon || self.options.showIconOnLoad) {
        self.$playIcon = $('<img>')
            .addClass('OxPlayIcon OxVideo')
            .attr({
                src: Ox.UI.getImageURL('symbol' + (
                    self.options.paused ? 'Play' : 'Pause'
                ), 'modern')
            })
            .appendTo(self.$videoContainer);
        if (self.options.showIcon) {
            self.$playIcon.addClass('OxInterface');
        }
        if (self.options.showIconOnLoad) {
            self.iconIsVisible = true;
        }
    }

    if (self.options.censored.length) {
        self.$copyrightIcon = Ox.Element({
                element: '<img>',
                // fixme: make this configurable
                tooltip: 'This part of this video is not available in<br/>your part of your country. Sorry for that.'
            })
            .addClass('OxCopyrightIcon OxVideo')
            .attr({
                src: Ox.UI.getImageURL('symbolNoCopyright', 'modern'),
            })
            .hide()
            .appendTo(self.$videoContainer);
    }

    /* 
    ----------------------------------------------------------------------------
    Markers
    ----------------------------------------------------------------------------
    */

    if (self.options.showMarkers) {

        self.$posterMarker = {};
        ['left', 'center', 'right'].forEach(function(position) {
            var titleCase = Ox.toTitleCase(position);
            self.$posterMarker[position] = $('<div>')
                .addClass('OxPosterMarker OxPosterMarker' + titleCase)
                .appendTo(self.$videoContainer);
        });

        self.$pointMarker = {};
        ['in', 'out'].forEach(function(point) {
            self.$pointMarker[point] = {};
            ['top', 'bottom'].forEach(function(edge) {
                var titleCase = Ox.toTitleCase(point) + Ox.toTitleCase(edge);
                self.$pointMarker[point][edge] = $('<img>')
                    .addClass('OxPointMarker OxPointMarker' + titleCase)
                    .attr({
                        src: Ox.UI.getImageURL('marker' + titleCase)
                    })
                    .appendTo(self.$videoContainer);
            });
        });

    }

    /* 
    ----------------------------------------------------------------------------
    Subtitles
    ----------------------------------------------------------------------------
    */

    if (self.options.subtitles.length || true) { // FIXME
        self.$subtitle = $('<div>')
            .addClass('OxSubtitle')
            .appendTo(self.$videoContainer);
    }

    /* 
    ----------------------------------------------------------------------------
    Controls
    ----------------------------------------------------------------------------
    */

    ['top', 'bottom'].forEach(function(edge) {

        var titleCase = Ox.toTitleCase(edge);

        if (self.options['controls' + titleCase].length) {

            self['$controls' + titleCase] = Ox.Bar({
                size: self.barHeight
            })
            .addClass('OxControls' + (self.options.externalControls ? '' : ' OxOnScreen'))
            .css({
                opacity: self.options.externalControls ? 1 : 0
            })
            .css(edge, 0)
            .appendTo(that.$element);

            self.options['controls' + titleCase].forEach(function(control) {

                if (control == 'close') {

                    self.$closeButton = Ox.Button({
                        style: 'symbol',
                        title: 'close',
                        tooltip: 'Close',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            that.triggerEvent('close');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'find') {

                    self.$findButton = Ox.Button({
                        style: 'symbol',
                        title: 'find',
                        tooltip: 'Find',
                        type: 'image'
                    })
                    .bindEvent({
                        click: toggleFind
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'fullscreen') {

                    self.$fullscreenButton = Ox.Button({
                        style: 'symbol',
                        tooltip: ['Enter Fullscreen', 'Exit Fullscreen'],
                        type: 'image',
                        value: self.options.fullscreen ? 'shrink' : 'grow',
                        values: ['grow', 'shrink']
                    })
                    .bindEvent({
                        click: function() {
                            toggleFullscreen('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'goto') {

                    self.$setButton = Ox.Button({
                        style: 'symbol',
                        title: 'goTo' + Ox.toTitleCase(self.options.type),
                        tooltip: 'Go to ' + Ox.toTitleCase(self.options.type) + ' Point',
                        type: 'image'
                    })
                    .bindEvent({
                        click: goToPoint
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'mute') {

                    self.$muteButton = Ox.Button({
                        style: 'symbol',
                        tooltip: ['Mute', 'Unmute'],
                        type: 'image',
                        value: self.options.muted ? 'unmute' : 'mute',
                        values: ['mute', 'unmute']
                    })
                    .bindEvent({
                        click: function() {
                            toggleMuted('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'next') {

                    self.$nextClipButton = Ox.Button({
                        style: 'symbol',
                        title: 'arrowRight',
                        tooltip: 'Next',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            goToNextClip(1);
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'open') {

                    self.$openButton = Ox.Button({
                        style: 'symbol',
                        title: 'arrowRight',
                        tooltip: 'Open in ' + Ox.getObjectById(
                            pandora.site.itemViews, pandora.user.ui.videoView
                        ).title + ' View',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            that.triggerEvent('open');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'play') {

                    self.$playButton = Ox.Button({
                        style: 'symbol',
                        // FIXME: this is retarded, fix Ox.Button
                        tooltip: ['Play', 'Pause'],
                        type: 'image',
                        value: self.options.paused ? 'play' : 'pause',
                        values: ['play', 'pause']
                    })
                    .bindEvent({
                        click: function() {
                            togglePaused('button');
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'playInToOut') {

                    self.$playInToOutButton = Ox.Button({
                        style: 'symbol',
                        title: 'playInToOut',
                        tooltip: 'Play In to Out',
                        type: 'image'
                    })
                    .bindEvent({
                        click: playInToOut
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'position') {

                    self.positionWidth = 48 +
                        !!self.options.showMilliseconds * 2
                        + self.options.showMilliseconds * 6;

                    self.$position = Ox.Element({
                            tooltip: self.options.type == 'play' ? 'Position'
                                : self.options.type == 'in' ? 'In Point'
                                : 'Out Point'
                        })
                        .addClass('OxPosition')
                        .css({
                            width: (self.positionWidth - 4) + 'px',
                        })
                        .html(formatPosition())
                        .bind({
                            click: function() {
                                if (self.options.enablePosition) {
                                    if (self.options.type == 'play') {
                                        if (!self.options.paused) {
                                            self.playOnSubmit = true;
                                            togglePaused();
                                        } else if (self.playOnLoad) {
                                            // if clicked during resolution switch,
                                            // don't play on load
                                            self.playOnLoad = false;
                                            self.playOnSubmit = true;
                                        }
                                    }
                                    self.$position.hide();
                                    self.$positionInput
                                        .value(formatPosition())
                                        .show()
                                        .focusInput(false);
                                }
                            }
                        })
                        .appendTo(self['$controls' + titleCase]);

                    self.$positionInput = Ox.Input({
                            value: formatPosition(),
                            width: self.positionWidth
                        })
                        .addClass('OxPositionInput')
                        .bindEvent({
                            focus: function() {
                                self.inputHasFocus = true;
                            },
                            blur: function() {
                                self.inputHasFocus = false;
                                submitPositionInput();
                            },
                            submit: function() {
                                self.inputHasFocus = false;
                                submitPositionInput();
                            }
                        })
                        .appendTo(self['$controls' + titleCase].$element);

                    self.$positionInput.children('input').css({
                            width: (self.positionWidth - 6) + 'px',
                            fontSize: '9px'
                        });                

                } else if (control == 'previous') {

                    self.$previousClipButton = Ox.Button({
                        style: 'symbol',
                        title: 'arrowLeft',
                        tooltip: 'Previous',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            goToNextClip(-1);
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'scale') {

                    self.$scaleButton = Ox.Button({
                        style: 'symbol',
                        tooltip: ['Scale to Fill', 'Scale to Fit'],
                        type: 'image',
                        value: self.options.scaleToFill ? 'fit' : 'fill',
                        values: ['fill', 'fit']
                    })
                    .bindEvent('change', function() {
                        toggleScale('button');
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'set') {

                    self.$setButton = Ox.Button({
                        style: 'symbol',
                        title: 'set' + Ox.toTitleCase(self.options.type),
                        tooltip: 'Set ' + Ox.toTitleCase(self.options.type) + ' Point',
                        type: 'image'
                    })
                    .bindEvent({
                        click: setPoint
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'settings') {

                    self.$settingsButton = Ox.Button({
                        style: 'symbol',
                        title: 'set',
                        tooltip: 'Settings',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            self.$settings.toggle();
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                    self.$settings = renderSettings().appendTo(that.$element);

                } else if (control == 'size') {

                    self.$sizeButton = Ox.Button({
                        style: 'symbol',
                        tooltip: ['Larger', 'Smaller'],
                        type: 'image',
                        value: self.options.sizeIsLarge ? 'shrink' : 'grow',
                        values: ['grow', 'shrink']
                    })
                    .bindEvent('change', toggleSize)
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'space') {

                    self['$space' + titleCase] = $('<div>')
                        .html('&nbsp;') // fixme: ??
                        .appendTo(self['$controls' + titleCase].$element);

                } else if (control == 'timeline') {

                    /*
                    if (self.options.showProgress) {
                        self.$progress = $('<img>')
                            .attr({
                                src: getProgressImageURL()
                            })
                            .css({
                                float: 'left',
                                height: self.barHeight + 'px',
                            })
                            .appendTo(self.$timelineImages.$element);
                    }
                    */

                    if (self.options.duration) {
                        self.$timeline = getTimeline()
                    } else {
                        self.$timeline = Ox.Element()
                            .html('&nbsp;');
                    }
                    self.$timeline.appendTo(self['$controls' + titleCase]);

                } else if (control == 'title') {

                    self.$title = $('<div>')
                        .addClass('OxTitle')
                        .html(self.options.title)
                        .appendTo(self['$controls' + titleCase].$element);

                } else if (control == 'volume') {

                    self.$volumeButton = Ox.Button({
                        style: 'symbol',
                        title: getVolumeImage(),
                        tooltip: 'Volume',
                        type: 'image'
                    })
                    .bindEvent({
                        click: toggleVolume
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'zapHome') {
                    
                    self.$zapHomeButton = Ox.Button({
                        style: 'symbol',
                        title: 'up',
                        tooltip: 'Home Channel',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            that.triggerEvent('zap', {direction: 0});
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);

                } else if (control == 'zapNext') {

                    self.$zapNextButton = Ox.Button({
                        style: 'symbol',
                        title: 'right',
                        tooltip: 'Next Channel',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            that.triggerEvent('zap', {direction: 1});
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);
                    
                } else if (control == 'zapPrevious') {

                    self.$zapPreviousButton = Ox.Button({
                        style: 'symbol',
                        title: 'left',
                        tooltip: 'Previous Channel',
                        type: 'image'
                    })
                    .bindEvent({
                        click: function() {
                            that.triggerEvent('zap', {direction: -1});
                        }
                    })
                    .appendTo(self['$controls' + titleCase]);
                    
                }

            });

        }

    });

    /* 
    ----------------------------------------------------------------------------
    Find
    ----------------------------------------------------------------------------
    */

    if (self.options.enableFind) {

        self.$find = $('<div>')
            .addClass('OxControls OxFind')
            .css({
                top: self.options.controlsTop.length ? '16px' : 0
            })
            .appendTo(that.$element);

        self.$results = Ox.Element({
                tooltip: 'Results'
            })
            .addClass('OxResults')
            .html('0')
            .appendTo(self.$find);

        self.$previousResultButton = Ox.Button({
                disabled: true,
                style: 'symbol',
                title: 'arrowLeft',
                tooltip: 'Previous',
                type: 'image'
            })
            .bindEvent({
                click: function() {
                    goToNextResult(-1);
                }
            })
            .appendTo(self.$find);

        self.$nextResultButton = Ox.Button({
                disabled: true,
                style: 'symbol',
                title: 'arrowRight',
                tooltip: 'Next',
                type: 'image'
            })
            .bindEvent({
                click: function() {
                    goToNextResult(1);
                }
            })
            .appendTo(self.$find);

        self.$findInput = Ox.Input({
                changeOnKeypress: true,
                value: self.options.find
            })
            .bindEvent({
                blur: function() {
                    self.inputHasFocus = false;
                },
                focus: function() {
                    self.inputHasFocus = true;
                },
                change: function(data) {
                    submitFindInput(data.value, false);
                },
                submit: function(data) {
                    self.inputHasFocus = false;
                    submitFindInput(data.value, true);
                }
            })
            .appendTo(self.$find);

        self.$clearButton = Ox.Button({
                disabled: !self.options.find,
                style: 'symbol',
                title: 'delete',
                tooltip: 'Clear',
                type: 'image'
            })
            .bindEvent({
                click: function() {
                    self.options.find = '';
                    self.results = [];
                    self.$results.html('0');
                    self.$findInput.clearInput();
                    self.subtitle && setSubtitleText();
                    self.$timeline && self.$timeline.options({
                        find: self.options.find,
                        results: self.results
                    });
                    //setTimeout(self.$findInput.focusInput, 10);
                }
            })
            .appendTo(self.$find);

        self.$hideFindButton = Ox.Button({
                style: 'symbol',
                title: 'close',
                tooltip: 'Hide',
                type: 'image'
            })
            .bindEvent({
                click: toggleFind
            })
            .appendTo(self.$find);
      
    }

    /* 
    ----------------------------------------------------------------------------
    Volume
    ----------------------------------------------------------------------------
    */

    if (self.options.enableVolume || true) { // fixme

        self.$volume = $('<div>')
            .addClass('OxControls OxVolume')
            .css({
                bottom: self.options.controlsBottom.length ? '16px' : 0
            })
            .appendTo(that.$element);

        self.$hideVolumeButton = Ox.Button({
                style: 'symbol',
                title: 'close',
                tooltip: 'Hide',
                type: 'image'
            })
            .bindEvent({
                click: toggleVolume
            })
            .appendTo(self.$volume);

        self.$muteButton = Ox.Button({
                style: 'symbol',
                tooltip: ['Mute', 'Unmute'],
                type: 'image',
                value: self.options.muted ? 'unmute' : 'mute',
                values: ['mute', 'unmute']
            })
            .bindEvent({
                click: function() {
                    toggleMuted('button');
                }
            })
            .appendTo(self.$volume);

        self.$volumeInput = Ox.Range({
                changeOnDrag: true,
                max: 1,
                min: 0,
                step: 0.001,
                value: self.options.muted ? 0 : self.options.volume
            })
            .bindEvent({
                change: function(data) {
                    setVolume(data.value);
                }
            })
            .appendTo(self.$volume);

        self.$volumeValue = $('<div>')
            .addClass('OxVolumeValue')
            .html(self.options.muted ? 0 : Math.round(self.options.volume * 100))
            .appendTo(self.$volume);

    }

    self.options.type != 'play' && setPosition(self.options.position);

    self.results = [];
    if (self.options.subtitles) {
        if (Ox.isArray(self.options.subtitles)) {
            loadedsubtitles();
        } else {
            if (self.options.subtitles.indexOf('\n') > -1) {
                self.options.subtitles = Ox.parseSRT(self.options.subtitles);
                loadedsubtitles();
            } else {
                Ox.get(self.options.subtitles, function(data) {
                    self.options.subtitles = Ox.parseSRT(data);
                    loadedsubtitles();
                });
                self.options.subtitles = [];
            }
        }
    }

    setSizes(false, function() {
        self.options.fullscreen && enterFullscreen();
    });

    function censor() {
        if (self.options.type == 'play') {
            self.$video
                .brightness(self.censored ? 0.05 : self.options.brightness)
                .volume(self.censored ? 0.01 : self.options.volume);
        } else {
            self.$brightness.css({
                opacity: 1 - (self.censored ? 0.05 : self.options.brightness)
            });
        }
        self.$copyrightIcon[self.censored ? 'show' : 'hide']();
    }

    function changeVolume(num) {
        showVolume();
        self.options.volume = Ox.limit(self.options.volume + num, 0, 1);
        setVolume(self.options.volume);
        self.$volumeInput && self.$volumeInput.value(self.options.volume);
    }

    function clearInterfaceTimeout() {
        clearTimeout(self.interfaceTimeout);
        self.interfaceTimeout = 0;
    }

    function dragstart() {
        self.drag = {
            position: self.options.position,
            paused: self.options.paused
        }
        !self.options.paused && togglePaused();
    }

    function drag(e) {
        setPosition(self.drag.position - e.clientDX / 25);
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function dragend() {
        !self.drag.paused && togglePaused();
    }

    function ended() {
        !self.options.paused && togglePaused();
        if (self.options.poster) {
            self.$poster.animate({
                opacity: 1
            }, 250);
            self.posterIsVisible = true;
        }
        if (self.options.showIconOnLoad) {
            self.$playIcon.animate({
                opacity: 1
            }, 250);
            self.iconIsVisible = true;
        }
        self.options.rewind && rewind();
        that.triggerEvent('ended');
    }

    function enterFullscreen() {
        that.bind({
            mousemove: function() {
                showControls();
                hideControls();                            
            }
        });
        showControls();
        hideControls();
        that.find('.OxControls').bind({
            mouseenter: function() {
                self.mouseIsInControls = true;
            },
            mouseleave: function() {
                self.mouseIsInControls = false;
            }
        });
        that.gainFocus();
    }

    function find(query) {
        var results = [];
        if (query.length) {
            query = query.toLowerCase();
            results = Ox.map(self.options.annotations, function(annotation) {
                return Ox.decodeHTML(Ox.stripTags(
                    annotation.text.toLowerCase()
                )).indexOf(query) > -1 ? {
                    id: annotation.id,
                    'in': annotation['in'],
                    out: annotation.out
                } : null;
            });
        }
        //Ox.print('FIND RESULTS:', results);
        return results;
    }

    function focusFind() {
        !self.interfaceIsVisible && showControls();
        // need timeout so the "f" doesn't appear in the input field
        setTimeout(function() {
            if (self.$find.is(':hidden')) {
                toggleFind();
            } else {
                self.$findInput.focusInput(true);
            }
        }, 0);
    }

    function formatPosition(position) {
        position = Ox.isUndefined(position) ? self.options.position : position;
        return Ox.formatDuration(position, self.options.showMilliseconds);
    }

    function getCensored() {
        var censored = false;
        Ox.forEach(self.options.censored, function(v) {
            if (
                v['in'] < self.options.position
                && v.out > self.options.position
            ) {
                censored = true;
                return false;
            }
        });
        return censored;        
    }

    function getCSS(element) {
        var css;
        if (element == 'copyrightIcon') {
            css = {
                width: self.iconSize + 'px',
                height: self.iconSize + 'px'
            };
        } else if (element == 'controlsTop' || element == 'controlsBottom') {
            css = {
                width: self.width + 'px'
            };
        } else if (element == 'find') {
            css = {
                width: Math.min(216, self.width) + 'px' // 128 + 4 * 16 + 24
            };
        } else if (element == 'loadingIcon') {
            css = {
                width: self.iconSize + 'px',
                height: self.iconSize + 'px'
            };
        } else if (element == 'logo') {
            var logoHeight = Math.round(self.height / 10),
                logoMargin = Math.round(self.height / 20);
            css = {
                left: logoMargin + 'px',
                top: logoMargin + (self.controlsTopAreVisible ? 16 : 0) + 'px',
                height: logoHeight + 'px',
            };
        } else if (element == 'player') {
            var height = self.options.fullscreen ? window.innerHeight : self.height;
            if (self.options.externalControls) {
                height += (
                    !!self.options.controlsTop.length +
                    !!self.options.controlsBottom.length
                ) * self.barHeight;
            }
            css = Ox.extend({
                width: self.width + 'px',
                height: height + 'px'
            }, self.options.fullscreen ? {
                left: 0,
                top: 0
            } : {}, self.exitFullscreen ? {
                left: self.absoluteOffset.left,
                top: self.absoluteOffset.top
            } : {});
        } else if (element == 'playIcon') {
            var playIconPadding = Math.round(self.iconSize * 1/8),
                playIconSize = self.iconSize - 2 * playIconPadding - 4;
            css = {
                width: playIconSize + 'px',
                height: playIconSize + 'px',
                padding: playIconPadding + 'px',
                borderRadius: Math.round(self.iconSize / 2) + 'px'
            };
        } else if (element == 'progress') {
            css = {
                width: self.timelineImageWidth + 'px',
                marginLeft: -self.timelineImageWidth + 'px'
            };
        } else if (element == 'subtitle') {
            css = {
                bottom: (parseInt(self.height / 16) + !!self.controlsBottomAreVisible * 16) + 'px',
                width: self.width + 'px',
                fontSize: parseInt(self.height / 20) + 'px',
                WebkitTextStroke: (self.height / 1000) + 'px rgb(0, 0, 0)'
            };
        } else if (element == 'spaceBottom' || element == 'timeline') {
            css = {
                width: self.timelineWidth + 'px'
            };
        } else if (element == 'spaceTop' || element == 'title') {
            css = {
                width: getTitleWidth() + 'px'
            };
        } else if (element == 'videoContainer') {
            css = {
                width: self.width + 'px',
                height: self.height + 'px'
            };
        } else if (element == 'volume') {
            css = {
                width: Math.min(184, self.width)
            };
        }
        return css;
    }

    function getPosition(e) {
        // fixme: no offsetX in firefox???
        if ($.browser.mozilla) {
            //Ox.Log('Video', e, e.layerX - 56)
            return Ox.limit(
                (e.layerX - 48 - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(),
                0, self.$video.duration()
            );
        } else {
            /*Ox.Log('Video', e.offsetX, Ox.limit(
                (e.offsetX - self.barHeight / 2) / self.timelineImageWidth * self.video.duration,
                0, self.video.duration
            ))*/
            return Ox.limit(
                (e.offsetX - self.barHeight / 2) / self.timelineImageWidth * self.$video.duration(),
                0, self.$video.duration()
            );
        }
    } 

    function getPosterMarkerCSS() {
        self.videoCSS = getVideoCSS();
        var left = Math.floor((self.videoCSS.width - self.videoCSS.height) / 2),
            right = Math.ceil((self.videoCSS.width - self.videoCSS.height) / 2);
        return {
            center: {
                left: self.videoCSS.left + left + 'px',
                top: self.videoCSS.top + 'px',
                width: (self.videoCSS.height - 2) + 'px',
                height: (self.videoCSS.height - 2) + 'px'
            },
            left: {
                left: self.videoCSS.left + 'px',
                top: self.videoCSS.top + 'px',
                width: left + 'px',
                height: self.videoCSS.height + 'px'
            },
            right: {
                left: self.videoCSS.left + left + self.videoCSS.height + 'px',
                top: self.videoCSS.top + 'px',
                width: right + 'px',
                height: self.videoCSS.height + 'px'
            }
        };
    }

    function getProgressImageURL() {
        //Ox.Log('Video', '---', self.timelineImageWidth)
        if (!self.timelineImageWidth) return;
        var width = self.timelineImageWidth,
            height = self.barHeight,
            canvas = $('<canvas>')
                .attr({
                    width: width,
                    height: height
                })[0],
            context = canvas.getContext('2d'),
            imageData, data;
        context.fillStyle = 'rgba(255, 0, 0, 0.5)';
        context.fillRect(0, 0, width, height);
        imageData = context.getImageData(0, 0, width, height),
        data = imageData.data;
        self.buffered.forEach(function(range) {
            var left = Math.round(range[0] * width / self.$video.duration()),
                right = Math.round(range[1] * width / self.$video.duration());
            Ox.loop(left, right, function(x) {
                Ox.loop(height, function(y) {
                    index = x * 4 + y * 4 * width;
                    data[index + 3] = 0;
                });
            });
        });
        context.putImageData(imageData, 0, 0);
        return canvas.toDataURL();
    }

    function getSubtitle() {
        var subtitle = '';
        self.options.enableSubtitles && Ox.forEach(self.options.subtitles, function(v) {
            if (
                v['in'] <= self.options.position
                && v.out >= self.options.position
            ) {
                subtitle = v.text;
                return false;
            }
        });
        return subtitle;
    }

    function getTimeline() {
        var $timeline = Ox.SmallVideoTimeline({
                //_offset: getTimelineLeft(),
                disabled: !self.options.enableTimeline,
                duration: self.options.duration,
                find: self.options.find,
                'in': self.options['in'],
                out: self.options.out,
                paused: self.options.paused,
                position: self.options.position,
                results: self.results,
                showMilliseconds: self.options.showMilliseconds,
                subtitles: self.options.enableSubtitles ? self.options.subtitles : [],
                timeline: self.options.timeline,
                type: 'player',
                width: getTimelineWidth()
            })
            .css({
                float: 'left'
            })
            .css({
                background: '-moz-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'
            })
            .css({
                background: '-webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'
            })
            .bindEvent({
                position: function(data) {
                    setPosition(data.position, 'timeline');
                    that.triggerEvent('position', {
                        position: self.options.position
                    });
                }
            });
        //Ox.Log('Video', '??', $timeline.find('.OxInterface'))
        $timeline.children().css({
            marginLeft: getTimelineLeft() + 'px'
        });
        $timeline.find('.OxInterface').css({
            marginLeft: getTimelineLeft() + 'px'
        });
        return $timeline;
    }

    function getTimelineLeft() {
        var left = 0;
        Ox.forEach(self.options.controlsBottom, function(control) {
            if (control == 'timeline') {
                return false;
            }
            left += control == 'position' ? self.positionWidth : 16
        });
        //Ox.print('TIMELINE_LEFT = ', left)
        return left;
    }

    function getTimelineWidth() {
        return (self.options.fullscreen ? window.innerWidth : self.options.width) -
            self.options.controlsBottom.reduce(function(prev, curr) {
                return prev + (
                    curr == 'timeline' || curr == 'space' ? 0 :
                    curr == 'position' ? self.positionWidth : 16
                );
            }, 0);
    }

    function getTitleWidth() {
        return (self.options.fullscreen ? window.innerWidth : self.options.width) -
            self.options.controlsTop.reduce(function(prev, curr) {
                return prev + (
                    curr == 'title' || curr == 'space' ? 0 : 16
                );
            }, 0);
    }

    function getVideoCSS() {
        var playerWidth = self.width,
            playerHeight = self.height,
            playerRatio = playerWidth / playerHeight,
            videoWidth = self.videoWidth,
            videoHeight = self.videoHeight,
            videoRatio = videoWidth / videoHeight,
            videoIsWider = videoRatio > playerRatio,
            width, height;
        if (self.options.scaleToFill) {
            width = videoIsWider ? playerHeight * videoRatio : playerWidth;
            height = videoIsWider ? playerHeight : playerWidth / videoRatio;
        } else {
            width = videoIsWider ? playerWidth : playerHeight * videoRatio;
            height = videoIsWider ? playerWidth / videoRatio : playerHeight;
        }
        width = Math.round(width);
        height = Math.round(height);
        return {
            left: parseInt((playerWidth - width) / 2),
            top: parseInt((playerHeight - height) / 2),
            width: width,
            height: height
        };
    }

    function getVolumeImage() {
        var symbol;
        if (self.options.muted || self.options.volume == 0) {
            symbol = 'Unmute';
        } else if (self.options.volume < 1/3) {
            symbol = 'VolumeUp';
        } else if (self.options.volume < 2/3) {
            symbol = 'VolumeDown';
        } else {
            symbol = 'Mute';
        }
        return symbol;
    }

    function goToNextClip(direction) {
        self.$video[direction == 1 ? 'playNext' : 'playPrevious']();
    }

    function goToNextResult(direction) {
        var found = false,
            result;
        if (self.results.length) {
            direction == -1 && self.results.reverse();
            Ox.forEach(self.results, function(v) {
                if (
                    direction == 1
                    ? v['in'] > self.options.position
                    : v.out < self.options.position
                ) {
                    result = v
                    found = true;
                    return false;
                }
            });
            direction == -1 && self.results.reverse();
            if (!found) {
                result = self.results[direction == 1 ? 0 : self.results.length - 1];
            }
            setPosition(result['in'] + self.secondsPerFrame);
            that.triggerEvent('position', {
                position: self.options.position
            });
            result.id && that.triggerEvent('select', result);
        }
    }

    function goToPoint() {
        that.triggerEvent('gotopoint');
    }

    function hideControlMenus() {
        ['find', 'settings', 'volume'].forEach(function(element) {
            var $element = self['$' + element];
            $element && $element.is(':visible') && $element.animate({
                opacity: 0
            }, 250, function() {
                $element.hide().css({opacity: 1});
            });
        });
        //self.options.fullscreen && hideControls();
    }

    function hideControls() {
        //Ox.Log('Video', 'hideControls');
        clearTimeout(self.interfaceTimeout);
        self.interfaceTimeout = setTimeout(function() {
            if (!self.exitFullscreen && !self.inputHasFocus && !self.mouseIsInControls) {
                self.interfaceIsVisible = false;
                self.controlsTopAreVisible = false;
                self.controlsBottomAreVisible = false;
                self.$controlsTop && self.$controlsTop.animate({
                    opacity: 0
                }, 250);
                self.$controlsBottom && self.$controlsBottom.animate({
                    opacity: 0
                }, 250);
                hideControlMenus();
                self.$logo && self.$logo.animate({
                    top: getCSS('logo').top,
                    opacity: 0.25
                }, 250, function() {
                    self.options.logoLink && self.$logo.unbind('click');
                    self.options.logoTitle &&
                        self.$logo.unbind('mouseenter').unbind('mouseleave');
                });
                self.$subtitle && self.$subtitle.animate({
                    bottom: getCSS('subtitle').bottom,
                }, 250);
            }
        }, self.options.fullscreen ? 2500 : 1000);
    }

    function hideLoadingIcon() {
        self.$loadingIcon.hide().attr({
            src: Ox.UI.getImageURL('symbolLoading')
                .replace('/classic/', '/modern/')
        });
    }

    function hideMarkers() {
        Ox.forEach(self.$posterMarker, function(marker) {
            marker.hide();
        });
        Ox.forEach(self.$pointMarker, function(markers) {
            Ox.forEach(markers, function(marker) {
                marker.hide();
            });
        });
    }

    function isEqual(a, b) {
        return Math.abs(a - b) < 0.001;
    }

    function loadImage() {
        self.$image
            .one({
                load: function() {
                    hideLoadingIcon();
                }
            })
            .attr({
                src: self.options.video(
                    // fixme: this keeps the frame from being beyond the end,
                    // but what should be avoided is setting position to a point
                    // beyond the beginning of the last frame
                    Math.min(
                        self.options.position,
                        Math.floor(self.options.duration * self.options.fps) / self.options.fps
                    ),
                    self.options.width
                )
            });
    }

    function loadedmetadata() {

        Ox.Log('Video', 'LOADEDMETADATA')

        var hadDuration = !!self.options.duration;

        self.loadedMetadata = true;

        self.videoWidth = self.$video.videoWidth();
        self.videoHeight = self.$video.videoHeight();
        self.videoCSS = getVideoCSS();
        self.posterMarkerCSS = getPosterMarkerCSS();
        self.$video.css(self.videoCSS);
        self.$poster && self.$poster.css(self.videoCSS);
        self.$posterMarker && Ox.forEach(self.$posterMarker, function(marker, position) {
            marker.css(self.posterMarkerCSS[position]);
        });

        self.out = self.options.playInToOut && self.out < self.$video.duration()
            ? self.out : self.$video.duration();
        self.options.duration = self.out - self['in'];
        Ox.Log('Video', '---------------------------------------- POS', self.options.position)
        //self.options.position = Ox.limit(self.options.position, self['in'], self.out);
        //self.$video.currentTime(self.options.position);

        setPosition(self.options.position);
        self.$video.muted(self.options.muted).volume(self.options.volume);

        if (!self.options.paused) {
            self.options.paused = true;
            togglePaused('button');
        } else if (self.options.paused && self.playOnLoad) {
            togglePaused('button');
        }
        self.$playButton && self.$playButton.options({disabled: false});

        hideLoadingIcon();
        if (self.options.showIcon || self.options.showIconOnLoad) {
            //!self.options.keepIconVisible && self.$playIcon.addClass('OxInterface');
            if (self.options.showIconOnLoad) {
                self.$playIcon.animate({
                    opacity: 1
                }, 250);
            }
        }

        !hadDuration && self.$timeline && self.$timeline.replaceWith(
            self.$timeline = getTimeline()
        );

        if (self.options.enableKeyboard && self.options.focus == 'load') {
            that.gainFocus();
        }

    }

    function loadedsubtitles() {
        if (self.options.find) {
            submitFindInput(self.options.find);
            if (self.options.duration) {
                // duration was known or video has loaded before subtitles
                self.$timeline && self.$timeline.options({
                    results: self.results,
                    subtitles: self.options.subtitles
                });
            }
        }
    }

    function playing() {
        self.options.position = self.$video.currentTime();
        if (
            (self.playInToOut && self.options.position >= self.options.out)
            || (self.options.playInToOut && self.options.position >= self.out)
        ) {
            if (self.isPlaylist) {
                self.$video.playNext();
            } else {
                togglePaused();
                if (self.options.rewind) {
                    rewind();
                } else {
                    setPosition(self.playInToOut ? self.options.out : self.out/*, 'video'*/);
                }
                self.playInToOut = false;
                //ended();
                that.triggerEvent('ended');
            }
        } else {
            setPosition(self.options.position, 'video');
        }
        that.triggerEvent('playing', {
            position: self.options.position
        });
    }

    function playInToOut() {
        if (self.options.out > self.options['in']) {
            self.playInToOut = true;
            setPosition(self.options['in']);
            self.options.paused && togglePaused();
        }
    }

    function pointschange() {
        var points = self.$video.points();
        self['in'] = points[0];
        self.out = points[1];
        self.options.duration = self.out - self['in'];
        setPosition(self['in']);
        Ox.Log('Video', 'POINTSCHANGE', self['in'], self.out, self.options.position, self.options.duration)
    }

    function progress() {
        var buffered = self.$video.buffered();
        for (var i = 0; i < buffered.length; i++) {
            self.buffered[i] = [buffered.start(i), buffered.end(i)];
            // fixme: firefox weirdness
            if (self.buffered[i][0] > self.buffered[i][1]) {
                self.buffered[i][0] = 0;
            }
        }
        self.$progress.attr({
            src: getProgressImageURL()
        });
    }

    function renderSettings() {
        var $settings = $('<div>')
            .addClass('OxControls OxSettings')
            .bind({
                click: function(e) {
                    var $target = $(e.target), resolution, title;
                    self.$settings.hide();
                    if (!$target.is('.OxLine') && !$target.is('.OxSpace')) {
                        title = $(e.target).parent().children()[0].innerHTML;
                        if (title == 'Download') {
                            that.triggerEvent('download');
                        } else if (title == 'Subtitles') {
                            toggleSubtitles();
                        } else {
                            resolution = parseInt(title);
                            if (resolution != self.options.resolution) {
                                self.options.resolution = resolution;
                                setResolution();
                            }
                        }
                        self.$settings.children('.OxItem').each(function() {
                            var children = $(this).children(),
                                title = children[0].innerHTML,
                                checked = (
                                    title == 'Subtitles'
                                    && self.options.enableSubtitles
                                ) || (
                                    Ox.last(title) == 'p'
                                    && parseInt(title) == self.options.resolution
                                );
                            $(children[1]).attr({
                                src: Ox.UI.getImageURL(
                                    'symbol' + (checked ? 'Check' : 'None')
                                )
                            });
                        });
                    }
                }
            }),
            items = Ox.merge(
                self.resolutions.map(function(resolution) {
                    return {
                        checked: resolution == self.options.resolution,
                        title: resolution + 'p'
                    }
                }),
                self.options.subtitles.length
                    ? [{}, {
                        checked: self.options.enableSubtitles,
                        title: 'Subtitles'
                    }]
                    : [],
                self.options.enableDownload
                    ? [{}, {title: 'Download'}]
                    : []
            ),
            height = 0;
        items.forEach(function(item) {
            var $item;
            if (item.title) {
                $item = $('<div>')
                    .addClass('OxItem')
                    .bind({
                        mouseenter: function() {
                            $(this).addClass('OxSelected');
                        },
                        mouseleave: function() {
                            $(this).removeClass('OxSelected');
                        }
                    })
                    .appendTo($settings);
                $('<div>').html(item.title).appendTo($item);
                $('<img>').attr({
                    src: Ox.UI.getImageURL(
                        'symbol' + (item.checked ? 'Check' : 'None')
                    )
                }).appendTo($item);
                height += 16;
            } else {
                $('<div>').addClass('OxSpace').appendTo($settings);
                $('<div>').addClass('OxLine').appendTo($settings);
                $('<div>').addClass('OxSpace').appendTo($settings);
                height += 1
            }
        });
        $settings.css({height: height + 'px'});
        return $settings;
    }

    function rewind() {
        setTimeout(function() {
            setPosition(self.options.playInToOut ? self.options['in'] : 0);
        }, 250);
    }

    function seeked() {
        Ox.Log('Video', 'seeked')
        clearTimeout(self.seekTimeout);
        self.seekTimeout = 0;
        Ox.Log('Video', 'hide loading icon')
        hideLoadingIcon();
        self.$playIcon && self.$playIcon.show();
    }

    function seeking() {
        Ox.Log('Video', 'XX seeking')
        if (!self.seekTimeout) {
            self.seekTimeout = setTimeout(function() {
                self.$playIcon && self.$playIcon.hide();
                Ox.Log('Video', 'XX show')
                showLoadingIcon();
            }, 250);
        }
    }

    function setCensored() {
        var censored = getCensored();
        if (censored != self.censored) {
            self.censored = censored;
            censor();
        }
    }

    function setMarkers() {
        //Ox.Log('Video', 'SET MARKERS', self.options.position, self.options['in'], self.options.out, self.$pointMarker);
        Ox.forEach(self.$posterMarker, function(marker) {
            isEqual(self.options.position, self.options.posterFrame) ?
                marker.show() : marker.hide();
        });
        Ox.forEach(self.$pointMarker, function(markers, point) {
            Ox.forEach(markers, function(marker) {
                //Ox.Log('Video', self.options.position, self.options[point], isEqual(self.options.position, self.options[point]))
                // fixme: there's a bug in jquery and/or webkit
                // on load, show() doesn't work
                isEqual(self.options.position, self.options[point]) ?
                    marker.css({display: 'block'}) : marker.hide();
            });
        });
    }

    function setPoint() {
        that.triggerEvent('setpoint');
    }

    function setPosition(position, from) {
        self.options.position = Ox.limit(position, self['in'], self.out);
        /*
        // disabled
        self.options.position = Math.round(
            position * self.options.fps
        ) / self.options.fps;
        */
        self.options.paused && self.options.showMarkers && setMarkers();
        self.options.censored.length && setCensored();
        self.options.enableSubtitles && self.$subtitle && setSubtitle();
        self.$position && self.$position.html(formatPosition());
        if (self.options.type == 'play') {
            if (self.loadedMetadata && from != 'video') {
                self.$video.currentTime(self.options.position);
            }
            if (self.iconIsVisible) {
                self.$playIcon.animate({
                    opacity: 0
                }, 250);
                self.iconIsVisible = false;
            }
            if (self.posterIsVisible) {
                self.$poster.animate({
                    opacity: 0
                }, 250);
                self.posterIsVisible = false;
            }
            self.$timeline /*&& from != 'timeline'*/ && self.$timeline.options({
                position: self.options.position
            });
        } else {
            //showLoadingIcon();
            loadImage();
        }
    }

    function setResolution() {
        if (!self.options.paused) {
            self.playOnLoad = true;
            togglePaused('button');
        }
        self.loadedMetadata = false;
        showLoadingIcon();
        self.$video.src(self.options.video[self.options.resolution]);
        self.$playButton && self.$playButton.options({disabled: true});
        that.triggerEvent('resolution', {
            resolution: self.options.resolution
        });
    }

    function setSize($element, css, animate, callback) {
        if ($element) {
            if (animate) {
                $element.animate(css, 250, function() {
                    callback && callback();
                });
            } else {
                $element.css(css);
                callback && callback();
            }
        }
    }

    function setSizes(animate, callback) {
        self.width = self.options.fullscreen ? window.innerWidth : self.options.width;
        self.height = self.options.fullscreen ? window.innerHeight : self.options.height;
        self.videoCSS = getVideoCSS();
        self.iconSize = Ox.limit(Math.round(self.height / 10), 16, 32);
        if (self.$timeline || self.$spaceBottom) {
            self.timelineWidth = getTimelineWidth();
            if (self.$timeline) {
                self.timelineImageWidth = self.timelineWidth - self.barHeight;
            }
        }
        setSize(that, getCSS('player'), animate, callback);
        setSize(self.$videoContainer, getCSS('videoContainer'), animate);
        setSize(self.$video, self.videoCSS, animate);
        setSize(self.$poster, self.videoCSS, animate);
        setSize(self.$logo, getCSS('logo'), animate);
        setSize(self.$loadingIcon, getCSS('loadingIcon'), animate);
        setSize(self.$playIcon, getCSS('playIcon'), animate);
        setSize(self.$copyrightIcon, getCSS('copyrightIcon'), animate);
        setSize(self.$subtitle, getCSS('subtitle'), animate);
        setSize(self.$controlsTop, getCSS('controlsTop'), animate);
        setSize(self.$title, getCSS('title'), animate);
        setSize(self.$spaceTop, getCSS('spaceTop'), animate);
        setSize(self.$controlsBottom, getCSS('controlsBottom'), animate);
        setSize(self.$timeline, getCSS('timeline'), animate, function() {
            self.$timeline && self.$timeline.options({
                width: self.timelineWidth
            });
        });
        setSize(self.$spaceBottom, getCSS('spaceBottom'), animate);
        setSize(self.$find, getCSS('find'), animate, function() {
            var width = Math.min(128, self.width - 88); // 4 * 16 + 24
            self.$findInput.options({
                width: width
            });
            self.$findInput.children('input').css({
                width: (width - 12) + 'px',
            });            
        });
        setSize(self.$volume, getCSS('volume'), animate, function() {
            self.$volumeInput.options({
                size: Math.min(128, self.width - 56)
            });
        });
        if (self.$posterMarker) {
            self.posterMarkerCSS = getPosterMarkerCSS();
            Ox.forEach(self.$posterMarker, function(marker, position) {
                setSize(marker, self.posterMarkerCSS[position], animate);
            });
        }
    }

    function setSubtitle() {
        var subtitle = getSubtitle();
        if (subtitle != self.subtitle) {
            self.subtitle = subtitle;
            setSubtitleText();
        }
    }

    function setSubtitleText() {
        //Ox.Log('Video', 'setSubTx', self.subtitle, self.options.find)
        self.$subtitle.html(
            self.subtitle
            ?  Ox.highlight(self.subtitle, self.options.find, 'OxHighlight')
                .replace(/\n/g, '<br/>')
            : '&nbsp;<br/>&nbsp;'
            // FIXME: weird bug, only in fullscreen, only in chrome
        );
        //Ox.Log('Video', '?!?', self.$subtitle.css('bottom'), self.$subtitle.height())
    }

    function sizechange() {
        self.videoWidth = self.$video.videoWidth();
        self.videoHeight = self.$video.videoHeight();
        self.videoCSS = getVideoCSS();
        self.$video.css(self.videoCSS);
    };

    function setVolume(volume) {
        self.options.volume = volume;
        if (!!self.options.volume == self.options.muted) {
            toggleMuted();
        } else {
            self.$volumeButton.options({
                title: getVolumeImage()
            });
            self.$volumeValue.html(
                self.options.muted ? 0 : Math.round(self.options.volume * 100)
            );
        }
        !self.censored && self.$video.volume(self.options.volume);
        that.triggerEvent('volume', {
            volume: self.options.volume
        });
    }

    function showControls() {
        //Ox.Log('Video', 'showControls');
        clearTimeout(self.interfaceTimeout);
        if (!self.interfaceIsVisible) {
            self.interfaceIsVisible = true;
            if (self.$controlsTop) {
                self.controlsTopAreVisible = true;
            }
            if (self.$controlsBottom) {
                self.controlsBottomAreVisible = true;
            }
            self.$controlsTop && self.$controlsTop.animate({
                opacity: 1
            }, 250);
            self.$controlsBottom && self.$controlsBottom.animate({
                opacity: 1
            }, 250);
            ['find', 'settings', 'volume'].forEach(function(element) {
                var $element = self['$' + element];
                $element && $element.is(':visible') && $element.animate({
                    opacity: 1
                }, 250);
            });
            self.$logo && self.$logo.animate({
                top: getCSS('logo').top,
                opacity: 0.5
            }, 250, function() {
                self.options.logoLink && self.$logo
                    .bind({
                        click: function() {
                            document.location.href = self.options.logoLink;
                        }
                    });
                self.options.logoTitle && self.$logo
                    .bind({
                        mouseenter: function(e) {
                            self.$logoTooltip.show(e);
                        },
                        mouseleave: self.$logoTooltip.hide
                    });
            });
            self.$subtitle && self.$subtitle.animate({
                bottom: getCSS('subtitle').bottom,
            }, 250);
        }
    }

    function showLoadingIcon() {
        self.$loadingIcon.attr({
            src: Ox.UI.getImageURL('symbolLoadingAnimated')
                .replace('/classic/', '/modern/')
        }).show();
    }

    function showVolume() {
        if (self.$volume) {
            !self.interfaceIsVisible && showControls();
            self.$volume.is(':hidden') && toggleVolume();
        }
    }

    function submitFindInput(value, hasPressedEnter) {
        self.options.find = value;
        self.results = find(self.options.find);
        if (self.$find) {
            self.$results.html(self.results.length);
            self.$previousResultButton.options({
                disabled: self.results.length <= 1
            });
            self.$nextResultButton.options({
                disabled: self.results.length <= 1
            });
            self.$clearButton.options({
                disabled: !self.options.find
            });
        }
        self.subtitle && setSubtitleText();
        self.$timeline && self.$timeline.options({
            find: self.options.find,
            results: self.results
        });
        if (hasPressedEnter) {
            if (self.results.length) {
                goToNextResult(1);
                that.gainFocus();
            } else {
                self.$findInput.focusInput(true);
            }
        }
        that.triggerEvent('find', {find: self.options.find});
    }

    function submitPositionInput() {
        self.$positionInput.hide();
        self.$position.html('').show();
        setPosition(Ox.parseDuration(self.$positionInput.value()));
        if (self.playOnSubmit) {
            togglePaused();
            self.$video.play();
            self.playOnSubmit = false;
        }
        if (self.focus == 'mouseenter' && !self.mouseHasLeft) {
            that.gainFocus();
        }
        self.mouseHasLeft && hideControls();
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function toggleFind() {
        var show = self.$find.is(':hidden');
        !show && self.$findInput.blurInput();
        self.$find.toggle();
        show && self.$findInput.focusInput(false);
    }

    function toggleFullscreen(from) {
        var parentOffset, playOnFullscreen;
        self.options.fullscreen = !self.options.fullscreen;
        if (!self.options.paused) {
            // video won't keep playing accross detach/append
            self.$video.pause();
            playOnFullscreen = true;
        }
        if (self.options.fullscreen) {
            self.$parent = that.parent();
            parentOffset = self.$parent.offset();
            self.absoluteOffset = that.offset();
            self.relativeOffset = {
                left: self.absoluteOffset.left - parentOffset.left,
                top: self.absoluteOffset.top - parentOffset.top
            };
            that.detach()
                .addClass('OxFullscreen')
                .css({
                    left: self.absoluteOffset.left + 'px',
                    top: self.absoluteOffset.top + 'px',
                    zIndex: 1000
                })
                .appendTo(Ox.UI.$body);
            setSizes(true, function() {
                playOnFullscreen && self.$video.play();
                enterFullscreen();
            });
        } else {
            // exitFullscreen flag makes the animation end on absolute position
            self.exitFullscreen = true;
            that.unbind('mousemove');
            that.find('.OxControls')
                .trigger('mouseleave')
                .unbind('mouseenter')
                .unbind('mouseleave');
            clearTimeout(self.interfaceTimeout);
            setSizes(true, function() {
                self.exitFullscreen = false;
                that.detach()
                    .removeClass('OxFullscreen')
                    .css({
                        left: self.relativeOffset.left + 'px',
                        top: self.relativeOffset.top + 'px',
                        zIndex: 1
                    })
                    .appendTo(self.$parent);
                playOnFullscreen && self.$video.play();
                self.options.enableKeyboard && that.gainFocus();
                //showControls();
            });
        }
        if (self.$fullscreenButton && from != 'button') {
            self.$fullscreenButton.toggle();
        }
        that.triggerEvent('fullscreen', {
            fullscreen: self.options.fullscreen
        });
    }

    function toggleMuted(from) {
        showVolume();
        self.options.muted = !self.options.muted;
        self.$video.muted(self.options.muted);
        if (!self.options.muted && !self.options.volume) {
            self.options.volume = 1;
            self.$video.volume(1);
        }
        if (self.$muteButton && from != 'button') {
            self.$muteButton.toggle();
        }
        self.$volumeButton && self.$volumeButton.options({
            title: getVolumeImage()
        });
        self.$volumeInput && self.$volumeInput.value(
            self.options.muted ? 0 : self.options.volume
        );
        self.$volumeValue && self.$volumeValue.html(
            self.options.muted ? 0 : Math.round(self.options.volume * 100)
        );
        that.triggerEvent('muted', {
            muted: self.options.muted
        });
    }

    function togglePaused(from) {
        if (!self.loadedMetadata) {
            return;
        }
        self.options.paused = !self.options.paused;
        self.$timeline && self.$timeline.options({
            paused: self.options.paused
        });
        if (self.options.paused) {
            self.$video.pause();
            clearInterval(self.playInterval);
            if (self.options.showIcon) {
                togglePlayIcon();
                self.options.showIcon && self.$playIcon.animate({
                    opacity: 1
                }, 250);
            }
        } else {
            if (self.options.playInToOut && self.options.position > self.options.out - self.secondsPerFrame) {
                setPosition(self.options['in']);
            }
            self.$video.play();
            self.playInterval = setInterval(playing, self.millisecondsPerFrame);
            if (self.options.showIcon) {            
                self.options.showIcon && self.$playIcon.animate({
                    opacity: 0
                }, 250, togglePlayIcon);
            }
            self.options.showMarkers && hideMarkers();
        }
        if (self.$playButton && from != 'button') {
            self.$playButton.toggle();
        }
        that.triggerEvent('paused', {
            paused: self.options.paused
        });
        self.options.paused && that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function togglePlayIcon() {
        self.$playIcon.attr({
            src: Ox.UI.getImageURL(
                'symbol' + (self.options.paused ? 'Play' : 'Pause'
            ), 'modern')
        });
    }

    function toggleScale(from) {
        self.options.scaleToFill = !self.options.scaleToFill;
        self.videoCSS = getVideoCSS();
        self.$video.animate(self.videoCSS, 250);
        self.$poster && self.$poster.animate(self.videoCSS, 250);
        if (self.$scaleButton && from != 'button') {
            self.$scaleButton.toggle();
        }
        if (self.$posterMarker) {
            self.posterMarkerCSS = getPosterMarkerCSS();
            Ox.forEach(self.$posterMarker, function(marker, position) {
                marker.animate(self.posterMarkerCSS[position], 250);
            });
        }
        that.triggerEvent('scale', {
            scale: self.options.scaleToFill ? 'fill' : 'fit'
        });
    }

    function toggleSize() {
        self.options.sizeIsLarge = !self.options.sizeIsLarge;
        that.triggerEvent('size', {
            size: self.options.sizeIsLarge ? 'large' : 'small'
        });
    }

    function toggleSubtitles() {
        self.options.enableSubtitles = !self.options.enableSubtitles;
        setSubtitle();
        self.$timeline && self.$timeline.options({
            subtitles: self.options.enableSubtitles ? self.options.subtitles : []
        });
        that.triggerEvent('subtitles', {
            subtitles: self.options.enableSubtitles
        });
    }

    function toggleVolume() {
        self.$volume.toggle();
    }

    self.setOption = function(key, value) {
        if (key == 'enableSubtitles') {
            self.options.enableSubtitles = !self.options.enableSubtitles;
            toggleSubtitles();
        } if (key == 'find') {
            setSubtitleText();
        } else if (key == 'fullscreen') {
            self.options.fullscreen = !self.options.fullscreen;
            toggleFullscreen();
        } else if (key == 'height' || key == 'width') {
            setSizes();
        } else if (key == 'in' || key == 'out') {
            self.options.paused && setMarkers();
            self.$timeline && self.$timeline.options(key, value);
        } else if (key == 'muted') {
            self.options.muted = !self.options.muted;
            toggleMuted();
        } else if (key == 'paused') {
            self.options.paused = !self.options.paused;
            togglePaused();
        } else if (key == 'position') {
            setPosition(value);
        } else if (key == 'posterFrame') {
            self.options.paused && setMarkers();
        } else if (key == 'resolution') {
            setResolution();
        } else if (key == 'scaleToFill') {
            self.options.scaleToFill = !self.options.scaleToFill;
            toggleScale();
        } else if (key == 'sizeIsLarge') {
            self.$sizeButton.toggle();
        }
    };

    that.changeVolume = function(num) {
        changeVolume(num);
        return that;
    };

    /*@
    playInToOut <f> play in to out
        () -> <o>  play in to out
    @*/
    that.playInToOut = function() {
        playInToOut();
        return that;
    };

    /*@
    togglePaused <f> toggle paused state
        () -> <o>  toggle paused state
    @*/
    that.togglePaused = function() {
        togglePaused();
        return that;
    }

    /*@
    toggleMuted <f> toggle muted state
        () -> <o>  toggle muted state
    @*/
    that.toggleMuted = function() {
        toggleMuted();
        return that;
    }

    return that;

};