Show:
/**
 * @module Player
 */


/**
 * I am the type definition of an Annotation. An annotation is a user-generated content
 * which is associated with start and end time of the main video.
 * 
 * An annotation can hold any type of {{#crossLink "Resource"}}Resource{{/crossLink}}.
 *
 * Annotations are grouped in annotation sets, which are assigned to a user. Each user can have 0 or 1 annotation set.
 *
 * Annotations are managed by the {{#crossLink "AnnotationsController"}}AnnotationsController{{/crossLink}}.
 *
 * @class Annotation
 * @category TypeDefinition
 */



FrameTrail.defineType(

    'Annotation',


    function(data){

        this.data = data;

        this.resourceItem = FrameTrail.newObject(
            ('Resource' + data.type.charAt(0).toUpperCase() + data.type.slice(1)),
            data
        )

        this.timelineElement   = $('<div class="timelineElement"></div>');
        this.tileElement       = $('<div class="tileElement" data-type="'+data.type+'"></div>');
        this.annotationElement = $('<div class="annotationElement"></div>');
        this.previewElement    = $('<div class="previewElement"></div>');


    },

    {
        /** I hold the data object of an Annotation, which is stored in the {{#crossLink "Database"}}Database{{/crossLink}} and saved in one of the hypervideos's annotations files.
         * @attribute data
         * @type {}
         */
        data:                   {},

        /**
         * I hold the Resource object of the annotation.
         * @attribute resourceItem
         * @type Resource
         */
        resourceItem:           {},

        /**
         * 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 annotationElement (a jquery-enabled HTMLElement), which contains the actual conent of my {{#crossLink "Annotation/resourceItem:attribute"}}resourceItem{{/crossLink}}.
         * @attribute annotationElement
         * @type HTMLElement
         */
        annotationElement:      null,

        /**
         * I hold the previewElement (a jquery-enabled HTMLElement), which shows the actual conent of my {{#crossLink "Annotation/resourceItem:attribute"}}resourceItem{{/crossLink}} in a special preview area besides the video.
         * @attribute previewElement
         * @type HTMLElement
         */
        previewElement:         null,

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

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



        /**
         * I render my DOM elements into the DOM.
         *
         * I am called, when the Annotation is initialized. My counterpart ist {{#crossLink "Annotation/removeFromDOM:method"}}Annotation/removeFromDOM{{/crossLink}}.
         *
         * @method renderInDOM
         */
        renderInDOM: function () {

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

            ViewVideo.AnnotationTimeline.append(this.timelineElement);
            this.updateTimelineElement();            
        
            this.annotationElement.empty();
            this.annotationElement.append( this.resourceItem.renderContent() );
            ViewVideo.AnnotationContainer.append(this.annotationElement);

            this.previewElement.empty();
            this.previewElement.append( this.resourceItem.renderContent() );
            ViewVideo.AnnotationPreviewContainer.append(this.previewElement);


            ViewVideo.AnnotationTileSlider.append(this.tileElement);
            
            this.timelineElement.unbind('hover');
            this.tileElement.unbind('hover');
            this.tileElement.unbind('click')
            this.timelineElement.hover(this.brushIn.bind(this), this.brushOut.bind(this));
            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('AnnotationsController').openedAnnotation == self ) {
                    self.closeAnnotation();
                } else {
                    self.openAnnotation();
                }
            });

        },


        /**
         * I update the CSS of the {{#crossLink "Annotation/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 + '%'
            });

        },


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

            this.timelineElement.remove();
            this.tileElement.remove();
            this.annotationElement.remove();
            this.previewElement.remove();

        },



        /**
         * I am called when the mouse pointer is hovering over one of my two DOM elements.
         *
         * I use the Raphael.js library to draw connecting lines betweem my tileElement and my timelineElement.
         *
         * @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 two DOM elements.
         * @method brushOut
         */
        brushOut: function () {

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

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

        },





        /**
         * 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');

            this.previewElement.addClass('active');
            
            if ( this.data.type == 'location' && this.previewElement.children('.resourceDetail').data('map') ) {
                this.previewElement.children('.resourceDetail').data('map').updateSize();
            }

        },


        /**
         * 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');

            this.previewElement.removeClass('active');

        },


        /**
         * An annotation can be "opened" and "closed".
         *
         * When I am called, I open the annotation, which means:
         * * I set the current play position to my data.start value
         * * I tell the {{#crossLink "AnnotationsController/openedAnnotation:attribute"}}AnnotationsController{{/crossLink}} to set me as the "openedAnnotation"
         *
         * @method openAnnotation
         * @return 
         */
        openAnnotation: function () {

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

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

            FrameTrail.module('AnnotationsController').openedAnnotation = this;
            
            this.timelineElement.addClass('open');
            this.tileElement.addClass('open');

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


        },

        /**
         * I tell the {{#crossLink "AnnotationsController/openedAnnotation:attribute"}}AnnotationsController{{/crossLink}} to set "openedAnnotation" to null.
         * @method closeAnnotation
         */
        closeAnnotation: function () {

            FrameTrail.module('AnnotationsController').openedAnnotation = null;

        },


        /**
         * I am called when the app switches to the editMode "annotations".
         *
         * I make sure
         * * that my {{#crossLink "Annotation/timelineElement:attribute"}}timelineElement{{/crossLink}} is resizable and draggable
         * * that my elements have click handlers for putting myself into focus.
         *
         * @method startEditing
         */
        startEditing: function () {


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

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

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

                if (AnnotationsController.annotationInFocus === self){
                    return AnnotationsController.annotationInFocus = null;
                }

                self.permanentFocusState = true;
                AnnotationsController.annotationInFocus = self;

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

            });
            

        },

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

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

            this.timelineElement.unbind('click');


        },


        /**
         * I make my {{#crossLink "Overlay/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('AnnotationsController').annotationInFocus = self;
                    }
                    
                },

                stop: function(event, ui) {

                    if (!self.permanentFocusState) {
                        FrameTrail.module('AnnotationsController').annotationInFocus = 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('AnnotationsController').stackTimelineView();

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

        },

        /**
         * I make my {{#crossLink "Annotation/timelineElement:attribute"}}timelineElement{{/crossLink}} resizable.
         * 
         * The event handling changes my this.data.start and this.data.end attributes
         * accordingly.
         *
         * @method makeTimelineElementResizeable
         * @return 
         */
        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('AnnotationsController').annotationInFocus = self;
                    }

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

                stop: function(event, ui) {
                    
                    if (!self.permanentFocusState) {
                        FrameTrail.module('AnnotationsController').annotationInFocus = 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('AnnotationsController').stackTimelineView();

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

        },


        /**
         * When I "got into focus" (which happens, when I become the referenced object in the AnnotationsController's
         * {{#crossLink "AnnotationsController/annotationInFocus:attribute"}}annotationInFocus 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 id="PreviewThumbContainer"></div>'
                                     + '    <button id="DeleteAnnotation">Delete</button>'
                                     + '</div>');

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

                    FrameTrail.module('AnnotationsController').deleteAnnotation(self);

                });

                propertiesControls.find('#PreviewThumbContainer').append(this.resourceItem.renderThumb());
                
            EditPropertiesContainer.addClass('active').append(propertiesControls);

            this.timelineElement.addClass('highlighted');


        },


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

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

            this.timelineElement.removeClass('highlighted');


        },


        /**
         * When the global state editMode is "annotations", the user can also choose to create
         * new annotations from other user's annotations (which makes a copy of that data and
         * places a new annotation in the user's collection of his/her own annotations).
         *
         * These annotation timelines from other users are called "compare timelines" (in contrast to the user's own timeline),.
         *
         * For this purpose, I create a special, jquery-enabled HTMLElement, which carries
         * all the necessary information to create a new annotation in its data attributes. The
         * returned element is draggable, and ready to be 
         * {{#crossLink "AnnotationsController:makeTimelineDroppable:method"}}dropped onto the annotation timeline{{/crossLink}}.
         *
         * @method renderCompareTimelineItem
         * @return HTMLElement
         */
        renderCompareTimelineItem: function() {

            var compareTimelineElement = $(
                    '<div class="compareTimelineElement" '
                +   ' data-start="'
                +   this.data.start
                +   '" data-end="'
                +   this.data.end
                +   '" data-resourceId="'
                +   this.data.resourceId
                +   '">'
                +   '    <div class="previewWrapper">'
                +   '    </div>'
                +   '</div>'
            ),

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

            compareTimelineElement.css({
                left:  positionLeft + '%',
                width: width + '%'
            });

            compareTimelineElement.find('.previewWrapper').append(
                this.resourceItem.renderThumb()
            );

            compareTimelineElement.draggable({
                containment:    '#MainContainer',
                axis:           'y',
                helper:         'clone',
                appendTo:       'body',
                zIndex:         1000,

                start: function( event, ui ) {
                    ui.helper.css({
                        left: $(event.currentTarget).offset().left + "px",
                        width: $(event.currentTarget).width() + "px",
                        height: $(event.currentTarget).height() + "px",
                        backgroundColor: $(event.currentTarget).css('background-color')
                    });
                },
                
                drag: function( event, ui ) {
                    ui.helper.css({
                        top: ui.position.top + ($('#SlideArea').css('margin-top')*2) + "px"
                    });
                }

            });


            return compareTimelineElement;

        }





    }

);