// <script> // this is here for the syntax highlighter

function outsideV(e, additionalMargin) {
    outsideV.w || (outsideV.w = $(window));
    if (!(e instanceof $))
        e = $(e);
    return e.offset().top + e.height() > outsideV.w.height() - (additionalMargin || 0) - 17;
} // outsideV

function quotedString(s) { return '"'+s.replace(/(['"\\])/g, "\\$1")+'"' }

function ajax(method, data, cb) {
	if (!data)
		data = {};
	data.token = "0.159041169798002";
	return $.post("?mode=section&id=ajax."+method, data, cb||getStdAjaxCB());
}//ajax

function addPagingButton(where) {
    $("<button>Paged list</button>").insertBefore(where || '#files').click(function(){
        $(this).remove();
        pageIt(true);
        delCookie('paged');
    });
}//addPagingButton

function pageIt(anim) {
    var rows = $('#files tr');
    if (!rows.length) return;
    
    page = 0; // this is global
    var pages = $("<div id='pages'>Page </div>").css('visibility','hidden').insertBefore('#files');
    var pageSize = 0;
    while (!outsideV(rows[pageSize], 20))
        if (++pageSize >= rows.length)
            return pages.remove();
    if (pageSize == 0) return; // this happens when the page is not formatted at this exact time, and the table is misplaced 

    Npages = Math.ceil(HFS.number / pageSize);
    if (Npages == 1)
        return pages.remove();
    $('#files').width($('#files').width()); // hold it still

    var s = '';
    for (var i=1; i <= Npages; i++)
        s += '<span>'+i+'</span> ';
    s = $(s);
    s.appendTo(pages).click(function(){
        page = Number(this.innerHTML)-1;
        $('#files tr:gt(0):visible').hide();
        $('#files tr:gt('+(page*pageSize)+'):lt('+pageSize+')').show();
        pages.find('span').removeClass('selectedPage').filter(':nth('+page+')').addClass('selectedPage');
    });
    s.first().addClass('selectedPage');		
    $('#files tr:gt('+((page+1)*pageSize)+')').hide();
    pages.append($("<button type='button'>No pages</button>").click(function(){
        pages.slideUp(function(){ pages.remove(); });
        $('#files tr:hidden').show();
        addPagingButton();
        setCookie('paged', 'no');
    }));
    pages.css({visibility:'', display:'none'});
    if (anim) pages.slideDown()
    else pages.show();		
}//pageIt

function selectionChanged() { $('#selected-counter').text( getSelectedItems().length ) }

function getItemName(el) {
    if (!el)
        return false
    el = $(el)
    let a = el.closest('a')
    if (!a.length)
        a = el.closest('.item').find('.item-link:first')
    // take the url, and ignore any #anchor part
    var s = a.attr('href') || a.attr('value');
    s = s.split('#')[0];
    // remove protocol and hostname
    var i = s.indexOf('://');
    if (i > 0)
        s = s.slice(s.indexOf('/',i+3));
    // current folder is specified. Remove it.
    if (s.indexOf(HFS.folder) == 0)
        s = s.slice(HFS.folder.length);
    // folders have a trailing slash that's not truly part of the name
    if (s.slice(-1) == '/')
        s = s.slice(0,-1);
    // it is encoded
    s = (decodeURIComponent || unescape)(s);
    return s;
} // getItemName

function submit(data, url) {
    var f = $('<form method="post">').attr('action',url||undefined).hide().appendTo('body')
    for (var k in data) {
        const v = data[k]
		if (!Array.isArray(v))
            f.append("<input type='hidden' name='"+k+"' value='"+v+"' />")
		else
		    for (let v2 of v)
            	f.append("<input type='hidden' name='"+k+"' value='"+v2+"' />")
	}
    f.submit()
}//submit

function putMsg(txt, time) {
    if (!time) time = 4000;
    var msgs = $('#msgs');
    msgs.slideDown();
    if (msgs.find('ul li:first').html() == txt)
        clearTimeout(lastTimeoutID);
    else
        msgs.find('ul').prepend("<li>"+txt+"</li>");
    lastTimeoutID = setTimeout("$('#msgs li:last').fadeOut(function(){$(this).detach(); if (!$('#msgs li').length) $('#msgs').slideUp(); });", time);
}//putMsg

RegExp.escape = function(text) {
    if (!arguments.callee.sRE) {
        var specials = '/.*+?|()[]{}\\'.split('');
        arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
    }
    return text.replace(arguments.callee.sRE, '\\$1');
}//escape

function include(url, type) {
    $.ajaxSetup({async: false}); // we'll wait.
    if (!type)
        type = /[^.]+$/.exec(url);
    var res;
    if  (type == 'js')
        res = $.getScript(url);
    else res = $.get(url, function(){ 
        if (type == 'css')
            $('head').append('<link rel="stylesheet" href="'+url+'" type="text/css" />');
    });
    $.ajaxSetup({async: true}); // restore it
    return res.responseText;
}//include

function dialog(content, cb) {
    let ret = $(`<div class="dialog-content">`).html(content).keydown(ev=>{
		if (ev.keyCode===27)
			ret.close()
	})
	ret.close = ()=>{
        ret.closest('.dialog-overlay').remove()
        cb && cb()
    }
    ret.appendTo(
        $('<div class="dialog-overlay">').appendTo('body')
            .click(ret.close)
    ).click(ev=> ev.stopImmediatePropagation())
	return ret
} // dialog

function showMsg(content, cb) {
    let ret = dialog($('<div>').css({ display:'inline-block', textAlign:'left' }).html(content), cb).css('text-align','center')
		.append($('<button class="pure-button">').text(`Ok`).click(ev=> ret.close()).css({ margin:'1em auto 0', display:'block' }))
	return ret
}//showMsg

function showError(msg, cb) {
    return msg && showMsg(msg, cb)
}

/*  cb: function(value, dialog)
	options: type:string(text,textarea,number), value:any, keypress:function
*/
function ask(msg, options, cb) {
    // 2 parameters means "options" is missing
    if (arguments.length == 2) {
        cb = options;
        options = {};
    }
	if (typeof options==='string')
		options = { type:options }
    msg += '<br />';
    var v = options.type
	if (!v)
	    msg += `<br><button class="pure-button">Ok</button>`
	else if (v == 'textarea')
		msg += '<textarea name="txt" cols="30" rows="8">'+options.value+'</textarea><br><button type="submit" class="pure-button">Ok</button>';
	else
		msg += `<input name="txt" type="${v}" value="${options.value||''}" />`
	let ret = dialog($('<form class="ask">')
		//.html($(`<i class="fa fa-times-rectangle close">`).click(ev=>ret.close()))
		.append(msg)
		.submit(ev=> {
			if (false !== cb(options.type ? $.trim(ret.find(':input').val()) : $(ev.target), $(ev.target).closest('form'))) {
                ret.close()
                return false
            }
		})
	)

    ret.find(':input').focus().select() // autofocus attribute seems to work only first time :(

	return ret
}//ask

// this is a factory for ajax request handlers
function getStdAjaxCB(what2do) {
    if (!arguments.length)
        what2do = true;
    return function(res){
        res = $.trim(res);

        if (res !== "ok") {
            showError("Error: "+res)
            if (res === 'bad session')
                location.reload();
            return;
        }
        // what2do is a list of actions we are supposed to do if the ajax result is "ok"
        if (what2do === null)
            return;            
        if (!$.isArray(what2do))
            what2do = [what2do];
        for (let w of what2do) {
            switch (typeof w) {
                case 'function': w(); break; // you specify exactly what to do
                case 'string':
                    switch (w[0]) {
                        case '!': showMsg(w.substr(1)); break;
                        case '>': location = w.substr(1); break;
                        default: putMsg(w); break;
                    }
                case 'boolean': if (w) location.reload(); break;
            }
        }
    }
}//getStdAjaxCB
        
function changePwd() {
    showMsg(`Sorry, you lack permissions for this action`)
}//changePwd

function getSelectedItems() { return $('#files .selector:checked') }

function getSelectedItemsName() {
    return getSelectedItems().get().map(x=> getItemName(x))
}//getSelectedItemsName

function deleteFiles(files) {
	ask(`Are you sure?`, ()=> submit({ action:'delete', selection:files }))
}

function moveFiles(files) {
	ask('Enter the destination folder', 'text', dst=>
		ajax('move', { dst, files:files.join(':') }, res=>{
			var a = res.split(';')
			a.pop()
			if (!a.length)
				return showMsg($.trim(res))
			var failed = 0;
			var ok = 0;
			var msg = '';
			for (let s of a) {
				s = $.trim(s)
				if (!s.length) {
					ok++;
					continue;
				}
				failed++;
				msg += s+'\n';
			}
			if (failed)
				msg = 'We met the following problems:\n'+msg;
			msg = (ok ? ok+' files were moved.\n' : 'No file was moved.\n')+msg;
			showMsg(msg, ()=> ok && location.reload())
		})
	)
}//moveFiles

function selectionMask() {
    ask('Please enter the file mask to select', {type:'text', value:'*'}, function(s){
        if (!s) return
        var re = s.match('^/([^/]+)/([a-zA-Z]*)');
        if (re)
            re = new RegExp(re[1], re[2]);
        else {
            var n = s.match(/^(\\*)/)[0].length;
            s = s.substring(n);
            var invert = !!(n % 2); // a leading "\" will invert the logic
            s = RegExp.escape(s).replace(/[?]/g,".");;
            if (s.match(/\\\*/)) {
                s = s.replace(/\\\*/g,".*");
                s = "^ *"+s+" *$";   // in this case let the user decide exactly how it is placed in the string  
            }
            re = new RegExp(s, "i");
        }
        $("#files .selector")
            .filter((i,e)=> invert ^ re.test(getItemName(e)))
            .prop('checked',true);
        selectionChanged()
    }); 
}//selectionMask

function setCookie(name,value,days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
} // setCookie

function delCookie(name) { setCookie(name,'', -1) }

function getCookie(name) {
	let a = document.cookie.match(new RegExp('(?:^| )' + name + '=([^;]+)'))
	return a && a[1]
} // getCookie

// quando in modalità selezione, viene mostrato una checkbox per ogni item, e viene anche mostrato un pannello per all/none/invert
var multiSelection = false
function toggleSelection() {
    $('#selection-panel').toggle()
	if (multiSelection = !multiSelection)
		$(`<input type='checkbox' class='selector' />`)
			.change(selectionChanged)
			.prependTo('.item-selectable a') // having the checkbox inside the A element will put it on the same line of A even with long A, otherwise A will start on a new line.
	else
		$('#files .selector').remove()
}//toggleSelection

function upload(){
	$(`<input type='file' name="file" multiple>`).change(function(){
		const files = this.files
		if (!files.length) return
		$('#upload-panel').slideDown('fast')
		uploadQ.add(done=> sendFiles(files, done))
	}).click()
}//upload

uploadQ = newQ().on('change', ev=>{
    const n = Math.max(0, ev.count-1) // we don't consider the one we are working
    $('#upload-q').text(n)
})

function newQ(){
    let a = []
	const ret = $({})
    ret.add = job=>{
        a.push(job)
		change()
        if (a.length!==1) return
		job(function consume(){
			a.shift() // trash it
			if (a.length)
				a[0](consume) // next
			else
				ret.trigger('empty')
			change()
		})
    }

    function change(){ ret.trigger({ type:'change', count:a.length }) }

	return ret
}//newQ

function sendFiles(files, done) {
    let formData = new FormData()
    for (let f of files)
        formData.append('file', f)
    $.ajax({
        type: 'POST',
        data: formData,
        success(data) {
            try {
                data = JSON.parse(data)
                for (let r of data) {
                    $('#upload-'+(r.err ? 'ko' : 'ok')).text((i,s)=> Number(s)+1)
                    $(r.err ? `<span title="${r.err}"><i class="fa fa-ban"></i> ${r.name}</span>` : `<a title="Size: ${r.size}&#013;Speed: ${r.speed}B/s" href="${r.url}"><i class="fa fa-${r.err?'ban':'check-circle'}"></i> ${r.name}</a>`)
                        .appendTo('#upload-results')
                }
            }
            catch(e){
                console.error(e)
                showError('Invalid server reply')
            }
        },
        complete: done,
        cache: false,
        contentType: false,
        processData: false,
        xhr(){
            let e = $('#upload-progress')
            let prog = e.find('progress').prop('value',0)
            e.slideDown('fast')
            let xhr = $.ajaxSettings.xhr()
            let last = 0
            let now = 0
            xhr.upload.onprogress = ev=> prog.prop('value', (now = ev.loaded)/ev.total)
            const h = setInterval(()=>{
                $('#progress-text').text(smartSize(now)+'B @ '+smartSize(now-last)+'/s')
                last = now
            },1000)
            xhr.upload.onload = ev=>{
                e.slideUp('fast')
                clearInterval(h)
            }
            return xhr
        }
    })
}//sendFiles

function smartSize(n, options={}) {
	const orders = ['','K','M','G','T','P']
	const order = options.order||1024
	const max = options.maxOrder||orders.length-1
	var i = 0
	while (n >= order && i<max) {
		n /= order
		++i
	}
	if (options.decimals===undefined)
		options.decimals = n<5 ? 1 : 0
	return round(n, options.decimals)
		+orders[i]
}//smartSize

function round(v, digits=0) {
	return !digits ? Math.round(v) : Math.round(v*Math.pow(10,digits)) / Math.pow(10,digits)
}//round

function log(){
	console.log.apply(console,arguments)
	return arguments[arguments.length-1]
}

function toggleTs(){
    const k = 'hideTs'
    $('#files').toggleClass(k)
    setCookie('ts', Number(!$('#files').hasClass(k)));
}

$(function(){
    $('.trash-me').detach(); // this was hiding things for those w/o js capabilities
    if (Number(getCookie('ts')))
        toggleTs()

    $('body').on('click','.item-menu', ev=>{
        const it = $(ev.target).closest('.item')
        const acc = it.hasClass('can-access')
        const name = getItemName(ev.target)
        dialog([
            $('<h3>').text(name),
            it.find('.item-ts').clone(),
            $('<div class="buttons">').html([
                it.closest('.can-delete').length>0 && $('<button class="pure-button"><i class="fa fa-trash"></i> Delete</button>').click(()=> deleteFiles([name])),
                ' ',
                it.closest('.can-rename').length>0 && $('<button class="pure-button"><i class="fa fa-edit"></i> Rename</button>').click(renameItem),
                ' ',
                it.closest('.can-comment').length>0 && $('<button class="pure-button"><i class="fa fa-quote-left"></i> Comment</button>').click(setComment),
                ' ',
                it.closest('.can-move').length>0 && $('<button class="pure-button"><i class="fa fa-truck"></i> Move</button>').click(()=> moveFiles([name])),
            ])
        ]).addClass('item-menu-dialog')

        //<button id='moveBtn' onclick='moveFiles()'>Move</button>


        function setComment() {
            var value = it.find('.comment').html() || ''
            ask(this.innerHTML, {type:'textarea', value}, s=>
                s===value || ajax('comment', { text:s, files:name }))
        }//setComment

        function renameItem(){
            ask(this.innerHTML+' '+name, { type:'text', value:name }, to=>
                ajax("rename", {from:name, to }));
        }

    })

    $('#select-invert').click(ev=> {
        $('#files .selector').prop('checked', (i,v)=> !v)
        selectionChanged()
    })
    $('#select-mask').click(selectionMask)
    $('#move-selection').click(ev=> moveFiles(getSelectedItemsName()))
    $('#delete-selection').click(ev=> deleteFiles(getSelectedItemsName()))

    $('#files .cannot-access .item-link>img').after(`<i class='fa fa-lock' title="No access"></i>`)
    //.filter('.can-delete,.can-access,.can-archive-item')
    $('.item:not(.cannot-access)').filter((i,e)=> $(e).closest('.can-delete,can-archive-item').length).addClass('item-selectable')
    if (! $('.item-selectable').length)
        $('#multiselection').hide()

    $('.additional-panel.closeable').prepend(
        $(`<i class="fa fa-times-circle close">`).click(ev=> $(ev.target).closest('.closeable').fadeOut('fast').trigger('closed')))

    $('#upload-panel').on('closed', ev=>{
        $('#upload-ok,#upload-ko').text('0')
        $('#upload-results').html('')
    })

    /* experiment
    $('.additional-panel.closeable').each((i,e)=>{
        swipable(e, 'right')
    })

    function swipable(e, dir) {
        e = $(e)
        e.mousedown(ev=>{
            e.css('position','relative')
            let o = { x:ev.pageX, y:ev.pageY }
            console.warn(o)
            e.mouseup(ev=>{
                e.css({ left:0, top:0 })
                e.off('mousemove.dragging')
            })
            e.on('mousemove.dragging', ev=> e.css({ left:ev.pageX-o.x, top:ev.pageY-o.y }))
        })
    }
    */

    if (HFS.paged)
        if (getCookie('paged') == 'no')
            addPagingButton('#actions button:first');
        else
            pageIt()

    
    selectionChanged()
});//onload

// TODO: paging