Show:
/**
 * @module Player
 */


/**
 * I am the type definition of a Videolink. 
 *
 * A video link is classical hyperlink (to either another video or any other resource locatable bei an URL)
 * and which has a start and a end time related to the time base of the main video.
 *
 * Videolinks are managed by the {{#crossLink "VideolinksController"}}VideolinksController{{/crossLink}}.
 *
 * @class Videolink
 * @category TypeDefinition
 */



FrameTrail.defineType(

    'Videolink',

    function(data){

        this.data = data;

        this.timelineElement  = $('<div class="timelineElement"></div>');
        this.tileElement      = $('<div class="tileElement"></div>');
        this.videolinkElement = $('<div class="videolinkElement"></div>');


    },

    {
        
        /**
         * I hold the data object of a Videolink, which is stored in the {{#crossLink "Database"}}Database{{/crossLink}} and saved in the hypervideos's links.json file.
         * @attribute data
         * @type {}
         */
        data:                   {},

        /**
         * I hold the timelineElement (a jquery-enabled HTMLElement), which indicates my start and end time.
         * @attribute timelineElement
         * @type HTMLElement
         */
        timelineElement:        null,
        
        /**
         * I hold the tileElement (a jquery-enabled HTMLElement), which shows a icon for me close to my position in the timeline.
         * @attribute tileElement
         * @type HTMLElement
         */
        tileElement:            null,
        
        /**
         * I hold the videolinkElement (a jquery-enabled HTMLElement), which shows the linked content in an iframe.
         * @attribute videolinkElement
         * @type HTMLElement
         */
        videolinkElement:       null,

        /**
         * I store my state, wether I am "active" (this is, when my timelineElement and tileElements are highlighted) or not.
         * @attribute activeState
         * @type Boolean
         */
        activeState:            false,
        

        /**
         * I store my state, wether I am "in focus" or not. See also:
         * * {{#crossLink "Videolink/gotInFocus:method"}}Videolink/gotInFocus(){{/crossLink}}
         * * {{#crossLink "Videolink/removedFromFocus:method"}}Videolink/removedFromFocus(){{/crossLink}}
         * * {{#crossLink "VideolinksController/videolinkInFocus:attribute"}}VideolinksController/videolinkInFocus{{/crossLink}}
         * @attribute permanentFocusState
         * @type Boolean
         */
        permanentFocusState:    false,
        

        /**
         * I render my ({{#crossLink "Videolink/timelineElement:attribute"}}this.timelineElement{{/crossLink}}
         * into the DOM.
         *
         * I am called, when the Videolink is initialized.
         *
         * The tileElement and videolinkElement however are rendere separately into the DOM (see 
         * {{#crossLink "Videolink/renderTilesAndContentInDOM:method"}}this.renderTilesAndContentInDOM{{/crossLink}}),
         * because their order in the DOM tree has to be sorted by this.data.start after each change.
         *
         * @method renderTimelineInDOM
         */
        renderTimelineInDOM: function () {

            var ViewVideo = FrameTrail.module('ViewVideo');

            this.timelineElement.unbind('hover');
            this.timelineElement.hover(this.brushIn.bind(this), this.brushOut.bind(this));

            ViewVideo.VideolinkTimeline.append(this.timelineElement);
            this.updateTimelineElement();
            
            
        },

        /**
         * I render my ({{#crossLink "Videolink/tileElement:attribute"}}this.tileElement{{/crossLink}}
         * and my {{#crossLink "Videolink/videolinkElement:attribute"}}this.videolinkElement{{/crossLink}} into the DOM.
         *
         * I am called, when the Videolink is initialized, and also every time, when the global state "editMode" leaves the state 
         * "links". This is the case, when the user has finished his/her changes to the Videolinks. All tiles and content
         * containers have to be re-rendered into the DOM, because they need to sorted by ascending value of this.data.start.
         *
         * @method renderTilesAndContentInDOM
         */
        renderTilesAndContentInDOM: function () {

            var ViewVideo = FrameTrail.module('ViewVideo'),
                iFrame    = $('<iframe frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>'),
                linkElem  = $('<a class="videolinkAnchor" href="'+ this.data.href +'">Go to Hypervideo</a>');

            this.videolinkElement.empty();
            this.videolinkElement.append(iFrame, linkElem);

            ViewVideo.VideolinkTileSlider.append(this.tileElement);
            ViewVideo.VideolinkContainer.append(this.videolinkElement);

            this.tileElement.unbind('hover');
            this.tileElement.unbind('click')
            this.tileElement.hover(this.brushIn.bind(this), this.brushOut.bind(this));
            
            // self = this necessary as self can not be kept in anonymous handler function 
            var self = this;
            this.tileElement.click(function() {
                if ( FrameTrail.module('VideolinksController').openedLink == self ) {
                    self.closeVideolink();
                } else {
                    self.openVideolink();
                }
            });
            
        },


        /**
         * I remove all my elements from the DOM. I am called when a Videolink is to be deleted.
         * @method removeFromDOM
         */
        removeFromDOM: function () {

            this.timelineElement.remove();
            this.tileElement.remove();
            this.videolinkElement.remove();

        },

        /**
         * I update the CSS of the {{#crossLink "Videolink/timelineElement:attribute"}}timelineElement{{/crossLink}}
         * to its correct position within the timeline.
         *
         * @method updateTimelineElement
         */
        updateTimelineElement: function () {

            var videoDuration   = FrameTrail.module('HypervideoModel').duration,
                positionLeft    = 100 * (this.data.start / videoDuration),
                width           = 100 * ((this.data.end - this.data.start) / videoDuration);

            this.timelineElement.css({
                top: '',
                left:  positionLeft + '%',
                right: '',
                width: width + '%'
            });

        },

        

        /**
         * When I am scheduled to be displayed, this is the method to be called.
         * @method setActive
         */
        setActive: function () {

            this.activeState = true;

            this.timelineElement.addClass('active');
            this.tileElement.addClass('active');

        },

        /**
         * When I am scheduled to disappear, this is the method to be called.
         * @method setInactive
         */
        setInactive: function () {

            this.activeState = false;

            this.timelineElement.removeClass('active');
            this.tileElement.removeClass('active');

        },


        /**
         * I am called when the mouse pointer is hovering over one of my tile or my timeline element
         * @method brushIn
         */
        brushIn: function () {

            this.timelineElement.addClass('brushed');
            this.tileElement.addClass('brushed');

            if ( FrameTrail.getState('editMode') == false || FrameTrail.getState('editMode') == 'preview' ) {
                clearRaphael();
                drawConnections( this.tileElement, this.timelineElement, 10, {stroke: "#6B7884"} );
            }

        },

        /**
         * I am called when the mouse pointer is leaving the hovering area over my tile or my timeline element.
         * @method brushOut
         */
        brushOut: function () {

            this.timelineElement.removeClass('brushed');
            this.tileElement.removeClass('brushed');

            if ( (FrameTrail.getState('editMode') ==  false || FrameTrail.getState('editMode') ==  'preview') ) {
                clearRaphael();
            }

        },


        /**
         * A video link can be "opened" and "closed".
         *
         * When I am called, I open the video link, which means:
         * * I set the current play position to my data.start value
         * * I tell the {{#crossLink "VideolinksController/openedLink:attribute"}}VideolinksController{{/crossLink}} to set me as the "openedLink".
         *
         * @method openVideolink
         */
        openVideolink: function () {

            var ViewVideo = FrameTrail.module('ViewVideo');


            FrameTrail.module('HypervideoController').currentTime = this.data.start;

            FrameTrail.module('VideolinksController').openedLink = this;

            //ViewVideo.ExpandButton.one('click', this.closeVideolink.bind(this));


        },

        /**
         * I tell the {{#crossLink "VideolinksController/openedLink:attribute"}}VideolinksController{{/crossLink}} to set "openedLink" to null.
         * @method closeVideolink
         * @return 
         */
        closeVideolink: function () {

            FrameTrail.module('VideolinksController').openedLink = null;

        },



        /**
         * I am called when the app switches to the editMode "links".
         *
         * I make sure
         * * that my {{#crossLink "Videolink/timelineElement:attribute"}}timelineElement{{/crossLink}} is resizable and draggable
         * * and that it has a click handler for putting myself into focus.
         *
         * @method startEditing
         */
        startEditing: function () {


            var self = this,
                VideolinksController = FrameTrail.module('VideolinksController');

            this.makeTimelineElementDraggable();
            this.makeTimelineElementResizeable();

            this.timelineElement.on('click', function(){

                if (VideolinksController.videolinkInFocus === self){
                    return VideolinksController.videolinkInFocus = null;
                }

                self.permanentFocusState = true;
                VideolinksController.videolinkInFocus = self;

                FrameTrail.module('HypervideoController').currentTime = self.data.start;

            });
            

        },

        /**
         * When the global editMode leaves the state "links", I am called to 
         * stop the editing features of the video link.
         *
         * @method stopEditing
         */
        stopEditing: function () {

            this.timelineElement.draggable('destroy');
            this.timelineElement.resizable('destroy');

            this.timelineElement.unbind('click');


        },


        /**
         * I make my {{#crossLink "Videolink/timelineElement:attribute"}}timelineElement{{/crossLink}} draggable.
         * 
         * The event handling changes my this.data.start and this.data.end attributes
         * accordingly.
         *
         * @method makeTimelineElementDraggable
         */
        makeTimelineElementDraggable: function () {

            var self = this;


            this.timelineElement.draggable({
                
                axis:        'x',
                containment: 'parent',
                snapTolerance: 10,

                drag: function(event, ui) {
                    
                    var closestGridline = FrameTrail.module('ViewVideo').closestToOffset($('.gridline'), {
                            left: ui.position.left,
                            top: ui.position.top
                        }),
                        snapTolerance = $(this).draggable('option', 'snapTolerance');

                    if (closestGridline) {
                        
                        $('.gridline').css('background-color', '#ff9900');

                        if ( ui.position.left - snapTolerance < closestGridline.position().left &&
                             ui.position.left + snapTolerance > closestGridline.position().left ) {

                            ui.position.left = closestGridline.position().left;

                            closestGridline.css('background-color', '#00ff00');

                        }
                    }

                    var videoDuration = FrameTrail.module('HypervideoModel').duration,
                        leftPercent   = 100 * (ui.helper.position().left / ui.helper.parent().width()),
                        newStartValue = leftPercent * (videoDuration / 100);

                    FrameTrail.module('HypervideoController').currentTime = newStartValue;    
                    
                },

                start: function(event, ui) {

                    if (!self.permanentFocusState) {
                        FrameTrail.module('VideolinksController').videolinkInFocus = self;
                    }
                    
                },

                stop: function(event, ui) {

                    if (!self.permanentFocusState) {
                        FrameTrail.module('VideolinksController').videolinkInFocus = null;
                    }
                    

                    var videoDuration = FrameTrail.module('HypervideoModel').duration,
                        leftPercent   = 100 * (ui.helper.position().left / ui.helper.parent().width()),
                        widthPercent  = 100 * (ui.helper.width() / ui.helper.parent().width());
                    
                    self.data.start = leftPercent * (videoDuration / 100);
                    self.data.end   = (leftPercent + widthPercent) * (videoDuration / 100);

                    self.updateTimelineElement();

                    FrameTrail.module('VideolinksController').stackTimelineView();

                    FrameTrail.module('HypervideoModel').newUnsavedChange('links');
                    
                }
            });

        },

        /**
         * I make my {{#crossLink "Videolink/timelineElement:attribute"}}timelineElement{{/crossLink}} resizable.
         * 
         * The event handling changes my this.data.start and this.data.end attributes
         * accordingly.
         *
         * @method makeTimelineElementResizeable
         */
        makeTimelineElementResizeable: function () {

            var self = this,
                endHandleGrabbed;


            this.timelineElement.resizable({
                
                containment: 'parent',
                handles:     'e, w',

                resize: function(event, ui) {
                    
                    var closestGridline = FrameTrail.module('ViewVideo').closestToOffset($('.gridline'), {
                            left: (endHandleGrabbed ? (ui.position.left + ui.helper.width()) : ui.position.left),
                            top: ui.position.top
                        }),
                        snapTolerance = $(this).draggable('option', 'snapTolerance');

                    if (closestGridline) {
                        
                        $('.gridline').css('background-color', '#ff9900');

                        if ( !endHandleGrabbed && 
                             ui.position.left - snapTolerance < closestGridline.position().left &&
                             ui.position.left + snapTolerance > closestGridline.position().left ) {

                            ui.position.left = closestGridline.position().left;
                            ui.size.width = ( ui.helper.width() + ( ui.helper.position().left - ui.position.left ) );

                            closestGridline.css('background-color', '#00ff00');

                        } else if ( endHandleGrabbed &&
                                    ui.position.left + ui.helper.width() - snapTolerance < closestGridline.position().left &&
                                    ui.position.left + ui.helper.width() + snapTolerance > closestGridline.position().left ) {
                        
                            ui.helper.width(closestGridline.position().left - ui.position.left);

                            closestGridline.css('background-color', '#00ff00');

                        }
                    }


                    var videoDuration = FrameTrail.module('HypervideoModel').duration,
                        leftPercent   = 100 * (ui.position.left / ui.helper.parent().width()),
                        widthPercent  = 100 * (ui.helper.width() / ui.helper.parent().width()),
                        newValue;

                    if ( endHandleGrabbed ) {

                        newValue = (leftPercent + widthPercent) * (videoDuration / 100);
                        FrameTrail.module('HypervideoController').currentTime = newValue;

                    } else {

                        newValue = leftPercent * (videoDuration / 100);
                        FrameTrail.module('HypervideoController').currentTime = newValue;

                    }
                    
                },

                start: function(event, ui) {

                    if (!self.permanentFocusState) {
                        FrameTrail.module('VideolinksController').videolinkInFocus = self;
                    }

                    endHandleGrabbed = $(event.originalEvent.target).hasClass('ui-resizable-e')
                    
                },

                stop: function(event, ui) {
                    
                    if (!self.permanentFocusState) {
                        FrameTrail.module('VideolinksController').videolinkInFocus = null;
                    }


                    var videoDuration = FrameTrail.module('HypervideoModel').duration,
                        leftPercent  = 100 * (ui.helper.position().left / ui.helper.parent().width()),
                        widthPercent = 100 * (ui.helper.width() / ui.helper.parent().width());

                    
                    self.data.start = leftPercent * (videoDuration / 100);
                    self.data.end   = (leftPercent + widthPercent) * (videoDuration / 100);

                    FrameTrail.module('VideolinksController').stackTimelineView();

                    FrameTrail.module('HypervideoModel').newUnsavedChange('links');
                    
                }
            });

        },


        /**
         * When I "got into focus" (which happens, when I become the referenced object in the VideolinksController's
         * {{#crossLink "VideolinksController/videolinkInFocus:attribute"}}videolinkInFocus attribute{{/crossLink}}),
         * then this method will be called.
         * 
         * @method gotInFocus
         */
        gotInFocus: function () {

            var EditPropertiesContainer = FrameTrail.module('ViewVideo').EditPropertiesContainer,
                self = this;

            EditPropertiesContainer.empty();
            
            var propertiesControls = $('<div>'
                                     + '    <div class="propertiesTypeIcon" data-type="videolink"></div>'
                                     + '    <div>Title:</div>'
                                     + '    <div>' + this.data.name + '</div><br>'
                                     + '    <div>Link:</div>'
                                     + '    <div><input id="VideolinkHref" type="text" value="'+ this.data.href +'"></div><br>'
                                     + '    <button id="DeleteVideolink">Delete</button>'
                                     + '</div>');

                propertiesControls.find('#VideolinkHref').change(function() {

                    self.data.href = $(this).val();
                    
                    FrameTrail.module('HypervideoModel').newUnsavedChange('links');

                });

                propertiesControls.find('#DeleteVideolink').click(function() {

                    FrameTrail.module('VideolinksController').deleteVideolink(self);

                });

            EditPropertiesContainer.addClass('active').append(propertiesControls);

            this.timelineElement.addClass('highlighted');


        },


        /**
         * See also: {{#crossLink "Videolink/gotIntoFocus:method"}}this.gotIntoFocus(){{/crossLink}}
         *
         * When I was "removed from focus" (which happens, when the VideolinksController's
         * {{#crossLink "VideolinksController/videolinkInFocus:attribute"}}videolinkInFocus attribute{{/crossLink}}),
         * is set either to null or to an other Videolink than myself),
         * then this method will be called.
         *
         * @method removedFromFocus
         */
        removedFromFocus: function () {

            FrameTrail.module('ViewVideo').EditPropertiesContainer.removeClass('active').empty();

            this.timelineElement.removeClass('highlighted');


        }



    }

);