//  ----------------------------------------------------------------------------
//  Initialize
//  ----------------------------------------------------------------------------

settings = {
    video: {
        setting: user.video_size || 'small',
        small: {
            w: 128,
            play: {
                left: '8px',
                top: '88px',
                width: '192px',
                height: function() { return (192 / video.aspectratio) + 'px' }
            },
            'in': {
                left: '208px',
                top: '88px',
                width: '196px',
                height: function() { return (192 / video.aspectratio) + 'px' }
            },
            out: {
                left: '412px',
                top: '88px',
                width: '196px',
                height: function() { return (192 / video.aspectratio) + 'px' }
            }
        },
        medium: {
            w: 320,
            play: {
                left: '8px',
                top: '88px',
                width: '400px',
                height: function() { return (400 / video.aspectratio) + 'px' }
            },
            'in': {
                left: '416px',
                top: '88px',
                width: '192px',
                height: function() { return ((400 / video.aspectratio - 24) / 2) + 'px' }
            },
            out: {
                left: '416px',
                top: function() { return (88 + (400 / video.aspectratio - 24) / 2 + 24) + 'px' },
                width: '192px',
                height: function() { return ((400 / video.aspectratio - 24) / 2) + 'px' }
            }
        },
        large: {
            w: 320,
            play: {
                left: '8px',
                top: '88px',
                width: '600px',
                height: function() { return (600 / video.aspectratio) + 'px' }
            },
            'in': {
                left: '0px',
                top: '0px',
                width: '0px',
                height: function() { return '0px' }
            },
            out: {
                left: '0px',
                top: '0px',
                width: '0px',
                height: function() { return '0px' }
            }
        }

    },
    font: {
        setting: user.font_size || 'small',
        small: {
            fontSize: '11px',
            lineHeight: '14px'
        },
        medium: {
            fontSize: '16px',
            lineHeight: '20px'
        },
        large: {
            fontSize: '21px',
            lineHeight: '26px'
        },
    },
    timelines: {
        setting: user.timeline_size || 'small'
    }
}

requests = new RequestsController();
/**
/ Initializes page, instantiates objects, gets templates, defines video.local, loads Room object if enough height on page
*/
$(document).ready(function() {

    updatePlayer();

    if ($.browser.safari) {
        $('#data').css({
            marginBottom: '0px'
        });
    }

    menu = new Menu();
    menu.load();

    selectedSort = $('#selectSort').val();
    $('#selectSort').change(function() { setSort($('#selectSort').val()) });
    $('#textInfo').html(getTimecode(video.duration).substr(0, 8));

    selectedBin = '';
    selectedLayer = -1;
    results = [];
    selectedResult = -1;
    loadTemplate('timeline.html', function(html) {
        htmlTimeline = html;
        timeline = new Timeline();
        timeline.load();
        playMarker = new PlayMarker();
        inMarker = new InOutMarker('in');
        outMarker = new InOutMarker('out');
        playMarker.load();
        inMarker.load();
        outMarker.load();
        loadTemplate('viewer.html', function(html) {
            htmlViewer = html;
            playViewer = new PlayViewer();
            inViewer = new InOutViewer('in');
            outViewer = new InOutViewer('out');
            playViewer.load();
            inViewer.load();
            outViewer.load();
            loadTemplate('box.html', function(html) {
                htmlBox = html;
                // fixme: call all these with an id, not with an element (and create the element later)
                // infoPanel = new Panel($('#bins'));
                // infoPanel.load();
                locationBin = new TermBin($('#bins'), 'location');
                keywordBin = new TermBin($('#bins'), 'keyword');
                descriptionBin = new TextBin($('#bins'), 'description');
                transcriptBin = new TextBin($('#bins'), 'transcript');
                bin = {'location': locationBin, 'keyword': keywordBin, 'description': descriptionBin, 'transcript': transcriptBin};
                binName = ['location', 'keyword', 'description', 'transcript'];
                loadEdits();
                loadCreators();
                creatorBin = new CreatorBin($('#bins'));
                creatorBin.load();
                setLayerState(0);
                for (var k in bin)
                    bin[k].load();
                cache = new Cache();
                layerClipboard = new LayerClipboard();
                layerHistory = new LayerHistory();
                searchHistory = new SearchHistory();
                keyboard = new KeyboardController();
                parseArguments();
                var left = 8;
                var top = settings.video[settings.video.setting].play.height + timeline.lines * 24 + 56;
                var width = 600
                var height = window.innerHeight - top - 120;
                if (height >= 32) {
                    loadTemplate('room.html', function(html) {
                        htmlRoom = html;
                        if (user.logged_in)
                            room = new Room($('#room'), 'notes', 'discussion', left, top, width, height);
                        else
                            room = new Room($('#room'), 'discussion', '', left, top, width, height);
                        room.load();
                    });
                }
                onresize = function() { resize() }
            });
        });
    });
});

//  ----------------------------------------------------------------------------
//  Argument parser functions
//  ----------------------------------------------------------------------------
/**
* Parses arguments in URL - sets inPoint / outPoint if passed in url, calls {@link find} if finds a search query string.
*/
function parseArguments() {
    var length = (base_url + '/' + video.id).length;
    if (window.location.href.substr(length, 7) == '/editor')
        length += 7;
    var operator = window.location.href.substr(length, 1);
    var query = window.location.href.substr(length + 1).split('#')[0];
    if (operator == '/') {
        if (query.indexOf('-') != -1) {
            var split = query.split('-');
            var inPos = getPos(split[0]);
            var outPos = getPos(split[1]);
            if (inPos > video.duration)
                inPos = video.duration;
            if (outPos > video.duration || outPos == 0)
                outPos = video.duration;
            else if (outPos < inPos)
                outPos = inPos;
            if (inPos != -1 && outPos != -1)
                timeline.set(inPos, inPos, outPos);
        }
        else {
            var playPos = getPos(query);
            if (playPos != -1)
                playMarker.set(playPos);
            else {
                var l = getLayer(query);
                if (l != -1) {
                    var b = getBin(query);
                    // alert(b+'/'+l)
                    playMarker.set(layers[l]['time_in']);
                    bin[b].selectLayer(l);
                }
            }
        }
    }
    else if (operator == '?') {
        var args = getArguments(['f', 'q']);
        args = parseQuery(args['f'], args['q']);
        if (args['f'] != 'all' || args['q'] != '')
            find(args['f'], args['q']);
    }
}

//  ----------------------------------------------------------------------------
//  Keyboard controller prototype
//  ----------------------------------------------------------------------------

/**
* @constructor
* @class KeyboardController class - listens for keyboard input
* @property {Int} state States: 0 (no focus), 1 (focus on input), 2 (focus on textarea)
* @property {Object} keys [{13: 'enter' ... }, {...}, {...}] <- key mappings for 3 different states
*/
function KeyboardController() {
    /*
        state: 0 (no focus), 1 (focus on input), 2 (focus on textarea)
    */
    this.state = 0;
    this.keys = [
        {
            13: 'enter',
            27: 'escape',
            32: 'space',
            37: 'left',
            38: 'up',
            39: 'right',
            40: 'down',
            48: ')',
            57: '(',
            65: 'a',
            67: 'c',
            68: 'd',
            69: 'e',
            70: 'f',
            71: 'g',
            72: 'h',
            73: 'i',
            75: 'k',
            76: 'l',
            77: 'm',
            78: 'n',
            79: 'o',
            80: 'p',
            83: 's',
            84: 't',
            86: 'v',
            88: 'x',
            90: 'z',
            187: '+',
            188: '<',
            189: '-',
            190: '>',
            192: '~',
            219: '[',
            221: ']'
        },
        {
            13: 'enter',
            27: 'escape'
        },
        {
            27: 'escape'
        }
    ];
    var t = this;
    $(document).keydown(function(event) { t.pressKey(event) });
}

/**
* Set state
*/
KeyboardController.prototype.setState = function(state) {
    this.state = state;
}

/**
* Function that gets called onkeydown - checks if key corresponding to current state exists in this.keys[] - executes corresponding action
*/
KeyboardController.prototype.pressKey = function(event) {
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
        return;
    }
    var code = event.which;
    if (typeof(this.keys[this.state][code]) == 'undefined')
        return true;
    var key = this.keys[this.state][code];
    // document.getElementById('menuBottom').innerHTML = key;
    if (key == 'space') {
        if (playViewer.state == 0)
            playViewer.play();
        else
            playViewer.pause();
        playViewer.playButton.change();
    }
    else if (key == 'p') {
        playViewer.playInToOut();
    }
    else if (key == 'm') {
        if (playViewer.sound == 1)
            playViewer.mute();
        else
            playViewer.unmute();
        playViewer.muteButton.change();
    }
    else if (key == '<' && playViewer.state == 0)
        playMarker.set(Math.max(playMarker.pos - 40, 0));
    else if (key == '>' && playViewer.state == 0)
        playMarker.set(Math.min(playMarker.pos + 40, video.duration));
    else if (key == 'left' && playViewer.state == 0)
        playMarker.set(Math.max(playMarker.pos - 1000, 0));
    else if (key == 'right' && playViewer.state == 0)
        playMarker.set(Math.min(playMarker.pos + 1000, video.duration));
    else if (key == 'up' && playViewer.state == 0)
        playMarker.set(Math.max(playMarker.pos - 600000, 0));
    else if (key == 'down' && playViewer.state == 0)
        playMarker.set(Math.min(playMarker.pos + 600000, video.duration));
    else if (key == '[')
        playMarker.set(inMarker.pos);
    else if (key == ']')
        playMarker.set(outMarker.pos);
    else if (key == '-' || key == '(')
        playMarker.set(getPreviousEdit());
    else if (key == '+' || key == ')')
        playMarker.set(getNextEdit());
    else if (key == 'i')
        inMarker.set(playMarker.pos);
    else if (key == 'o')
        outMarker.set(playMarker.pos);
    else if (key == 'a')
        timeline.set(playMarker.pos, 0, video.duration);
    else if (key == 'c')
        layerClipboard.copy();
    else if (key == 'v')
        layerClipboard.paste();
    else if (key == 'z')
        layerHistory.undo();
    else if (key == '~' || key == 'x')
        layerHistory.redo();
    else if ((key == 'n') && timeline.state != 2)
        selectNextLayer();
    else if (key == 'e') {
        if (timeline.state == 1 && layers[selectedLayer]['editable'])
            setTimeout('bin[selectedBin].editLayer(selectedLayer)', 100);
        else if (timeline.state == 2) {
            // the following line works in firefox and webkit, but not in safari 3.0.4
            // in safari, $('#' + selectedBin + 'Input').focus() doesn't focus the textarea
            // setTimeout("$('#' + selectedBin + 'Input').focus()", 100)
            if (typeof(bin[selectedBin].layerBox) == 'undefined')
                setTimeout("$('#' + selectedBin + 'Input').focus()", 100)
            else
                setTimeout("$('textarea').focus()", 100)
        }
    }
    else if (key == 'f')
        setTimeout("$('#inputFind').focus()", 100);
    else if (key == 'g')
        getNextResult();
    else if (key == 's')
        selectResult();
    else if (key == 'l' && user['logged_in'] && timeline.state != 2)
        setTimeout('locationBin.addLayer()', 100);
    else if (key == 'k' && user['logged_in'] && timeline.state != 2)
        setTimeout('keywordBin.addLayer()', 100);
    else if (key == 'd' && user['logged_in'] && timeline.state != 2)
        setTimeout('descriptionBin.addLayer()', 100);
    else if (key == 't' && user['logged_in'] && timeline.state != 2)
        setTimeout('transcriptBin.addLayer()', 100);
    else if (key == 'escape') {
        if (typeof(dialog) != 'undefined')
            dialog.close();
        else if (timeline.state == 1) {
            if (typeof(bin[selectedBin].layerBox) != 'undefined')
                bin[selectedBin].layerBox[selectedLayer].button['select'].change();
            bin[selectedBin].deselectLayer();
        }
        else if (timeline.state == 2) {
            if (keyboard.state == 0)
                bin[selectedBin].cancelLayer(selectedLayer);
            else {
                // the following line works in firefox and webkit, but not in safari 3.0.4
                // in safari, $('#' + selectedBin + 'Input').blur() doesn't blur the textarea
                // $('#' + selectedBin + 'Input').blur();
                if (keyboard.state == 1)
                    $('#' + selectedBin + 'Input').blur();
                else if (keyboard.state == 2)
                    $('textarea').blur();
            }
        }
    }
    else if (key == 'enter') {
        if (typeof(dialog) != 'undefined')
            dialog.close();
        else if (timeline.state == 2)
            bin[selectedBin].saveLayer(selectedLayer);
    }
    else if (key == 'h')
        help();
}

//  ----------------------------------------------------------------------------
//  Menu prototype
//  ----------------------------------------------------------------------------

function Menu() {
    
}
Menu.prototype.load = function() {
    var html = '<div id="menuLeft"></div>';
    html += '<div style="float: left; width: 264px; height: 16px; margin: 4px">';
    html += '<div id="elementSelectMenu" class="enabled element264 elementSelect264">Actions...</div>';
    html += '<select id="selectMenu" class="transparent" style="position: absolute; left: 172px; top: 4px; width: 264px" onChange="menu.action()">'
    html += '<option value="actions">Actions...</option>';
    html += '<option disabled="disabled"></option>';
    html += '<option disabled="disabled">Video Size</option>';
    if (settings.video.setting == 'large') {
        html += '<option id="videoSmall" value="videoSmall">&nbsp;&nbsp;Small</option>';
        html += '<option id="videoMedium" value="videoMedium">&nbsp;&nbsp;Medium</option>';
        html += '<option id="videoLarge" value="videoLarge">&#10003;Large</option>';
    } else if (settings.video.setting == 'medium') {
        html += '<option id="videoSmall" value="videoSmall">&nbsp;&nbsp;Small</option>';
        html += '<option id="videoMedium" value="videoMedium">&#10003;Medium</option>';
        html += '<option id="videoLarge" value="videoLarge">&nbsp;&nbsp;Large</option>';
    } else {
        html += '<option id="videoSmall" value="videoSmall">&#10003;Small</option>';
        html += '<option id="videoMedium" value="videoMedium">&nbsp;&nbsp;Medium</option>';
        html += '<option id="videoLarge" value="videoLarge">&nbsp;&nbsp;Large</option>';
    }
    html += '<option disabled="disabled"></option>';
    html += '<option disabled="disabled">Font Size</option>';
    if (settings.font.setting == 'large') {
        html += '<option id="small" value="small">&nbsp;&nbsp;Small</option>';
        html += '<option id="medium" value="medium">&nbsp;&nbsp;Medium</option>';
        html += '<option id="large" value="large">&#10003;Large</option>';
    } else if (settings.font.setting == 'medium') {
        html += '<option id="small" value="small">&nbsp;&nbsp;Small</option>';
        html += '<option id="medium" value="medium">&#10003;Medium</option>';
        html += '<option id="large" value="large">&nbsp;&nbsp;Large</option>';
    } else {
        html += '<option id="small" value="small">&#10003;Small</option>';
        html += '<option id="medium" value="medium">&nbsp;&nbsp;Medium</option>';
        html += '<option id="large" value="large">&nbsp;&nbsp;Large</option>';
    }
    html += '<option disabled="disabled"></option>';
    html += '<option disabled="disabled">View</option>';
    html += '<option id="markerOption" value="marker">&nbsp;&nbsp;Show Layers in Selection</option>';
    html += '<option id="markerDescription" disabled="disabled">&nbsp;&nbsp;(Currently Showing Layers at Marker)</option>';
    html += '<option disabled="disabled"></option>';
    html += '<option disabled="disabled">Selection</option>';
    html += '<option value="info">&nbsp;&nbsp;Selection Info...</option>';
    html += '<option value="link">&nbsp;&nbsp;Link to This Selection...</option>';
    html += '<option id="download" value="download">&nbsp;&nbsp;Download This Selection...</option>';
    html += '<option disabled="disabled"></option>';
    html += '<option disabled="disabled">Annotations</option>';
    if (user.logged_in) {
        html += '<option value="import">&nbsp;&nbsp;Upload Annotations...</option>';
    } else {
        html += '<option value="import" disabled="disabled">&nbsp;&nbsp;Upload Annotations...</option>';
    }
    html += '<option value="export">&nbsp;&nbsp;Download Annotations...</option>';
    html += '</select>';
    html += '</div>';
    html += '<div id="menuRight" style="text-align: right"></div>';
    $('#menu').html(html);
    this.button = {}
    this.button['info'] = new Button($('#menuLeft'), 'info', 'Info', function() { info() });
    this.button['help'] = new Button($('#menuLeft'), 'help', 'Help', function() { help() });
    this.button['getPoster'] = new Button($('#menuRight'), 'getPoster', 'Go to Poster Frame', function() { playMarker.set(video.poster_frame) });
    this.button['setPoster'] = new Button($('#menuRight'), 'setPoster', 'Set Poster Frame', function() { setPosterFrame() });
    this.button['unlock'] = new Button($('#menuRight'), 'unlock', 'Unlock Poster Frame', function() { unlockPosterFrame() }, 'lock', 'Lock Poster Frame', function() { lockPosterFrame() });
    this.button['setPoster'].disable();
    if (!video.editable) {
        this.button['unlock'].disable();
    }
}
Menu.prototype.update = function() {
    if (timeline.state == 0 || selectedLayer == -1) {
        if (inMarker.pos == outMarker.pos)
            $('#download').attr({ disabled: 'disabled' });
        else
            $('#download').removeAttr('disabled');
    } else {
        this.box.button['link'].enable();
    }
}
Menu.prototype.action = function() {
    var val = $('#selectMenu').val();
    $('#selectMenu').val('actions');
    if (val == 'video') {
        // this is unused
        var html = '<div id="elementSelectSetting" class="enabled element264 elementSelect264">' + settings.video.setting.capitalize() + '</div>';
        html += '<select id="selectSetting" class="element264 transparent" style="margin-left: -264px" onChange="changeSelectSetting()">';
        var arr = ['small', 'medium', 'large'];
        for (var i in arr) {
            if (settings.video.setting == arr[i]) {
                var selected = ' selected="selected"'
            } else {
                var selected = ''
            }
            html += '<option value="' + arr[i] + '"' + selected + '>' + arr[i].capitalize() + '</option>';
        }
        html += '</select>';
        dialog = new Dialog('Video Settings...', html, [
            ['Set', function() { changeVideoSettings() }],
            ['Cancel', function() { dialog.close() }]
        ], 280, 100);
        dialog.load();
    } else if (val == 'videoSmall') {
        settings.video.setting = 'small';
        changeVideoSettings();
        $('#videoSmall').html('&#10003;Small');
        $('#videoMedium').html('&nbsp;&nbsp;Medium');
        $('#videoLarge').html('&nbsp;&nbsp;Large');
        $.getJSON('/preferences/edit?video_size=small', function() {});
    } else if (val == 'videoMedium') {
        settings.video.setting = 'medium';
        changeVideoSettings();
        $('#videoSmall').html('&nbsp;&nbsp;Small');
        $('#videoMedium').html('&#10003;Medium');
        $('#videoLarge').html('&nbsp;&nbsp;Large');
        $.getJSON('/preferences/edit?video_size=medium', function() {});
    } else if (val == 'videoLarge') {
        settings.video.setting = 'large';
        changeVideoSettings();
        $('#videoSmall').html('&nbsp;&nbsp;Small');
        $('#videoMedium').html('&nbsp;&nbsp;Medium');
        $('#videoLarge').html('&#10003;Large');
        $.getJSON('/preferences/edit?video_size=large', function() {});
    } else if (val == 'timeline') {
        // this is unused
    } else if (val == 'marker') {
        if ($('#markerOption').html() == '&nbsp;&nbsp;Show Layers in Selection') {
            setLayerState(1);
            $('#markerOption').html('&nbsp;&nbsp;Show Layers at Marker');
            $('#markerDescription').html('&nbsp;&nbsp;(Currently Showing Layers in Selection)');
        } else {
            setLayerState(0);
            $('#markerOption').html('&nbsp;&nbsp;Show Layers in Selection');
            $('#markerDescription').html('&nbsp;&nbsp;(Currently Showing Layers at Marker)');
        }
    } else if (val == 'info') {
        if (timeline.state == 0 || selectedLayer == -1) {
            if (timeline.state == 0)
                var title = 'Selection'
            else
                var title = selectedBin.capitalize();
            var html = '<div>' + getTimecode(inMarker.pos) + ' &ndash; ' + getTimecode(outMarker.pos) + '</div>';
            html += '<div>Duration: <span>' + getTimecode(outMarker.pos - inMarker.pos) + '</span></div>';
            /* maybe not so important to fix>
            if (inMarker.pos == outMarker.pos)
                element.find('span').addClass('warning');
            element = $('<div></div>');
            element.addClass('italic');
            element.html('Press H for Help');
            this.box.appendContent(element);
            */
            /*
            if (inMarker.pos == outMarker.pos)
                this.box.button['link'].disable();
            else
                this.box.button['link'].enable();
            */
        }
        else {
            var title = selectedBin.capitalize();
            var html = '<div class="italic">' + layers[selectedLayer]['value'].abbreviate(32, '...') + '</div>';
            html += '<div>' + getTimecode(inMarker.pos) + ' &ndash; ' + getTimecode(outMarker.pos) + '</div>';
            html += '<div>Duration: ' + getTimecode(outMarker.pos - inMarker.pos) + '</div>';
            html += '<div>Contributor: ' + layers[selectedLayer]['creator'] + '</div>';
            var date = new Date();
            date.setTime(layers[selectedLayer]['created'] * 1000);
            html += '<div>Created: ' + date.format('%F %T') + '</div>';
            /*
            var str = ''
            for (var i = 0; i < 26; i++) {
                str += '%%' + String.fromCharCode(i + 65) + '=%' + String.fromCharCode(i + 65) + ' ';
                str += '%%' + String.fromCharCode(i + 97) + '=%' + String.fromCharCode(i + 97) + ' ';
            }
            str += '%%+=%+';
            $('#menuTop').html(date.format(str))
            */
        }
        dialog = new Dialog(title, html, [
            ['Close', function() { dialog.close() }]
        ], 280, 210);
        dialog.load();
    } else if (val == 'link') {
        link();
    } else if (val == 'download') {
        download();
    } else if (val == 'import') {
        importAnnotations();
    } else if (val == 'export') {
        exportAnnotations();
    } else if (val == 'small') {
        settings.font.setting = 'small';
        $('.content').css({
            fontSize: settings.font[settings.font.setting].fontSize,
            lineHeight: settings.font[settings.font.setting].lineHeight
        });
        $('#small').html('&#10003;Small');
        $('#medium').html('&nbsp;&nbsp;Medium');
        $('#large').html('&nbsp;&nbsp;Large');
        $.getJSON('/preferences/edit?font_size=small', function() {});
    } else if (val == 'medium') {
        settings.font.setting = 'medium';
        $('.content').css({
            fontSize: settings.font[settings.font.setting].fontSize,
            lineHeight: settings.font[settings.font.setting].lineHeight
        });
        $('#small').html('&nbsp;&nbsp;Small');
        $('#medium').html('&#10003;Medium');
        $('#large').html('&nbsp;&nbsp;Large');
        $.getJSON('/preferences/edit?font_size=medium', function() {});
    } else if (val == 'large') {
        settings.font.setting = 'large';
        $('.content').css({
            fontSize: settings.font[settings.font.setting].fontSize,
            lineHeight: settings.font[settings.font.setting].lineHeight
        });
        $('#small').html('&nbsp;&nbsp;Small');
        $('#medium').html('&nbsp;&nbsp;Medium');
        $('#large').html('&#10003;Large');
        $.getJSON('/preferences/edit?font_size=large', function() {});
    }
}

//  ----------------------------------------------------------------------------
//  Timeline prototype
//  ----------------------------------------------------------------------------

/**
* @class Timeline class
* @property {Int} state States: 0 (marked), 1 (selected), 2 (editable)
* @property {Int} lines Number of lines in timeline (based on video.duration)
* @property {String} className defines CSS classes for 'layer' overlay on timeline based on current state
* @constructor
*/
function Timeline() {
    /*
        state: 0 (marked), 1 (selected), 2 (editable)
    */
    this.className = ['Marked', 'Selected', 'Editable'];
    this.html = htmlTimeline;
    this.lines = Math.ceil(video.duration / 600000);
}

/**
* Initializes timeline CSS, event handlers, sets this.state to 0.
*/
Timeline.prototype.load = function() {
    this.setState(0);
    $('#timeline').css('top', (parseInt(settings.video[settings.video.setting].play.height().replace('px', '')) + 44) + 'px');
    $('#timeline').html(this.html);
    $('#timelineImage').css('height', (this.lines * 24) + 'px');
    $('#timelineImage').css('background', 'url(' + [base_url, video.id, 'timeline.png'].join('/') + ')');
    for (var i = 0; i < this.lines; i++) {
        var element = $('<div></div>');
        element.addClass('timelineLine');
        element.addClass('enabled');
        element.attr('id', 'timelineInterfaceLine' + i);
        element.attr('title', 'Set Position');
        element.css('top', i * 24 + 2);
        if (i == this.lines - 1)
            element.css('width', (Math.ceil(video.duration / 1000) % 600) + 'px');
        function func(t, i) {
            return function(event) { t.click(i, event) }
        }
        element.click(func(this, i));
        $('#timelineInterface').append(element);
    }
}

/**
* Sets In, Out and Play markers on timeline
* @param {Int} playPos Current video position (in ms)
* @param {Int} inPos Current in point (in ms)
* @param {Int} outPos Current out point (in ms)
*/
Timeline.prototype.set = function(playPos, inPos, outPos) {
    playMarker.set(playPos);
    if (layerHistory.state == 1) {
        layerHistory.setState(0);
        inMarker.set(inPos);
        layerHistory.setState(1);
    }
    else
        inMarker.set(inPos);
    outMarker.set(outPos);
}

/**
* Sets this.state to state, calls this.markLayer()
* @param {Int} state
*/
Timeline.prototype.setState = function(state) {
    this.state = state;
    if (typeof(inMarker) != 'undefined' && typeof(outMarker) != 'undefined')
        this.markLayer();
}

/**
* Called when user clicks on timeline, sets playMarker position to corresponding position
* @param {Int} i The line-number on which the user has clicked
* @param {Object} event Browser event object
*/
Timeline.prototype.click = function(i, event) {
    var pos = i * 600000 + (event.clientX - 8) * 1000;
    playMarker.set(pos);
}

/**
* Mark a layer on timeline with current in and out points. Function calls this.mark with "layer", and current in and out
*/
Timeline.prototype.markLayer = function() {
    this.clearLayer();
    this.mark('layer', inMarker.pos, outMarker.pos);
}

/**
* Mark a search result on timeline. Function calls this.mark with "results" and in and out point of result
*/
Timeline.prototype.markResult = function(inPos, outPos) {
    this.mark('results', inPos, outPos);
}

/**
* Function to mark overlay layers on the timeline
* @param {String} name Currently can be either "layer" or "results"
* @param {Int} inPos In point of overlay, in miliseconds
* @param {Int} outPos Out point of overlay, in miliseconds
*/
Timeline.prototype.mark = function(name, inPos, outPos) {
    var inLine = Math.floor(inPos / 600000);
    var inLeft = Math.floor(inPos) % 600000 / 1000;
    var outLine = Math.floor(outPos / 600000);
    var outRight = 599 - Math.floor(outPos / 1000) % 600;
    for (var i = inLine; i <= outLine; i++) {
        var element = $('<div></div>');
        element.addClass('timelineLine');
        if (name == 'layer')
            element.addClass('timeline' + this.className[this.state]);
        else if (name == 'results')
            element.addClass('timelineHighlighted');
        if (i == inLine)
            element.css('left', inLeft + 'px');
        if (i == outLine)
            element.css('right', outRight + 'px');
        element.css('top', (i * 24 + 2) + 'px');
        $('#timeline' + name.capitalize()).append(element);
    }
}

/**
* Remove a layer on the timeline
*/
Timeline.prototype.clearLayer = function() {
    $('#timelineLayer').html('');
}

/**
* Clear all result overlays from timeline
*/

Timeline.prototype.clearResults = function() {
    $('#timelineResults').html('');
}

//  ----------------------------------------------------------------------------
//  Marker prototype
//  ----------------------------------------------------------------------------

/**
* @class Marker class - base class for different markers to be set on Timeline
* @property {Boolean} loaded Signifies whether marker is loaded
* @property {Int} pos Marker position, in miliseconds
* @constructor
*/
function Marker() { }

/**
* Function to load marker, sets this.loaded to false and calls this.set with this.pos
*/
Marker.prototype.load = function() {
    this.loaded = false;
    this.set(this.pos);
}

/**
* Sets marker to pos on timeline
* @param {Int} pos Position to set marker to, in miliseconds
*/
Marker.prototype.set = function(pos) {
    this.pos = pos;
    var element = $('#' + this.name + 'Marker');
    // fixme: next one line was changed last minute from ceil to floor. check for unintended consequences.
    element.css('left', (Math.floor(this.pos / 1000) % 600 - 5) + 'px');
    element.css('top', (Math.floor(this.pos / 600000) * 24 + 2) + 'px');
    this.loaded = true;
}

/**
* @class Marker for current video position
* @augments Marker 
* @property {Int} state State: 0 (set by user), 1 (set by player)
* @property {String} name Used in Id of Dom element, is "play" for playMarker
* @constructor
*/
function PlayMarker() {
    /*
        state: 0 (set by user), 1 (set by player)
    */
    this.name = 'play';
    this.pos = 0;
    this.state = 0;
}
PlayMarker.prototype = new Marker();

/**
* Sets video to pos, loads current layers if position has changed, enables or disables poster-framing.
* @param {Int} pos Position, in miliseconds
*/
PlayMarker.prototype.set = function(pos) {
    var posHasChanged = pos != this.pos;
    if (this.loaded == false || posHasChanged || pos == video.poster_frame) {
        if (this.loaded == false || posHasChanged) {
            var crossedEdit = crossesEdit(this.pos, pos)
            Marker.prototype.set.call(this, pos);
        }
        // fixme: make generic isLoaded function
        if (typeof(playViewer) != 'undefined' && playViewer.loaded) {
            playViewer.set();
            if (playViewer.state != 0 && this.state == 0)
                player.set(pos);
            else if(video.local && this.state == 0)
                document.getElementById('player').currentTime = pos/1000;
        }
    }
    if (typeof(menu) != 'undefined') {
        if (this.pos == video.poster_frame) {
            menu.button['getPoster'].disable();
            if (video.editable && menu.button['unlock'].state == 1)
                menu.button['setPoster'].disable();
        }
        else {
            menu.button['getPoster'].enable();
            if (video.editable && menu.button['unlock'].state == 1)
                menu.button['setPoster'].enable();
        }
    }
    if (posHasChanged) {
        // fixme: bin does not exist yet when marker is first set
        // instead, empty bins should be there...
        if (typeof(bin) != 'undefined') {
            if (layerState == 0 && timeline.state != 2) {
                // fixme: cant next two lines go inside if (crossedEdit)?
                if (selectedLayer != -1 && (this.pos < layers[selectedLayer]['time_in'] || this.pos >= layers[selectedLayer]['time_out']))
                    bin[selectedBin].deselectLayer();
                if (crossedEdit)
                    loadLayers();
            }
        }
    }
}

/**
* Sets State
* @param {Int} state
*/
PlayMarker.prototype.setState = function(state) {
    this.state = state;
}

/**
* @class Defines In / Out Marker Class
* @property {Int} pos Position of marker, in miliseconds
* @augments Marker
* @constructor
* @param {String} name Name of Marker - "in" or "out"
*/
function InOutMarker(name) {
    this.name = name;
    this.pos = 0;
}

InOutMarker.prototype = new Marker();

/**
* Sets in / out marker to pos, updates corresponding Viewer, loads Layers if required, updates History
* @param {Int} pos Position to set marker to, in miliseconds
*/
InOutMarker.prototype.set = function(pos) {
    var posHasChanged = pos != this.pos
    if (this.loaded == false || posHasChanged) {
        var crossesMarker = (this.name == 'in' && pos > outMarker.pos) || (this.name == 'out' && pos < inMarker.pos);
        if (crossesMarker && layerHistory.state == 1) {
            layerHistory.setState(0);
            timeline.set(playMarker.pos, pos, pos);
            layerHistory.setState(1);
        }
        else
            Marker.prototype.set.call(this, pos);
    }
    if (this.name == 'in') {
        if (typeof(inViewer) != 'undefined' && inViewer.loaded)
            inViewer.set();
    }
    else {
        if (typeof(outViewer) != 'undefined' && outViewer.loaded)
            outViewer.set();
    }
    if (posHasChanged) {
        // fixme: shouldn't there be timeline.getState() ?
        if (timeline.state == 1) {
            if (typeof(bin[selectedBin].layerBox) != 'undefined')
                bin[selectedBin].layerBox[selectedLayer].button['select'].change();
            bin[selectedBin].deselectLayer();
        }
        else
            timeline.markLayer();
        if (typeof(bin) != 'undefined' && layerState == 1 && timeline.state != 2)
            loadLayers();
        if (typeof(layerHistory) != 'undefined')
            layerHistory.add(inMarker.pos, outMarker.pos);
    }
}

//  ----------------------------------------------------------------------------
//  Viewer prototype
//  ----------------------------------------------------------------------------
/**
* @class Viewer class - currently defines 3 viewers - PlayViewer, InViewer and OutViewer
* @property {jQuery Object} buttons jQuery element of Viewer buttons
* @property {String} name Name of viewer ("play", "in", or "out")
* @constructor
*/

function Viewer() {}

/**
* Loads Viewer, sets CSS, loads buttons, sets event handlers for interface and input box
*/
Viewer.prototype.load = function() {
    $('#viewers').append(parseTemplate(htmlViewer, {'name': this.name}));
    updateViewer(this.name);
    this.buttons = $('#' + this.name + 'Buttons');
    var t = this;
    $('#' + name + 'Interface').mouseover(function() { t.showInterface() });
    $('#' + name + 'Interface').mouseout(function() { t.hideInterface() });
    $('#' + this.name + 'Viewer input').focus(focusInput);
    $('#' + this.name + 'Viewer input').blur(blurInput);
    $('#' + this.name + 'Viewer input').change(function() { t.changeTimecode(this.value) });

    if(video.local) {
      var canvas = document.createElement('canvas');
      canvas.width = settings.video[settings.video.setting][this.name].width.replace('px', '');
      canvas.height = parseInt(settings.video[settings.video.setting][this.name].height().replace('px', ''));
      canvas.id = this.name + 'Image';
      $('#' + this.name + 'Image').replaceWith(canvas);
    }

    this.set(this.pos);
}

/**
* Sets image to current frame (PlayMarker.pos). In the case of local file, uses canvas to draw image.
*/
Viewer.prototype.set = function() {
    if (this.name != 'play' || this.state == 0) {
        var pos = this.pos;
        if (this.pos == video.duration)
            pos -= 40;
        if(video.local) {
          video.frames.getFrame(pos/1000, this.name + 'Image');
        }
        else {
          var size = '.' + settings.video[settings.video.setting].w;
          var url = [base_url, video.id, 'frame', getTimecode(pos) + size +'.jpg'].join('/');
          $('#' + this.name + 'Image').attr('src', url);
          $('#' + this.name + 'Image').attr('height', settings.video[settings.video.setting][this.name].height().replace('px', ''));
        }
    }
    this.updateIndicators();
    if (typeof(inViewer) != 'undefined' && inViewer.loaded)
        inViewer.updateInterface();
    if (typeof(outViewer) != 'undefined' && outViewer.loaded)
        outViewer.updateInterface();
    this.setTimecode(this.pos);
}

/**
* Sets the time-code in the Viewer input box to current time-code in npt format, called by this.set()
* @param {Int} pos Timecode to set to, in miliseconds
*/
Viewer.prototype.setTimecode = function(pos) {
    $('#' + this.name + 'Viewer input').val(getTimecode(pos));
}

/**
* Update Indicator overlays for showing current in point, out point and poster frame based on current playMarker.pos
*/
Viewer.prototype.updateIndicators = function() {
    if (playMarker.pos == inMarker.pos)
        playViewer.showIndicators('in');
    else
        playViewer.hideIndicators('in');
    if (playMarker.pos == outMarker.pos)
        playViewer.showIndicators('out');
    else
        playViewer.hideIndicators('out');
    if (inViewer.loaded && outMarker.loaded) {
        if (inMarker.pos == outMarker.pos) {
            inViewer.showIndicators('out');
            outViewer.showIndicators('in');
        }
        else {
            inViewer.hideIndicators('out');
            outViewer.hideIndicators('in');
        }
    }
    if (this.pos == video.poster_frame)
        this.showIndicators('poster');
    else
        this.hideIndicators('poster');
}

/**
* Shows indicator overlay over viewer - poster, in or out.
* @param {String} name Accepts "poster", "in" or "out"
*/
Viewer.prototype.showIndicators = function(name) {
    if (name == 'poster') {
        $('#' + this.name + 'FrameTopLeft').show();
        $('#' + this.name + 'FrameTopRight').show();
        $('#' + this.name + 'FrameBottomLeft').show();
        $('#' + this.name + 'FrameBottomRight').show();
    }
    else {
        $('#' + this.name + 'Point' + name.capitalize() + 'Top').show();
        $('#' + this.name + 'Point' + name.capitalize() + 'Bottom').show();
    }
}

/**
* Hides indicator overlayer over viewer.
* @param {String} name Accepts "poster", "in" or "out"
*/
Viewer.prototype.hideIndicators = function(name) {
    if (name == 'poster') {
        $('#' + this.name + 'FrameTopLeft').hide();
        $('#' + this.name + 'FrameTopRight').hide();
        $('#' + this.name + 'FrameBottomLeft').hide();
        $('#' + this.name + 'FrameBottomRight').hide();
    }
    $('#' + this.name + 'Point' + name.capitalize() + 'Top').hide();
    $('#' + this.name + 'Point' + name.capitalize() + 'Bottom').hide();
}

/**
* Shows viewer interface
*/
Viewer.prototype.showInterface = function() {
    $('#' + this.name + 'Interface').css('opacity', 1);
}

/**
* Hides viewer interface
*/
Viewer.prototype.hideInterface = function() {
    $('#' + this.name + 'Interface').css('opacity', 0);
}

/**
* @augments Viewer
* @class PlayViewer Extends Viewer - class for the video player viewer.
* @property {Int} state States: 0 (paused), 1 (playing), 2 (playing in to out)
* @property {Int} sound Default = 1
* @property {String} name "play"
* @property {Object} playButton Switch Object for Play
* @property {Object} playInToOutButton Button Object for Play in to Out
* @property {Object} muteButton Switch Object for Mute
* @constructor
*/
function PlayViewer() {
    /*
        state: 0 (paused), 1 (playing), 2 (playing in to out)
    */
    this.name = 'play';
    this.pos = 0;
    this.state = 0;
    this.sound = 1;
}
PlayViewer.prototype = new Viewer();

/**
* Loads viewer, initializes buttons
*/
PlayViewer.prototype.load = function(html) {
    Viewer.prototype.load.call(this, html);
    $('#playInterface').attr('title', 'Play');
    $('#playInterface').click(function() { playViewer.playButton.change(); playViewer.play() });
    this.playButton = new Switch(this.buttons, 'play', 'Play', function() { playViewer.play() }, 'pause', 'Pause', function() { playViewer.pause() })
    this.playInToOutButton = new Button(this.buttons, 'playInToOut', 'Play In to Out', function() { playViewer.playInToOut() })
    this.muteButton = new Switch(this.buttons, 'mute', 'Mute', function() { playViewer.mute() }, 'unmute', 'Unmute', function() { playViewer.unmute() });
    this.loaded = true;
}

/**
* Calls Viewer.set with current playMarker.pos
*/
PlayViewer.prototype.set = function() {
    this.pos = playMarker.pos
    Viewer.prototype.set.call(this);
}

/**
* Set State
* @param {Int} state
*/
PlayViewer.prototype.setState = function(state) {
    this.state = state;
}

/**
* change the position of the video if a user changes the time-code in the Viewer input box. Called with time-code as string in npt format.
* @param {String} timecode Time-code as string in npt.
*/
PlayViewer.prototype.changeTimecode = function(timecode) {
    var pos = getPos(timecode);
    if (pos == -1 || pos > video.duration)
        this.setTimecode(playMarker.pos)
    else {
        $('#playViewer input').blur();
        playMarker.set(pos);
    }
}

/**
* Function called when user clicks "play" - plays video, hides image, updates state. 
* Some css hacks for video overlays not working - check possibilities to fix in the future.
*/
PlayViewer.prototype.play = function() {
    if (this.state == 0)
        this.state = 1;
    $('#playInterface').css('background', 'url(/static/images/interfacePause.png) no-repeat center center');
    $('#playInterface').attr('title', 'Pause');
    player.set(playMarker.pos);
    player.play();
    setTimeout("playViewer.playing()", 150);
    setTimeout("$(player.player).css('visibility', 'visible');", 150);
    if (player.supportsOverlay)
        setTimeout("$(player.player).css('z-index', 2);", 150);
    else
        setTimeout("$(player.player).css('z-index', 4);", 150);
}

/**
* Play Video from current in to out point
*/
PlayViewer.prototype.playInToOut = function() {
    if (this.state == 0)
        this.playButton.change();
    this.state = 2;
    playMarker.set(inMarker.pos);
    this.play();
}

/**
* Loop that runs every 500ms while video is playing and updates playMarker and playViewer states.
*/
PlayViewer.prototype.playing = function() {
    // fixme: make timeout an interval
    if (playViewer.state != 0) {
        var pos = player.get();
        if (pos > 0) {
            playMarker.setState(1);
            playMarker.set(pos);
            playMarker.setState(0);
        }
        if (playViewer.state == 2 && playMarker.pos >= outMarker.pos) {
            playViewer.pause();
            playViewer.playButton.change();
            playMarker.set(inMarker.pos);
        }
        setTimeout("playViewer.playing()", 500);
    } 
}

/**
* Pauses video, sets Viewer image to current frame, hides video player
*/
PlayViewer.prototype.pause = function() {
    this.state = 0;
    $('#playInterface').css('background', 'url(/static/images/interfacePlay.png) no-repeat center center');
    $('#playInterface').attr('title', 'Play');
    player.pause();
    var pos = player.get();
    if (pos > 0)
        playMarker.set(pos);
    setTimeout("$(player.player).css('z-index', -1).css('visibility', 'hidden');", 300);
}

/**
* Mutes video
*/
PlayViewer.prototype.mute = function() {
    this.sound = 0;
    player.mute();
}

/**
* Unmutes video
*/
PlayViewer.prototype.unmute = function() {
    this.sound = 1;
    player.unmute();
}

/**
* @augments Viewer
* @class Class for In and Out Viewers.
* @property {String} name "in" or "out"
* @property {Object} marker inMarker or outMarker object
* @property {Object} getButton Button to set video to current in / out point.
* @property {Object} setButton Button to set in / out point to current point in video.
* @constructor
* @param {String} name "in" or "out"
*/
function InOutViewer(name) {
    this.name = name;
    if (name == 'in')
        this.marker = inMarker;
    else
        this.marker = outMarker;
    this.pos = 0;
}
InOutViewer.prototype = new Viewer();

/**
* Loads Viewer, inits CSS, loads buttons
*/
InOutViewer.prototype.load = function(html) {
    Viewer.prototype.load.call(this, html);
    this.showIndicators(this.name);
    $('#' + this.name + 'Interface').attr('title', 'Go to ' + this.name.capitalize() + ' Point');
    var t = this;
    $('#' + this.name + 'Interface').click(function() { playMarker.set(t.marker.pos) });
    this.getButton = new Button(this.buttons, 'get' + this.name.capitalize(), 'Go to ' + this.name.capitalize() + ' Point', function() { playMarker.set(t.marker.pos) });
    this.setButton = new Button(this.buttons, 'set' + this.name.capitalize(), 'Set ' + this.name.capitalize() + ' Point', function() { t.marker.set(playMarker.pos) });
    this.updateInterface();
    this.loaded = true;
}

/**
* Calls Viewer.set to update image on Viewer
*/
InOutViewer.prototype.set = function(pos) {
    this.pos = this.marker.pos
    Viewer.prototype.set.call(this);
    if (typeof(menu) != 'undefined' && (timeline.state == 0 || (timeline.state == 2 && selectedLayer == -1)))
        menu.update();
    /*
    if (typeof(infoPanel) != 'undefined' && (timeline.state == 0 || (timeline.state == 2 && selectedLayer == -1)))
        infoPanel.update();
    */
}

/**
* set buttons to disabled if current video position == current Viewer position
*/
InOutViewer.prototype.updateInterface = function() {
    if (this.pos == playViewer.pos)
        this.disableInterface();
    else
        this.enableInterface();
}

/**
* Show interface, enable buttons
*/
InOutViewer.prototype.enableInterface = function() {
    $('#' + this.name + 'Interface').show();
    this.getButton.enable();
    this.setButton.enable();
}

/**
* Hide interface, disable buttons
*/
InOutViewer.prototype.disableInterface = function() {
    $('#' + this.name + 'Interface').hide();
    this.getButton.disable();
    this.setButton.disable();
}

/**
* Updates Viewer time-code if changed by user in input box
* @param {String} timecode Timecode in npt format
*/
InOutViewer.prototype.changeTimecode = function(timecode) {
    var pos = getPos(timecode);
    if (pos == -1 || pos > video.duration)
        this.setTimecode(this.marker.pos)
    else {
        $('#' + this.name + 'Viewer input').blur();
        this.marker.set(pos);
    }
}

//  ----------------------------------------------------------------------------
//  Panel prototype
//  ----------------------------------------------------------------------------

/**
* @class Class defining Panel with download, info, help buttons, etc. + details of current selection
* @property {Object} box Box Object which holds buttons, etc.
* @property {jQuery Object} parent jQuery element of the parent Dom element.
* @param {jQuery Object} parent jQuery element of the parent Dom element.
* @constructor
*/ 
function Panel(parent) {
    this.parent = parent;
}

/**
* Function to load panel, initialize Box, various buttons
*/
Panel.prototype.load = function() {
    this.box = new Box(this.parent, 'info', 'infoBox', true, false, true, video.height + 16);
    this.box.load();
    this.box.addButton('TopRight', 'link', 'Link to this Selection', function() { link() });
    this.box.addButton('BottomLeft', 'getPoster', 'Go to Poster Frame', function() { playMarker.set(video.poster_frame) });
    this.box.addButton('BottomLeft', 'setPoster', 'Set Poster Frame', function() { setPosterFrame() });
    this.box.addSwitch('BottomLeft', 'unlock', 'Unlock Poster Frame', function() { unlockPosterFrame() }, 'lock', 'Lock Poster Frame', function() { lockPosterFrame() });
    this.box.addSwitch('BottomRight', 'marker', 'Show Layers in Selection', function() { setLayerState(1) }, 'selection', 'Show Layers at Marker', function() { setLayerState(0) });
    this.box.addButton('BottomRight', 'download', 'Download Annotations', function() { download() });
    this.box.addButton('BottomRight', 'info', 'Info', function() { info() });
    this.box.addButton('BottomRight', 'help', 'Help', function() { help() });
    this.box.button['setPoster'].disable();
    if (!video.editable)
        this.box.button['unlock'].disable();
    this.update()
}

/**
* Update panel with info for current in and out points / bins selected
*/
Panel.prototype.update = function() {
    this.box.clearContent();
    if (timeline.state == 0 || selectedLayer == -1) {
        if (timeline.state == 0)
            this.box.setTitle('Selection');
        else
            this.box.setTitle(selectedBin.capitalize());
        var element = $('<div></div>');
        element.html(getTimecode(inMarker.pos) + ' &ndash; ' + getTimecode(outMarker.pos));
        this.box.appendContent(element);
        element = $('<div></div>');
        element.html('Duration: <span>' + getTimecode(outMarker.pos - inMarker.pos) + '</span>');
        if (inMarker.pos == outMarker.pos)
            element.find('span').addClass('warning');
        this.box.appendContent(element);
        element = $('<div></div>');
        element.addClass('italic');
        element.html('Press H for Help');
        this.box.appendContent(element);
        if (inMarker.pos == outMarker.pos)
            this.box.button['link'].disable();
        else
            this.box.button['link'].enable();
    }
    else {
        this.box.setTitle(selectedBin.capitalize());
        var element = $('<div></div>');
        element.addClass('italic');
        element.html(layers[selectedLayer]['value'].abbreviate(32, '...'));
        this.box.appendContent(element);
        element = $('<div></div>');
        element.html(getTimecode(inMarker.pos) + ' &ndash; ' + getTimecode(outMarker.pos));
        this.box.appendContent(element);
        element = $('<div></div>');
        element.html('Duration: ' + getTimecode(outMarker.pos - inMarker.pos));
        this.box.appendContent(element);
        element = $('<div></div>');
        element.html('Contributor: ' + layers[selectedLayer]['creator']);
        this.box.appendContent(element);
        element = $('<div></div>');
        var date = new Date();
        date.setTime(layers[selectedLayer]['created'] * 1000);
        element.html('Created: ' + date.format('%F %T'));
        /*
        var str = ''
        for (var i = 0; i < 26; i++) {
            str += '%%' + String.fromCharCode(i + 65) + '=%' + String.fromCharCode(i + 65) + ' ';
            str += '%%' + String.fromCharCode(i + 97) + '=%' + String.fromCharCode(i + 97) + ' ';
        }
        str += '%%+=%+';
        $('#menuTop').html(date.format(str))
        */
        this.box.appendContent(element);
        this.box.button['link'].enable();
    }
}


//  ----------------------------------------------------------------------------
//  Bin prototype
//  ----------------------------------------------------------------------------

/**
* @class Bin - Is base class for TermBin, LocationBin, TextBin and CreatorBin
* @property {jQuery Object} parent jQuery object of parent element to create Bin in.
* @property {Object} box Box object while holds html / buttons, etc.
* @property {Object} layerBox Box object to hold individual layers if is a multi-layer Bin.
* @property {Array} layers Layer information for Bin data.
* @property {Boolean} loaded
* @constructor
*/
function Bin(parent) {
    if (arguments.length > 0)
        this.init(parent);
}

/**
* Initializes bin
*/
Bin.prototype.init = function(parent) {
    this.parent = parent
    this.layers = [];
}

/**
* Loads Box object, adds buttons
*/
Bin.prototype.load = function() {
    this.box = new Box(this.parent, this.name, 'binBox', true, false, false, 0);
    this.box.load();
    this.box.setTitle(this.name.capitalize() + 's');
    var t = this;
    this.box.addSwitch('TopLeft', 'collapse', 'Collapse ' + this.name.capitalize() + 's', function() { t.box.collapse() }, 'expand', 'Expand ' + this.name.capitalize() + 's', function() { t.box.expand() })
    this.box.addButton('TopRight', 'add', 'Add ' + this.name.capitalize(), function() { t.addLayer() });
    if (!user['logged_in'])
        this.box.button['add'].disable();
    this.update();
    this.loaded = true;
}

/**
* Appends layer data if exists
*/
Bin.prototype.update = function() {
    this.box.clearContent();
    if (this.layers.length) {
        for (var i in this.layers) {
            if (i > 0)
                this.box.appendContent(this.separator);
            this.box.appendContent(this.formatLayer(i));
        }
    }
    else {
        var element = $('<span></span>');
        element.addClass('disabled');
        element.html('No ' + this.name.capitalize() + 's')
        this.box.appendContent(element);
    }
    $('.content').css({
        fontSize: settings.font[settings.font.setting].fontSize,
        lineHeight: settings.font[settings.font.setting].lineHeight
    });

}

/**
* Marks Bin Layer as selected - sets globals selectedBin and selectedLayer, sets overlay on timeline
*/
Bin.prototype.selectLayer = function(k) {
    if (selectedLayer != -1) {
        if (typeof(bin[selectedBin].layerBox) != 'undefined')
            bin[selectedBin].layerBox[selectedLayer].button['select'].change();
        bin[selectedBin].deselectLayer();
    }
    selectedBin = this.name;
    selectedLayer = k;
    // next line would set cursor to in and cause bin update
    // timeline.set(layers[k]['time_in'], layers[k]['time_in'], layers[k]['time_out']);
    timeline.set(playMarker.pos, layers[k]['time_in'], layers[k]['time_out']);
    timeline.setState(1);
    // infoPanel.update();
    $('#' + this.name + k + 'Content').addClass('selected');
    this.expand();
    if (selectedBin != 'location')
        hideMap();
}

/**
* Deselects Bin layer.
*/
Bin.prototype.deselectLayer = function() {
    selectedBin = '';
    selectedLayer = -1;
    timeline.setState(0);
    // infoPanel.update();
    $('.selected').removeClass('selected');
    /*
    if (typeof(mapState) != 'undefined' && mapState == 0)
        hideMap();
    */
}

/**
* Adds an empty layer to Bin, selects it, and calls this.editLayer
*/
Bin.prototype.addLayer = function() {
    if (locationBin.box.hasEditbar)
        hideMap();
    if (typeof(creator[getCreator(user.name)]) != 'undefined' && creator[getCreator(user.name)].state == 0) {
        creator[getCreator(user.name)].state = 1;
        creatorBin.update();
        loadLayers();
    }
    if (selectedLayer != -1) {
        if (typeof(bin[selectedBin].layerBox) != 'undefined')
            bin[selectedBin].layerBox[selectedLayer].button['select'].change();
        bin[selectedBin].deselectLayer();
    }
    selectedBin = this.name;
    this.editLayer(selectedLayer);
}

/**
* Function to make layer edittable
* @param {Int} k Array position of layer to make edittable (if is a Bin that holds many layers)
*/
Bin.prototype.editLayer = function(k) {
    if (locationBin.box.hasEditbar)
        hideMap();
    $('#inputFind').attr('disabled', 'disabled');
    timeline.setState(2);
    // infoPanel.update();
    // infoPanel.box.button['marker'].disable();
    for (c in creator)
        creatorBin.checkbox[creator[c]['name']].disable();
    this.box.button['collapse'].disable();
    for (var b in bin) {
        bin[b].box.button['add'].disable();
        for (var l in bin[b].layers) {
            var v = bin[b].layers[l]
            if (typeof(bin[b].layerBox) == 'undefined') {
                var element = $('#' + b + v + 'Content');
                element.removeClass('link');
                element.removeClass('enabled');
                element.attr('title', '');
                element.unbind('click');
            }
            else {
                var button = bin[b].layerBox[v].button;
                button['select'].disable();
                if (layers[v]['editable']) {
                    button['edit'].disable();
                    button['remove'].disable();
                }
            }
        }
    }
    if (typeof(this.layerBox) == 'undefined')
        var box = this.box;
    else
        var box = this.layerBox[k];
    if (k != -1)
        box.button['remove'].remove();
    var t = this;
    box.addButton('BottomRight', 'cancel', 'Cancel Edit', function() { t.cancelLayer() } );
    if (k != -1)
        box.addButton('BottomRight', 'undo', 'Undo Edit', function() { t.undoLayer() } );
    box.addButton('BottomRight', 'save', 'Save Layer', function() { t.saveLayer() } );
    if (k != -1) {
        box.addButton('BottomRight', 'remove', 'Remove Layer', function() { t.removeLayer() } );
        box.button['remove'].disable();
    }
    this.expand();
}

/**
* Cancels edit of layer, enables other bin buttons / layers
*/
Bin.prototype.cancelLayer = function() {
    var k = selectedLayer;
    $('#inputFind').attr('disabled', '');
    // fixme: check if keyboard state doesn't get set to zero before
    keyboard.setState(0);
    if (k == -1)
        timeline.setState(0);
    else {
        timeline.set(playMarker.pos, layers[selectedLayer]['time_in'], layers[selectedLayer]['time_out']);
        timeline.setState(1);
    }
    // infoPanel.update();
    // infoPanel.box.button['marker'].enable();
    for (c in creator)
        creatorBin.checkbox[creator[c]['name']].enable();
    this.box.button['collapse'].enable();
    for (var b in bin) {
        bin[b].box.button['add'].enable();
        for (var l in bin[b].layers) {
            var v = bin[b].layers[l]
            if (typeof(bin[b].layerBox) == 'undefined') {
                if (v != selectedLayer) {
                    var element = $('#' + b + v + 'Content');
                    element.addClass('link');
                    element.addClass('enabled');
                    element.attr('title', 'Select ' + bin[b].name.capitalize());
                    function func(b, v) {
                        return function() { bin[b].selectLayer(v) }
                    }
                    element.click(func(b, v));
                }
            }
            else {
                bin[b].layerBox[v].button['select'].enable();
                if (layers[v]['editable']) {
                    bin[b].layerBox[v].button['edit'].enable();
                    bin[b].layerBox[v].button['remove'].enable();
                }
            }
        }
    }
    if (typeof(this.layerBox) == 'undefined')
        var box = this.box;
    else
        var box = this.layerBox[k];
    box.button['cancel'].remove();
    if (k != -1)
        box.button['undo'].remove();
    box.button['save'].remove();
}

/**
* Undo layer to last change
*/
Bin.prototype.undoLayer = function() {
    var k = selectedLayer;
    if (k == -1)
        $('.editable').val('');
    else {
        timeline.set(playViewer.pos, layers[k]['time_in'], layers[k]['time_out']);
        $('.editable').val(layers[k]['value']);
    }
    $('.editable').focus();
}

/**
* checks if layer is valid, then calls addLayer() or editLayer() to save selected layer data
*/
Bin.prototype.saveLayer = function() {
    var value = $('#' + selectedBin + 'Input').val();
    var duration = outMarker.pos - inMarker.pos;
    if (value == '' || duration == 0) {
        if (duration != 0)
            error('layerValue');
        else if (value != '')
            error('layerDuration');
        else
            error('layerValueDuration');
        return false;
    }
    for (i in this.layers) {
        k = this.layers[i];
        if (layers[k]['value'] == value && layers[k]['time_in'] == inMarker.pos && layers[k]['time_out'] == outMarker.pos && layers[k]['creator'] == user.name) {
            error('layerExists');
            return false;
        }
    }
    if (selectedLayer == -1)
        addLayer();
    else
        editLayer();
}

/**
* Removes layer k
* @param {Int} k Array index of layer to be removed
*/

Bin.prototype.removeLayer = function(k) {
    removeLayer(k);
}

/**
* Expand Bin box
*/
Bin.prototype.expand = function() {
    var box = bin[selectedBin].box;
    if (box.state == 0) {
        box.button['collapse'].change();
        box.expand();
    }
}

/**
* If Bin is selected, adds "selected" class
*/
Bin.prototype.onCollapse = function() {
    // fixme: show highlighted, too
    if (selectedBin == this.name)
        $('#' + this.name + 'Title').addClass('selected');
}

/**
* If Bin is selected, removes "selected" class
*/
Bin.prototype.onExpand = function() {
    if (selectedBin == this.name)
        $('#' + this.name + 'Title').removeClass('selected');
}

/**
* @augments Bin
* @class TermBin class defines bins for "term" layers.
* @param {jQuery Object} parent jQuery Object of parent element to append bin to
* @param {String} name Name of TermBin
* @property {String} separator Separator for TermBins is ";"
* @property {Object} box
* @property {Array} layers
* @constructor
*/
function TermBin(parent, name) {
    this.name = name;
    this.separator = '; ';
    this.init(parent);
}
TermBin.prototype = new Bin();

/**
* Selects layer k
* @param {Int} k Array index of layer to select
*/
TermBin.prototype.selectLayer = function(k) {
    ///*
    if (this.box.hasEditbar)
        mapState = 1;
    else
        mapState = 0;
    //*/
    Bin.prototype.selectLayer.call(this, k);
    var element = $('#' + this.name + k + 'Content')
    element.removeClass('link');
    element.removeClass('enabled');
    element.attr('title', '');
    element.unbind('click');
    var t = this;
    this.box.addSwitch('BottomLeft', 'select', 'Select ' + this.name.capitalize(), function() { t.selectLayer(k) }, 'deselect', 'Deselect ' + this.name.capitalize(), function() { t.deselectLayer() });
    this.box.button['select'].change();
    this.box.addButton('BottomLeft', 'edit', 'Edit ' + this.name.capitalize(), function() { t.editLayer(k) });
    // fixme: subclass the TermBin (LocationBin)
    if (this.name == 'location')
        this.box.addButton('BottomLeft', 'map', 'Show Location on Map', function() { showMap() });
    else
        this.box.addButton('BottomLeft', 'find', 'Find ' + this.name.capitalize(), function() { t.findLayer() });
    this.box.addButton('BottomRight', 'remove', 'Remove ' + this.name.capitalize(), function() { t.removeLayer(k) });
    if (!layers[k]['editable']) {
        this.box.button['edit'].disable();
        this.box.button['remove'].disable();
    }
    this.box.showToolbar();
    ///*
    if (mapState == 1)
        showMap();
    //*/
}

/**
* Deselects currently selected layer
*/
TermBin.prototype.deselectLayer = function() {
    var k = selectedLayer;
    var element = $('#' + this.name + k + 'Content');
    element.addClass('link');
    element.addClass('enabled');
    element.attr('title', 'Select ' + this.name.capitalize());
    var t = this;
    element.click(function() { t.selectLayer(k) });
    Bin.prototype.deselectLayer.call(this);
    this.box.button['select'].remove();
    this.box.button['edit'].remove();
    if (this.name == 'location')
        this.box.button['map'].remove();
    else
        this.box.button['find'].remove();
    this.box.button['remove'].remove();
    this.box.hideToolbar();
    if (this.box.hasEditbar)
        hideMap();
    /*
    if (mapState == 0)
        hideMap();
    */
}

/**
* Makes layer k edittable
* @param {Int} k Array index of layer
*/
TermBin.prototype.editLayer = function(k) {
    Bin.prototype.editLayer.call(this, k);
    if (k == -1)
        var val = '';
    else {
        $('#' + this.name + k + 'Content').removeClass('selected');
        $('#' + this.name + k + 'Content').addClass('editable');
        var val = layers[k]['value'];
    }
    var element = $('<input />')
    element.addClass('editable'); 
    element.attr('id', this.name + 'Input');
    element.val(val);
    element.focus(focusBinInput);
    element.blur(blurBinInput);
    $('#' + this.name + 'Editbar').html(element);
    this.box.showEditbar();
    element.focus();
    if (k == -1)
        this.box.showToolbar();
    else {
        this.box.button['select'].disable();
        this.box.button['edit'].disable();
        if (this.name == 'location')
            this.box.button['map'].disable();
        else
            this.box.button['find'].disable();
    }
}

/**
* Cancel layer edit
*/
TermBin.prototype.cancelLayer = function() {
    var k = selectedLayer;
    Bin.prototype.cancelLayer.call(this);
    if (k != -1) {
        var element = $('#' + this.name + k + 'Content');
        element.removeClass('editable');
        element.addClass('selected');
    }
    this.box.hideEditbar();
    if (k == -1)
        this.box.hideToolbar();
    else {
        this.box.button['select'].enable();
        this.box.button['edit'].enable();
        if (this.name == 'location')
            this.box.button['map'].enable();
        else
            this.box.button['find'].enable();
        this.box.button['remove'].enable();
    }
}

/**
* Function returns layer element formatted correctly
* @param {Int} i Array index of layer to be formatted
* @returns {jQuery Object} jQuery Element of layer formatted and with event handlers attached
*/
TermBin.prototype.formatLayer = function(i) {
    var k = this.layers[i]
    var element = $('<span></span>');
    element.attr('id', this.name + k + 'Content');
    if (layers[k]['value_highlighted'])
        element.html(layers[k]['value_highlighted']);
    else
        element.html(layers[k]['value_html']);
    if (selectedLayer == k)
        element.addClass('selected');
    else {
        element.addClass('link');
        element.addClass('enabled');
        element.attr('title', 'Select ' + this.name.capitalize());
        var t = this;
        element.click(function() { t.selectLayer(k) })
    }
    return element;
}

/**
* Updates location.href to query with value of current layer to search for all instances of layer.value
*/
TermBin.prototype.findLayer = function() {
    document.location.href = base_url + '/?f=' + selectedBin + '&q=' + layers[selectedLayer].value;
}

/**
* @augments Bin
* @class Class for Bins containing Text data - description and transcript.
* @param {jQuery Object} parent jQuery Object of parent element
* @param {String} name Name of Bin
* @property {String} name Name of Bin
* @property {Object} box
* @property {Array} layers
* @property {String} separator Separator html - <div class="separator"></div>
* @property {Array} layerBox Array of layer boxes contained within Bin
* @constructor
*/
function TextBin(parent, name) {
    this.name = name;
    this.separator = '<div class="separator"></div>';
    this.layerBox = {};
    this.init(parent);
}
TextBin.prototype = new Bin();

/**
* Add Layer to Bin
*/
TextBin.prototype.addLayer = function() {
    var element = $('<div></div>');
    element.attr('id', 'add' + this.name.capitalize());
    this.box.prependContent(element);
    // this is a strange hack: formatLayer appends a box and returns ''
    element.append(this.formatLayer(-1) + this.separator);
    Bin.prototype.addLayer.call(this);
}

/**
* Make Layer k edittable
* @param {Int} k Array index of layer
*/
TextBin.prototype.editLayer = function(k) {
    if (k != selectedLayer) {
        this.selectLayer(k);
        this.layerBox[k].button['select'].change();
    }
    Bin.prototype.editLayer.call(this, k);
    $('#' + this.name + k + 'Content').removeClass('content');
    var element = $('<textarea></textarea>');
    element.addClass('editable');
    element.attr('id', this.name + 'Input');
    // safari bug: always adds margin below textarea (dependent on line-height)
    if ($.browser.safari)
        element.css('margin-bottom', '-4px');
    if (k != -1)
        element.val(layers[k]['value']);
    element.focus(focusBinTextarea);
    element.blur(blurBinTextarea);
    this.layerBox[k].setContent(element);
    element.focus();
}

/**
* Cancel edit of layer
*/
TextBin.prototype.cancelLayer = function() {
    var k = selectedLayer;
    Bin.prototype.cancelLayer.call(this, k);
    if (k == -1)
        $('#add' + this.name.capitalize()).remove();
    else {
        var element = $('#' + this.name + k + 'Content');
        element.addClass('content');
        element.html(layers[k]['value_html']);
    }
}

/**
* Formats layer at index i and returns an empty string
* @param {Int} i Array Index of layer to format
* @returns {String} Always returns empty string after formatting layer.
*/
TextBin.prototype.formatLayer = function(i) {
    var element = $('<div></div>');
    if (i == -1) {
        var k = -1;
        var parent = $('#add' + this.name.capitalize());
    }
    else {
        var k = this.layers[i];
        var parent = $('#' + this.name + 'Content');
    }
    this.layerBox[k] = new Box(parent, this.name + k, 'layerBox', false, false, true, 0);
    this.layerBox[k].load();
    if (k != -1) {
        var t = this;
        this.layerBox[k].addSwitch('BottomLeft', 'select', 'Select ' + t.name.capitalize(), function() { t.selectLayer(k) }, 'deselect', 'Deselect ' + t.name.capitalize(), function() { t.deselectLayer() })
        this.layerBox[k].addButton('BottomLeft', 'edit', 'Edit ' + t.name.capitalize(), function() { t.editLayer(k) })
        this.layerBox[k].addButton('BottomRight', 'remove', 'Remove ' + t.name.capitalize(), function() { t.removeLayer(k) })
        var created = new Date(layers[k]['created'] * 1000);
        var credit = '<div style="text-align:right;margin-right: 20px;color:  rgb(64,64,64); margin-top: 1px">by ' +
		     layers[k]['creator'] + ', ' + created.format('%F') + 
                    '</div>';
        this.layerBox[k].parent.find('#' + this.layerBox[k].name + 'Toolbar').append(credit);
        if (!layers[k]['editable']) {
            this.layerBox[k].button['edit'].disable();
            this.layerBox[k].button['remove'].disable();
        }
        if (layers[k]['value_highlighted'])
            this.layerBox[k].appendContent(layers[k]['value_highlighted']);
        else
            this.layerBox[k].appendContent(layers[k]['value_html']);
    }
    if (selectedLayer == k) {
        $('#' + this.name + k + 'Content').addClass('selected')
        if (k != -1)
            this.layerBox[k].button['select'].change();
    }
    return '';
}

//  ----------------------------------------------------------------------------
//  Creator Bin prototype
//  ----------------------------------------------------------------------------

/**
* @augments Bin
* @class Bin to hold Layer Creator information (from global "creator" array)
* @see creator
* @property {jQuery Object} parent
* @property {Object} checkbox Switch Object to check creator names (toggle hide and display of their layers)
* @property {Object} box Box Object
* @param {jQuery Object} parent Parent element to append Bin to
* @constructor
*/
function CreatorBin(parent) {
    this.parent = parent;
    this.checkbox = {};
}

/**
* Loads Bin Box, sets to default collapsed state
*/
CreatorBin.prototype.load = function() {
    this.box = new Box(this.parent, 'user', 'binBox', true, false, false, 0);
    this.box.load();
    this.box.setTitle('Contributors');
    t = this;
    this.box.addSwitch('TopLeft', 'collapse', 'Collapse Contributors', function() { t.box.collapse() }, 'expand', 'Expand Contributors', function() { t.box.expand() });
    this.box.collapse();
    this.box.button['collapse'].change();
    this.update();
}

/**
* Update Bin contents with names of all creators
* @see creator
*/
CreatorBin.prototype.update = function() {
    this.box.clearContent();
    if (creator.length > 0) {
        for (var k in creator) {
            var name = creator[k].name;
            var element = $('<div></div>');
            element.addClass('creator');
            element.attr('id', 'creator' + k);
            this.box.appendContent(element);
            // var parent = this.parent.find('#' + this.name + 'Buttons' + corner);
            element = $('<div></div>');
            element.addClass('checkbox');
            t = this;
            function check(k) {
                return function() { t.checkCreator(k) }
            }
            function uncheck(k) {
                return function() { t.uncheckCreator(k) }
            }
            this.checkbox[name] = new Switch(element, 'unchecked', 'Show Layers by ' + name, check(k), 'checked', 'Hide Layers by ' + name, uncheck(k));
            if (creator[k].state == 1)
                this.checkbox[name].change();
            $('#creator' + k).append(element);
            element = $('<div></div>');
            element.addClass('name');
            element.html(name);
            $('#creator' + k).append(element);
        }
    }
    else {
        var element = $('<span></span>');
        element.addClass('disabled');
        element.html('No Contributors');
        this.box.appendContent(element);
    }
}

/**
* Function called when a creator is checked. Sets creator state to 1. Loads layers.
*/
CreatorBin.prototype.checkCreator = function(k) {
    creator[k].state = 1;
    loadLayers();
}

/**
* Function called when a creator is unchecked. Sets creator state to 0 (Unloads creator's layers) and deselects current layer if is unchecked creator's.
*/
CreatorBin.prototype.uncheckCreator = function(k) {
    creator[k].state = 0;
    loadLayers();
    if (selectedLayer != -1 && layers[selectedLayer].creator == creator[k]['name']) {
        if (!selectNextLayer()) {
            if (typeof(bin[selectedBin].layerBox) != 'undefined')
                bin[selectedBin].layerBox[selectedLayer].button['select'].change();
            bin[selectedBin].deselectLayer();
        }
    }
}

//  ----------------------------------------------------------------------------
//  Layer functions
//  ----------------------------------------------------------------------------

// fixme: wrap an object around these functions

/**
* Sets global variable layerState to state.
* States: 0 (show layers at marker), 1 (show layers in selection)
* @param {Int} state State to set layerState to.
*/
function setLayerState(state) {
    /*
        layerState: 0 (show layers at marker), 1 (show layers in selection)
    */
    layerState = state;
    if (selectedLayer != -1) {
        if (layerState == 0) {
            if (playMarker.pos < layers[selectedLayer]['time_in'] || playMarker.pos >= layers[selectedLayer]['time_out'])
                bin[selectedBin].deselectLayer();
        }
        else if (layerState == 1) {
            if (inMarker.pos == outMarker.pos || inMarker.pos >= layers[selectedLayer]['time_out'] || outMarker.pos <= layers[selectedLayer]['time_in'])
                bin[selectedBin].deselectLayer();
        }
    }
    loadLayers();
}

/**
* Load all current and active layers
*/
function loadLayers() {
    for (var k in bin)
        bin[k].layers = [];
    for (var k in layers) {
        var v = layers[k]
        if (creator[getCreator(v['creator'])].state == 1) {
            if (layerState == 0) {
                if (playMarker.pos >= v['time_in'] && playMarker.pos < v['time_out'])
                    bin[v['track']].layers.push(k);
            }
            else if (layerState == 1) {
                if (inMarker.pos != outMarker.pos && inMarker.pos < v['time_out'] && outMarker.pos > v['time_in'])
                    bin[v['track']].layers.push(k);
            }
        }
    }
    for (var k in bin) {
        if (selectedSort == 'name')
            bin[k].layers.sort(sortBinLayersByName);
        else if (selectedSort == 'duration')
            bin[k].layers.sort(sortBinLayersByDuration);
        if (bin[k]['loaded'])
            bin[k].update();
    }
}

/**
* Load unique edit points in layers and sort them. Store in global variable (Array) called edit.
*/
function loadEdits() {
    edit = [];
    for (var k in layers) {
        edit.push(layers[k].time_in);
        edit.push(layers[k].time_out);
    }
    edit = unique(edit);
    edit.sort(sortEdits);
}

/**
* Load creators - store in global variable (Array) called creator (with creator.state and creator.name)
*/
function loadCreators() {
    var creatorName = [];
    for (var k in layers)
        creatorName.push(layers[k].creator);
    creatorName = unique(creatorName);
    if (typeof(creator) != 'undefined') {
        var creatorState = {};
        for (var k in creator)
            creatorState[creator[k].name] = creator[k].state;
    }
    creator = [];
    for (var k in creatorName) {
        if (typeof(creatorState) != 'undefined' && typeof(creatorState[creatorName[k]]) != 'undefined')
            var state = creatorState[creatorName[k]];
        else
            var state = 1;
        creator[k] = {'name': creatorName[k], 'state': state};
    }
    creator.sort(sortCreators);    
}

/**
* Gets the Bin ('track') for passed id
* @param {String} id Id of layer (Layer ID from DB)
* @returns {String} track Currently "keyword", "location", "description", or "transcript"
* @throws {Int} -1 If doesn't find layer matching id
*/
function getBin(id) {
    // fixme: getBin and getLayer may usually be called together - so maybe they should be one function
    for (var k in layers) {
        if (layers[k].id == id)
            return layers[k].track;
    }
    return -1;
}

/**
* Gets layer matching id passed
* @param {String} id Id of layer (layer Id from Db)
* @returns {Int} Array index of matching layer
* @throws {Int} -1 if does not find layer with id
*/
function getLayer(id) {
    for (var k in layers) {
        if (layers[k]['id'] == id)
            return k;
    }
    return -1;
}

/**
* Gets creator array index matching name passed
* @param {String} Creator name
* @returns {Int} k Array index of matching creator in global variable creator.
* @throws {Int} -1 If it does not find a creator name matching name passed.
*/
function getCreator(name) {
    for (var k in creator) {
        if (creator[k]['name'] == name)
            return k;
    }
    return -1;
}

/**
* Selects next layer and returns true. Returns false if there are no layers.
* @returns {Boolean} true
* @throws {Boolean} false
*/
function selectNextLayer() {
    var length = 0;
    for (var k in bin)
        length += bin[k].layers.length;
    if (length > 0) {
        var nextBin = selectedBin;
        var nextLayer = selectedLayer;
        while (nextBin == '' || bin[nextBin].layers.length == 0 || bin[nextBin].layers.indexOf(nextLayer) == bin[nextBin].layers.length - 1) {
            if (nextBin == '' || nextBin == binName[binName.length - 1])
                nextBin = binName[0];
            else
                nextBin = binName[binName.indexOf(nextBin) + 1];
            nextLayer = -1;
        }
        if (nextLayer == -1)
            nextLayer = bin[nextBin].layers[0];
        else
            nextLayer = bin[nextBin].layers[parseInt(bin[nextBin].layers.indexOf(nextLayer)) + 1];
        if (typeof(bin[nextBin].layerBox) != 'undefined')
            bin[nextBin].layerBox[nextLayer].button['select'].change();
        bin[nextBin].selectLayer(nextLayer);
        return true;
    }
    else
        return false;
}

/**
* Get previous edit point.
* @returns {Int} Timecode of previous edit, in miliseconds or 0 if no previous edit point.
*/
function getPreviousEdit() {
    if (edit.length == 0 || edit[0] >= playViewer.pos)
        return 0;
    for (k in edit) {
        if (edit[k] >= playViewer.pos)
            return edit[k - 1];
    }
    return edit[edit.length-1];
}

/**
* Get Next edit point.
* @returns {Int} Timecode of next edit, in miliseconds. If at last edit point, returns video.duration
*/
function getNextEdit() {
    for (k in edit) {
        if (edit[k] > playViewer.pos)
            return edit[k];
    }
    return video.duration;
}

/**
* Check if there is an edit point between two timecodes
* @param {Int} pos0 First time-code, in miliseconds
* @param {Int} pos1 Second time-code, in miliseconds (pos1 does not have to be > pos0, the function checks for this)
* @returns {Boolean} True if there is a value in edit[] array in between two time-codes, False if not.
*/
function crossesEdit(pos0, pos1) {
    if (pos0 == pos1)
        return false;
    if (pos0 > pos1) {
        var pos = pos0
        pos0 = pos1
        pos1 = pos
    }
    for (k in edit)
        if (edit[k] > pos0 && edit[k] <= pos1)
            return true
    return false
}

/**
* Get next result from results global variable. Set global var selectedResult++ .
*/
function getNextResult() {
    if (results.length > 0) {
        if (selectedResult == results.length - 1)
            selectedResult = 0;
        else
            selectedResult++;
    }
    playMarker.set(layers[results[selectedResult]]['time_in']);
}

/**
* Selects Layer and Bin which hold the selected result. Refers to globals: results,bin, layers.
*/
function selectResult() {
    if (results.length > 0) {
        var resultBin = getBin(layers[results[selectedResult]]['id']);
        bin[resultBin].selectLayer(results[selectedResult]);
        if (typeof(bin[resultBin].layerBox) != 'undefined')
            bin[resultBin].layerBox[results[selectedResult]].button['select'].change();
    }
}

//  ----------------------------------------------------------------------------
//  Sort functions
//  ----------------------------------------------------------------------------

/**
* Set sort order for layers to s
* @param {String} s Sort to use (String)
*/
function setSort(s) {
    selectedSort = s;
    $('#elementSelectSort').html('Sort: ' + selectedSort.capitalize());
    loadLayers();
}

/**
* Sorts layers by index
* @param {Object} a Layer a with property "index"
* @param {Object} b Layer b with property "index"
* @returns {Int} 1 if a > b, -1 if b > a, 0 if neither.
*/
function sortLayers(a, b) {
    if (a['index'] > b['index'])
        return 1;
    if (a['index'] < b['index'])
        return -1;
    return 0;    
}

/**
* Sorts layers within a bin alphabetically by value
* @param {Int} a Array index of layer a
* @param {Int} b Array index of layer b
* @returns {Int} 1 if a > b, -1 if b > a, 0 if neither.
*/
function sortBinLayersByName(a, b) {
    if (layers[a]['value'].toLowerCase() > layers[b]['value'].toLowerCase())
        return 1;
    if (layers[a]['value'].toLowerCase() < layers[b]['value'].toLowerCase())
        return -1;
    return 0;
}

/**
* Sorts layers within a bin by duration (shortest first)
* @param {Int} a Array index of layer a
* @param {Int} b Array index of layer b
* @returns {Int} 1 if a is shorter than b, -1 if b is shorter than a, 0 if neither
*/
function sortBinLayersByDuration(a, b) {
    if (layers[a]['time_out'] - layers[a]['time_in'] < layers[b]['time_out'] - layers[b]['time_in'])
        return 1;
    if (layers[a]['time_out'] - layers[a]['time_in'] > layers[b]['time_out'] - layers[b]['time_in'])
        return -1;
    return 0;
}

/**
* Function to sort edits
* @param {Int} Edit a
* @param {Int} Edit b
* @returns {Int} a - b
*/
function sortEdits(a, b) {
    return a - b;
}

/**
* Sort creators alphabetically
* @param {Object} a Creator
* @param {Object} b Creator
* @returns {Int} 1 if a comes after b, -1 if b comes after a, 0 if neither.
*/
function sortCreators(a, b) {
    if (a['name'].toLowerCase() > b['name'].toLowerCase())
        return 1;
    if (a['name'].toLowerCase() < b['name'].toLowerCase())
        return -1;
    return 0;
}

//  ----------------------------------------------------------------------------
//  Notes prototype
//  ----------------------------------------------------------------------------

/**
* @class Class for "Rooms" - Room for Notes and Room for Discussion.
* @property {jQuery Object} parent Parent element to append room to
* @property {String} name0 Currently, either "Discussion", or "notes"
* @property {String} name1 Currently, either "Discussion", or "notes"
* @property {Int} left Left position of room
* @property {Int} top Top position of room
* @property {Int} width Width of room
* @property {Int} height Height of room
* @property {jQuery Object} element jQuery element of the Room.
* @property {String} content Content of room, from global video object.
* @property {String} content_html Content_html of room, from global video object.
* @constructor
* @param {jQuery Object} parent Parent to append Room to
* @param {String} name0 "Notes", or "Discussion"
* @param {String} name1 "Notes", or "Discussion"
* @param {Int} left left pos
* @param {Int} top Top pos
* @param {Int} width Width
* @param {Int} height Height
*/
function Room(parent, name0, name1, left, top, width, height) {
    this.parent = parent;
    this.name0 = name0;
    this.name1 = name1;
    this.title0 = 'Room for ' + this.name0.capitalize();
    this.title1 = 'Room for ' + this.name1.capitalize();
    this.left = left;
    this.top = top;
    this.width = width;
    this.height = height;
    this.content = video[name0];
    this.content_html = video[name0 + '_html'];
}

/**
* loads Room element on page, adds switches, appends to parent element
*/
Room.prototype.load = function() {
    this.element = $(parseTemplate(htmlRoom, {'name': this.name0, 'title': this.title0, 'content': this.content_html}));
    this.element.css('left', this.left + 'px');
    this.element.css('top', this.top + 'px');
    if (user.logged_in) {
        var t = this;
        var elementSwitch = this.element.find('.roomSwitch');
        elementSwitch.addClass('enabled');
        elementSwitch.attr('title', 'Switch to ' + this.title1);
        elementSwitch.click(function() { t.clickTitle() });
    }
    var elementContent = this.element.find('.roomContent');
    elementContent.css('width', this.width + 'px');
    elementContent.css('height', (this.height - 16) + 'px');
    if (user.logged_in)
        this.enableContent();
    this.parent.append(this.element);
}

/**
* Make Room "enabled" / editable - adds onclick event handler on room element to fire this.clickContent
*/
Room.prototype.enableContent = function() {
    var t = this;
    var element = this.element.find('.roomContent');
    element.addClass('enabled');
    element.attr('title', 'Edit ' + this.name0.capitalize());
    element.click(function() { t.clickContent() });
}

/**
* Disables room / makes it not-editable, removes onclick event handler
*/
Room.prototype.disableContent = function() {
    var element = this.element.find('.roomContent');
    element.removeClass('enabled');
    element.attr('title', '');
    element.unbind('click');
}

/**
* Toggles between name0 room and name1 room - calls Room constructor with name1 as name0 ..
*/
Room.prototype.clickTitle = function() {
    $('#' + this.name0 + 'Room').remove();
    room = new Room(this.parent, this.name1, this.name0, this.left, this.top, this.width, this.height);
    room.load();
}

/**
* Called when a logged in user clicks on room content - allows user to edit
*/
Room.prototype.clickContent = function() {
    var t = this;
    this.disableContent();
    var element = $('<textarea></textarea>');
    element.attr('id', this.name0 + 'Input');
    element.addClass('roomInput');
    if ($.browser.safari) {
        element.css('width', '603px');
        element.css('margin-left', '-3px');
    }
    element.val(this.content);
    element.focus(function() { focusTextarea() });
    element.blur(function() { blurTextarea(); t.save() });
    $('#' + this.name0 + 'Content').html(element);
}

/**
* Saves room edit - adds request to requests
*/
Room.prototype.save = function() {
    var parameters = {};
    parameters[this.name0] = $('#' + this.name0 + 'Input').val(); 
    requests.add('/' + video.id + '/edit', parameters, saveRoomCallback);
}

/**
* Callback function fired after room has been saved, updates video object data and room content
*/
function saveRoomCallback(result) {
    video[room.name0] = result.video[room.name0];
    video[room.name0 + '_html'] = result.video[room.name0 + '_html'];
    video[room.name1] = result.video[room.name1];
    video[room.name1 + '_html'] = result.video[room.name1 + '_html'];
    room.content = video[room.name0];
    room.content_html = video[room.name0 + '_html'];
    $('.roomContent').html(room.content_html);
    room.enableContent();
}

//  ----------------------------------------------------------------------------
//  Clipboard and History prototypes
//  ----------------------------------------------------------------------------

/**
* @class Layer Clipboard class - to copy / paste timecode spans
* @property {Int} inPos In position of Span, in miliseconds
* @property {Int} outPos Out position of Span, in miliseconds
* @constructor
*/
function LayerClipboard() {
    this.inPos = inMarker.pos;
    this.outPos = outMarker.pos;
}

/**
* Copy current in and out positions to Clipboard
*/
LayerClipboard.prototype.copy = function () {
    this.inPos = inMarker.pos;
    this.outPos = outMarker.pos;
}

/**
* Set timeline with in and out positions from clipboard
*/
LayerClipboard.prototype.paste = function () {
    timeline.set(playMarker.pos, this.inPos, this.outPos);
}

/**
* @class LayerHistory class - maintains a history of selected in and out points
* @property {Array} history - Array of history objects with inPos, outPos and selected as properties
* @property {Int} position Current position in history
* @property {Int} state States: 0 {disabled), 1 (enabled)
* @constructor
*/
function LayerHistory() {
    /*
        state: 0 (disabled), 1 (enabled)
    */
    // fixme: selected is not used
    this.history = [{'inPos': inMarker.pos, 'outPos': outMarker.pos, 'selected': -1}];
    this.position = 0;
    this.state = 1;
}

/**
* Sets this.state to state
* @param {Int} state 0 = disabled, 1 = enabled
*/
LayerHistory.prototype.setState = function(state) {
    this.state = state;
}

/**
* Add a new history item with passed inPos and outPos
* @param {Int} inPos In Point, in ms
* @param {Int} outPos Out Point, in ms
*/
LayerHistory.prototype.add = function(inPos, outPos) {
    if (this.state == 1) {
        while (this.history.length > this.position + 1)
            this.history.pop();
        var i = this.history.length - 1;
        if (inMarker.pos != this.history[i]['inPos'] || outMarker.pos != this.history[i]['outPos']) {
            this.history.push({'inPos': inMarker.pos, 'outPos': outMarker.pos, 'selected': -1})
            this.position++;
        }
    }
    // this.debug();
}

/**
* Undo - set previously selected layer to selected
*/
LayerHistory.prototype.undo = function() {
    if (this.position > 0) {
        this.position--;
        this.state = 0;
        timeline.set(playMarker.pos, this.history[this.position]['inPos'], this.history[this.position]['outPos']);
        this.state = 1;
    }
    // this.debug();
}

/**
* Redo - set next selected layer as selected
*/
LayerHistory.prototype.redo = function() {
    if (this.position < this.history.length - 1) {
        this.position++;
        this.state = 0;
        timeline.set(playMarker.pos, this.history[this.position]['inPos'], this.history[this.position]['outPos']);
        this.state = 1;
    }
    // this.debug();
}

/**
* Debug / testing function for layerHistory
*/
LayerHistory.prototype.debug = function() {
    $('#menuTop').html('');
    for (var i in this.history) {
        if (i == this.position)
            $('#menuTop').append('&lt;');
        $('#menuTop').append('[' + Math.floor(this.history[i]['inPos'] / 1000) + '-' + Math.floor(this.history[i]['outPos'] / 1000) + ']')
        if (i == this.position)
            $('#menuTop').append('&gt;');
    }
}

//
//
//

/**
* Sends request to server to find layers in current event in field "f", matching query "q"
* @param {String} f Field to search in
* @param {String} q Query string to search for
*/
function find(f, q) {
    if (q == '[Clear Recent Searches]') {
        searchHistory.clear();
        return false;
    }
    var fq = parseQuery(f, q);
    f = fq['f'];
    q = fq['q'];
    $('#elementSelectFind').html('Find: ' + f.capitalize());
    $('#selectFind').val(f);
    $('#inputFind').val(q);
    if (q)
        searchHistory.add(q);
    requests.add('/' + video.id + '/find', {'f': f, 'q': q}, findCallback);
}

/**
* Callback function called after server returns result of find - marks results on timeline.
* @param {Array} result Array holding matched results, as json object from server
*/
function findCallback(result) {
    // fixme: always use foo.bar instead of foo['bar']
    // fixme: layers no longer sorted by time if we add new layers at the end of the array
    $('#inputFind').blur();
    keyboard.setState(0);
    for (var k in layers)
        layers[k]['value_highlighted'] = '';
    timeline.clearResults();
    results = [];
    selectedResult = -1;
    for (var k in result.layers) {
        for (var l in layers) {
            if (result.layers[k].id == layers[l].id) {
                timeline.markResult(layers[l].time_in, layers[l].time_out);
                layers[l].value_highlighted = result.layers[k].value_highlighted;
                results[k] = l;
                break;
            }
        }
    }
    if (result.results) {
        selectedResult = 0;
        playMarker.set(layers[results[0]]['time_in']);
    }
    for (var k in bin)
        bin[k].update();
}

/**
* Clears a search - calls find with an empty string as query
*/
function clearFind() {
    var val = $('#inputFind').val();
    $('#inputFind').val('');
    if (val != '')
        find($('#selectFind').val(), '');
}

/**
* Show Map, add buttons to location Bin
*/
function showMap() {
    loadTemplate('map.html', function(html) {
        $('#locationEditbar').html(html);
        $('#locationEditbar').css('height', '192px');
        $('#gLocation').attr('src', base_url + '/map/iframe/' + layers[selectedLayer].id);
        locationBin.box.showEditbar();
        locationBin.box.button.map.disable();
        if (typeof(locationBin.box.button.cancel) != 'undefined')
            locationBin.box.button.cancel.remove();
        if (typeof(locationBin.box.button.link) != 'undefined')
            locationBin.box.button.link.remove();
        locationBin.box.button.remove.remove();
        locationBin.box.addButton('BottomRight', 'cancel', 'Hide Map', function() { hideMap() } );
        locationBin.box.addButton('BottomRight', 'link', 'Show Large Map', function() { document.location.href = base_url + '/map/layer/' + layers[selectedLayer].id });
        locationBin.box.addButton('BottomRight', 'remove', 'Remove Layer', function() { locationBin.removeLayer() } );
        locationBin.box.button.remove.disable();
    });
}

/**
* Hide map
*/
function hideMap() {
    //k does not seem to be used?
    var k = selectedLayer;
    $('#locationEditbar').html('');
    $('#locationEditbar').css('height', '16px');
    locationBin.box.hideEditbar();
    if (locationBin.box.hasToolbar) {
        locationBin.box.button['map'].enable();
        locationBin.box.button['cancel'].remove();
        locationBin.box.button['link'].remove();
    }
}

/**
* Make request to add a layer to video
*/
function addLayer() {
    var parameters = {
        'track': selectedBin,
        'value': $('#' + selectedBin + 'Input').val(),
        'time_in': inMarker.pos,
        'time_out': outMarker.pos
    }
    requests.add('/' + video.id + '/add', parameters, addLayerCallback);
}

/**
* Callback function called after layer has been added - reloads layers, edits
* @param {Object} result Result json object from server
*/
function addLayerCallback(result) {
    if (!result.result) {
        error('result');
        return false;
    }
    var k = layers.length;
    layers[k] = result.layer;
    bin[result.layer.track].cancelLayer();
    loadCreators();
    creatorBin.update();
    loadLayers();
    loadEdits();
    if (layerState == 0 && (playMarker.pos < layers[k].time_in || playMarker.pos >= layers[k].time_out))
        playMarker.set(layers[k].time_in);
    bin[result.layer.track].update();
    bin[result.layer.track].selectLayer(layers.length - 1);
    if (typeof(bin[selectedBin].layerBox) != 'undefined')
        bin[selectedBin].layerBox[selectedLayer].button['select'].change();
    if ($('#inputFind').val() != '')
        find($('#selectFind').val(), $('#inputFind').val())
}

/**
* Makes a request to edit selectedLayer
*/
function editLayer() {
    var parameters = {
        'id': layers[selectedLayer].id,
        'value': $('#' + selectedBin + 'Input').val(),
        'time_in': inMarker.pos,
        'time_out': outMarker.pos
    }
    requests.add('/' + video.id + '/edit', parameters, editLayerCallback);
}

/**
* Callback function called after layer has been editted
* @param {Object} result Result json object from server
*/
function editLayerCallback(result) {
    if (!result.result) {
        error('result');
        return false;
    }
    var k = getLayer(result.layer.id)
    layers[k] = result.layer;
    loadEdits();
    if (layerState == 0 && (playMarker.pos < layers[k].time_in || playMarker.pos >= layers[k].time_out))
        playMarker.set(layers[k].time_in);
    bin[result.layer.track].cancelLayer();
    bin[result.layer.track].update();
    bin[result.layer.track].selectLayer(k);
    if (typeof(bin[selectedBin].layerBox) != 'undefined')
        bin[selectedBin].layerBox[selectedLayer].button['select'].change();
    if ($('#inputFind').val() != '')
        find($('#selectFind').val(), $('#inputFind').val());
}

/**
* Make request to remove layer with array index k in layers
* @param {Int} k Array index in layers of layer to remove.
*/
function removeLayer(k) {
    parameters = {
        'id': layers[k].id
    }
    requests.add('/' + video.id + '/remove', parameters, removeLayerCallback)
}

/**
* Callback function called after layer has been removed.
* @param {Object} result Result json object from server
*/
function removeLayerCallback(result) {
    if (!result.result) {
        error('result');
        return false;
    }
    var b = getBin(result.id);
    var k = getLayer(result.id);
    bin[b].selectLayer(k);
    selectNextLayer();
    delete layers[k];
    loadCreators();
    creatorBin.update();
    loadLayers();
    loadEdits();
    bin[b].update();
}

/**
* Makes request to set poster frame to current playMarker.pos
*/
function setPosterFrame() {
    requests.add('/' + video.id + '/edit', {'poster_frame': playMarker.pos}, setPosterFrameCallback);
}
/**
* Callback function called after poster frame has been set
* @param {Object} result Result json object from server
*/
function setPosterFrameCallback(result) {
    if (!result.result) {
        error('result');
        return false;
    }
    video.poster_frame = result.video.poster_frame;
    timeline.set(video.poster_frame, inMarker.pos, outMarker.pos);
}

/**
* Enable setPoster button on infoPanel
*/
function unlockPosterFrame() {
    if (playMarker.pos != video.poster_frame)
        menu.button['setPoster'].enable();
}

/**
* Disable setPoster button on infoPanel
*/
function lockPosterFrame() {
    if (playMarker.pos != video.poster_frame)
        menu.button['setPoster'].disable();
}

/**
* Generates a link to currently selected layer - eg. http://padmo.pad.ma/Vfc3nh5d/00:02:24.000-00:04:08.000
*/
function link() {
    loadTemplate('link.editor.html', function(html) {
        var link = base_url + '/' + video.id + '/' + getTimecode(inMarker.pos) + '-' + getTimecode(outMarker.pos);
        var linkSelection = '<a class="link" href="' + link + '">' + link + '</a>';
        if (timeline.state == 0 || selectedLayer == -1) {
            var layer = '';
            var linkLayer = '';
        }
        else {
            var layer = 'Link to this ' + selectedBin + ':';
            var link = base_url + '/' + video.id + '/' + layers[selectedLayer]['id'];
            var linkLayer = '<a class="link" href="' + link + '">' + link + '</a>';
        }
        html = parseTemplate(html, {
            'linkSelection': linkSelection,
            'layer': layer,
            'linkLayer': linkLayer
        });
        dialog = new Dialog('Link', html, [['Close', function() { dialog.close() }]], 552, 138);
        dialog.load();
    });
}

function download() {
    loadTemplate('download.editor.html', function(html) {
        var link = base_url + '/' + video.id + '/download/' + getTimecode(inMarker.pos) + '-' + getTimecode(outMarker.pos) + '.ogv';
        var linkSelection = '<a class="link" href="' + link + '">' + link + '</a>';
        html = parseTemplate(html, {
            'linkSelection': linkSelection,
        });
        dialog = new Dialog('Download', html, [['Close', function() { dialog.close() }]], 552, 138);
        dialog.load();
    });
}

function importAnnotations() {
    var html = '<form id="form" action="/' + video.id + '/import" method="post" enctype="multipart/form-data">';
    html += '<div id="elementSelectTrack" class="element400 elementSelect400 enabled">Select Track...</div>'
    html += '<select id="selectTrack" name="track" class="element400 enabled transparent" style="margin-left: -400px" onChange="changeTrack()">'
    html += '<option value="select">Select Track...</option>';
    html += '<option value="location">Locations</option>';
    html += '<option value="keyword">Keywords</option>';
    html += '<option value="description">Descriptions</option>';
    html += '<option value="transcript">Transcripts</option>';
    html += '</select>';
    html += '<input id="elementFileInput" class="element400 elementInput400 disabled" style="width: 396px; margin-top: 8px; margin-bottom: -16px" value="Subtitle File" />';
    html += '<input id="fileInput" type="file" name="file" size="40" style="width: 396px; opacity: 0" onChange="changeFile()" />';
    html += '</form>';
    dialog = new Dialog('Import Annotations', html, [
        ['Upload File', function() { uploadFile() }],
        ['Select .srt File', function() {
            var val = $('#elementFileInput').val();
            $('#elementFileInput').val('Click here to select .srt file')
            setTimeout(function() {
                $('#elementFileInput').val(val);
            }, 2000);
        }],
        ['Cancel', function() { dialog.close() }]
    ], 416, 128);
    dialog.load(function() {
        var frame = $('<iframe name="frame" src="about:blank" style="width: 0px; height: 0px" />');
        frame.load(function() {
            var frameBody = $(window.frames['frame'].document.getElementsByTagName('pre')[0]);
            // alert(frameBody.html());
            try {
                var result = eval('(' + frameBody.html() + ')');
            } catch(e) {
                var result = { result: 'failed', message: 'Upload failed' };
            }
            // var result = frameBody.html();
            if (result) { // catch if frame is loaded for the first time, before the request
                var track = $('#selectTrack').val();
                frameBody.html('');
                $('#dialogContent').html(result.message);
                setTimeout(function() {
                    bin[track].update();
                    dialog.close();
                }, 2500);
            }
        });
        $('body').append(frame);
        $('#form').attr('target', 'frame');
        $('.buttonbar :first-child').removeClass('enabled');
        $('.buttonbar :first-child').addClass('disabled');
    });
}
function changeTrack() {
    //alert($("#selectTrack option[selected]").length)
    $('#elementSelectTrack').html($("#selectTrack option[selected]").html());
    if ($('#selectTrack').val() == 'select') {
        $('.buttonbar :first-child').removeClass('enabled');
        $('.buttonbar :first-child').addClass('disabled');
    } else {
        if ($('#fileInput').val()) {
            $('.buttonbar :first-child').removeClass('disabled');
            $('.buttonbar :first-child').addClass('enabled');
        }
    }
}
function changeFile() {
    $('#elementFileInput').val($('#fileInput').val());
    if ($('#selectTrack').val() != 'select') {
        $('.buttonbar :first-child').removeClass('disabled');
        $('.buttonbar :first-child').addClass('enabled');
    }
}
function uploadFile() {
    $('#form').trigger('submit');
    $('#elementSelectTrack').addClass('disabled');
    $('#selectTrack option').attr( { disabled: 'disabled' });
    $('.buttonbar div').addClass('disabled');
}

/**
* Generates downloadable layer data
*/
function exportAnnotations() {
    for (var k in layers)
        layers[k]['index'] = layers[k]['time_in'] * 1000000000 + layers[k]['time_out'];
    layers.sort(sortLayers);
    var value = [];
    for (var k in layers) {
        var v = layers[k];
        value[k] = '<span class="bold">';
        value[k] += getTimecode(v['time_in']) + ' &ndash; ' + getTimecode(v['time_out']);
        var date = new Date();
        date.setTime(v['created'] * 1000);
        value[k] += ' [' + v['track'].capitalize() + ', ' + v['creator'] + ', ' + date.format('%F %T') + ']';
        value[k] += '</span>';
        value[k] += '<br />' + v['value_html'];
    }
    if (layers.length == 0)
        var html = 'No Annotations';
    else {
        var html = '<span class="bold">Download Annotations as Subtitle Files</span><br />';
        var arr = ['locations', 'keywords', 'descriptions', 'transcripts'];
        for (var i in arr) {
            var link = base_url + '/' + video.id + '/export/' + arr[i] + '.srt';
            html += '<a href="' + link + '" class="link">' + link + '</a><br />';
        }
        html += '<br />';
        html += value.join('<br /><br />');
    }
    dialog = new Dialog('Annotations', html, [['Close', function() { dialog.close() }]], 552, 276);
    dialog.load();
}

/**
* Generates some meta-info on layers
*/
function info() {
    loadTemplate('info.editor.html', function(html) {
        var l = {'location': 0, 'keyword': 0, 'description': 0, 'transcript': 0};
        var length = 0
        for (k in layers) {
            var v = layers[k];
            l[v['track']]++;
            length += v.time_out - v.time_in;
        }
        if (layers.length)
            length = getTimecode(Math.floor(length / layers.length));
        html = parseTemplate(html, {
            'length': getTimecode(video.duration),
            'layers': layers.length,
            'layersPerMinute': Math.round(layers.length / (video.duration / 60000) * 10) / 10,
            'layerLength': length,
            'locations': l['location'],
            'keywords': l['keyword'],
            'descriptions': l['description'],
            'transcripts': l['transcript'],
            'edits': edit.length,
            'editsPerMinute': Math.round(edit.length / (video.duration / 60000) * 10) / 10,
            'creators': creator.length
        });
        dialog = new Dialog('Info', html, [['Close', function() { dialog.close() }]], 280, 280);
        dialog.load();
    });
}

/**
* Throws up help dialog
*/
function help() {
    loadTemplate('help.editor.html', function(html) {
        dialog = new Dialog('Help', html, [['Close', function() { dialog.close() }]], 280, 280);
        dialog.load();
    });
}

/**
* Throws an error dialog based on type passed
* @param {String} type Type of error - currently can be layerValue, layerDuration, layerValueDuration, layerExists or result
*/
function error(type) {
    // fixme: don't declare message every time it is called
    var message = {
        'layerValue': 'Your _bin_ is empty.',
        'layerDuration': 'The duration of your _bin_ is zero, since your in and out points are the same.',
        'layerValueDuration': 'Your _bin_ is empty, and its duration is zero, since your in and out points are the same.',
        'layerExists': 'You have already added the same _bin_ with the same in and out points.',
        'result': 'An error occurred.<br />We were unable to save your input.'
    };
    var vars = {'bin': selectedBin };
    for (var k in vars) {
         var regexp = new RegExp('_' + k + '_', 'g')
         message[type] = message[type].replace(regexp , vars[k]);
    }
    dialog = new Dialog('Error', message[type], [['Close', function() { dialog.close() }]], 280, 140);
    dialog.load();
}

//
//
//

/**
* Resize room element on browser resize
*/
function resize() {
    if (typeof room != 'undefined') {
        if (room.name0 == 'notes')
            var element = $('#notesContent');
        else
            var element = $('#discussionContent');
        var top = video.height + timeline.lines * 24 + 32;
        var height = window.innerHeight - top - 112;
        element.css('height', height);
    }
}

//
//
//

/**
* Set keyboard state to 1 on focus in input box
*/
function focusInput() {
    keyboard.setState(1);
    $(this).removeClass('blurred');
    $(this).addClass('focused');
}

/**
* Set keyboard state to 0 on blur of input box
*/
function blurInput() {
    keyboard.setState(0);
    $(this).removeClass('focused');
    $(this).addClass('blurred');
}

/**
* Set keyboard state to 2 on focus in textarea
*/
function focusTextarea() {
    keyboard.setState(2);
}

/**
* Set keyboard state to 0 on blur of textarea
*/
function blurTextarea() {
    keyboard.setState(0);
}

/**
* Set keyboard state to 1 when focus on input inside Bin
*/
function focusBinInput() {
    keyboard.setState(1);
    $(this).removeClass('blurred');
    $(this).addClass('focused');
    if (typeof(bin[selectedBin].box.button['save']) != 'undefined')
        bin[selectedBin].box.button['save'].element.attr('src', base_url + '/static/images/buttonSave.png');
}

/**
* Set keyboard state to 0 on blur of input inside Bin
*/
function blurBinInput() {
    keyboard.setState(0);
    $(this).removeClass('focused');
    $(this).addClass('blurred');
    bin[selectedBin].box.button['save'].element.attr('src', base_url + '/static/images/buttonSaveFocused.png');
}

/**
* set keyboard state to 2 on focus in textarea in Bin
*/
function focusBinTextarea() {
    keyboard.setState(2);
    $(this).removeClass('blurred');
    $(this).addClass('focused');
    if (typeof(bin[selectedBin].layerBox[selectedLayer].button['save']) != 'undefined')
        bin[selectedBin].layerBox[selectedLayer].button['save'].element.attr('src', base_url + '/static/images/buttonSave.png')
}

/**
* Set keyboard state to 0 on blur of textarea in Bin
*/
function blurBinTextarea() {
    keyboard.setState(0);
    $(this).removeClass('focused');
    $(this).addClass('blurred');
    bin[selectedBin].layerBox[selectedLayer].button['save'].element.attr('src', base_url + '/static/images/buttonSaveFocused.png')
}


function changeSelectSetting() {
    $('#elementSelectSetting').html($('#selectSetting').val().capitalize());
}

function changeVideoSettings() {
    // settings.video.setting = $('#selectSetting').val();
    updatePlayer();
    updateViewer('play');
    updateViewer('in');
    updateViewer('out');
    if (settings.video.setting == 'large') {
        $('#inViewer').hide();
        $('#outViewer').hide();
    } else {
        $('#inViewer').show();
        $('#outViewer').show();
    }
    // dialog.close();
}

function updateViewer(name) {
    $('#' + name + 'Viewer').css('left', settings.video[settings.video.setting][name].left);
    $('#' + name + 'Viewer').css('top', settings.video[settings.video.setting][name].top);
    $('#' + name + 'Viewer').css('width', settings.video[settings.video.setting][name].width);
    $('#' + name + 'Viewer').css('height', settings.video[settings.video.setting][name].height());
    $('#' + name + 'Viewer .viewerOverlay').css('width', settings.video[settings.video.setting][name].width);
    $('#' + name + 'Viewer .viewerOverlay').css('height', settings.video[settings.video.setting][name].height());
    $('#' + name + 'Viewer .frame').css('width', ((parseInt(settings.video[settings.video.setting][name].width.replace('px', '')) - parseInt(settings.video[settings.video.setting][name].height().replace('px', ''))) / 2 + 16) + 'px');
    $('#' + name + 'Viewer .frame').css('height', (parseInt(settings.video[settings.video.setting][name].height().replace('px', '')) / 2) + 'px');
    $('#' + name + 'Viewer .timecode').css('width', settings.video[settings.video.setting][name].width);
    $('#' + name + 'Viewer .timecode').css('top', settings.video[settings.video.setting][name].height());
    // alert(settings.video[settings.video.setting][this.name].width.substr(0, -2));
    $('#' + name + 'Input').css('width', (parseInt(settings.video[settings.video.setting][name].width.replace('px', '')) - 53) + 'px');
    $('#timeline').css('top', (parseInt(settings.video[settings.video.setting].play.height().replace('px', '')) + 44) + 'px');

    if(video.local) {
      $('#' + name + 'Image')
        .width(settings.video[settings.video.setting][name].width.replace('px', ''))
        .height(parseInt(settings.video[settings.video.setting][name].height().replace('px', '')))
        .css('width', settings.video[settings.video.setting][name].width)
        .css('height',settings.video[settings.video.setting][name].height());
    }

}

function updatePlayer() {
    video.stream_width = settings.video[settings.video.setting].w;
    if (typeof player != 'undefined' && typeof player.update != 'undefined') {
        var video_url = video.urls[video.stream_width];
        if (typeof video_url == 'undefined')
          video_url = video.urls['128'];
        player.update(
            video_url,
            settings.video[settings.video.setting].play.width.replace('px', ''),
            settings.video[settings.video.setting].play.height().replace('px', '')
        );
    }
    $('#player').attr({
      width: settings.video[settings.video.setting].play.width.replace('px', ''),
      height: settings.video[settings.video.setting].play.height().replace('px', '')
    });
}

