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

'use strict';

/*@
Ox.VideoEditor <f:Ox.Element> VideoEditor Object
    () ->              <f> VideoEditor Object
    (options) ->       <f> VideoEditor Object
    (options, self) -> <f> VideoEditor Object
    options <o> Options object
    self    <o> shared private variable
@*/

// fixme: should be VideoAnnotationEditor

Ox.VideoEditor = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
            .defaults({
                annotationsSize: 0,
                censored: [],
                cuts: [],
                duration: 0,
                enableSubtitles: false,
                find: '',
                fps: 25,
                getFrameURL: null,
                getLargeTimelineImageURL: null,
                getSmallTimelineImageURL: null,
                'in': 0,
				height: 0,
                layers: [],
                muted: false,
                out: 0,
                position: 0,
                posterFrame: 0,
                posterFrameControls: false,
                resolution: 0,
                showAnnotations: false,
                showLargeTimeline: true,
                subtitles: [],
                tooltips: false,
                videoRatio: 16/9,
                videoSize: 'small',
                video: '',
                volume: 1,
                width: 0
            })
            .options(options || {})
            .mousedown(function() {
                that.gainFocus();
            })
            .bindEvent({
                key_0: toggleMuted,
                key_shift_0: function() {
                    movePositionBy(-self.options.position);
                },
                key_alt_left: function() {
                },
                key_alt_right: function() {
                },
                key_alt_shift_left: function() {
                },
                key_alt_shift_right: function() {
                },
                key_backslash: function() {
                    select('subtitle');
                },
                key_closebracket: function() {
                    movePositionTo('subtitle', 1);
                },
                key_comma: function() {
                    movePositionTo('cut', -1);
                },
                key_dot: function() {
                    movePositionTo('cut', 1);
                },
                key_down: function() {
                    movePositionBy(self.sizes.timeline[0].width);
                },
                key_f: function() {
                    setTimeout(function() {
                        self.$findInput.focusInput(true);
                    });
                },
                key_g: function() {
                    self.results.length && setPosition(getNextPosition('result', 1));
                },
                key_i: function() {
                    setPoint('in', self.options.position);
                },
                key_left: function() {
                    movePositionBy(-0.04);
                },
                key_o: function() {
                    setPoint('out', self.options.position);
                },
                key_openbracket: function() {
                    movePositionTo('subtitle', -1);
                },
                key_p: playInToOut,
                key_right: function() {
                    movePositionBy(0.04);
                },
                key_s: function() {
                    // toggleSize
                },
                key_shift_comma: function() {
                    movePositionTo('match', -1);
                },
                key_shift_dot: function() {
                    movePositionTo('match', 1);
                },
                key_shift_down: function() {
                    movePositionBy(self.options.duration);
                },
                key_shift_g: function() {
                    self.results.length && setPosition(getNextPosition('result', -1));
                },
                key_shift_left: function() {
                    movePositionBy(-1);
                },
                key_shift_i: function() {
                    goToPoint('in');
                },
                key_shift_o: function() {
                    goToPoint('out');
                },
                key_shift_p: function() {
                    // go to poster frame
                },
                key_shift_right: function() {
                    movePositionBy(1);
                },
                key_shift_up: function() {
                    movePositionBy(-self.options.position);
                },
                key_slash: function() {
                    select('cut');
                },
                key_space: togglePaused,
                key_up: function() {
                    movePositionBy(-self.sizes.timeline[0].width);
                }
            });

    Ox.extend(self, {
        $player: [],
        $timeline: [],
        controlsHeight: 16,
        margin: 8,
    });

    self.words = [];
    Ox.forEach(Ox.count(Ox.words(self.options.subtitles.map(function(subtitle) {
        return subtitle.text;
    }).join(' '))), function(count, word) {
        self.words.push({count: count, word: word});
    });
    self.words = self.words.sort(function(a, b) {
        return b.count - a.count;
    }).map(function(obj) {
        return obj.word;
    });

    self.$editor = Ox.Element()
        .addClass('OxVideoEditor')
        .click(function(e) {
            var $target = $(e.target);
            !$target.is('.OxPosition') && !$target.is('input') && that.gainFocus();
        });

    self.sizes = getSizes();

    ['play', 'in', 'out'].forEach(function(type, i) {
        self.$player[i] = Ox.VideoPlayer({
                censored: self.options.censored,
                controlsBottom: type == 'play' ?
                    ['play', 'playInToOut', 'volume', 'size', 'space', 'position'] :
                    ['goto', 'set', 'space', 'position'],
                duration: self.options.duration,
                enableMouse: true,
                enablePosition: true,
                enableSubtitles: self.options.enableSubtitles,
                externalControls: true,
                find: self.options.find,
                height: self.sizes.player[i].height,
                id: 'player' + Ox.toTitleCase(type),
                'in': self.options['in'],
                muted: self.options.muted,
                out: self.options.out,
                paused: true,
                position: type == 'play' ? self.options.position : self.options[type],
                posterFrame: self.options.posterFrame,
                resolution: self.options.resolution,
                showMarkers: true,
                showMilliseconds: 3,
                sizeIsLarge: self.options.videoSize == 'large',
                subtitles: self.options.subtitles,
                type: type,
                video: type == 'play' ? self.options.video : self.options.getFrameURL,
                volume: self.options.volume,
                width: self.sizes.player[i].width
            })
            .css({
                left: self.sizes.player[i].left + 'px',
                top: self.sizes.player[i].top + 'px'
            })
            .bindEvent(type == 'play' ? {
                muted: function(data) {
                    that.triggerEvent('muted', data);
                },
                paused: function(data) {
                    that.triggerEvent('paused', data);
                },
                playing: changePlayer,
                position: function(data) {
                    changePlayer(data);
                    that.triggerEvent('position', data);
                },
                resolution: function(data) {
                    that.triggerEvent('resolution', data);
                },
                size: toggleSize,
                subtitles: function(data) {
                    that.triggerEvent('subtitles', data);
                },
                volume: function(data) {
                    that.triggerEvent('volume', data);
                }
            } : {
                gotopoint: function() {
                    goToPoint(type);
                },
                position: function(data) {
                    setPoint(type, data.position);
                },
                setpoint: function() {
                    setPoint(type, self.options.position);
                }
            })
            .appendTo(self.$editor);
    });

    self.$timeline[0] = Ox.LargeVideoTimeline({
            cuts: self.options.cuts,
            duration: self.options.duration,
            find: self.options.find,
            getImageURL: self.options.getLargeTimelineImageURL,
            id: 'timelineLarge',
            'in': self.options['in'],
            //matches: self.options.matches,
            out: self.options.out,
            position: self.options.position,
            subtitles: self.options.subtitles,
            type: 'editor',
            width: self.sizes.timeline[0].width
        })
        .css({
            left: self.sizes.timeline[0].left + 'px',
            top: self.sizes.timeline[0].top + 'px'
        })
        .bindEvent({
            position: changeTimelineLarge
        })
        .appendTo(self.$editor);

    self.$timeline[1] = Ox.BlockVideoTimeline({
            cuts: self.options.cuts,
            duration: self.options.duration,
            find: self.options.find,
            getImageURL: self.options.getSmallTimelineImageURL,
            id: 'timelineSmall',
            'in': self.options['in'],
            out: self.options.out,
            position: self.options.position,
            results: find(self.options.find),
            showPointMarkers: true,
            showSubtitles: true,
            subtitles: self.options.subtitles,
            videoId: self.options.videoId,
            width: self.sizes.timeline[1].width
        })
        .css({
            left: self.sizes.timeline[1].left + 'px',
            top: self.sizes.timeline[1].top + 'px',
        })
        .bindEvent({
            position: changeTimelineSmall
        })
        .appendTo(self.$editor);

    self.$annotations = Ox.Element()
        .css({
            overflowY: 'auto'
        });
    self.$annotationPanel = [];

    self.options.layers.forEach(function(layer, i) {
        self.$annotationPanel[i] = Ox.AnnotationPanel(
                Ox.extend({
                    width: self.options.annotationsSize - Ox.UI.SCROLLBAR_SIZE
                }, layer)
            )
            .bindEvent({
                add: function(data) {
                    data.layer = layer.id;
                    data['in'] = self.options['in'];
                    data.out = self.options.out;
                    that.triggerEvent('addannotation', data);
                },
                remove: function(data) {
                    data = {
                        ids: [data],
                        layer: layer.id
                    };
                    that.triggerEvent('removeannotations', data);
                },
                select: function(data) {
                    self.options.layers.forEach(function(layer_, i_) {
                        if (i_ != i) {
                            // FIXME: the way AnnotationPanel is set up,
                            // it does not actually have that method
                            // self.$annotationPanel[i_].deselectItems();
                        }
                    });
                    selectAnnotation(data);
                },
                submit: updateAnnotation
            })
            .appendTo(self.$annotations);
    });

    self.$videobar = Ox.Bar({
        size: 16
    }).addClass('OxVideoPlayer');

    self.resolutions = [];
    Ox.forEach(self.options.video, function(url, resolution) {
        Ox.Log('Video', url, resolution);
        self.resolutions.push(
            {id: resolution + '', title: resolution + 'p',
             checked: self.options.resolution == resolution}
        );
    });

    self.$keyboardShortcuts = $('<div>').css({margin: '16px'});
    [
        {key: Ox.UI.symbols.space, action: 'Play/Pause'},
        {key: 'P', action: 'Play In to Out'},
        {key: '0', action: 'Mute/Unmute'},
        {key: '-', action: 'Turn Volume Down'},
        {key: '+', action: 'Turn Volume Up'},
        {key: Ox.UI.symbols.arrow_left, action: 'Go One Frame Back'},
        {key: Ox.UI.symbols.arrow_right, action: 'Go One Frame Forward'},
        {key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_left, action: 'Go One Second Back'},
        {key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_right, action: 'Go One Second Forward'},
        {key: Ox.UI.symbols.arrow_up, action: 'Go One Line Up'},
        {key: Ox.UI.symbols.arrow_down, action: 'Go One Line Down'},
        {key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_up, action: 'Go to First Frame'},
        {key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_down, action: 'Go to Last Frame'},
        {key: 'I', action: 'Set In Point'},
        {key: 'O', action: 'Set Out Point'},
        {key: Ox.UI.symbols.shift + 'I', action: 'Go to Out Point'},
        {key: Ox.UI.symbols.shift + 'O', action: 'Go to Out Point'},
        {key: '[', action: 'Go to Previous Annotation'},
        {key: ']', action: 'Go to Next Annotation'},
        {key: '\\', action: 'Select Current Annotation'},
        {key: '<', action: 'Go to Previous Cut'},
        {key: '>', action: 'Go to Next Cut'},
        {key: '/', action: 'Select Current Cut'},
        {key: 'F', action: 'Find'},
        {key: Ox.UI.symbols.shift + 'G', action: 'Go to Previous Result'},
        {key: 'G', action: 'Go to Next Result'},
        {key: 'S', action: 'Select Current Annotation'},
        {key: 'E', action: 'Edit Selected Annotation'},
        {key: Ox.UI.symbols['return'], action: 'Submit'},
        {key: Ox.UI.symbols.escape, action: 'Cancel'},
    ].forEach(function(shortcut) {
        self.$keyboardShortcuts.append(
            $('<div>').css({display: 'table-row'})
                .append(
                    $('<div>').css({
                        display: 'table-cell',
                        height: '16px',
                        paddingRight: '16px',
                        //fontWeight: 'bold',
                        textAlign: 'right'
                    })
                    .html(shortcut.key)
                )
                .append(
                    $('<div>').css({display: 'table-cell'})
                        .html(shortcut.action)
                )
        );
    });

    self.$videoMenuButton = Ox.MenuButton({
            items: Ox.merge(
                [
                    {id: 'toggleSize', title: 'Large Player', selected: self.options.playerSize == 'large', keyboard: 'shift +'},
                    {}
                ],
                {
                    'id': 'resolution', title:'Resolution',
                    items: [ { group: 'resolution', min: 1, max: 1, items: self.resolutions } ]
                },
                [
                    {},
                    {id: 'largeTimeline', title: 'Hide Large Timeline', disabled: true},
                    {id: 'subtitlesTimeline', title: 'Hide Subtitles on Large Timeline', disabled: true},
                    {},
                    {id: 'downloadVideo', title: 'Download Video...', disabled: true},
                    {id: 'downloadSelection', title: 'Download Selection...', disabled: true},
                    {id: 'embedSelection', title: 'Embed Selection...', disabled: true},
                    {},
                    {id: 'keyboard', title: 'Keyboard Shortcuts...', keyboard: 'h'}
                ]
            ),
            title: 'set',
            tooltip: 'Actions and Settings',
            type: 'image'
        })
        .css({float: 'left'})
        .bindEvent({
            click: function(data) {
                var id = data.id;
                if (id == 'toggleSize') {
                    toggleSize();
                } else if (id == 'keyboard') {
                    var dialog = Ox.Dialog({
                        buttons: [
                            Ox.Button({id: 'close', title: 'Close'})
                                .bindEvent({click: function() { dialog.close(); }})
                        ],
                        content: self.$keyboardShortcuts,
                        height: 384,
                        keys: {enter: 'close', escape: 'close'},
                        title: 'Keyboard Shortcuts',
                        width: 256
                    }).open();
                } else if (id == 'resolution') {
                    self.options.resolution = parseInt(data.checked[0].id);
                    self.$player[0].options({resolution: self.options.resolution});
                }
            }
        })
        .appendTo(self.$videobar);

    self.$selectButton = Ox.Button({
            style: 'symbol',
            title: 'select',
            type: 'image'
        })
        .css({float: 'left'})
        .bindEvent({
            click: function() {
                self.$menuButton.find('input').trigger('click')
            }
        });
        //.appendTo(self.$videobar);


    self.$resolutionSelect = Ox.Select({
            items: self.resolutions,
            width: 48,
        })
        .css({float: 'left'})
        .bindEvent({
            change: function(data) {
            }
        });
        //.appendTo(self.$videobar);

    if (self.options.posterFrameControls) {

        self.$goToPosterButton = Ox.Button({
                style: 'symbol',
                title: 'goToPoster',
                tooltip: 'Go to Poster Frame',
                type: 'image'
            })
            .css({float: 'left'})
            .bindEvent({
                click: function() {
                    setPosition(self.options.posterFrame);
                }
            })
            .appendTo(self.$videobar);

        self.$setPosterButton = Ox.Button({
                disabled: true,
                style: 'symbol',
                title: 'setPoster',
                tooltip: 'Set Poster Frame',
                type: 'image'
            })
            .css({float: 'left'})
            .bindEvent({
                click: function() {
                    self.$goToPosterButton.toggleOption('disabled');
                    self.$setPosterButton.toggleOption('disabled');
                    self.$unlockPosterButton.toggle();
                }
            })
            .appendTo(self.$videobar);

        self.$unlockPosterButton = Ox.Button({
                style: 'symbol',
                title: [
                    {id: 'lock', title: 'lock'},
                    {id: 'unlock', title: 'unlock', selected: true}
                ],
                tooltip: ['Lock Poster Frame', 'Unlock Poster Frame'],
                type: 'image'
            })
            .css({float: 'left'})
            .bindEvent({
                click: function() {
                    self.$setPosterButton.toggleOption('disabled');
                }
            })
            .appendTo(self.$videobar);

    }

    self.$clearButton = Ox.Button({
            disabled: self.options.find === '',
            style: 'symbol',
            title: 'close',
            tooltip: 'Clear',
            type: 'image'
        })
        .css({float: 'right'})
        .bindEvent({
            click: function() {
                self.$findInput.clearInput();
                submitFindInput('');
            }
        })
        .appendTo(self.$videobar);

    self.$findInput = Ox.Input({
            autocomplete: self.words,
            autocompleteReplace: true,
            autocompleteSelect: true,
            autocompleteSelectHighlight: true,
            autocompleteSelectMax: 10,
            autocompleteSelectSubmit: true,
            changeOnKeypress: true,
            placeholder: 'Find...',
            value: self.options.find,
            width: 96
        })
        .css({float: 'right', background: 'transparent'})
        .bindEvent({
            change: function(data) {
                submitFindInput(data.value, false);
            },
            submit: function(data) {
                submitFindInput(data.value, true);
            }
        })
        .appendTo(self.$videobar);

    self.$findButton = Ox.Button({
            //disabled: true,
            style: 'symbol',
            title: 'find',
            tooltip: 'Find',
            type: 'image'
        })
        .css({float: 'right'})
        .bindEvent({
            click: function() {

            }
        });
        //.appendTo(self.$videobar);

    self.$nextButton = Ox.Button({
            disabled: true,
            style: 'symbol',
            title: 'arrowRight',
            tooltip: 'Next Result',
            type: 'image'
        })
        .css({float: 'right'})
        .bindEvent({
            click: function() {
                setPosition(getNextPosition('result', 1));
            }
        })
        .appendTo(self.$videobar);

    self.$previousButton = Ox.Button({
            disabled: true,
            style: 'symbol',
            title: 'arrowLeft',
            tooltip: 'Previous Result',
            type: 'image'
        })
        .css({float: 'right'})
        .bindEvent({
            click: function() {
                setPosition(getNextPosition('result', -1));
            }
        })
        .appendTo(self.$videobar);

    self.$results = $('<div>')
        .css({float: 'right', width: '36px', padding: '2px 4px 0 0', fontSize: '9px', textAlign: 'right', cursor: 'default', opacity: 0.25})
        .html('0')
        .appendTo(self.$videobar.$element);

    self.$annotationsbar = Ox.Bar({
        size: 16
    }).addClass('OxVideoPlayer');

    self.$annotationsMenuButton = Ox.MenuButton({
            items: [
                {id: 'annotations', title: 'Show Annotations', disabled: true},
                {id: 'showAnnotationsAtPosition', title: 'At Current Position', checked: true},
                {id: 'showAnnotationsInSelection', title: 'In Current Selection'},
                {id: 'showAllAnnotations', title: 'All'},
                {},
                {id: 'textSize', title: 'Font Size', disabled: true},
                {id: 'smallText', title: 'Small', checked: true},
                {id: 'mediumText', title: 'Medium'},
                {id: 'largeText', title: 'Large'}
            ],
            max: 2,
            title: 'set',
            tooltip: 'Actions and Settings',
            type: 'image'
        })
        .css({float: 'left'})
        .appendTo(self.$annotationsbar);

    that.$element = Ox.SplitPanel({
        elements: [
            {
                element: Ox.SplitPanel({
                    elements: [
                        {
                            element: self.$videobar,
                            size: 16
                        },
                        {
                            element: self.$editor
                        }
                    ],
                    orientation: 'vertical'
                })
            },
            {
                collapsed: !self.options.showAnnotations,
                collapsible: true,
                element: Ox.SplitPanel({
                    elements: [
                        {
                            element: self.$annotationsbar,
                            size: 16
                        },
                        {
                            element: self.$annotations,
                        }
                    ],
                    orientation: 'vertical'
                })
                .bindEvent({
                    resize: resizeAnnotations,
                    toggle: toggleAnnotations
                }),
                resizable: true,
                resize: [192, 256, 320, 384],
                size: self.options.annotationsSize,
                tooltip: self.options.tooltips ? 'annotations' : false
            }
        ],
        orientation: 'horizontal'
    });

    // we need a timeout so that a chained bindEvent
    // actually catches the event
    self.options.find && setTimeout(function() {
        submitFindInput(self.options.find, true);
    }, 0);

    function changePlayer(data) {
        self.options.position = data.position;
        self.$timeline[0].options({
            position: data.position
        });
        self.$timeline[1].options({
            position: data.position
        });
    }

    function changeTimelineLarge(data) {
        self.options.position = data.position;
        self.$player[0].options({
            position: data.position
        });
        self.$timeline[1].options({
            position: data.position
        });
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function changeTimelineSmall(data) {
        self.options.position = data.position;
        self.$player[0].options({
            position: data.position
        });
        self.$timeline[0].options({
            position: data.position
        });
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function find(query) {
        var results = [];
        if (query.length) {
            query = query.toLowerCase();
            results = Ox.map(self.options.subtitles, function(subtitle) {
                return subtitle.text.toLowerCase().indexOf(query) > -1 ? {
                    'in': subtitle['in'],
                    out: subtitle.out
                } : null;
            });
        }
        return results;
    }

    // fixme: why not goToNextPosition()?
    function getNextPosition(type, direction) {
        var found = false,
            position = 0,
            positions;
        if (type == 'cut') {
            positions = self.options.cuts;
        } else if (type == 'result') {
            positions = self.results.map(function(v) {
                return v['in'];
            });
        } else if (type == 'subtitle') {
            positions = self.options.subtitles.map(function(v) {
                return v['in'];
            });
        }
        direction == -1 && positions.reverse();
        Ox.forEach(positions, function(v) {
            if (direction == 1 ? v > self.options.position : v < self.options.position) {
                position = v;
                found = true;
                return false;
            }
        });
        direction == -1 && positions.reverse();
        if (!found) {
            position = positions[direction == 1 ? 0 : positions.length - 1];
        }
        return position;
    }

    function getPoints(type) {
        var found = false,
            points,
            positions = [];
        if (type == 'cut') {
            positions = self.options.cuts;
        } else if (type == 'match') {
            // ...
        } else if (type == 'subtitle') {
            self.options.subtitles.forEach(function(v, i) {
                positions.push(v['in']);
                positions.push(v.out);
            });
        }
        positions.indexOf(0) == -1 && positions.unshift(0);
        positions.indexOf(self.options.duration) == -1 &&
            positions.push(self.options.duration);
        Ox.forEach(positions, function(v, i) {
            if (v > self.options.position) {
                points = [positions[i - 1], positions[i]];
                found = true;
                return false;
            }
        });
        return points;
    }

    function getSizes(scrollbarIsVisible) {
        //Ox.Log('Video', 'getSizes', scrollbarIsVisible)
        var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE,
			contentWidth = self.options.width
			    - (self.options.showAnnotations * self.options.annotationsSize) - 1
			    - (scrollbarIsVisible ? scrollbarWidth : 0),
            height,
            lines,
			size = {
                player: [],
                timeline: []
            },
            width, widths;
        if (self.options.videoSize == 'small') {
            width = 0;
            widths = Ox.divideInt(contentWidth - 4 * self.margin, 3);
            [1, 0, 2].forEach(function(v, i) {
                size.player[v] = {
                    left: (i + 0.5) * self.margin + width,
                    top: self.margin / 2,
                    width: widths[i],
                    height: Math.round(widths[1] / self.options.videoRatio)
                };
                width += widths[i];
            });
        } else {
            size.player[0] = {
                left: self.margin / 2,
                top: self.margin / 2,
                width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.options.videoRatio) * 2/3),
            };
            size.player[0].height = Math.round(size.player[0].width / self.options.videoRatio);
            size.player[1] = {
                left: size.player[0].left + size.player[0].width + self.margin,
                top: size.player[0].top,
                width: contentWidth - 3 * self.margin - size.player[0].width
            };
            size.player[1].height = Math.ceil(size.player[1].width / self.options.videoRatio);
            size.player[2] = {
                left: size.player[1].left,
                top: size.player[0].top + size.player[1].height + self.controlsHeight + self.margin,
                width: size.player[1].width,
                height: size.player[0].height - size.player[1].height - self.controlsHeight - self.margin
            };
        }
        size.timeline[0] = {
            left: self.margin / 2,
            top: size.player[0].height + self.controlsHeight + 1.5 * self.margin,
            width: contentWidth - 2 * self.margin,
            height: 64
        };
        size.timeline[1] = {
            left: size.timeline[0].left,
            top: size.timeline[0].top + size.timeline[0].height + self.margin,
            width: size.timeline[0].width
        };
        lines = Math.ceil(self.options.duration / size.timeline[1].width);
        height = getHeight();
        self.$editor.css({
            overflowY: (scrollbarIsVisible && height <= self.options.height - 16) ? 'scroll' : 'auto'
        });
        //Ox.Log('Video', 'getSizes', scrollbarIsVisible, height, self.options.height, size)
        return (!scrollbarIsVisible && height > self.options.height - 16) ? getSizes(true) : size;
        function getHeight() {
            return size.player[0].height + self.controlsHeight +
                size.timeline[0].height + lines * 16 +
                (lines + 3) * self.margin;
        }
    }

    function goToPoint(point) {
        setPosition(self.options[point]);
    }

    function movePositionBy(sec) {
        setPosition(Ox.limit(self.options.position + sec, 0, self.options.duration));
    }

    function movePositionTo(type, direction) {
        setPosition(getNextPosition(type, direction));
    }

    function playInToOut() {
        self.$player[0].playInToOut();
    }

    function resizeAnnotations(data) {
        self.options.annotationsSize = data.size;
        setSizes();
    }

    function resizeEditor(data) {
        var width = data.size - 2 * margin + 100;
        resizeVideoPlayers(width);
        $timelineLarge.options({
            width: width
        });
        $timelineSmall.options({
            width: width
        });
    }

    function resizePlayers() {
        self.$player.forEach(function(v, i) {
            v.options({
                width: size[i].width,
                height: size[i].height
            })
            .css({
                left: size[i].left + 'px',
                top: size[i].top + 'px',
            });
        });
    }

    function selectAnnotation(data) {
        setPosition(data['in']);
        setPoint('in', data['in']);
        setPoint('out', data.out);
    }
    function updateAnnotation(data) {
        data['in'] = self.options['in'];
        data.out = self.options.out;
        that.triggerEvent('updateannotation', data);
    }

    function select(type) {
        self.options.points = getPoints(type);
        setPoints();
    }

    function setPoint(point, position) {
        var otherPoint = point == 'in' ? 'out' : 'in';
        self.options[point] = position;
        self.$player.forEach(function($player) {
            $player.options(point, self.options[point]);
        });
        self.$player[point == 'in' ? 1 : 2].options({
            position: self.options[point]
        });
        self.$timeline.forEach(function($timeline) {
            $timeline.options(point, self.options[point]);
        });
        if (self.options['in'] > self.options.out) {
            setPoint(point == 'in' ? 'out' : 'in', position);
        }
        that.triggerEvent('points', {
            'in': self.options['in'],
            out: self.options.out
        });
    }

    function setPosition(position) {
        self.options.position = position;
        self.$player[0].options({
            position: self.options.position
        });
        self.$timeline.forEach(function(v) {
            v.options({
                position: self.options.position
            });
        });
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

    function setSizes() {
        self.sizes = getSizes();
        self.$player.forEach(function($player, i) {
            $player.options({
                height: self.sizes.player[i].height,
                width: self.sizes.player[i].width
            })
            .css({
                left: self.sizes.player[i].left + 'px',
                top: self.sizes.player[i].top + 'px'
            });
        });
        self.$timeline.forEach(function($timeline, i) {
            $timeline.options({
                width: self.sizes.timeline[i].width
            })
            .css({
                left: self.sizes.timeline[i].left + 'px',
                top: self.sizes.timeline[i].top + 'px'
            });
        });
    }

    function submitFindInput(value, hasPressedEnter) {
        self.options.find = value;
        self.results = find(self.options.find);
        self.$results
            .css({opacity: self.results.length ? 1 : 0.25})
            .html(self.results.length);
        self.$previousButton.options({
            disabled: self.results.length <= 1
        });
        self.$nextButton.options({
            disabled: self.results.length <= 1
        });
        self.$clearButton.options({
            disabled: !self.options.find
        });
        self.$player.forEach(function($player) {
            $player.options({find: self.options.find});
        });
        self.$timeline.forEach(function($timeline) {
            $timeline.options({find: self.options.find});
        });
        self.$timeline[1].options({results: self.results});
        if (hasPressedEnter) {
            that.triggerEvent('find', {find: self.options.find});
            if (self.results.length) {
                setPosition(getNextPosition('result', 1));
            } else {
                self.$findInput.focusInput(true);
            }
        }
    }

    function toggleAnnotations(data) {
        self.options.showAnnotations = !data.collapsed;
        setSizes();
        that.triggerEvent('toggleannotations', {
            showAnnotations: self.options.showAnnotations
        });
    }

    function toggleMuted() {
        self.$player[0].toggleMuted();
    }

    function togglePaused() {
        self.$player[0].togglePaused();
        /* fixme
        self.$player[0].options('paused') && that.triggerEvent('position', {

        });
        */
    }

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

    self.setOption = function(key, value) {
        if (key == 'width' || key == 'height') {
            Ox.Log('Video', 'XXXX setSizes', key, value, self.options.width, self.options.height)
            setSizes();
        } else if (key == 'position') {
            setPosition(value);
        } else if (key == 'showAnnotations') {
            that.$element.toggle(1);
        }
    };

    /*@
    addAnnotation <f> add annotation
        (layer, item) -> <o> add annotation to layer
        layer <s> layer id
        item <o> annotation to add
    @*/
    that.addAnnotation = function(layer, item) {
        var i = Ox.getPositionById(self.options.layers, layer);
        self.$annotationPanel[i].addItem(item);
    };

    /*@
    removeAnnotations <f> add annotation
        (layer, ids) -> <o> remove annotation from layer
        layer <s> layer id
        ids <a> array of item ids to remove
    @*/
    that.removeAnnotations = function(layer, ids) {
        var i = Ox.getPositionById(self.options.layers, layer);
        self.$annotationPanel[i].removeItems(ids);
    };

    return that;

};