/* 
 *  Copyright 2012 Institut de recherche et d'innovation 
 *  contributor(s) : Raphael Velt, Karim Hamidou, Samuel Huron, Thibaut Cavalie, Anthony Ly
 *   
 *  contact@iri.centrepompidou.fr
 *  http://www.iri.centrepompidou.fr 
 *   
 *  This software is a computer program whose purpose is to show and add annotations on a video .
 *  This software is governed by the CeCILL-C license under French law and
 *  abiding by the rules of distribution of free software. You can  use, 
 *  modify and/ or redistribute the software under the terms of the CeCILL-C
 *  license as circulated by CEA, CNRS and INRIA at the following URL
 *  "http://www.cecill.info". 
 *  
 *  The fact that you are presently reading this means that you have had
 *  knowledge of the CeCILL-C license and that you accept its terms.
*/

if (typeof window.IriSP === "undefined") {
    window.IriSP = {};
}

IriSP.serializers = {};

IriSP._ = _;

IriSP.jQuery = jQuery;

/* COLOR SCHEMES
 * Color Brewer Set1 (9 classes): [ "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999" ]
 * Color Brewer Set3 (12 classes): ["#8DD3C7", "#FFFFB3", "#BEBADA", "#FB8072", "#80B1D3", "#FDB462", "#B3DE69", "#FCCDE5", "#D9D9D9", "#BC80BD", "#CCEBC5", "#FFED6F"]
 * Color Brewer Paired (11 classes): ["#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#FF7F00", "#CAB2D6", "#6A3D9A", "#FFFF99" ]
 * d3 Category10: ["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"];
 * d3 Category20: ["#1f77b4 ", "#aec7e8 ", "#ff7f0e ", "#ffbb78 ", "#2ca02c ", "#98df8a ", "#d62728 ", "#ff9896 ", "#9467bd ", "#c5b0d5 ", "#8c564b ", "#c49c94 ", "#e377c2 ", "#f7b6d2 ", "#7f7f7f ", "#c7c7c7 ", "#bcbd22 ", "#dbdb8d ", "#17becf ", "#9edae5"];
 * d3 Category20b: ["#393b79 ", "#5254a3 ", "#6b6ecf ", "#9c9ede ", "#637939 ", "#8ca252 ", "#b5cf6b ", "#cedb9c ", "#8c6d31 ", "#bd9e39 ", "#e7ba52 ", "#e7cb94 ", "#843c39 ", "#ad494a ", "#d6616b ", "#e7969c ", "#7b4173 ", "#a55194 ", "#ce6dbd ", "#de9ed6"];
 * d3 Category20c: ["#3182bd ", "#6baed6 ", "#9ecae1 ", "#c6dbef ", "#e6550d ", "#fd8d3c ", "#fdae6b ", "#fdd0a2 ", "#31a354 ", "#74c476 ", "#a1d99b ", "#c7e9c0 ", "#756bb1 ", "#9e9ac8 ", "#bcbddc ", "#dadaeb ", "#636363 ", "#969696 ", "#bdbdbd ", "#d9d9d9"];
 */

IriSP.vizcolors = ["#1f77b4 ", "#aec7e8 ", "#ff7f0e ", "#ffbb78 ", "#2ca02c ", "#98df8a ", "#d62728 ", "#ff9896 ", "#9467bd ", "#c5b0d5 ", "#8c564b ", "#c49c94 ", "#e377c2 ", "#f7b6d2 ", "#7f7f7f ", "#c7c7c7 ", "#bcbd22 ", "#dbdb8d ", "#17becf ", "#9edae5"];

/* END init.js */
/* TODO: Separate Project-specific data from Source */

/* model.js is where data is stored in a standard form, whatever the serializer */
IriSP.Model = (function (ns) {
    
    function pad(n, x, b) {
        b = b || 10;
        var s = (x).toString(b);
        while (s.length < n) {
            s = "0" + s;
        }
        return s;
    }
    
    function rand16(n) {
        return pad(n, Math.floor(Math.random()*Math.pow(16,n)), 16);
    }
    
    var uidbase = rand16(8) + "-" + rand16(4) + "-", uidincrement = Math.floor(Math.random()*0x10000);

var Model = {
    _SOURCE_STATUS_EMPTY : 0,
    _SOURCE_STATUS_WAITING : 1,
    _SOURCE_STATUS_READY : 2,
    getUID : function() {
        return uidbase + pad(4, (++uidincrement % 0x10000), 16) + "-" + rand16(4) + "-" + rand16(6) + rand16(6);
    },
    isLocalURL : function(url) {
        var matches = url.match(/^(\w+:)\/\/([^/]+)/);
        if (matches) {
            return(matches[1] === document.location.protocol && matches[2] === document.location.host)
        }
        return true;
    },
    regexpFromTextOrArray : function(_textOrArray, _testOnly, _iexact) {
        var _testOnly = _testOnly || false,
            _iexact = _iexact || false;
        function escapeText(_text) {
            return _text.replace(/([\\\*\+\?\|\{\[\}\]\(\)\^\$\.\#\/])/gm, '\\$1');
        }
        var _source = 
            typeof _textOrArray === "string"
            ? escapeText(_textOrArray)
            : ns._(_textOrArray).map(escapeText).join("|"),
            _flags = 'im';
        if (!_testOnly) {
            _source = '(' + _source + ')';
            _flags += 'g';
        }
        if (_iexact) {
            _source = '^' + _source + '$';
        }
        return new RegExp( _source, _flags);
    },
    isoToDate : function(_str) {
        // http://delete.me.uk/2005/03/iso8601.html
        var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
        var d = _str.match(new RegExp(regexp));
    
        var offset = 0;
        var date = new Date(d[1], 0, 1);
    
        if (d[3]) { date.setMonth(d[3] - 1); }
        if (d[5]) { date.setDate(d[5]); }
        if (d[7]) { date.setHours(d[7]); }
        if (d[8]) { date.setMinutes(d[8]); }
        if (d[10]) { date.setSeconds(d[10]); }
        if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
        if (d[14]) {
            offset = (Number(d[16]) * 60) + Number(d[17]);
            offset *= ((d[15] == '-') ? 1 : -1);
        }
    
        offset -= date.getTimezoneOffset();
        time = (Number(date) + (offset * 60 * 1000));
        var _res = new Date();
        _res.setTime(Number(time));
        return _res;
    },
    dateToIso : function(_d) {
        var d = _d ? new Date(_d) : new Date();
        return d.getUTCFullYear()+'-'  
            + pad(2, d.getUTCMonth()+1)+'-'  
            + pad(2, d.getUTCDate())+'T'  
            + pad(2, d.getUTCHours())+':'  
            + pad(2, d.getUTCMinutes())+':'  
            + pad(2, d.getUTCSeconds())+'Z'  
    }
}

/*
 * Model.List is a class for a list of elements (e.g. annotations, medias, etc. that each have a distinct ID)
 */
Model.List = function(_directory) {
    Array.call(this);
    this.directory = _directory;
    this.idIndex = [];
    this.__events = {};
    if (typeof _directory == "undefined") {
        console.trace();
        throw "Error : new Model.List(directory): directory is undefined";
    }
}

Model.List.prototype = new Array();

Model.List.prototype.hasId = function(_id) {
    return ns._(this.idIndex).include(_id);
}

/* On recent browsers, forEach and map are defined and do what we want.
 * Otherwise, we'll use the Underscore.js functions
 */
if (typeof Array.prototype.forEach === "undefined") {
    Model.List.prototype.forEach = function(_callback) {
        var _this = this;
        ns._(this).forEach(function(_value, _key) {
            _callback(_value, _key, _this);
        });
    }
}

if (typeof Array.prototype.map === "undefined") {
    Model.List.prototype.map = function(_callback) {
        var _this = this;
        return ns._(this).map(function(_value, _key) {
            return _callback(_value, _key, _this);
        });
    }
}

Model.List.prototype.pluck = function(_key) {
    return this.map(function(_value) {
        return _value[_key];
    });
}

/* We override Array's filter function because it doesn't return an Model.List
 */
Model.List.prototype.filter = function(_callback) {
    var _this = this,
        _res = new Model.List(this.directory);
    _res.addElements(ns._(this).filter(function(_value, _key) {
        return _callback(_value, _key, _this);
    }));
    return _res;
}

Model.List.prototype.slice = function(_start, _end) {
    var _res = new Model.List(this.directory);
    _res.addElements(Array.prototype.slice.call(this, _start, _end));
    return _res;
}

Model.List.prototype.splice = function(_start, _end) {
    var _res = new Model.List(this.directory);
    _res.addElements(Array.prototype.splice.call(this, _start, _end));
    this.idIndex.splice(_start, _end);
    return _res;
}

/* Array has a sort function, but it's not as interesting as Underscore.js's sortBy
 * and won't return a new Model.List
 */
Model.List.prototype.sortBy = function(_callback) {
    var _this = this,
        _res = new Model.List(this.directory);
    _res.addElements(ns._(this).sortBy(function(_value, _key) {
        return _callback(_value, _key, _this);
    }));
    return _res;
}

/* Title and Description are basic information for (almost) all element types,
 * here we can search by these criteria
 */
Model.List.prototype.searchByTitle = function(_text, _iexact) {
    var _iexact = _iexact || false,
        _rgxp = Model.regexpFromTextOrArray(_text, true);
    return this.filter(function(_element) {
        return _rgxp.test(_element.title);
    });
}

Model.List.prototype.searchByDescription = function(_text, _iexact) {
    var _iexact = _iexact || false,
        _rgxp = Model.regexpFromTextOrArray(_text, true);
    return this.filter(function(_element) {
        return _rgxp.test(_element.description);
    });
}

Model.List.prototype.searchByTextFields = function(_text, _iexact) {
    var _iexact = _iexact || false,
        _rgxp =  Model.regexpFromTextOrArray(_text, true);
    return this.filter(function(_element) {
        var keywords = (_element.keywords || _element.getTagTexts() || []).join(", ");
        return _rgxp.test(_element.description) || _rgxp.test(_element.title) || _rgxp.test(keywords);
    });
}

Model.List.prototype.getTitles = function() {
    return this.map(function(_el) {
        return _el.title;
    });
}

Model.List.prototype.addId = function(_id) {
    var _el = this.directory.getElement(_id)
    if (!this.hasId(_id) && typeof _el !== "undefined") {
        this.idIndex.push(_id);
        Array.prototype.push.call(this, _el);
    }
}

Model.List.prototype.push = function(_el) {
    if (typeof _el === "undefined") {
        return;
    }
    var _index = (ns._(this.idIndex).indexOf(_el.id));
    if (_index === -1) {
        this.idIndex.push(_el.id);
        Array.prototype.push.call(this, _el);
    } else {
        this[_index] = _el;
    }
}

Model.List.prototype.addIds = function(_array) {
    var _l = _array.length,
        _this = this;
    ns._(_array).forEach(function(_id) {
        _this.addId(_id);
    });
}

Model.List.prototype.addElements = function(_array) {
    var _this = this;
    ns._(_array).forEach(function(_el) {
        _this.push(_el);
    });
}

Model.List.prototype.removeId = function(_id, _deleteFromDirectory) {
    var _deleteFromDirectory = _deleteFromDirectory || false,
        _index = (ns._(this.idIndex).indexOf(_id));
    if (_index !== -1) {
        this.splice(_index,1);
    }
    if (_deleteFromDirectory) {
        delete this.directory.elements[_id];
    }
}

Model.List.prototype.removeElement = function(_el, _deleteFromDirectory) {
    var _deleteFromDirectory = _deleteFromDirectory || false;
    this.removeId(_el.id);
}

Model.List.prototype.removeIds = function(_list, _deleteFromDirectory) {
    var _deleteFromDirectory = _deleteFromDirectory || false,
        _this = this;
    ns._(_list).forEach(function(_id) {
        _this.removeId(_id);
    });
}

Model.List.prototype.removeElements = function(_list, _deleteFromDirectory) {
    var _deleteFromDirectory = _deleteFromDirectory || false,
        _this = this;
    ns._(_list).forEach(function(_el) {
        _this.removeElement(_el);
    });
}

Model.List.prototype.on = function(_event, _callback) {
    if (typeof this.__events[_event] === "undefined") {
        this.__events[_event] = [];
    }
    this.__events[_event].push(_callback);
}

Model.List.prototype.off = function(_event, _callback) {
    if (typeof this.__events[_event] !== "undefined") {
        this.__events[_event] = ns._(this.__events[_event]).reject(function(_fn) {
            return _fn === _callback;
        });
    }
}

Model.List.prototype.trigger = function(_event, _data) {
    var _list = this;
    ns._(this.__events[_event]).each(function(_callback) {
        _callback.call(_list, _data);
    });
}

/* A simple time management object, that helps converting millisecs to seconds and strings,
 * without the clumsiness of the original Date object.
 */

Model.Time = function(_milliseconds) {
    this.milliseconds = 0;
    this.setMilliseconds(_milliseconds);
}

Model.Time.prototype.setMilliseconds = function(_milliseconds) {
    var _ante = this.milliseconds;
    switch(typeof _milliseconds) {
        case "string":
            this.milliseconds = parseInt(_milliseconds);
            break;
        case "number":
            this.milliseconds = Math.floor(_milliseconds);
            break;
        case "object":
            this.milliseconds = parseInt(_milliseconds.valueOf());
            break;
        default:
            this.milliseconds = 0;
    }
    if (this.milliseconds === NaN) {
        this.milliseconds = _ante;
    }
}

Model.Time.prototype.setSeconds = function(_seconds) {
    this.milliseconds = 1000 * _seconds;
}

Model.Time.prototype.getSeconds = function() {
    return this.milliseconds / 1000;
}

Model.Time.prototype.getHMS = function() {
    var _totalSeconds = Math.abs(Math.floor(this.getSeconds()));
    return {
        hours : Math.floor(_totalSeconds / 3600),
        minutes : (Math.floor(_totalSeconds / 60) % 60),
        seconds : _totalSeconds % 60,
        milliseconds: this.milliseconds % 1000
    } 
}

Model.Time.prototype.add = function(_milliseconds) {
    this.milliseconds += new Model.Time(_milliseconds).milliseconds;
}

Model.Time.prototype.valueOf = function() {
    return this.milliseconds;
}

Model.Time.prototype.toString = function(showCs) {
    var _hms = this.getHMS(),
        _res = '';
    if (_hms.hours) {
        _res += _hms.hours + ':'
    }
    _res += pad(2, _hms.minutes) + ':' + pad(2, _hms.seconds);
    if (showCs) {
        _res += "." + Math.round(_hms.milliseconds / 100)
    }
    return _res;
}

/* Model.Reference handles references between elements
 */

Model.Reference = function(_source, _idRef) {
    this.source = _source;
    this.id = _idRef;
    if (typeof _idRef === "object") {
        this.isList = true;
    } else {
        this.isList = false;
    }
    this.refresh();
}

Model.Reference.prototype.refresh = function() {
    if (this.isList) {
        this.contents = new Model.List(this.source.directory);
        this.contents.addIds(this.id);
    } else {
        this.contents = this.source.getElement(this.id);
    }
    
}

Model.Reference.prototype.getContents = function() {
    if (typeof this.contents === "undefined" || (this.isList && this.contents.length != this.id.length)) {
        this.refresh();
    }
    return this.contents;
}

Model.Reference.prototype.isOrHasId = function(_idRef) {
    if (this.isList) {
        return (ns._(this.id).indexOf(_idRef) !== -1)
    } else {
        return (this.id == _idRef);
    }
}

/* */

Model.Element = function(_id, _source) {
    this.elementType = 'element';
    this.title = "";
    this.description = "";
    this.__events = {}
    if (typeof _source === "undefined") {
        return;
    }
    if (typeof _id === "undefined" || !_id) {
        _id = Model.getUID();
    }
    this.id = _id;
    this.source = _source;
    if (_source !== this) {
        this.source.directory.addElement(this);
    }
}

Model.Element.prototype.toString = function() {
    return this.elementType + (this.elementType !== 'element' ? ', id=' + this.id + ', title="' + this.title + '"' : '');
}

Model.Element.prototype.setReference = function(_elementType, _idRef) {
    this[_elementType] = new Model.Reference(this.source, _idRef);
}

Model.Element.prototype.getReference = function(_elementType) {
    if (typeof this[_elementType] !== "undefined") {
        return this[_elementType].getContents();
    }
}

Model.Element.prototype.getRelated = function(_elementType, _global) {
    _global = (typeof _global !== "undefined" && _global);
    var _this = this;
    return this.source.getList(_elementType, _global).filter(function(_el) {
        var _ref = _el[_this.elementType];
        return _ref && _ref.isOrHasId(_this.id);
    });
}

Model.Element.prototype.on = function(_event, _callback) {
    if (typeof this.__events[_event] === "undefined") {
        this.__events[_event] = [];
    }
    this.__events[_event].push(_callback);
}

Model.Element.prototype.off = function(_event, _callback) {
    if (typeof this.__events[_event] !== "undefined") {
        this.__events[_event] = ns._(this.__events[_event]).reject(function(_fn) {
            return _fn === _callback;
        });
    }
}

Model.Element.prototype.trigger = function(_event, _data) {
    var _element = this;
    ns._(this.__events[_event]).each(function(_callback) {
        _callback.call(_element, _data);
    });
}

/* */

Model.Playable = function(_id, _source) {
    Model.Element.call(this, _id, _source);
    if (typeof _source === "undefined") {
        return;
    }
    this.elementType = 'playable';
    this.currentTime = new Model.Time();
    this.volume = .5;
    this.paused = true;
    this.muted = false;
    var _this = this;
    this.on("play", function() {
        _this.paused = false;
    });
    this.on("pause", function() {
        _this.paused = true;
    });
    this.on("timeupdate", function(_time) {
        _this.currentTime = _time;
        _this.getAnnotations().filter(function(_a) {
            return (_a.end <= _time || _a.begin > _time) && _a.playing
        }).forEach(function(_a) {
            _a.playing = false;
            _a.trigger("leave");
            _this.trigger("leave-annotation",_a);
        });
        _this.getAnnotations().filter(function(_a) {
            return _a.begin <= _time && _a.end > _time && !_a.playing
        }).forEach(function(_a) {
            _a.playing = true;
            _a.trigger("enter");
            _this.trigger("enter-annotation",_a);
        });
    });
}

Model.Playable.prototype = new Model.Element();

Model.Playable.prototype.getCurrentTime = function() { 
    return this.currentTime;
}

Model.Playable.prototype.getVolume = function() {
    return this.volume;
}

Model.Playable.prototype.getPaused = function() {
    return this.paused;
}

Model.Playable.prototype.getMuted = function() {
    return this.muted;
}

Model.Playable.prototype.setCurrentTime = function(_time) {
    this.trigger("setcurrenttime",_time);
}

Model.Playable.prototype.setVolume = function(_vol) {
    this.trigger("setvolume",_vol);
}

Model.Playable.prototype.setMuted = function(_muted) {
    this.trigger("setmuted",_muted);
}

Model.Playable.prototype.play = function() {
    this.trigger("setplay");
}

Model.Playable.prototype.pause = function() {
    this.trigger("setpause");
}

Model.Playable.prototype.show = function() {}

Model.Playable.prototype.hide = function() {}

/* */

Model.Media = function(_id, _source) {
    Model.Playable.call(this, _id, _source);
    this.elementType = 'media';
    this.duration = new Model.Time();
    this.video = '';
    var _this = this;
}

Model.Media.prototype = new Model.Playable();

/* Default functions to be overriden by players */
    
Model.Media.prototype.setDuration = function(_durationMs) {
    this.duration.setMilliseconds(_durationMs);
}

Model.Media.prototype.getAnnotations = function() {
    return this.getRelated("annotation");
}

Model.Media.prototype.getAnnotationsByTypeTitle = function(_title) {
    var _annTypes = this.source.getAnnotationTypes().searchByTitle(_title).pluck("id");
    if (_annTypes.length) {
        return this.getAnnotations().filter(function(_annotation) {
            return ns._(_annTypes).indexOf(_annotation.getAnnotationType().id) !== -1;
        });
    } else {
        return new Model.List(this.source.directory)
    }
}

/* */

Model.Tag = function(_id, _source) {
    Model.Element.call(this, _id, _source);
    this.elementType = 'tag';
}

Model.Tag.prototype = new Model.Element();

Model.Tag.prototype.getAnnotations = function() {
    return this.getRelated("annotation");
}

/* */
Model.AnnotationType = function(_id, _source) {
    Model.Element.call(this, _id, _source);
    this.elementType = 'annotationType';
}

Model.AnnotationType.prototype = new Model.Element();

Model.AnnotationType.prototype.getAnnotations = function() {
    return this.getRelated("annotation");
}

/* Annotation
 * */

Model.Annotation = function(_id, _source) {
    Model.Element.call(this, _id, _source);
    this.elementType = 'annotation';
    this.begin = new Model.Time();
    this.end = new Model.Time();
    this.tag = new Model.Reference(_source, []);
    this.playing = false;
    var _this = this;
    this.on("click", function() {
        _this.getMedia().setCurrentTime(_this.begin);
    });
}

Model.Annotation.prototype = new Model.Element();

Model.Annotation.prototype.setBegin = function(_beginMs) {
    this.begin.setMilliseconds(Math.max(0,_beginMs));
    this.trigger("change-begin");
    if (this.end < this.begin) {
        this.setEnd(this.begin);
    }
}

Model.Annotation.prototype.setEnd = function(_endMs) {
    this.end.setMilliseconds(Math.min(_endMs, this.getMedia().duration.milliseconds));
    this.trigger("change-end");
    if (this.end < this.begin) {
        this.setBegin(this.end);
    }
}

Model.Annotation.prototype.setDuration = function(_durMs) {
    this.setEnd(_durMs + this.begin.milliseconds);
}

Model.Annotation.prototype.setMedia = function(_idRef) {
    this.setReference("media", _idRef);
}

Model.Annotation.prototype.getMedia = function() {
    return this.getReference("media");
}

Model.Annotation.prototype.setAnnotationType = function(_idRef) {
    this.setReference("annotationType", _idRef);
}

Model.Annotation.prototype.getAnnotationType = function() {
    return this.getReference("annotationType");
}

Model.Annotation.prototype.setTags = function(_idRefs) {
    this.setReference("tag", _idRefs);
}

Model.Annotation.prototype.getTags = function() {
    return this.getReference("tag");
}

Model.Annotation.prototype.getTagTexts = function() {
    return this.getTags().getTitles();
}

Model.Annotation.prototype.getDuration = function() {
    return new Model.Time(this.end.milliseconds - this.begin.milliseconds)
}

/* */

Model.MashedAnnotation = function(_mashup, _annotation) {
    Model.Element.call(this, _mashup.id + "_" + _annotation.id, _annotation.source);
    this.elementType = 'mashedAnnotation';
    this.annotation = _annotation;
    this.begin = new Model.Time();
    this.end = new Model.Time();
    this.duration = new Model.Time();
    this.title = this.annotation.title;
    this.description = this.annotation.description;
    this.color = this.annotation.color;
    var _this = this;
    this.on("click", function() {
        _mashup.setCurrentTime(_this.begin);
    });
    this.on("enter", function() {
        _this.annotation.trigger("enter");
    });
    this.on("leave", function() {
        _this.annotation.trigger("leave");
    });
}

Model.MashedAnnotation.prototype = new Model.Element(null);

Model.MashedAnnotation.prototype.getMedia = function() {
    return this.annotation.getReference("media");
}

Model.MashedAnnotation.prototype.getAnnotationType = function() {
    return this.annotation.getReference("annotationType");
}

Model.MashedAnnotation.prototype.getTags = function() {
    return this.annotation.getReference("tag");
}

Model.MashedAnnotation.prototype.getTagTexts = function() {
    return this.annotation.getTags().getTitles();
}

Model.MashedAnnotation.prototype.getDuration = function() {
    return this.annotation.getDuration();
}

Model.MashedAnnotation.prototype.setBegin = function(_begin) {
    this.begin.setMilliseconds(_begin);
    this.duration.setMilliseconds(this.annotation.getDuration());
    this.end.setMilliseconds(_begin + this.duration);
}

/* */

Model.Mashup = function(_id, _source) {
    Model.Playable.call(this, _id, _source);
    this.elementType = 'mashup';
    this.duration = new Model.Time();
    this.segments = new Model.List(_source.directory);
    this.loaded = false;
    var _this = this;
    this._updateTimes = function() {
        _this.updateTimes();
        _this.trigger("change");
    }
    this.on("add", this._updateTimes);
    this.on("remove", this._updateTimes);
}

Model.Mashup.prototype = new Model.Playable();

Model.Mashup.prototype.checkLoaded = function() {
    var loaded = !!this.segments.length;
    this.getMedias().forEach(function(_m) {
        loaded = loaded && _m.loaded;
    });
    this.loaded = loaded;
    if (loaded) {
        this.trigger("loadedmetadata");
    }
}

Model.Mashup.prototype.updateTimes = function() {
    var _time = 0;
    this.segments.forEach(function(_segment) {
        _segment.setBegin(_time);
        _time = _segment.end;
    });
    this.duration.setMilliseconds(_time);
}

Model.Mashup.prototype.addAnnotation = function(_annotation, _defer) {
    var _mashedAnnotation = new Model.MashedAnnotation(this, _annotation),
        _defer = _defer || false;
    this.segments.push(_mashedAnnotation);
    _annotation.on("change-begin", this._updateTimes);
    _annotation.on("change-end", this._updateTimes);
    if (!_defer) {
        this.trigger("add");
    }
}

Model.Mashup.prototype.addAnnotationById = function(_elId, _defer) {
    var _annotation = this.source.getElement(_elId),
        _defer = _defer || false;
    if (typeof _annotation !== "undefined") {
        this.addAnnotation(_annotation, _defer);
    }
}

Model.Mashup.prototype.addAnnotations = function(_segments) {
    var _this = this;
    ns._(_segments).forEach(function(_segment) {
        _this.addAnnotation(_segment, true);
    });
    this.trigger("add");
}

Model.Mashup.prototype.addAnnotationsById = function(_segments) {
    var _this = this;
    ns._(_segments).forEach(function(_segment) {
        _this.addAnnotationById(_segment, true);
    });
    this.trigger("add");
}

Model.Mashup.prototype.removeAnnotation = function(_annotation, _defer) {
    var _defer = _defer || false;
    _annotation.off("change-begin", this._updateTimes);
    _annotation.off("change-end", this._updateTimes);
    this.segments.removeId(this.id + "_" + _annotation.id);
    if (!_defer) {
        this.trigger("remove");
    }
}

Model.Mashup.prototype.removeAnnotationById = function(_annId, _defer) {
    var _defer = _defer || false;
    var _annotation = this.source.getElement(_annId);

    if (_annotation) {
        this.removeAnnotation(_annotation, _defer);
    }
    if (!_defer) {
        this.trigger("remove");
    }
}

Model.Mashup.prototype.setAnnotations = function(_segments) {
    while (this.segments.length) {
        this.removeAnnotation(this.segments[0].annotation, true);
    }
    this.addAnnotations(_segments);
}

Model.Mashup.prototype.setAnnotationsById = function(_segments) {
    while (this.segments.length) {
        this.removeAnnotation(this.segments[0].annotation, true);
    }
    this.addAnnotationsById(_segments);
}

Model.Mashup.prototype.hasAnnotation = function(_annotation) {
    return !!ns._(this.segments).find(function(_s) {
        return _s.annotation === _annotation
    });
}

Model.Mashup.prototype.getAnnotation = function(_annotation) {
    return ns._(this.segments).find(function(_s) {
        return _s.annotation === _annotation
    });
}

Model.Mashup.prototype.getAnnotationById = function(_id) {
    return ns._(this.segments).find(function(_s) {
        return _s.annotation.id === _id
    });
}

Model.Mashup.prototype.getAnnotations = function() {
    return this.segments;
}

Model.Mashup.prototype.getOriginalAnnotations = function() {
    var annotations = new Model.List(this.source.directory);
    this.segments.forEach(function(_s) {
        annotations.push(_s.annotation);
    });
    return annotations;
}

Model.Mashup.prototype.getMedias = function() {
    var medias = new Model.List(this.source.directory);
    this.segments.forEach(function(_annotation) {
        medias.push(_annotation.getMedia())
    })
    return medias;
}

Model.Mashup.prototype.getAnnotationsByTypeTitle = function(_title) {
    var _annTypes = this.source.getAnnotationTypes().searchByTitle(_title).pluck("id");
    if (_annTypes.length) {
        return this.getAnnotations().filter(function(_annotation) {
            return ns._(_annTypes).indexOf(_annotation.getAnnotationType().id) !== -1;
        });
    } else {
        return new Model.List(this.source.directory)
    }
}

Model.Mashup.prototype.getAnnotationAtTime = function(_time) {
    var _list = this.segments.filter(function(_annotation) {
        return _annotation.begin <= _time && _annotation.end > _time;
    });
    if (_list.length) {
        return _list[0];
    } else {
        return undefined;
    }
}

Model.Mashup.prototype.getMediaAtTime = function(_time) {
    var _annotation = this.getAnnotationAtTime(_time);
    if (typeof _annotation !== "undefined") {
        return _annotation.getMedia();
    } else {
        return undefined;
    }
}

/* */

Model.Source = function(_config) {
    Model.Element.call(this, false, this);
    this.status = Model._SOURCE_STATUS_EMPTY;
    this.elementType = "source";
    if (typeof _config !== "undefined") {
        var _this = this;
        ns._(_config).forEach(function(_v, _k) {
            _this[_k] = _v;
        })
        this.callbackQueue = [];
        this.contents = {};
        this.get();
    }
}

Model.Source.prototype = new Model.Element();

Model.Source.prototype.addList = function(_listId, _contents) {
    if (typeof this.contents[_listId] === "undefined") {
        this.contents[_listId] = new Model.List(this.directory);
    }
    this.contents[_listId].addElements(_contents);
}

Model.Source.prototype.getList = function(_listId, _global) {
    _global = (typeof _global !== "undefined" && _global);
    if (_global || typeof this.contents[_listId] === "undefined") {
        return this.directory.getGlobalList().filter(function(_e) {
            return (_e.elementType === _listId);
        });
    } else {
        return this.contents[_listId];
    }
}

Model.Source.prototype.forEach = function(_callback) {
    var _this = this;
    ns._(this.contents).forEach(function(_value, _key) {
        _callback.call(_this, _value, _key);
    })
}

Model.Source.prototype.getElement = function(_elId) {
    return this.directory.getElement(_elId);
}

Model.Source.prototype.get = function() {
    this.status = Model._SOURCE_STATUS_WAITING;
    this.handleCallbacks();
}

/* We defer the callbacks calls so they execute after the queue is cleared */
Model.Source.prototype.deferCallback = function(_callback) {
    var _this = this;
    ns._.defer(function() {
        _callback.call(_this);
    });
}

Model.Source.prototype.handleCallbacks = function() {
    this.status = Model._SOURCE_STATUS_READY;
    while (this.callbackQueue.length) {
        this.deferCallback(this.callbackQueue.splice(0,1)[0]);
    }
}
Model.Source.prototype.onLoad = function(_callback) {
    if (this.status === Model._SOURCE_STATUS_READY) {
        this.deferCallback(_callback);
    } else {
        this.callbackQueue.push(_callback);
    }
}

Model.Source.prototype.serialize = function() {
    return this.serializer.serialize(this);
}

Model.Source.prototype.deSerialize = function(_data) {
    this.serializer.deSerialize(_data, this);
}

Model.Source.prototype.getAnnotations = function(_global) {
    _global = (typeof _global !== "undefined" && _global);
    return this.getList("annotation", _global);
}

Model.Source.prototype.getMedias = function(_global) {
    _global = (typeof _global !== "undefined" && _global);
    return this.getList("media", _global);
}

Model.Source.prototype.getTags = function(_global) {
    _global = (typeof _global !== "undefined" && _global);
    return this.getList("tag", _global);
}

Model.Source.prototype.getMashups = function(_global) {
    _global = (typeof _global !== "undefined" && _global);
    return this.getList("mashup", _global);
}

Model.Source.prototype.getAnnotationTypes = function(_global) {
    _global = (typeof _global !== "undefined" && _global);
    return this.getList("annotationType", _global);
}

Model.Source.prototype.getAnnotationsByTypeTitle = function(_title, _global) {
    _global = (typeof _global !== "undefined" && _global);
    var _res = new Model.List(this.directory),
        _annTypes = this.getAnnotationTypes(_global).searchByTitle(_title);
    _annTypes.forEach(function(_annType) {
        _res.addElements(_annType.getAnnotations(_global));
    })
    return _res;
}

Model.Source.prototype.getDuration = function() {
    var _m = this.currentMedia;
    if (typeof _m !== "undefined") {
        return this.currentMedia.duration;
    }
}

Model.Source.prototype.getCurrentMedia = function(_opts) {
    if (typeof this.currentMedia === "undefined") {
        if (_opts.is_mashup) {
            var _mashups = this.getMashups();
            if (_mashups.length) {
                this.currentMedia = _mashups[0];
            }
        } else {
            var _medias = this.getMedias();
            if (_medias.length) {
                this.currentMedia = _medias[0];
            }
        }
    }
    return this.currentMedia;
}

Model.Source.prototype.merge = function(_source) {
    var _this = this;
    _source.forEach(function(_value, _key) {
        _this.getList(_key).addElements(_value);
    });
}

/* */

Model.RemoteSource = function(_config) {
    Model.Source.call(this, _config);
}

Model.RemoteSource.prototype = new Model.Source();

Model.RemoteSource.prototype.get = function() {
    this.status = Model._SOURCE_STATUS_WAITING;
    var _this = this,
        urlparams = this.url_params || {},
        dataType = (Model.isLocalURL(this.url) ? "json" : "jsonp");
    urlparams.format = dataType;
    ns.jQuery.ajax({
        url: this.url,
        dataType: dataType,
        data: urlparams,
        traditional: true,
        success: function(_result) {
            _this.deSerialize(_result);
            _this.handleCallbacks();
        }
    });
}

/* */

Model.Directory = function() {
    this.remoteSources = {};
    this.elements = {};
}

Model.Directory.prototype.remoteSource = function(_properties) {
    if (typeof _properties !== "object" || typeof _properties.url === "undefined") {
        throw "Error : Model.Directory.remoteSource(configuration): configuration.url is undefined";
    }
    var _config = ns._({ directory: this }).extend(_properties);
    _config.url_params = _config.url_params || {};
    var _hash = _config.url + "?" + ns.jQuery.param(_config.url_params);
    if (typeof this.remoteSources[_hash] === "undefined") {
        this.remoteSources[_hash] = new Model.RemoteSource(_config);
    }
    return this.remoteSources[_hash];
}

Model.Directory.prototype.newLocalSource = function(_properties) {
    var _config = ns._({ directory: this }).extend(_properties),
        _res = new Model.Source(_config);
    return _res;
}

Model.Directory.prototype.getElement = function(_id) {
    return this.elements[_id];
}

Model.Directory.prototype.addElement = function(_element) {
    this.elements[_element.id] = _element;
}

Model.Directory.prototype.getGlobalList = function() {
    var _res = new Model.List(this);
    _res.addIds(ns._(this.elements).keys());
    return _res;
}

return Model;

})(IriSP);

/* END model.js */

/* LDT Platform Serializer */

if (typeof IriSP.serializers === "undefined") {
    IriSP.serializers = {}
}

IriSP.serializers.ldt = {
    types :  {
        media : {
            serialized_name : "medias",
            deserializer : function(_data, _source) {
                var _res = new IriSP.Model.Media(_data.id, _source);
                _res.video = (
                    typeof _data.url !== "undefined"
                    ? _data.url
                    : (
                        typeof _data.href !== "undefined"
                        ? _data.href
                        : null
                    )
                );
                if (typeof _data.meta.item !== "undefined" && _data.meta.item.name === "streamer") {
                    _res.streamer = _data.meta.item.value;
                }
                _res.title = _data.meta["dc:title"];
                _res.description = _data.meta["dc:description"];
                _res.setDuration(_data.meta["dc:duration"]);
                _res.url = _data.meta.url;
                if (typeof _data.meta.img !== "undefined" && _data.meta.img.src !== "undefined") {
                    _res.thumbnail = _data.meta.img.src;
                }
                return _res;        
            },
            serializer : function(_data, _source, _dest) {
                var _res = {
                    id : _data.id,
                    url : _data.video,
                    meta : {
                        "dc:title": _data.title || "",
                        "dc:description": _data.description || "",
                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                        "dc:creator" : _data.creator || _source.creator,
                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
                        "dc:duration" : _data.duration.milliseconds
                    }
                }
                _dest.medias.push(_res);
                var _list = {
                    id: IriSP.Model.getUID(),
                    meta : {
                        "dc:title": _data.title || "",
                        "dc:description": _data.description || "",
                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                        "dc:creator" : _data.creator || _source.creator,
                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
                        "id-ref": _data.id
                    },
                    items: _source.getAnnotationTypes().filter(function(_at) {
                        return _at.media === _data;
                    }).map(function(_at) {
                        return {
                            "id-ref": _at.id
                        }
                    })
                }
                _dest.lists.push(_list);
                _dest.views[0].contents.push(_data.id);
            }
        },
        tag : {
            serialized_name : "tags",
            model_name : "tag",
            deserializer : function(_data, _source) {
                var _res = new IriSP.Model.Tag(_data.id, _source);
                _res.title = _data.meta["dc:title"];
                return _res;        
            },
            serializer : function(_data, _source, _dest) {
                var _res = {
                    id : _data.id,
                    meta : {
                        "dc:title": _data.title || "",
                        "dc:description": _data.description || "",
                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                        "dc:creator" : _data.creator || _source.creator,
                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
                    }
                }
                _dest.tags.push(_res);
            }
        },
        annotationType : {
            serialized_name : "annotation-types",
            deserializer : function(_data, _source) {
                var _res = new IriSP.Model.AnnotationType(_data.id, _source);
                _res.title = _data["dc:title"];
                _res.description = _data["dc:description"];
                return _res;        
            },
            serializer : function(_data, _source, _dest) {
                var _res = {
                    id : _data.id,
                    "dc:title": _data.title || "",
                    "dc:description": _data.description || "",
                    "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                    "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                    "dc:creator" : _data.creator || _source.creator,
                    "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
                }
                _dest["annotation-types"].push(_res);
                _dest.views[0].annotation_types.push(_data.id);
            }
        },
        annotation : {
            serialized_name : "annotations",
            deserializer : function(_data, _source) {
                var _res = new IriSP.Model.Annotation(_data.id, _source);
                _res.title = _data.content.title || "";
                _res.description = _data.content.description || "";
                if (typeof _data.content.img !== "undefined" && _data.content.img.src !== "undefined") {
                    _res.thumbnail = _data.content.img.src;
                }
                _res.created = IriSP.Model.isoToDate(_data.meta["dc:created"]);
                if (typeof _data.color !== "undefined") {
                    var _c = parseInt(_data.color).toString(16);
                    while (_c.length < 6) {
                        _c = '0' + _c;
                    }
                    _res.color = '#' + _c;
                }
                _res.setMedia(_data.media);
                _res.setAnnotationType(_data.meta["id-ref"]);
                _res.setTags(IriSP._(_data.tags).pluck("id-ref"));
                _res.keywords = _res.getTagTexts();
                _res.setBegin(_data.begin);
                _res.setEnd(_data.end);
                _res.creator = _data.meta["dc:creator"] || "";
                _res.project = _data.meta.project || "";
                if (typeof _data.meta["dc:source"] !== "undefined" && typeof _data.meta["dc:source"].content !== "undefined") {
                    _res.source = JSON.parse(_data.meta["dc:source"].content);
                }
                if (typeof _data.content.audio !== "undefined" && _data.content.audio.href) {
                    _res.audio = _data.content.audio;
                }
                return _res;
            },
            serializer : function(_data, _source, _dest) {
                var _color = parseInt(_data.color.replace(/^#/,''),16).toString();
                var _res = {
                    id : _data.id,
                    begin : _data.begin.milliseconds,
                    end : _data.end.milliseconds,
                    content : {
                        title : _data.title || "",
                        description : _data.description || "",
                        audio : _data.audio,
                        img: {
                            src: _data.thumbnail
                        }
                    },
                    color: _color,
                    media : _data.media.id,
                    meta : {
                        "id-ref" : _data.getAnnotationType().id,
                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                        "dc:creator" : _data.creator || _source.creator,
                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
//                        project : _source.projectId
                    },
                    tags : IriSP._(_data.tag.id).map(function(_id) {
                       return {
                           "id-ref" : _id
                       } 
                    })
                }
                _dest.annotations.push(_res);
            }
        },
        mashup : {
            serialized_name : "lists",
            deserializer : function(_data, _source) {
                if (typeof _data.meta !== "object" || typeof _data.meta.listtype !== "string" || _data.meta.listtype !== "mashup") {
                    return undefined;
                }
                var _res = new IriSP.Model.Mashup(_data.id, _source);
                _res.title = _data.meta["dc:title"];
                _res.description = _data.meta["dc:description"];
                _res.creator = _data.meta["dc:creator"];
                _res.setAnnotationsById(_data.items);
                return _res;        
            },
            serializer : function(_data, _source, _dest) {
                var _res = {
                    meta : {
                        "dc:title": _data.title || "",
                        "dc:description": _data.description || "",
                        "dc:created" : IriSP.Model.dateToIso(_data.created || _source.created),
                        "dc:modified" : IriSP.Model.dateToIso(_data.modified || _source.modified),
                        "dc:creator" : _data.creator || _source.creator,
                        "dc:contributor" : _data.contributor || _source.contributor || _data.creator || _source.creator,
                        listtype: "mashup"
                    },
                    items: _data.segments.map(function(_annotation) {
                        return _annotation.annotation.id;
                    }),
                    id: _data.id
                }
                _dest.lists.push(_res);
            }
        }
    },
    serialize : function(_source) {
        var _res = {
                meta: {
                    "dc:creator": _source.creator,
                    "dc:contributor" : _source.contributor || _source.creator,
                    "dc:created": IriSP.Model.dateToIso(_source.created),
                    "dc:modified" : IriSP.Model.dateToIso(_source.modified),
                    "dc:title": _source.title || "",
                    "dc:description": _source.description || "",
                    id: _source.projectId || _source.id
                },
                views: [
                    {
                        id: IriSP.Model.getUID(),
                        contents: [],
                        annotation_types: []
                    }
                ],
                lists: [],
                "annotation-types": [],
                medias: [],
                tags: [],
                annotations: []
            },
            _this = this;
        _source.forEach(function(_list, _typename) {
            if (typeof _this.types[_typename] !== "undefined") {
                _list.forEach(function(_el) {
                    _this.types[_typename].serializer(_el, _source, _res);
                });
            }
        });
        return JSON.stringify(_res);
    },
    deSerialize : function(_data, _source) {
        if (typeof _data !== "object" || _data === null) {
            return;
        }
        IriSP._(this.types).forEach(function(_type, _typename) {
            var _listdata = _data[_type.serialized_name],
                _list = new IriSP.Model.List(_source.directory);
            if (typeof _listdata !== "undefined" && _listdata !== null) {
                if (_listdata.hasOwnProperty("length")) {
                    var _l = _listdata.length;
                    for (var _i = 0; _i < _l; _i++) {
                        var _element = _type.deserializer(_listdata[_i], _source);
                        if (typeof _element !== "undefined" && _element) {
                            _list.push(_element);
                        }
                    }
                } else {
                    var _element = _type.deserializer(_listdata, _source);
                    if (typeof _element !== "undefined" && _element) {
                        _list.push(_element);
                    }
                }
            }
            _source.addList(_typename, _list);
        });
        
        if (typeof _data.meta !== "undefined") {
            _source.projectId = _data.meta.id;
        }
        
        if (typeof _data.meta !== "undefined" && typeof _data.meta.main_media !== "undefined" && typeof _data.meta.main_media["id-ref"] !== "undefined") {
            _source.currentMedia = _source.getElement(_data.meta.main_media["id-ref"]);
        }
    }
}

/* END ldt-serializer.js */

IriSP.serializers.segmentapi = {
    deSerialize : function(_data, _source) {
        var _annotationlist = new IriSP.Model.List(_source.directory),
            _medialist = new IriSP.Model.List(_source.directory);
        _source.addList("media", _medialist);
        
        function deserializeObject(_s) {
            var _ann = new IriSP.Model.Annotation(_s.element_id, _source),
                _media = _source.getElement(_s.iri_id);
            if (!_media) {
                _media = new IriSP.Model.Media(_s.iri_id, _source);
                _source.getMedias().push(_media);
            }
            _ann.setMedia(_s.iri_id);
            _ann.title = _s.title;
            _ann.description = _s.abstract;
            _ann.begin = new IriSP.Model.Time(_s.start_ts);
            _ann.end = new IriSP.Model.Time(_s.start_ts + _s.duration);
            _ann.keywords = (_s.tags ? _s.tags.split(",") : []);
            _ann.project_id = _s.project_id;
            _annotationlist.push(_ann);
        }
        
        if (typeof _data.objects !== "undefined") {
            IriSP._(_data.objects).each(deserializeObject);
        } else {
            deserializeObject(_data);
        }
        _source.addList("annotation", _annotationlist);
    }
}

/* END segmentapi-serializer.js */
IriSP.serializers.content = {
    deSerialize : function(_data, _source) {
        var _medialist = new IriSP.Model.List(_source.directory);
        
        function deserializeObject(_m, i) {
            var _media = new IriSP.Model.Media(_m.iri_id, _source);
            _media.video = _m.media_url;
            _media.title = _m.title;
            _media.description = _m.description;
            _media.setDuration(_m.duration);
            _media.thumbnail = _m.image;
            _media.color = IriSP.vizcolors[i % IriSP.vizcolors.length];
            _media.keywords = _m.tags;
            _medialist.push(_media);
        }
        
        if (typeof _data.objects !== "undefined") {
            IriSP._(_data.objects).each(deserializeObject);
        } else {
            deserializeObject(_data, 0);
        }
        
        _source.addList("media", _medialist);
    }
};

/* END contentapi-serializer.js */
IriSP.mashupcore = function(project, mashup) {
    
    var currentMedia,
        intervaltemplate = _.template('<span class="frise-indication" style="left:<%= left %>%;"><%= time.toString() %></span>'),
        viztemplate = _.template('<div class="frise-segment annotation" data-segment-id="<%= segmentid %>" style="background-color:<%= color %>; left:<%= left %>%; width:<%= width %>%;"></div>');

    function updateMashupUI() {
        var vizhtml = '', t = 0, k = mashup.duration ? (100 / mashup.duration) : 0;
        mashup.segments.forEach(function(_s) {
            var vizdata = {
                left: k * t,
                width: k * _s.duration,
                color: _s.color,
                segmentid: _s.annotation.id
            }
            vizhtml += viztemplate(vizdata);
            t += _s.duration.milliseconds;
        });
        
        var intervals = [ 1000, 2000, 5000, 10000, 30000, 60000, 120000, 300000, 600000, 900000, 1800000, 3600000, 7200000 ];
        
        function createIntervals(maxn) {
            for (var i = 0; i < intervals.length; i++) {
                if (mashup.duration / intervals[i] <= maxn) {
                    var html = '';
                    for (var j = intervals[i]; j < mashup.duration; j += intervals[i]) {
                        html += intervaltemplate({ left: k * j, time: new IriSP.Model.Time(j) });
                    }
                    return html;
                }
            }
            return "";
        }
        
        $(".mashup-total-duration").text(mashup.duration.toString());
        $(".mashup-frise .frise-segments").html(vizhtml);
        $(".mashup-frise .frise-indications").html(createIntervals(6));
        
        if (currentMedia === mashup) {
            $(".Ldt-Ctrl-Time-Total").text(currentMedia.duration.toString());
            if (mashupTimecode > mashup.duration) {
                mashup.setCurrentTime(mashup.duration);
            }
            changeCurrentAnnotation();
            mashup.trigger("enter-annotation",mashup.currentAnnotation);
        }
        
    }
    
    /* Slider */
    
    var timeSlider = $(".Ldt-Slider"),
        timeSliderContainer = $(".Ldt-Slider-Container"),
        slidersRange = 920;
    timeSlider.slider({
        range: "min",
        value: 0,
        min: 0,
        max: slidersRange,
        slide: function(event, ui) {
            if (currentMedia) {
                var t = currentMedia.duration * ui.value / slidersRange;
                currentMedia.setCurrentTime(t);
            }
        }
    });
    
    var timeSliderHandle = timeSlider.find('.ui-slider-handle'),
        timeSliderMaximized = false,
        timeSliderTimeoutId = false,
        timeSliderMinimizedHeight = 4,
        timeSliderMaximizedHeight = 10,
        timeSliderTimeoutDuration = 1500,
        timeTooltip = $(".Ldt-Slider-Time");
    
    timeSliderContainer.css(calculateSliderCss(timeSliderMinimizedHeight));
    timeSliderHandle.css(calculateHandleCss(timeSliderMinimizedHeight));
    
    function timeSliderMouseOver() {
        if (timeSliderTimeoutId) {
            window.clearTimeout(timeSliderTimeoutId);
            timeSliderTimeoutId = false;
        }
        if (!timeSliderMaximized) {
           timeSliderAnimateToHeight(timeSliderMaximizedHeight);
           timeSliderMaximized = true;
        }
    }
    
    function timeSliderMouseOut() {
        timeTooltip.hide();
        if (timeSliderTimeoutId) {
            clearTimeout(timeSliderTimeoutId);
            timeSliderTimeoutId = false;
        }
        timeSliderTimeoutId = setTimeout(function() {
            if (timeSliderMaximized) {
                timeSliderAnimateToHeight(timeSliderMinimizedHeight);
                timeSliderMaximized = false;
            }
            timeSliderTimeoutId = false;
        }, timeSliderTimeoutDuration);
    }
    
    timeSliderContainer
        .mouseover(function() {
            timeTooltip.show();
            timeSliderMouseOver();
        })
        .mouseout(timeSliderMouseOut);
    timeSlider.mousemove(function(_e) {
        if (!currentMedia) {
            return;
        }
        var _x = _e.pageX - timeSlider.offset().left,
            _t = new IriSP.Model.Time(
                Math.max(0, currentMedia.duration * Math.min(1, _x / timeSlider.width()))
            );
        timeTooltip.text(_t.toString()).css("left",_x);
    });
    
    $(".Ldt-Ctrl").mouseover(timeSliderMouseOver).mouseout(timeSliderMouseOut);
    
    function timeSliderAnimateToHeight(_height) {
        timeSliderContainer.stop().animate(
            calculateSliderCss(_height),
            500,
            function() {
                IriSP.jQuery(this).css("overflow","visible");
            });
        timeSliderHandle.stop().animate(
            calculateHandleCss(_height),
            500,
            function() {
                IriSP.jQuery(this).css("overflow","visible");
            });
    }

    function calculateSliderCss(_size) {
        return {
            height: _size + "px",
            "margin-top": (timeSliderMinimizedHeight - _size) + "px"
        };
    }

    function calculateHandleCss(_size) {
        return {
            height: (2 + _size) + "px",
            width: (2 + _size) + "px",
            "margin-left": -Math.ceil(2 + _size / 2) + "px" 
        }
    }
    
    /* Controller Widget */
   
    var volBlock = $(".Ldt-Ctrl-Volume-Control");
    
    $('.Ldt-Ctrl-Sound')
        .click(function() {
            if (currentMedia) {
                currentMedia.setMuted(!currentMedia.getMuted());
            }
        })
        .mouseover(function() {
            volBlock.show();
        })
        .mouseout(function() {
            volBlock.hide();
        });
    volBlock.mouseover(function() {
        volBlock.show();
    }).mouseout(function() {
        volBlock.hide();
    });
    
    var volBar = $(".Ldt-Ctrl-Volume-Bar");
    
    function ctrlVolumeUpdater() {
        if (currentMedia) {
            var _muted = currentMedia.getMuted(),
                _vol = currentMedia.getVolume();
            if (_vol === false) {
                _vol = .5;
            }
            var _soundCtl = $(".Ldt-Ctrl-Sound");
            _soundCtl.removeClass("Ldt-Ctrl-Sound-Mute Ldt-Ctrl-Sound-Half Ldt-Ctrl-Sound-Full");
            if (_muted) {        
                _soundCtl.attr("title", gettext("Unmute"))
                    .addClass("Ldt-Ctrl-Sound-Mute");    
            } else {
                _soundCtl.attr("title", gettext("mute"))
                    .addClass(_vol < .5 ? "Ldt-Ctrl-Sound-Half" : "Ldt-Ctrl-Sound-Full" )
            }
            volBar.slider("value", _muted ? 0 : 100 * _vol);
            volBar.attr("title",gettext("Volume:") + Math.floor(100 * _vol) + '%');
        }
    }
    
    volBar.slider({
        slide: function(event, ui) {
            if (currentMedia) {
                currentMedia.setVolume(ui.value / 100);
            }
        }
    });
    
    $(".Ldt-Ctrl-Play").click(function() {
        if (currentMedia) {
            if (currentMedia.getPaused()) {        
                currentMedia.play();
            } else {
                currentMedia.pause();
            }
        }
    });
    
    /* UI Events */

    function onCurrentMediaPlay() {
        $(".Ldt-Ctrl-Play")
            .attr("title", gettext("Pause"))
            .removeClass("Ldt-Ctrl-Play-PlayState")
            .addClass("Ldt-Ctrl-Play-PauseState")
    }
    
    function onCurrentMediaPause() {
        $(".Ldt-Ctrl-Play")
            .attr("title", gettext("Play"))
            .removeClass("Ldt-Ctrl-Play-PauseState")
            .addClass("Ldt-Ctrl-Play-PlayState")
    }
    
    function onCurrentMediaTimeupdate(_time) {
        $(".Ldt-Ctrl-Time-Elapsed").text(_time.toString());
        timeSlider.slider("value",slidersRange * _time / currentMedia.duration);
    }
    
    /* Mashup Player */
   
    mashup.currentMedia = null;
    mashup.currentAnnotation = null;
    mashup.seeking = false;
    var mashupSegmentBegin,
        mashupSegmentEnd,
        mashupTimecode = 0,
        seekdiv = $(".video-wait"),
        mashupTimedelta;
    
    function showSeek() {
        if (currentMedia.seeking) {
            seekdiv.show();
        }
    }
    
    function changeCurrentAnnotation() {
        if (mashupTimecode >= mashup.duration) {
            if (!mashup.paused) {
                mashup.paused = true;
                mashup.trigger("pause");
            }
            mashupTimecode = 0;
        }
        var _annotation = mashup.getAnnotationAtTime( mashupTimecode );
        if (typeof _annotation === "undefined") {
            if (mashup.currentMedia) {
                mashup.currentMedia.pause();
                if (!mashup.paused) {
                    mashup.paused = true;
                    mashup.trigger("pause");
                }
            }
            return;
        }
        mashup.currentAnnotation = _annotation;
        mashupSegmentBegin = mashup.currentAnnotation.annotation.begin.milliseconds;
        mashupSegmentEnd = mashup.currentAnnotation.annotation.end.milliseconds;
        mashupTimedelta = mashupSegmentBegin - mashup.currentAnnotation.begin.milliseconds;
        mashup.currentMedia = mashup.currentAnnotation.getMedia();
        project.getMedias().forEach(function(_media) {
            if (_media !== mashup.currentMedia) {
                _media.hide();
                _media.pause();
            } else {
                _media.show();
            }
        });
        
        mashup.currentMedia.setCurrentTime( mashupTimecode + mashupTimedelta);
        mashup.currentMedia.seeking = true;
        
        if (!mashup.paused) {
            mashup.currentMedia.play();
            mashup.seeking = true;
            setTimeout(showSeek,200);
        }
        mashup.trigger("timeupdate", new IriSP.Model.Time(mashupTimecode));

    }
    
    function addMedia(media) {
        if (media.has_player) {
            return;
        }
        media.has_player = true;
        var videourl = media.video;
        if (typeof IriSP.video_url_transform === "function") {
            videourl = IriSP.video_url_transform(media.video);
        }
        var videoid = "video_" + media.id,
            videoEl = $('<video>'),
            width = $(".video").width(),
            height = $(".video").height(),
/*
            mp4_file = videourl.replace(/\.webm$/i,'.mp4'),
            webm_file = videourl.replace(/\.mp4$/i,'.webm'),
*/
            mp4_src = $('<source>'),
/*
            webm_src = $('<source>'),
*/
            lastseektime,
            currentseektime;
        mp4_src.attr({
            src: videourl, //mp4_file,
            type: "video/mp4"
        });
/*
        webm_src.attr({
            src: webm_file,
            type: "video/webm"
        });
*/
        videoEl.attr({
            id : videoid,
            width : width,
            height : height
        }).css({
            position : "absolute",
            left: 0,
            top: 0,
            width : width,
            height : height
        });
        videoEl.append(mp4_src); //.append(webm_src);
        $(".video").append(videoEl);
        
        media.show = function() {
            videoEl.show();
        }
        media.hide = function() {
            videoEl.hide();
        }
        
        var popcorn = Popcorn("#" + videoid);
        
        // Binding functions to Popcorn
        
        media.on("setcurrenttime", function(_milliseconds) {
            if (media.loaded) {
                if (!media.seeking) {
                    lastseektime = currentseektime = _milliseconds.valueOf();
                    popcorn.currentTime(currentseektime / 1000);
                    media.seeking = true;
                    setTimeout(showSeek,200);
                } else {
                    lastseektime = _milliseconds.valueOf();
                }
            }
        });
        
        media.on("setvolume", function(_vol) {
            media.volume = _vol;
            if (media.loaded) {
                popcorn.volume(_vol);
            }
        });
        
        media.on("setmuted", function(_muted) {
            media.muted = _muted;
            if (media.loaded) {
                popcorn.muted(_muted);
            }
        });
        
        media.on("setplay", function() {
            if (media.loaded) {
                popcorn.play();
            }
        });
        
        media.on("setpause", function() {
            if (media.loaded) {
                popcorn.pause();
            }
        });
        
        // Binding Popcorn events to media
        
        function getVolume() {
            media.muted = popcorn.muted();
            media.volume = popcorn.volume();
        }
        
        popcorn.on("loadedmetadata", function() {
            getVolume();
            media.loaded = true;
            media.trigger("loadedmetadata");
            media.trigger("volumechange");
        })
        
        popcorn.on("timeupdate", function() {
            media.trigger("timeupdate", new IriSP.Model.Time(1000*popcorn.currentTime()));
        });
        
        popcorn.on("volumechange", function() {
            getVolume();
            media.trigger("volumechange");
        })
        
        popcorn.on("play", function() {
            media.trigger("play");
        });
        
        popcorn.on("pause", function() {
            media.trigger("pause");
        });
        
        popcorn.on("seeked", function() {
            if (currentseektime !== lastseektime) {
                currentseektime = lastseektime;
                popcorn.currentTime(currentseektime / 1000);
                media.seeking = true;
            } else {
                media.trigger("seeked");
            }
        });
        
        // Binding UI Events and Mashup Playing to Media
        
        media.on("loadedmetadata", function() {
            if (media === currentMedia) {
                seekdiv.hide();
            }
            mashup.checkLoaded();
        });
        
        media.on("play", function() {
            if (media === currentMedia) {
                onCurrentMediaPlay();
            }
            if (mashup === currentMedia && media === mashup.currentMedia) {
                mashup.trigger("play");
            }
        });
        
        media.on("pause", function() {
            if (media === currentMedia) {
                onCurrentMediaPause();
            }
            if (mashup === currentMedia && media === mashup.currentMedia) {
                mashup.trigger("pause");
            }
        });
        
        media.on("timeupdate", function(_time) {
            if (media === currentMedia) {
                $(".frise-position").css("left",(100*_time/media.duration)+"%");
                onCurrentMediaTimeupdate(_time);
            }
            if (mashup === currentMedia && !mashup.paused && media === mashup.currentMedia && !media.seeking) {
                if ( _time < mashupSegmentEnd ) {
                    if ( _time >= mashupSegmentBegin ) {
                        mashupTimecode = _time - mashupTimedelta;
                    } else {
                        mashupTimecode = mashupSegmentBegin - mashupTimedelta;
                        media.setCurrentTime(mashupSegmentBegin);
                    }
                } else {
                    mashupTimecode = mashupSegmentEnd - mashupTimedelta;
                    media.pause();
                    changeCurrentAnnotation();
                }
                mashup.trigger("timeupdate", new IriSP.Model.Time(mashupTimecode));
            }
        });
        
        media.on("seeked", function() {
            media.seeking = false;
            if (mashup === currentMedia && media === mashup.currentMedia && mashup.seeking) {
                mashup.seeking = false;
            }
            seekdiv.hide();
        });
        
        media.on("volumechange", function() {
            if (media === currentMedia) {
                ctrlVolumeUpdater();
            }
            mashup.muted = media.muted;
            mashup.volume = media.volume;
            mashup.trigger("volumechange");
        });
        
        project.on("set-current", function(_m) {
            if (_m !== media && (_m !== mashup || mashup.currentMedia !== media)) {
                media.hide();
            }
        });
        
    }

    // Mashup Events
    
    mashup.on("setcurrenttime", function(_milliseconds) {
        mashupTimecode = _milliseconds;
        changeCurrentAnnotation();
    });
    
    mashup.on("setvolume", function(_vol) {
        mashup.getMedias().forEach(function(_media) {
            _media.setVolume(_vol);
        });
        mashup.volume = _vol;
    });
    
    mashup.on("setmuted", function(_muted) {
        mashup.getMedias().forEach(function(_media) {
            _media.setMuted(_muted);
        });
        mashup.muted = _muted;
    });
    
    mashup.on("setplay", function() {
        mashup.paused = false;
        changeCurrentAnnotation();
    });
    
    mashup.on("setpause", function() {
        mashup.paused = true;
        if (mashup.currentMedia) {
            mashup.currentMedia.pause();
        }
    });
    
    mashup.on("loadedmetadata", function() {
        if (mashup === currentMedia) {
            changeCurrentAnnotation();
        }
    });
    
    mashup.on("volumechange", function() {
        if (mashup === currentMedia) {
            ctrlVolumeUpdater();
        }
    });
    /* Mashup Events to UI */
   
    mashup.on("play", function() {
        if (mashup === currentMedia) {
            onCurrentMediaPlay();
        }
    });
    
    mashup.on("pause", function() {
        if (mashup === currentMedia) {
            onCurrentMediaPause();
        }
    });
    
    mashup.on("timeupdate", function(_time) {
        if (mashup === currentMedia) {
            $(".frise-position").css("left",(100*_time/mashup.duration)+"%");
            onCurrentMediaTimeupdate(_time);
        }
    });
    
    mashup.on("add", function() {
        mashup.getMedias().forEach(addMedia);
    })
    
    mashup.on("change",updateMashupUI);
    
    mashup.on("enter-annotation", function(segment) {
        var a = segment.annotation;
        $(".annotation-title").text(a.title);
        $(".annotation-begin").text(a.begin.toString());
        $(".annotation-end").text(a.end.toString());
        $(".annotation-tags").text(a.keywords.join(", "));
        $(".annotation-media-title").text(a.getMedia().title);
        $(".annotation-description").text(a.description);
        var p = (segment.begin + segment.end) / (2 * mashup.duration);
        $(".mashup-description .pointer").css("left", (100 * p) + "%");
        project.trigger("mouseout-annotation");
    });
    
    project.on("mouseover-annotation", function(annotation) {
        $(".annotation").removeClass("active");
        if (!annotation) {
            return;
        }
        var segment = mashup.getAnnotation(annotation);
        $(".annotation[data-segment-id='" + annotation.id + "']").addClass("active");
        $(".mashup-tooltip").show().css({
            left: (100 * ( segment.begin + segment.end ) / ( 2 * mashup.duration ) ) + "%"
        });
        $(".mashup-tooltip .segment-tooltip").text(annotation.title);
    });
    
    project.on("mouseout-annotation", function() {
        if (currentMedia === mashup && mashup.currentAnnotation) {
            $(".annotation").removeClass("active");
            $(".item-video.annotation[data-segment-id='" + mashup.currentAnnotation.annotation.id + "']").addClass("active");
        }
        $(".mashup-tooltip").hide();
    });
    
    project.on("click-annotation", function(annotation) {
        if (!annotation) {
            return;
        }
        var segment = mashup.getAnnotation(annotation);
        project.trigger("set-current", mashup);
        if (segment) {
            mashup.setCurrentTime(segment.begin);
        }
    })
    
    project.on("set-current", function(media) {
        currentMedia = media;
        if (currentMedia.elementType === "media") {
            if (!media.has_player) {
                addMedia(media);
            }
            media.show();
            if (!currentMedia.loaded) {
                seekdiv.show();
            }
        }
        if (currentMedia.elementType === "mashup") {
            mashup.checkLoaded();
        }
        $(".Ldt-Ctrl-Time-Total").text(currentMedia.duration.toString());
        currentMedia.trigger("timeupdate",currentMedia.getCurrentTime());
        currentMedia.trigger("pause");
    });
    
    var mouseoverSegment;
    
    $(".mashup-frise")
    .mousemove(function(evt) {
        if (!mashup.duration.milliseconds) {
            return;
        }
        var el = $(this), t = ( evt.pageX - el.offset().left ) * mashup.duration / el.width(), segment = mashup.getAnnotationAtTime(t);
        if (segment) {
            if (segment !== mouseoverSegment) {
                project.trigger("mouseover-annotation", segment.annotation);
            }
        } else {
            if (mouseoverSegment) {
                project.trigger("mouseout-annotation");
            }
        }
        mouseoverSegment = segment;
    })
    .mouseleave(function() {
        project.trigger("mouseout-annotation");
        mouseoverSegment = undefined;
    })
    
    mashup.trigger("add");
    
}

/* END mashupcore.js */

IriSP.editor = function(options) {
    
    /* Load Media List */
    
    var directory = new IriSP.Model.Directory(),
        apidirectory = new IriSP.Model.Directory(),
        project = directory.remoteSource({
            url: IriSP.endpoints.content,
            url_params: _({}).extend(options.filter),
            serializer: IriSP.serializers.content
        }),
        mashup = new IriSP.Model.Mashup(false, project),
        mediatemplate = _.template(
            '<li class="item-video media" data-media-id="<%= id %>"><div class="media-count-wrap"><span class="media-count"></span></div>'
            + '<img class="thumbnail" src="<%= thumbnail %>" alt="<%= title %>" />'
            + '<div class="video-info"><h3 class="title-video"><%= title %></h3><p class="description"><%= description %></p>'
            + '<p class="time-length"><%= gettext("Duration:") %> <span><%= duration.toString() %></span></p></div><div class="media-found-segments"></div></li>'
        ),
        segmenttemplate = _.template(
            '<li class="item-video annotation" data-segment-id="<%= annotation.id %>" data-media-id="<%= annotation.getMedia().id %>">'
            + '<img class="thumbnail" src="<%= annotation.thumbnail %>" alt="<%= annotation.getMedia().title %>" />'
            + '<div class="validate <%= annotation.status %>"><div class="validate-tooltip"><ul><li><%= annotation.status_messages.join("</li><li>") %></li></ul></div></div><div class="video-info"><h3 class="segment-title"><%= annotation.title %></h3>'
            + '<p class="title-video"><%= annotation.getMedia().title %></p><p class="duration"><%= annotation.begin.toString() %> - <%= annotation.end.toString() %> (<%= annotation.getDuration().toString() %>)</p>'
            + '<ul class="tools"><li><a class="edit" href="#" title="<%= gettext("Edit segment") %>"></a></li><li><a class="bottom" href="#" title="<%= gettext("Move segment down") %>"></a></li>'
            + '<li><a class="top" href="#" title="<%= gettext("Move segment up") %>"></a></li><li><a class="delete" href="#" title="<%= gettext("Delete segment") %>"></a></li></ul></div></li>'
        ),
        mediasegmenttemplate = _.template(
            '<div class="media-segment">'
            + '<div class="media-segment-section" style="left:<%= left %>px; width:<%= width %>px; background:<%= color %>; top: <%= top %>px;" data-segment-id="<%= annotation.id %>"></div>'
            + '<div class="popin media-segment-popin" style="left:<%= popleft %>px; top: <%= 5+top %>px;"><div style="left:<%= pointerpos %>px;" class="pointer"></div><div class="popin-content">'
            + '<h3 class="segment-title"><%= annotation.title %></h3>'
            + '<p><%= annotation.description %></p><% if (annotation.keywords.length) { console.log(annotation.keywords); print("<p><strong>" + gettext("Tags:") + "</strong> " + annotation.keywords.join(", ") + "</p>"); } %>'
            + '<a href="#" class="button reprendre-segment" data-segment-id="<%= annotation.id %>"><%= gettext("Clone segment") %></a>'
            + '<p><%= gettext("From:") %> <span><%= annotation.begin.toString() %></span> <%= gettext("to:") %> <span><%= annotation.end.toString() %></span> (<%= gettext("duration:") %> <span><%= annotation.getDuration().toString() %></span>)</p>'
            + '</div></div></div>'
        ),
        mediasegmentlisttemplate = _.template(
            '<div class="media-segment-list" style="height: <%= height %>px"><div class="media-current-section" style="left: <%= left %>px; width: <%= width %>px;"></div><div class="media-segment-list-inner"></div><%= segments %></div>'
        ),
        mediafoundtemplate = _.template(
            '<div class="media-segment"><div class="media-segment-section" style="left:<%= left %>px; width:<%= width %>px; background:<%= color %>; top: <%= top %>px;"></div>'
            + '<div class="popin media-found-popin" style="left:<%= popleft %>px; top: <%= 5+top %>px;"><div style="left:<%= pointerpos %>px;" class="pointer"></div><div class="popin-content">'
            + '<h3 class="segment-title"><%= title %></h3>'
            + '<p><%= description %></p><% if (tags) { print("<p><strong>" + gettext("Tags:") + "</strong> " + tags + "</p>"); } %>'
            + '<a href="#" class="button clone-segment" data-segment-id="<%= annotation.id %>"><%= gettext("Clone segment") %></a>'
            + '</div></div></div>'
        ),
        mediafoundlisttemplate = _.template(
            '<div class="media-found-list" style="height: <%= height %>px"><div class="media-segment-list-inner"></div><%= segments %></div>'
        ),
        mashupstatus = '',
        mediasegmentscache = {},
        mashupModeAfterSave = false,
        addMode = false,
        currentMedia, currentSegment;
        
    IriSP.mashupcore(project, mashup);
    
    /* Validation of segments and mashup */
    
    var segmentcritical = [
        {
            validate: function(_s) {
                return (_s.getDuration() >= 1000);
            },
            message: gettext("A segment must be at least one second long")
        },
        {
            validate: function(_s) {
                return (_s.getDuration() < 180000);
            },
            message: gettext("A segment must be at most three minutes long")
        },
        {
            validate: function(_s) {
                return (!!_s.title && _s.title !== gettext("Untitled segment"));
            },
            message: gettext("A segment must have a title")
        }
    ];
    var segmentwarning = [
        {
            validate: function(_s) {
                return (!!_s.description);
            },
            message: gettext("A segment should have a description")
        },
        {
            validate: function(_s) {
                return (!!_s.keywords.length);
            },
            message: gettext("A segment should have tags")
        }
    ];
    
    var mashupcritical = [
        {
            validate: function(_m) {
                return _m.segments.length > 2;
            },
            message: gettext("A hashcut must be made from at least three segments")
        },
        {
            validate: function(_m) {
                return (!!_m.title && _m.title !== gettext("Untitled Hashcut"));
            },
            message: gettext("A hashcut must have a title")
        }
    ];
    var mashupwarning = [
        {
            validate: function(_m) {
                return !!_m.description
            },
            message: gettext("A hashcut should have a description")
        }
    ];

    /* Fill left column with Media List */

    project.onLoad(function() {
        var html = '';
        project.getMedias().forEach(function(_m) {
            html += mediatemplate(_m);
        });
        $(".col-left .list-video").html(html);
        project.getMedias().forEach(function(_m) {
            apidirectory.remoteSource({
                url: IriSP.endpoints.segment,
                url_params: {
                    iri_id: _m.id,
                    limit: 0
                },
                serializer: IriSP.serializers.segmentapi
            }).onLoad(function() {
                var medias = this.getMedias(),
                    annotations = this.getAnnotations().filter(function(annotation) {
                        return annotation.getDuration() > 0;
                    });;
                if (medias && medias.length) {
                    var mediaid = medias[0].id;
                    el = $(".item-video[data-media-id='" + mediaid + "'] .media-count");
                    el.text(annotations.length).parent().show();
                    mediasegmentscache[mediaid] = annotations;
                    if (currentMedia && mediaid === currentMedia.id && currentSegment) {
                        showOtherSegments();
                    }
                }
            });
        });
    });
    
    /* Search Media with left column form */
    
    $(".col-left input").on("keyup change input paste", function() {
        var val = $(this).val();
        if (val.length < 2) {
            val = false;
        }
        if (val) {
            var find = IriSP.Model.regexpFromTextOrArray(val, true),
                replace = IriSP.Model.regexpFromTextOrArray(val, false);
        }
        $(".col-left .item-video").each(function() {
            var li = $(this),
                mediaid = li.attr("data-media-id"),
                media = directory.getElement(mediaid);
            if (!val) {
                li.find(".title-video").text(media.title);
                li.find(".description").text(media.description);
                li.find(".media-found-segments").html("");
                li.show();
            } else {
                var apimedia = apidirectory.getElement(mediaid);
                if (apimedia) {
                    var annotations = apimedia.getAnnotations().searchByTextFields(val);
                } else {
                    var annotations = [];
                }
                var found = find.test(media.title) || find.test(media.description) || annotations.length;
                if (found) {
                    li.find(".title-video").html(media.title.replace(replace, '<span style="background: #fc00ff; color: #ffffff;">$1</span>'));
                    li.find(".description").html(media.description.replace(replace, '<span style="background: #fc00ff; color: #ffffff;">$1</span>'));
                    var html = '',
                        k = 230 / media.duration,
                        lines = [];
                    _(annotations).each(function(_a, i) {
                        var pos = k * (_a.begin + _a.end) / 2,
                            corrpos = Math.max(76, Math.min(156, pos)),
                            line = IriSP._(lines).find(function(line) {
                                return !IriSP._(line.annotations).find(function(ann) {
                                    return ann.begin < _a.end && ann.end > _a.begin
                                });
                            });
                        if (!line) {
                            line = { index: lines.length, annotations: []};
                            lines.push(line); 
                        }
                        line.annotations.push(_a);
                        vizdata = {
                            annotation : _a,
                            left : k * _a.begin,
                            width : k * _a.getDuration(),
                            top: 8 * line.index,
                            color: IriSP.vizcolors[i % IriSP.vizcolors.length],
                            title: _a.title.replace(replace, '<span style="background: #fc00ff; color: #ffffff;">$1</span>'),
                            description: _a.description.replace(replace, '<span style="background: #fc00ff; color: #ffffff;">$1</span>'),
                            tags: _a.keywords.join(", ").replace(replace, '<span style="background: #fc00ff; color: #ffffff;">$1</span>'),
                            popleft : corrpos,
                            pointerpos : (pos - corrpos),
                        }
                        html += mediafoundtemplate(vizdata);
                    });
                    html = mediafoundlisttemplate({
                        height: 8 * lines.length,
                        segments: html
                    });
                    li.find(".media-found-segments").html(html);
                    li.show();
                } else {
                    li.hide();
                }
            }
        })
    });
    
    /* Fill right column when mashup is updated */
    
    function updateMashupUI() {
        var listhtml = '', critical = false, warning = false, messages = [];
        mashup.segments.forEach(function(_s) {
            listhtml += segmenttemplate(_s);
            if (_s.annotation.status === "critical") {
                critical = true;
            }
        });
        if (critical) {
            messages.push(gettext("One or more segments are invalid"));
        }
        
        _(mashupcritical).each(function(sc) {
            if (!sc.validate(mashup)) {
                critical = true;
                messages.push(sc.message);
            }
        });
        _(mashupwarning).each(function(sc) {
            if (!sc.validate(mashup)) {
                warning = true;
                messages.push(sc.message);
            }
        });
        mashup.status = critical ? "critical" : (warning ? "warning" : "valid");
        if (!messages.length) {
            messages.push(gettext("Your hashcut is valid!"));
        }
        mashupstatus = ' - ' + _(messages).join('\n - ');
        
        $(".publier-button").toggleClass("disable", critical);
        
        $(".liste-segment .validate").removeClass("critical warning valid").addClass(mashup.status);
        $(".liste-segment .validate-tooltip").html("<ul><li>" + messages.join("</li><li>")+"</li></ul>");
        
        $(".col-right .list-video").html(listhtml).find(".item-video:last-child .bottom, .item-video:first-child .top").addClass("disable");
        
        project.trigger("mouseout-annotation");
    }
    
    mashup.on("setcurrent", function() {
        currentMedia = mashup;
    });
    
    mashup.on("change",updateMashupUI);
    
    /* Slice Widget */
   
    var sliceSlider = $(".Ldt-Slice"),
        sliceStartTime,
        slidersRange = 920;
    
    sliceSlider.slider({
        range: true,
        values: [0, slidersRange],
        min: 0,
        max: slidersRange,
        start: function() {
            if (currentMedia) {
                if (!currentMedia.getPaused()) {
                    currentMedia.pause();
                }
            }
        },
        slide: function(event, ui) {
            if (currentMedia && currentSegment) {
                var t = currentMedia.duration * ui.value / slidersRange;
                if (ui.value === ui.values[0]) {
                    currentSegment.setBegin(t);
                } else {
                    currentSegment.setEnd(t);
                }
            }
        }
    });
    
    sliceSlider.find(".ui-slider-handle:first")
        .addClass("Ldt-Slice-left-handle")
        .click(function() {
            if (currentMedia && currentSegment) {
                currentMedia.setCurrentTime(currentSegment.begin);
            }
        });
    sliceSlider.find(".ui-slider-handle:last")
        .addClass("Ldt-Slice-right-handle")
        .click(function() {
            if (currentMedia && currentSegment) {
                currentMedia.setCurrentTime(currentSegment.end);
            }
        });
    
    
    /* Update Segment UI */
    
    function updateSegmentUI() {
        if (currentMedia && currentSegment) {
            var start = currentSegment.begin,
                end = currentSegment.end,
                dur = currentSegment.getDuration(),
                f = slidersRange / currentMedia.duration,
                tangleStart = $(".tangle-start"),
                tangleEnd = $(".tangle-end"),
                tangleDuration = $(".tangle-duration"),
                k = 100 / currentMedia.duration,
                p = k * (start + end) / 2;
            sliceSlider.slider( "values", [ f * start, f * end ] );
            tangleStart.text(start.toString(tangleStart.hasClass("active"))).attr("data-milliseconds",start.milliseconds);
            tangleEnd.text(end.toString(tangleEnd.hasClass("active"))).attr("data-milliseconds",end.milliseconds);
            tangleDuration.text(dur.toString(tangleDuration.hasClass("active"))).attr("data-milliseconds",dur.milliseconds);
            $(".segmentation .pointer").css("left", p + "%");
            $(".media-current-section").css({
                left: (k * start) + "%",
                width: (k * dur) + "%"
            });
            var messages = [],
                critical = false,
                warning = false;
            _(segmentcritical).each(function(sc) {
                if (!sc.validate(currentSegment)) {
                    critical = true;
                    messages.push(sc.message);
                }
            });
            _(segmentwarning).each(function(sc) {
                if (!sc.validate(currentSegment)) {
                    warning = true;
                    messages.push(sc.message);
                }
            });
            currentSegment.status = critical ? "critical" : (warning ? "warning" : "valid");
            if (!messages.length) {
                messages.push(gettext("This segment is valid!"))
            }
            currentSegment.status_messages = _(messages);
            
            $(".segmentation .validate").removeClass("critical warning valid").addClass(currentSegment.status);
            $(".segmentation .validate-tooltip").html("<ul><li>" + currentSegment.status_messages.join("</li><li>")+"</li></ul>");
        }
    }
    
    function setMedia(media) {
        if (currentMedia) {
            currentMedia.pause();
        }
        currentMedia = media;
        project.trigger("set-current", media);
        if (currentMedia.elementType == "media") {
            showSegmentation();
            $(".tab-media-title").text(currentMedia.title);
            
            addMode = !(currentSegment && mashup.hasAnnotation(currentSegment));
            
            if (!currentSegment) {
                currentSegment = new IriSP.Model.Annotation(false, project);
                currentSegment.setMedia(currentMedia.id);
                currentSegment.setBegin(currentMedia.getCurrentTime());
                currentSegment.setEnd(Math.min(currentMedia.getCurrentTime() +  180000, currentMedia.duration));
                currentSegment.title = gettext("Untitled segment");
                currentSegment.color = currentMedia.color;
                currentSegment.thumbnail = currentMedia.thumbnail;
                currentSegment.created = new Date();
                currentSegment.keywords = [];
                currentSegment.description = "";
                currentSegment.on("change-begin", function() {
                    if (currentMedia && currentSegment === this) {
                        currentMedia.setCurrentTime(this.begin);
                        updateSegmentUI();
                    }
                });
                currentSegment.on("change-end", function() {
                    if (currentMedia && currentSegment === this) {
                        currentMedia.setCurrentTime(this.end);
                        updateSegmentUI();
                    }
                });
            }
            if (currentMedia.loaded) {
                currentMedia.setCurrentTime(currentSegment.begin);
            }
            $(".add-segment").val(addMode ? gettext("Add segment to hashcut") : gettext("Save segment"));
            $(".create-or-edit").text(addMode ? gettext("Create new segment") : gettext("Edit existing segment"));
            $("#segment-title").val(currentSegment.title);
            $("#segment-description").val(currentSegment.description);
            var segment_tags = $("#segment-tags");
            segment_tags.tagit("option","onTagRemoved",function(){});
            segment_tags.tagit("option","onTagAdded",function(){});
            segment_tags.tagit("removeAll");
            _(currentSegment.keywords).each(function(tag) {
                segment_tags.tagit("createTag",tag);
            });
            segment_tags.tagit("option","onTagRemoved",updateSegmentTags);
            segment_tags.tagit("option","onTagAdded",updateSegmentTags);
            updateSegmentUI();
            var relatedSegments = mashup.segments.filter(function(_s) {
                return _s.getMedia() === currentMedia && _s.annotation !== currentSegment;
            });
            if (relatedSegments.length) {
                $(".self-media-segments").show();
            } else {
                $(".self-media-segments").hide();
            }
            $(".self-media-segments .media-segments-list").html(mediaSegmentList(_(relatedSegments).pluck("annotation")));
            showOtherSegments();
            project.trigger("mouseout-annotation");
        }
        if (currentMedia.elementType === "mashup") {
            showPreview();
        }
    }
    
    function mediaSegmentList(_annotations) {
        var html = '',
            k = $(".Ldt-Slider").width() / currentMedia.duration,
            lines = [];
        _(_annotations).each(function(_a, i) {
            var pos = k * (_a.begin + _a.end) / 2,
                corrpos = Math.max(145, Math.min(305, pos)),
                line = IriSP._(lines).find(function(line) {
                    return !IriSP._(line.annotations).find(function(ann) {
                        return ann.begin < _a.end && ann.end > _a.begin
                    });
                });
            if (!line) {
                line = { index: lines.length, annotations: []};
                lines.push(line); 
            }
            line.annotations.push(_a);
            vizdata = {
                annotation : _a,
                popleft : corrpos,
                left : k * _a.begin,
                width : k * _a.getDuration(),
                height: 8,
                top: 8 * line.index,
                pointerpos : (pos - corrpos),
                color: IriSP.vizcolors[i % IriSP.vizcolors.length]
            }
            html += mediasegmenttemplate(vizdata);
        });
        return mediasegmentlisttemplate({
            height: 8 * lines.length,
            left: k * currentSegment.begin,
            width: k * currentSegment.getDuration(),
            segments: html
        });
    }
    
    /* Show Related Segments */
    
    function showOtherSegments() {
        var annotations = mediasegmentscache[currentMedia.id];
        $(".other-media-segments .media-segments-list").html(mediaSegmentList(annotations));
        if (annotations && annotations.length) {
            $(".other-media-segments").show();
        }
        else {
            $(".other-media-segments").hide();
        }
     }
    /* Set In, Out */
   
    $(".Ldt-Ctrl-SetIn").click(function() {
        if (currentMedia && currentSegment) {
            currentSegment.setBegin(currentMedia.getCurrentTime());
        }
    });
    $(".Ldt-Ctrl-SetOut").click(function() {
        if (currentMedia && currentSegment) {
            currentSegment.setEnd(currentMedia.getCurrentTime());
        }
    });
       
    /* Segment Form interaction */
   
    $("#segment-title").on("keyup change input paste", function() {
        if (currentMedia && currentSegment) {
            currentSegment.title = $(this).val();
            updateSegmentUI();
            mashup.trigger("change");
        }
    });
    $("#segment-title").on("focus click", function() {
        if ($(this).val() === gettext("Untitled segment")) {
            $(this).val("");
        }
    });
    $("#segment-description").on("keyup change input paste", function() {
        if (currentMedia && currentSegment) {
            currentSegment.description = $(this).val();
            updateSegmentUI();
            mashup.trigger("change");
        }
    });
    $("#segment-form").submit(function() {
        currentSegment.title = $("#segment-title").val();
        currentSegment.description = $("#segment-description").val();
        currentSegment.keywords = $("#segment-tags").tagit("assignedTags");
        updateSegmentUI();
        if (addMode) {
            mashup.addAnnotation(currentSegment);
            currentSegment = undefined;
            setMedia(currentMedia);
        } else {
            mashup.trigger("change");
            var segment = mashup.getAnnotation(currentSegment);
            currentSegment = undefined;
            if (mashupModeAfterSave) {
                setMedia(mashup);
                if (segment) {
                    mashup.setCurrentTime(segment.begin);
                    mashup.trigger("enter-annotation",segment);
                }
            } else {
                setMedia(currentMedia);
            }
        }
        return false;
    });
    
    $("#segment-tags").tagit();
    
    
    /* We have to defer this function because the tagit events
     * are triggered before the data are updated */
    function updateSegmentTags() {
        window.setTimeout(function() {
            if (currentMedia && currentSegment) {
                currentSegment.keywords = $("#segment-tags").tagit("assignedTags");
                updateSegmentUI();
                mashup.trigger("change");
            }
        }, 0);
    }
    
    /* Click on media items */
   
    $(".col-left").on("click", ".item-video", function() {
        currentSegment = undefined;
        setMedia(project.getElement($(this).attr("data-media-id")));
    });
    
    /* Click on Tabs */
    
    function showSegmentation() {
        $(".col-middle").removeClass("empty-mode pvw-mode").addClass("segment-mode");
        return false;
    }
    function showPreview() {
        $(".col-middle").removeClass("empty-mode segment-mode").addClass("pvw-mode");
        return false;
    }
    function showEmpty() {
        $("video").hide();
        $(".col-middle").removeClass("pvw-mode segment-mode").addClass("empty-mode");
        return false;
    }
    
    $(".tab-pvw").click(function() {
        if (mashup.segments.length) {
            setMedia(mashup);
        }
    });
    
    /* Click on segments */
    
    function reorganizeMashup() {
        var ids = $(".organize-segments .item-video").map(function(){return $(this).attr("data-segment-id")});
        mashup.setAnnotationsById(ids);
    }
    
    project.on("mouseover-annotation", function(annotation) {
        var mediaid = annotation.getMedia().id;
        $(".media").removeClass("active");
        $(".media[data-media-id='" + mediaid + "']").addClass("active");
    });
    
    project.on("mouseout-annotation", function() {
        $(".media").removeClass("active");
        var mediaid = undefined;
        if (currentMedia && currentMedia.elementType === "media") {
            mediaid = currentMedia.id;
            if (currentSegment) {
                $(".annotation").removeClass("active");
                $(".annotation[data-segment-id='" + currentSegment.id + "']").addClass("active");
            }
        }
        if (currentMedia === mashup && mashup.currentMedia) {
            mediaid = mashup.currentMedia.id
        }
        $(".media[data-media-id='" + mediaid + "']").addClass("active");
    });
    
    $(".organize-segments")
    .sortable({
        stop : reorganizeMashup
    })
    .on("mouseover", ".item-video", function() {
        project.trigger("mouseover-annotation", project.getElement($(this).attr("data-segment-id")));
    })
    .on("mouseout", ".item-video", function() {
        project.trigger("mouseout-annotation");
    })
    .on("click", ".item-video", function() {
        var segment = project.getElement($(this).attr("data-segment-id"));
        if (currentMedia === mashup) {
            project.trigger("click-annotation", segment);
        } else {
            currentSegment = segment;
            setMedia(segment.getMedia());
        }
    })
    .on("click", ".edit", function(e) {
        var currentItem = $(this).parents(".item-video"),
            media = project.getElement(currentItem.attr("data-media-id")),
            segment = project.getElement(currentItem.attr("data-segment-id"));
        mashupModeAfterSave = !!(currentMedia === mashup);
        currentSegment = segment;
        setMedia(media);
        return false;
    })
    .on("click", ".top", function(e){
        var currentItem = $(this).parents(".item-video");
        currentItem.insertBefore(currentItem.prev());
		reorganizeMashup();
		return false;
    })
    .on("click", ".bottom", function(e){
        var currentItem = $(this).parents(".item-video");
        currentItem.insertAfter(currentItem.next());
		reorganizeMashup();
        return false;
    })
    .on("click", ".delete", function(e){
        var id = $(this).parents(".item-video").attr("data-segment-id");
        mashup.removeAnnotationById(id);
        if (!mashup.segments.length) {
            showEmpty();
        }
        return false;
    });
    
    $(".mashup-frise").click(function(evt) {
        if (!mashup.duration.milliseconds) {
            return;
        }
        var el = $(this), t = ( evt.pageX - el.offset().left ) * mashup.duration / el.width(), segment = mashup.getAnnotationAtTime(t);
        if (segment) {
            if (currentMedia === mashup) {
                project.trigger("click-annotation", segment.annotation);
            } else {
                currentSegment = segment.annotation;
                setMedia(currentSegment.getMedia());
            }
        }
    });
    
    /* Tangles */
    var tangleMsPerPixel = 100,
        activeTangle,
        tangleStartX,
        tangleStartVal,
        tangleHasMoved;
    
    $(".time-tangle").mousedown(function(evt) {
        activeTangle = $(this);
        activeTangle.addClass("active");
        tangleStartVal = +activeTangle.attr("data-milliseconds");
        tangleStartX = evt.pageX;
        tangleHasMoved = false;
        $(this).siblings(".time-tangle").addClass("deactivate");
        return false;
    });
    $(document)
        .mousemove(function(evt) {
            if (activeTangle) {
                tangleHasMoved = true;
                var newval = new IriSP.Model.Time(tangleMsPerPixel * (evt.pageX - tangleStartX) + tangleStartVal);
                activeTangle.trigger("valuechange", newval);
                return false;
            }
        })
        .mouseup(function() {
            if (activeTangle) {
                activeTangle.text(activeTangle.text().replace(/\.\d+$/,''));
                $(".time-tangle").removeClass("active deactivate");
                activeTangle = undefined;
            }
        });
        
    $(".tangle-start")
        .mouseup(function(evt) {
            if (!tangleHasMoved && currentMedia && currentSegment) {
                currentMedia.setCurrentTime(currentSegment.begin);
            }
        })
        .on("valuechange", function(evt, val) {
            if (currentMedia && currentSegment) {
                currentSegment.setBegin(val);
            }
        });
    $(".tangle-end")
        .mouseup(function(evt) {
            if (!tangleHasMoved && currentMedia && currentSegment) {
                currentMedia.setCurrentTime(currentSegment.end);
            }
        })
        .on("valuechange", function(evt, val) {
            if (currentMedia && currentSegment) {
                currentSegment.setEnd(val);
            }
        });
    $(".tangle-duration").on("valuechange", function(evt, val) {
        if (currentMedia && currentSegment) {
            currentSegment.setDuration(val);
        }
    });
    
    /* Click on current segment in Preview */
    
    $(".mashup-description .edit").click(function() {
        if (mashup.currentAnnotation) {
            mashupModeAfterSave = !!(currentMedia === mashup);
            currentSegment = mashup.currentAnnotation.annotation;
            setMedia(mashup.currentAnnotation.getMedia());
        }
        return false;
    });
    
    /* Handling related segments */
   
    function cloneSegment(sid) {
        var s = directory.getElement(sid) || apidirectory.getElement(sid),
            media = directory.getElement(s.getMedia().id);
        
        currentSegment = new IriSP.Model.Annotation(false, project);
        currentSegment.setMedia(media.id);
        currentSegment.setBegin(s.begin);
        currentSegment.setEnd(s.end);
        currentSegment.title = gettext("Copy of ") + s.title;
        currentSegment.description = s.description;
        currentSegment.keywords = s.keywords;
        currentSegment.color = media.color;
        currentSegment.thumbnail = media.thumbnail;
        currentSegment.created = new Date();
        currentSegment.on("change-begin", function() {
            if (currentMedia && currentSegment === this) {
                currentMedia.setCurrentTime(this.begin);
                updateSegmentUI();
            }
        });
        currentSegment.on("change-end", function() {
            if (currentMedia && currentSegment === this) {
                currentMedia.setCurrentTime(this.end);
                updateSegmentUI();
            }
        });
        setMedia(media);
    }
    
    $(".media-segments-list").on("mouseover", ".media-segment", function() {
        $(this).find(".media-segment-popin").show();
    }).on("mouseout", ".media-segment", function() {
        $(this).find(".media-segment-popin").hide();
    }).on("click", ".reprendre-segment", function() {
        cloneSegment($(this).attr("data-segment-id"));
        return false;
    }).on("click", ".media-segment-section", function() {
        var sid = $(this).attr("data-segment-id"),
            s = directory.getElement(sid) || apidirectory.getElement(sid);
        if (s.media.id === currentMedia.id) {
            currentMedia.setCurrentTime(s.begin);
        }
    });
    
    $(".col-left").on("mouseover", ".media-segment", function() {
        $(this).find(".media-found-popin").show();
    }).on("mouseout", ".media-segment", function() {
        $(this).find(".media-found-popin").hide();
    }).on("click", ".clone-segment", function() {
        cloneSegment($(this).attr("data-segment-id"));
        return false;
    });
    /* Changing Hashcut Title and description */
    
    mashup.title = gettext("Untitled Hashcut");
    $(".title-video-wrap a").text(mashup.title);
    $("#hashcut-title").val(mashup.title);
    
    $("#hashcut-title").on("keyup change input paste", function() {
        mashup.title = $(this).val();
        $(".title-video-wrap a").text(mashup.title);
        mashup.trigger("change");
    });
    $("#hashcut-title").on("focus click", function() {
        if ($(this).val() === gettext("Untitled Hashcut")) {
            $(this).val("");
        }
    });
    
    $("#hashcut-description").on("keyup change input paste", function() {
        mashup.description = $(this).val();
        mashup.trigger("change");
    });
    
    $("#hashcut-form").submit(function() {
        $(".update-title").hide();
        return false;
    })
    
    /* Publication */
   
    function onLeave() {
        return gettext("You haven't published your hashcut yet.\nIf you leave this page, it will be lost.");
    }
    
    $(window).on("beforeunload", onLeave);
   
    $(".publier-button").click(function() {
        if ($(this).hasClass("disable")) {
            alert(gettext("The hashcut can't be published because:")+"\n\n"+mashupstatus);
            return false;
        }
        var postproject = directory.newLocalSource(),
            medias = mashup.getMedias()
            annotations = mashup.getOriginalAnnotations();
        postproject.addList("annotationType");
        postproject.addList("tag");
        medias.forEach(function(_m) {
            var anntype = new IriSP.Model.AnnotationType(false, postproject);
            anntype.title = "Segments from " + _m.title;
            anntype.media = _m;
            postproject.getAnnotationTypes().push(anntype);
        });
        annotations.forEach(function(_a) {
            _a.setAnnotationType(
                postproject.getAnnotationTypes().filter(
                    function(_at) { return _at.media === _a.getMedia() }
                )[0].id);
            var tagids = [];
            _(_a.keywords).each(function(keyword) {
                var tags = postproject.getTags().searchByTitle(keyword);
                if (tags.length) {
                    tagids.push(tags[0].id);
                } else {
                    var tag = new IriSP.Model.Tag(false, postproject);
                    tag.title = tag.description = keyword;
                    postproject.getTags().push(tag);
                    tagids.push(tag.id);
                }
            });
            _a.setTags(tagids);
        });
        postproject.addList("annotation",annotations);
        postproject.addList("media",medias);
        postproject.addList("mashup",[mashup]);
        postproject.creator = options.creator;
        postproject.created = new Date();
        postproject.modified = new Date();
        postproject.title = mashup.title;
        postproject.description = mashup.description;
        var waitscreen = $('<div class="full-wait">');
        waitscreen.appendTo('body');
        $.ajax({
            type: "POST",
            url: IriSP.endpoints.project,
            data: IriSP.serializers.ldt.serialize(postproject),
            contentType: "application/cinelab",
            headers: {
                "X-CSRFToken": options.csrf_token
            },
            success: function(data, status, request){
                var location = request.getResponseHeader("Location"),
                    projid = location.match(/([^/]+)\/?$/)[1],
                    destination = IriSP.endpoints.hashcut_page.replace('__PROJECT_ID__', projid);
                $(window).off("beforeunload", onLeave);
                document.location.href = destination;
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert(gettext("Server error\nYour hashcut couldn't be published"));
                waitscreen.remove();
            }
        });
        
        return false;
    });
    
    mashup.trigger("change");
}

/* END editor.js */
IriSP.mashupplayer = function(options) {
    
    var directory = new IriSP.Model.Directory(),
        project = directory.remoteSource({
            url: IriSP.endpoints.ldt.replace('__PROJECT_ID__', options.id),
            serializer: IriSP.serializers.ldt
        }),
        apidirectory = new IriSP.Model.Directory(),
        mashup,
        mediatemplate = _.template('<li class="item-video media" data-media-id="<%= media.id %>">'
            + '<a href="<%= IriSP.endpoints.media_page.replace("__CONTENT_ID__", media.id) %>"><img class="thumbnail" alt="<%= media.title %>" src="<%= media.thumbnail %>"></a><div class="video-info">'
            + '<h3 class="title-video"><a href="<%= IriSP.endpoints.media_page.replace("__CONTENT_ID__", media.id) %>"><%= media.title %></a></h3><p class="description"><%= media.description %></p>'
            + '<p class="time-length">Durée : <span><%= media.duration.toString() %></span></p><div class="frise">'
            + '<div class="frise-overflow"><div class="frise-segments"><%= segments %></div></div></div></div></li>');
        segmenttemplate = _.template('<div style="background-color:<%= annotation.color %>; left:<%= left %>%; width: <%= width %>%;"'
            + ' class="frise-segment annotation" data-segment-id="<%= annotation.id %>" title="<%= annotation.title %>"></div>')
    
    project.onLoad(function() {
        mashup = project.getMashups()[0];
        IriSP.mashupcore(project, mashup);
        project.trigger("set-current",mashup);
        
        $(".info-duration td").text(mashup.duration.toString());
        
        $(".info-description td").text(mashup.description);
        
        mashup.getMedias().forEach(function(media) {
            apidirectory.remoteSource({
                url: IriSP.endpoints.content + media.id,
                serializer: IriSP.serializers.content
            }).onLoad(function() {
                var m = apidirectory.getElement(media.id);
                if (m) {
                    media.thumbnail = m.thumbnail;
                }
                var segments = mashup.segments.filter(function(segment) {
                    return segment.getMedia() === media;
                });
                var segmentshtml = '', k = media.duration ? (100 / media.duration) : 0;
                segments.forEach(function(segment) {
                    var vizdata = {
                        annotation: segment.annotation,
                        left: k * segment.annotation.begin,
                        width: k * segment.annotation.getDuration()
                    }
                    segmentshtml += segmenttemplate(vizdata);
                });
                var mediadata = {
                    media: media,
                    segments: segmentshtml
                }
                
                $(".list-video").append(mediatemplate(mediadata));
            });
        });
        
        
        project.on("mouseover-annotation", function(annotation) {
            var mediaid = annotation.getMedia().id;
            $(".media").removeClass("active");
            $(".media[data-media-id='" + mediaid + "']").addClass("active");
        });
        
        project.on("mouseout-annotation", function(annotation) {
            $(".media").removeClass("active");
            var mediaid = mashup.currentMedia.id;
            $(".media[data-media-id='" + mediaid + "']").addClass("active");
        });
        
        $(".list-video")
        .on("mouseover", ".frise-segment", function() {
            project.trigger("mouseover-annotation", project.getElement($(this).attr("data-segment-id")));
        })
        .on("click", ".frise-segment", function() {
            project.trigger("click-annotation", project.getElement($(this).attr("data-segment-id")));
        })
        .on("mouseover", ".item-video", function() {
            $(".media").removeClass("active");
        })
        .on("mouseout", ".item-video", function() {
            project.trigger("mouseout-annotation");
        });
        
        $(".mashup-frise")
        .click(function(evt) {
            if (!mashup.duration.milliseconds) {
                return;
            }
            var el = $(this), t = ( evt.pageX - el.offset().left ) * mashup.duration / el.width(), segment = mashup.getAnnotationAtTime(t);
            if (segment) {
                project.trigger("click-annotation", segment.annotation);
            }
        });
        
        
    });
}

/* END mashupplayer.js */
IriSP.mediaplayer = function(options) {
    
    var directory = new IriSP.Model.Directory(),
        content = directory.remoteSource({
            url: IriSP.endpoints.content + options.id,
            serializer: IriSP.serializers.content
        }),
        apidirectory = new IriSP.Model.Directory(),
        segmenttemplate = _.template(
            '<div class="media-segment" data-mashup-id="<%= annotation.project_id %>">'
            + '<div class="media-segment-section" style="left:<%= left %>px; width:<%= width %>px; background:<%= color %>; top: <%= top %>px;" data-segment-id="<%= annotation.id %>"></div>'
            + '<div class="popin media-segment-popin" style="left:<%= popleft %>px; top: <%= 5+top %>px;"><div style="left:<%= pointerpos %>px;" class="pointer"></div><div class="popin-content">'
            + '<h3 class="segment-title"><%= annotation.title %></h3>'
            + '<p><%= annotation.description %></p><% if (annotation.keywords.length) { print("<p><strong>" + gettext("Tags:") + "</strong> " + annotation.keywords.join(", ") + "</p>"); } %>'
            + '<p><%= gettext("From:") %> <span><%= annotation.begin.toString() %></span> <%= gettext("to:") %> <span><%= annotation.end.toString() %></span> (<%= gettext("duration:") %> <span><%= annotation.getDuration().toString() %></span>)</p>'
            + '<p class="mashup-link"><%= gettext("From hashcut:") %> <a href="<%= IriSP.endpoints.hashcut_page.replace("__PROJECT_ID__", annotation.project_id) %>"></a></p>'
            + '</div></div></div>'
        ),
        segmentlisttemplate = _.template(
            '<div class="media-segment-list" style="height: <%= height %>px"><div class="media-segment-list-inner"></div><%= segments %><div class="frise-position"></div></div>'
        ),
        projtemplate = _.template(
            '<li class="item-video mashup" data-mashup-id="<%= ldt_id %>">'
            + '<a href="<%= IriSP.endpoints.hashcut_page.replace("__PROJECT_ID__", ldt_id) %>"><img class="thumbnail" alt="<%= title %>" src="<%= image %>"></a><div class="video-info">'
            + '<h3 class="title-video"><a href="<%= IriSP.endpoints.hashcut_page.replace("__PROJECT_ID__", ldt_id) %>"><%= title %></a></h3><p class="description"><%= description %></p>'
            + '</p></div></li>'
        ),
        media;
    
    function mediaSegmentList(_annotations) {
        var html = '',
            k = $(".Ldt-Slider").width() / media.duration,
            lines = [];
        _(_annotations).each(function(_a, i) {
            var pos = k * (_a.begin + _a.end) / 2,
                corrpos = Math.max(106, Math.min(516, pos)),
                line = IriSP._(lines).find(function(line) {
                    return !IriSP._(line.annotations).find(function(ann) {
                        return ann.begin < _a.end && ann.end > _a.begin
                    });
                });
            if (!line) {
                line = { index: lines.length, annotations: []};
                lines.push(line); 
            }
            line.annotations.push(_a);
            vizdata = {
                annotation : _a,
                popleft : corrpos,
                left : k * _a.begin,
                width : k * _a.getDuration(),
                height: 8,
                top: 8 * line.index,
                pointerpos : (pos - corrpos),
                color: IriSP.vizcolors[i % IriSP.vizcolors.length]
            }
            html += segmenttemplate(vizdata);
        });
        return segmentlisttemplate({
            height: 8 * lines.length,
            segments: html
        });
    }
    
    content.onLoad(function() {
        
        IriSP.mashupcore(content, new IriSP.Model.Mashup(false, content));
        
        media = content.getMedias()[0];
        
        apidirectory.remoteSource({
            url: IriSP.endpoints.segment,
            url_params: {
                iri_id: options.id,
                limit: 0
            },
            serializer: IriSP.serializers.segmentapi
        }).onLoad(function() {
            var medias = this.getMedias(),
                annotations = this.getAnnotations(),
                projlist = {};
            $(".media-segments").html(mediaSegmentList(annotations));
            annotations.forEach(function(a) {
                projlist[a.project_id] = 1 + (projlist[a.project_id] || 0);
            });
            var projkeys = _(projlist)
                .chain()
                .keys()
                .sortBy(function(v) {
                    return - projlist[v];
                })
                .first(8)
                .value();
            $.ajax({
                url: IriSP.endpoints.project,
                dataType: "json",
                data: {
                    format: "json",
                    ldt_id__in: projkeys
                },
                traditional: true,
                success: function(data) {
                    var proj = _(data.objects)
                        .filter(function(p) {
                            return /<inst/gm.test(p.ldt);
                        });
                    _(proj).each(function(p) {
                        $(".media-segment[data-mashup-id='" + p.ldt_id + "']").each(function() {
                            $(this)
                                .find(".mashup-link").show()
                                .find("a").text(p.title);
                        });
                    });
                    var html = _(proj)
                        .chain()
                        .sortBy(function(p) {
                            return - projlist[p.ldt_id];
                        })
                        .map(projtemplate)
                        .value()
                        .join("");
                    $(".list-video").html(html);
                }
            });
        });
    
        content.trigger("set-current", media);
        
        $(".info-duration td").text(media.duration.toString());
        $(".info-description td").text(media.description);
        $(".info-tags td").text(media.keywords);
        
    });
    
    $(".media-segments").on("mouseover", ".media-segment", function() {
        var el = $(this);
        el.find(".media-segment-popin").show();
        var pid = el.attr("data-mashup-id");
        $(".item-video[data-mashup-id='" + pid + "']").addClass("active");
    }).on("mouseout", ".media-segment", function() {
        $(this).find(".media-segment-popin").hide();
        $(".item-video").removeClass("active");
    }).on("click", ".media-segment-section", function() {
        var sid = $(this).attr("data-segment-id"),
            s = apidirectory.getElement(sid);
        media.setCurrentTime(s.begin);
    });
    
    $(".list-video").on("mouseover", ".item-video", function() {
        var pid = $(this).attr("data-mashup-id");
        $(".media-segment[data-mashup-id='" + pid + "']").addClass("active");
    }).on("mouseout", ".item-video", function() {
        $(".media-segment").removeClass("active");
    });
}

/* END mediaplayer.js */

