/**
* File upload 
 * @augments EicComponent
 * @category EICUI/Components
* @author Nike
* @version 1.0
*/
class FileUpload extends EicComponent { 

    /**
     * Triggered just before upload of file(s) is launched
     * Return False to prevent theupload to happen
     *
     * @event FileUpload#onBeforeUploadAllStart
     * @type {event}
     */
    onBeforeUploadAllStart() { return(true) }

    /**
     * Triggered when the upload of file(s) is launched
     *
     * @event FileUpload#onUploadAllStart
     * @type {event}
     */
    onUploadAllStart() { }

    /**
     * Triggered when the upload of file(s) is launched
     *
     * @event FileUpload#onUploadAllEnd
     * @type {event}
     */
    onUploadAllEnd() { }

    /**
     * Triggered just before upload of file(s) is launched
     * Return False to prevent theupload to happen
     *
     * @event FileUpload#onBeforeUploadOneStart
     * @type {event}
     * @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onBeforeUploadOneStart(file) { return(true) }

    /**
     * Triggered when the upload of file(s) is launched
     *
     * @event FileUpload#onUploadOneStart
     * @type {event}
     * @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onUploadOneStart(file) { }

    /**
     * Triggered when the upload of file(s) is launched
     *
     * @event FileUpload#onUploadOneEnd
     * @type {event}
     * @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onUploadOneEnd(file) { }

    /**
     * Triggered on attempting to add a file with wrong extension/mime to the list 
     *
     * @event FileUpload#onWrongFileType
     * @type {event}
     * @param {file} fileInfo The offening file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onWrongFileType(file) { console.warn('Wrong file type !', file) }

    /**
     * Triggered when a file is added in the list
     *
     * @event FileUpload#onFileAdded
     * @type {event}
     * @param {integer} fileIdx The file index in this.filesList
     * @param {file} fileInfo The added file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onFileAdded(fileIdx, file) { }

    /**
     * Triggered when a file is removed from the list
     *
     * @event FileUpload#onFileRemoved
     * @type {event}
     * @param {integer} fileIdx The file index in this.filesList
     * @param {file} fileInfo The removed file object (https://developer.mozilla.org/en-US/docs/Web/API/File)
     */
    onFileRemoved(fileIdx, file) { }



    /**
    * @param {element|string} el A DOM element or the selector string for the element which will contain the component
    * @param {object} [options] The grid options
    * @param {string} [options.uploadUrl] Where to POST the files
    * @param {string} [options.browseLabel] Text of the browser button
    * @param {string} [options.submitLabel] Text of the upload button
    * @param {string} [options.dndLabel] Text of the drop area
    * @param {array} [options.allowedMimes] list of mim types accepted 
    * @param {array} [options.allowedExtensions] list of file extenstions accepted (dot excluded)
    * @param {boolean} [options.allowDrop] Enables drag & drop
    * @param {Integer} [options.maxFiles] Set to 1 to disable file list 
    * @param {Integer} [options.minFiles] Upload button will show only if files list greater or equal  
    * @param {object} [options.mimeTypes] Recognized mime types (unknown types show extension as name, and an attachment icon)
    * @param {string} [options.mimetypes[mime].label] type name used in list
    * @param {string} [options.mimetypes[mime].icon] icon used in list
    * @param {string} [options.noUpload] Use when processiong files in browser : no upload to server, no upload/reset buttons
    */
    constructor(el, options) { 
        super(el, options);
        this.options = {
            browseLabel: 'Choose file...',
            submitLabel : 'upload',
            resetLabel: 'clear list',
            noUpload : false,
            dndLabel: 'Drag and drop file here',
            allowedMimes: ['application/pdf'],
            allowedExtensions: ['pdf'],
            allowDrop: true,
            maxSize: 1024*1024*1024,
            maxFiles: 1,
            minFiles: 1,
            uploadMethod: 'POST',
            mimeTypes: {
                'video/x-msvideo':  {label: 'Video' , icon: 'youtube'},
                'video/mp4':        {label: 'Video' , icon: 'youtube'},
                'video/mpeg':       {label: 'Video' , icon: 'youtube'},
                'video/ogg':        {label: 'Video' , icon: 'youtube'},
                
                'application/x-bzip':           {label: 'Compressed file' , icon: 'zip'},
                'application/x-bzip2':          {label: 'Compressed file' , icon: 'zip'},
                'application/gzip':             {label: 'Compressed file' , icon: 'zip'},
                'application/zip':              {label: 'Compressed file' , icon: 'zip'},
                'application/x-7z-compressed':  {label: 'Compressed file' , icon: 'zip'},
                
                'application/msword':                                                       {label: 'Text document' , icon: 'writing'},
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document':  {label: 'Text document' , icon: 'writing'},
                'application/vnd.oasis.opendocument.text':                                  {label: 'Text document' , icon: 'writing'},
                
                'application/vnd.ms-excel':                                          {label: 'Spreadsheet' , icon: 'xls'},
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': {label: 'Spreadsheet' , icon: 'xls'},
                'application/vnd.oasis.opendocument.spreadsheet':                    {label: 'Spreadsheet' , icon: 'xls'},
                'text/csv':                                                          {label: 'Spreadsheet' , icon: 'xls'},
                
                'image/gif':        {label: 'Image' , icon: 'image'},
                'image/jpeg':       {label: 'Image' , icon: 'image'},
                'image/png':        {label: 'Image' , icon: 'image'},
                'image/svg+xml':    {label: 'Image' , icon: 'image'},
                
                'application/pdf':  {label: 'PDF document' , icon: 'pdf'},
            }
        };

        this.setOptions(options);
        this.el.setAttribute('eicfileupload', '');
        this.fileItemTpl = `
            <div class="file-preview-item">
                <div class="file-icon">%{icon}</div>
                <div class="file-name">%{name}</div>
                <div class="file-size">%{size}</div>
                <div class="file-type">%{type}</div>
                <div class="file-actions">%{actions}</div>
            </div>
        `
        this.browseBtn = new Button(null, {
            icon: null,
            severity: 'primary',
            disabled: false,
            badge: false,
            rounded: false,
            size: 'small',
            hint: null,
            label: this.options.browseLabel
        })
        this.browseBtn.click = (e) => { 
                e.stopPropagation(); e.preventDefault(); 
                this.fileBtn.click()
        }

        if(!this.options.noUpload){
            this.uploadBtn = new Button(null, {
                icon: null,
                severity: 'primary',
                disabled: false,
                badge: false,
                rounded: false,
                size: null,
                hint: null,
                label: this.options.submitLabel,
                onclick: this.uploadAllFiles.bind(this)
            })
            this.uploadBtn.el.classList = 'uploadBtn custom-class'

            this.resetBtn = new Button(null, {
                icon: null,
                severity: '',
                disabled: false,
                badge: false,
                rounded: false,
                size: null,
                hint: null,
                label: this.options.resetLabel
            })
            this.resetBtn.click = this.clearAllFiles.bind(this)           
        }

        this.el.appendChild(ui.create(`
            <div class="file-upload">
                <div class="file-drop-area">
                    <span class="file-msg">${options.allowDrop ? this.options.dndLabel : ''}</span>
                    <input type="file" name="file" class="file-input" aria-label="Choose file ${options.allowDrop ? ' or '+ this.options.dndLabel : ''}">
                </div>
                <div class="file-preview-area"></div>
                <div class="buttons-area cols-2"></div>
            </div>
        `))

        this.fileBtn = this.el.querySelector('.file-input')
        this.fileBtn.setAttribute('accept', [...this.options.allowedMimes, ...this.options.allowedExtensions.map(item => '.'+item)].join(', '))
        if(this.options.maxFiles>1) this.fileBtn.setAttribute('multiple', '')

        this.dropArea = this.el.querySelector('.file-drop-area')
        this.dropAreaText = this.el.querySelector('.file-drop-area .file-msg')
        this.dropArea.prepend(this.browseBtn.el)
        this.previewArea = this.el.querySelector('.file-preview-area')
        this.buttonsArea = this.el.querySelector('.buttons-area')

        if(!this.options.noUpload){
            this.buttonsArea.append(this.uploadBtn.el)
            this.buttonsArea.append(this.resetBtn.el)
        }

        this.filesList = []
        this.fileBtn.addEventListener("change", (e) => {
            if((!e.target.files) || (e.target.files.length==0)) return
            if((this.filesList.length + e.target.files.length) > this.options.maxFiles) return
            this.addFiles(e.target.files)
        }) 
        if(options.allowDrop) {
            this.initDnd()
            this.enableDnd()
        } else this.disableDnd()

        this.refreshFilesList() //List still empty but that hides buttons depending on options
    }

    initDnd() {        
        window.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); })
        window.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); })
        this.dropArea.addEventListener('dragenter', ((e) => { 
            e.stopPropagation(); e.preventDefault();
            if(this.dndDisabled) return
            this.dropArea.classList.add('dragover')
        }).bind(this));
        this.dropArea.addEventListener('dragleave', ((e) =>{ 
            e.stopPropagation(); e.preventDefault();
            let elBelow = document.elementFromPoint(e.clientX, e.clientY) 
            if( (elBelow.classList.contains('file-drop-area')) 
             || (elBelow.classList.contains('file-msg')) 
             || (elBelow.classList.contains('file-input'))
            ) return //leaving towards the child, so still over it           
            if(this.dndDisabled) return
            this.dropArea.classList.remove('dragover')
        }).bind(this))
        this.dropArea.addEventListener('drop', ((e) => {
            e.stopPropagation(); e.preventDefault();
            if(this.dndDisabled) return
            this.dropArea.classList.remove('dragover')     
            if((!e.dataTransfer) || (!e.dataTransfer.files) || (e.dataTransfer.files.length==0)) return
            if((this.filesList.length + e.dataTransfer.files.length) > this.options.maxFiles) return
            this.addFiles(e.dataTransfer.files)
        }).bind(this));
    }

    disableDnd() {
        this.dndDisabled = true
        this.dropAreaText.classList.add('disabled')
    }

    enableDnd() {
        this.dndDisabled = false
        this.dropAreaText.classList.remove('disabled')
    }

    addFiles(files) {
        for(let file of files) {
            if((file.type!='') && this.checkFileType(file) && (file.size <= this.options.maxSize)) {
                this.filesList.push(file)
                this.onFileAdded(this.filesList.length-1, file)
                this.refreshFilesList()
            } else {                
                this.onWrongFileType(file)
            }
        }
    }

    removeFile(e) {
        e.stopPropagation(); e.preventDefault(); 
        let el = (e.target.tagName.toLocaleLowerCase()=='button') ? e.target : e.target.parentElement
        let fileIdx = el.dataset['id']
        let [file, ] = this.filesList.splice(fileIdx,1)
        this.refreshFilesList()
        this.onFileRemoved(fileIdx, file)
    }

    checkFileType(file) {
        let ext = file.name.substring(file.name.lastIndexOf('.')+1)
        return((this.options.allowedMimes.indexOf(file.type)>-1) && (this.options.allowedExtensions.indexOf(ext)>-1))
    }

    refreshFilesList() {
        let html = ''; let itemLine, file, ext, icon, type;
        for(let fileIdx in this.filesList) {
            file = this.filesList[fileIdx]
            ext = file.name.substring(file.name.lastIndexOf('.')+1)
            if(this.options.mimeTypes.hasOwnProperty(file.type)) {
                icon = this.options.mimeTypes[file.type].icon
                type = this.options.mimeTypes[file.type].label
            } else {
                icon = 'attachment'
                type = '".'+ext+'"'
            }
            icon = (this.options.mimeTypes.hasOwnProperty(file.type)) ? this.options.mimeTypes[file.type].icon : 'attachment'
            itemLine = this.fileItemTpl
            itemLine = itemLine.replace('%{icon}', `<i class="icon-${icon}"></i>`)
            itemLine = itemLine.replace('%{size}', this.formatSize(file.size))
            itemLine = itemLine.replace('%{type}', type) 
            itemLine = itemLine.replace('%{actions}', `
                <button eicbutton class="file-remove" data-id="${fileIdx}" basic danger xxsmall title="remove" aria-enabled="true" aria-label="remove">
                    remove
                </button>
            `)
            itemLine = itemLine.replace('%{name}', file.name) // Do this one last in case some %{xxx} in the name
            html += itemLine
        }
        this.previewArea.innerHTML = html // Caution to myself: Re-creating all avoids multi-click-events
        this.previewArea.querySelectorAll('.file-remove').forEach((el) => { 
            el.addEventListener('click', this.removeFile.bind(this))
        })

        if(!this.options.noUpload){
            if(this.filesList.length >= this.options.minFiles) this.uploadBtn.disabled = false
            else this.uploadBtn.disabled = true
        }

        if(this.filesList.length < this.options.maxFiles) {
            this.browseBtn.disabled = false
            this.enableDnd()
        } else { 
            this.browseBtn.disabled = true
            this.disableDnd()
        }

        if(!this.options.noUpload){
            if(this.filesList.length > 0) this.resetBtn.disabled = false
            else this.resetBtn.disabled = true
        }

    }

    formatSize(size) {
        if (!+size) return '0 Bytes'
        const k = 1024
        const units = ['Bytes', 'KB', 'MB', 'GB'] // Over GB, really ?
        const i = Math.floor(Math.log(size) / Math.log(k))
        return `${parseFloat((size / Math.pow(k, i)).toFixed(2))} ${units[i]}`
    }

    clearAllFiles(e) {
        e.stopPropagation(); e.preventDefault();
        this.filesList = []
        this.refreshFilesList()
        if(this.options.allowDrop) this.enableDnd()
    }

    uploadAllFiles(e) {
        e.stopPropagation(); e.preventDefault(); 
        if(!this.onBeforeUploadAllStart()) return
        this.browseBtn.disabled = true
        this.disableDnd()
        this.uploadBtn.loading = true
        this.resetBtn.disabled = true
        for(let fileIdx in this.filesList) {
            this.filesList[fileIdx].completed = false
            this.uploadFile(fileIdx)
        }
        this.onUploadAllStart()
    }

    async uploadFile(fileIdx) {
        let file = this.filesList[fileIdx]
        let actionEl = this.previewArea.querySelector('.file-remove[data-id="'+fileIdx+'"]').parentElement

         // Check file type --> if img add extension
        let ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
        if (['jpeg', 'png', 'gif', 'jpg', 'svg'].includes(ext)) {
            this.options.uploadUrl += '.' + ext; // Add extension
        }

        let ok = await this.onBeforeUploadOneStart(file)
        if((!ok) || (!this.options.uploadUrl)) {
            this.filesList[fileIdx].completed = true
            actionEl.innerHTML = '<span class="err">Failed !</span>'
            this.uploadOneEnd(fileIdx)
            return
        }
        actionEl.innerHTML = '(0 %)'
        let xhrRequest = new XMLHttpRequest();
        xhrRequest.open(this.options.uploadMethod, this.options.uploadUrl);
        // Caution: This fucking AWS shit uses Content-Type as mime-type for the file, contrary to the rest of the web. 
        // Yeah, I know multi-files...multi-part etc...but AWS signed URL system is one file at a time anyway.
        xhrRequest.setRequestHeader('Content-Type', this.filesList[fileIdx].type);
        xhrRequest.upload.addEventListener("progress", this.uploadProgress.bind(this, fileIdx, actionEl));
        xhrRequest.upload.addEventListener("error", ((err) => {
            this.filesList[fileIdx].completed = true
            actionEl.innerHTML = '<span class="err">Failed !</span>'
            this.uploadOneEnd(fileIdx)
        }).bind(this))
        xhrRequest.upload.addEventListener("timeout", ((err) => {
            this.filesList[fileIdx].completed = true
            actionEl.innerHTML = '<span class="err">Timed-out !</span>'
            this.uploadOneEnd(fileIdx)
        }).bind(this))        
        xhrRequest.send(file);
        this.onUploadOneStart()
    }
    
    uploadProgress(fileIdx, actionEl, e) {
        if(!e.lengthComputable) return
        let percentDone = Math.floor((e.loaded / e.total) * 100);
        actionEl.innerHTML = `(${percentDone} %)`
        if(e.loaded == e.total) {
            this.filesList[fileIdx].completed = true
            this.uploadOneEnd(fileIdx)
        }
    }

    uploadOneEnd(fileIdx) {
        this.onUploadOneEnd(this.filesList[fileIdx])
        let allFinished = this.filesList.reduce((acc, x)=> (x.completed && acc), true)
        if(allFinished) {
            this.uploadBtn.loading = false
            this.uploadBtn.disabled = true
            this.resetBtn.disabled = false
            this.onUploadAllEnd()
        }
    }
    
}
