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

/*@
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
@*/

Ox.VideoEditor = function(options, self) {

    var self = self || {},
        that = new Ox.Element({}, self)
            .defaults({
                annotationsSize: 0,
                cuts: [],
                duration: 0,
                find: '',
                frameURL: function() {},
                fps: 25,
                getFrameURL: null,
                getLargeTimelineImageURL: null,
                getSmallTimelineImageURL: null,
                'in': 0,
				height: 0,
                largeTimeline: true,
                layers: [],
                out: 0,
                position: 0,
                posterFrame: 0,
                showAnnotations: false,
                subtitles: [],
                videoHeight: 0,
                videoWidth: 0,
                videoSize: 'small',
                video: '',
                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();
                    });
                },
                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);
                }
            });

    $.extend(self, {
        $player: [],
        $timeline: [],
        controlsHeight: 16,
        margin: 8,
        videoRatio: self.options.videoWidth / self.options.videoHeight
    });

    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 = new 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] = new Ox.VideoPlayer({
                controlsBottom: type == 'play' ?
                    ['play', 'playInToOut', 'volume', 'space', 'position'] :
                    ['goto', 'set', 'space', 'position'],
                duration: self.options.duration,
                externalControls: true,
                find: self.options.find,
                height: self.sizes.player[i].height,
                id: 'player' + Ox.toTitleCase(type),
                'in': self.options['in'],
                out: self.options.out,
                paused: true,
                position: type == 'play' ? self.options.position : self.options[type],
                posterFrame: self.options.posterFrame,
                showMarkers: true,
                showMilliseconds: 3,
                subtitles: self.options.subtitles,
                type: type,
                video: type == 'play' ? self.options.video : self.options.getFrameURL,
                width: self.sizes.player[i].width
            })
            .css({
                left: self.sizes.player[i].left + 'px',
                top: self.sizes.player[i].top + 'px'
            })
            .bindEvent(type == 'play' ? {
                position: changePlayer,
                size: toggleSize
            } : {
                gotopoint: function() {
                    goToPoint(type);
                },
                position: function(data) {
                    setPoint(type, data.position);
                },
                setpoint: function() {
                    setPoint(type, self.options.position);
                }
            })
            .appendTo(self.$editor);
    });

    self.$timeline[0] = new 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] = new Ox.BlockVideoTimeline({
            cuts: self.options.cuts,
            duration: self.options.duration,
            find: self.options.find,
            getImageURL: self.options.getSmallTimelineImageURL,
            id: 'timelineSmall',
            //matches: self.options.matches,
            'in': self.options['in'],
            out: self.options.out,
            position: self.options.position,
            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 = new Ox.Element()
        .css({
            overflowY: 'auto'
        })
        .bindEvent({
            resize: resizeAnnotations,
            toggle: toggleAnnotations
        });
    self.$annotationPanel = [];

    self.options.layers.forEach(function(layer, i) {
        self.$annotationPanel[i] = new Ox.AnnotationPanel(
                $.extend({
                    width: self.options.annotationSize
                }, layer)
            )
            .bindEvent({
                add: function(event, data) {
                    data.layer = layer.id; 
                    data['in'] = self.options['in'];
                    data.out = self.options.out;
                    that.triggerEvent('addAnnotation', data);
                },
                'delete': function(event, data) {
                    data.layer = layer.id;
                    that.triggerEvent('removeAnnotations', data);
                },
                select: function(event, data) {
                    self.options.layers.forEach(function(l, j) { // fixme: l? j?
                        if(l.id != layer.id) {
                            self.$annotationPanel[j].deselectItems();
                        }
                    });
                    selectAnnotation(event, data);
                },
                submit: updateAnnotation
            });
        self.$annotationPanel[i]
            .appendTo(self.$annotations);
    });

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

    self.$sizeButton = Ox.Button({
            style: 'symbol',
            title: [
                {id: 'grow', title: 'grow', selected: self.options.size == 'small'},
                {id: 'shrink', title: 'shrink', selected: self.options.size == 'large'}
            ],
            tooltip: ['Larger', 'Smaller'],
            type: 'image'
        })
        .css({float: 'left'})
        .bindEvent({
            click: toggleSize
        })
        .appendTo(self.$menubar);

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

    self.$resolutionSelect = Ox.Select({
            items: [{id: '96', title: '96p'},{id: '240', title: '240p'}],//self.resolutions,
            width: 48
        })
        .css({float: 'left'})
        .bindEvent({
            change: function() {

            }
        })
        .appendTo(self.$menubar);
    

    //$('<div>').css({float: 'left', width: '8px', height: '1px'}).appendTo(self.$menubar.$element);

    self.$linkButton = Ox.Button({
            style: 'symbol',
            title: 'arrowRight',
            tooltip: 'Link...',
            type: 'image'
        })
        .css({float: 'left'})
        //.css({marginLeft: '16px'})
        .bindEvent({
            click: function() {
                window.location.hash =
                    Ox.formatDuration(self.options['in'], 3) + '-' +
                    Ox.formatDuration(self.options.out, 3);
            }
        })
        .appendTo(self.$menubar);

    self.$embedButton = Ox.Button({
            style: 'symbol',
            title: 'embed',
            tooltip: 'Embed...',
            type: 'image'
        })
        .css({float: 'left'})
        .bindEvent({
            click: function() {

            }
        })
        .appendTo(self.$menubar);

    self.$downloadButton = Ox.Button({
            style: 'symbol',
            title: 'download',
            tooltip: 'Download...',
            type: 'image'
        })
        .css({float: 'left'})
        .bindEvent({
            click: function() {

            }
        })
        .appendTo(self.$menubar);

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

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

    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.toggleDisabled();
            }
        })
        .appendTo(self.$menubar);

    self.$helpButton = Ox.Button({
            style: 'symbol',
            title: 'help',
            tooltip: 'Help',
            type: 'image'
        })
        .css({float: 'right'})
        .bindEvent({
            click: function() {

            }
        })
        .appendTo(self.$menubar);

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

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

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

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

    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.$menubar);

    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.$menubar);

    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.$menubar.$element);

    that.$element = Ox.SplitPanel({
        elements: [
            {
                element: Ox.SplitPanel({
                    elements: [
                        {
                            element: self.$menubar,
                            size: 16
                        },
                        {
                            element: self.$editor
                        }
                    ],
                    orientation: 'vertical'
                })
            },
            {
                collapsed: !self.options.showAnnotations,
                collapsible: true,
                element: self.$annotations,
                resizable: true,
                resize: [192, 256, 320, 384],
                size: self.options.annotationsSize
            }
        ],
        orientation: 'horizontal'
    });

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

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

    function changeTimelineSmall(event, data) {
        self.options.position = data.position;
        self.$player[0].options({
            position: data.position
        });
        self.$timeline[0].options({
            position: data.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 = $.map(self.results, function(v, i) {
                return v['in'];
            });
        } else if (type == 'subtitle') {
            positions = $.map(self.options.subtitles, function(v, i) {
                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.print('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.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.videoRatio) * 2/3),
            }
            size.player[0].height = Math.round(size.player[0].width / self.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.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.print('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]);
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

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

    function movePositionTo(type, direction) {
        setPosition(getNextPosition(type, direction));
        that.triggerEvent('position', {
            position: self.options.position
        });
    }

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

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

    function resizeEditor(event, data) {
        var width = data - 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(event, data) {
        setPosition(data['in']);
        setPoint('in', data['in']);
        setPoint('out', data.out);
    }
    function updateAnnotation(event, data) {
        data['in'] = self.options.points[0];
        data.out = self.options.points[1];
        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);
        }
    }

    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
            });
        });
    }

    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.$timeline[0].options({
            find: self.options.find,
        });
        self.$timeline[1].options({
            find: self.options.find,
            results: self.results
        });
        self.$findInput.find('input').css({
            opacity: self.results.length ? 1 : 0.25
        });
        if (hasPressedEnter) {
            self.results.length ?
                setPosition(getNextPosition('result', 1)) :
                self.$findInput.focusInput();
        }
    }

    function toggleAnnotations(event, data) {
        self.options.showAnnotations = !data.collapsed;
        setSizes();
    }

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

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

    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.print('XXXX setSizes', key, value, self.options.width, self.options.height)
            setSizes();
        } else if (key == 'position') {
            self.$player[0].position(value);
        }
    };

    that.addAnnotation = function(layer, item) {
        var i = Ox.getPositionById(self.options.layers, layer);
        self.$annotationPanel[i].addItem(item);
    }

    that.removeAnnotations = function(layer, ids) {
        var i = Ox.getPositionById(self.options.layers, layer);
        self.$annotationPanel[i].removeItems(ids);
    }

    return that;

};
