// Cache frequently-used selectors and data table
const $tb = $('#s3objects-table');
const $bc = $('#breadcrumb');
const $bl = $('#bucket-loader');

// Debug utility to complement console.log
const DEBUG = (() => {
    const timestamp = () => {};
    timestamp.toString = () => `[DEBUG ${moment().format()}]`;

    return {
        log: console.log.bind(console, '%s', timestamp),
    };
})();

function validateEmail(email) {
  const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test(email);
}

// Utility to convert bytes to readable text e.g. "2 KB" or "5 MB"
function bytesToSize(bytes) {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    if (bytes === 0) return '0 Bytes';
    const ii = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
    return `${Math.round(bytes / (1024 ** ii), 2)} ${sizes[ii]}`;
}

// Escape strings of HTML
function htmlEscape(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/\//g, '&#x2F;')
        .replace(/`/g, '&#x60;')
        .replace(/=/g, '&#x3D;');
}

function colonEscape(id) {
   return id.replace(/:/,"\\:");
}

// Convert cars/vw/golf.png to golf.png
function fullpath2filename(path, escape = false) {
    const rc = path.replace(/^.*[\\/]/, '');
    return escape ? htmlEscape(rc) : rc;
}

// Convert cars/vw/golf.png to cars/vw/
function fullpath2pathname(path, escape = false) {
    const index = path.lastIndexOf('/');
    const rc = (index === -1) ? '/' : path.substring(0, index + 1);
    return escape ? htmlEscape(rc) : rc;
}

function isfolder(path) {
    return path.endsWith('/');
}

function stripLeadTrailSlash(s) {
    return s.replace(/^\/+/g, '').replace(/\/+$/g, '');
}

//
// Shared service that all controllers can use
//
function SharedService($rootScope) {
    DEBUG.log('SharedService init');

    const shared = {
        settings: null
    };

    shared.getSettings = () => this.settings;

    shared.addFiles = (files) => { this.added_files = files; };

    shared.getAddedFiles = () => this.added_files;

    shared.hasAddedFiles = () => Object.prototype.hasOwnProperty.call(this, 'added_files');

    shared.resetAddedFiles = () => { delete this.added_files; };

    shared.changeSettings = (settings) => {
		
		
		DEBUG.log('SharedService::changeSettings');
        DEBUG.log('SharedService::changeSettings settings', settings);

        this.settings = settings;
        $.fn.dataTableExt.afnFiltering.length = 0;
		$rootScope.$broadcast('broadcastChangeSettings', { settings });
    };

    shared.viewRefresh = () => $rootScope.$broadcast('broadcastViewRefresh');

    shared.trashObjects = (keys) => $rootScope.$broadcast('broadcastTrashObjects', { keys });
	
	shared.sendObjects = (keys) => $rootScope.$broadcast('broadcastSendObjects', { keys });
	
	shared.emailObjects = (keys) => $rootScope.$broadcast('broadcastEmailObjects', { keys });

    // Common error handling is done here in the shared service.
    shared.showError = (params, err) => {
        DEBUG.log(err);
        const { message, code } = err;
        const errors = Object.entries(err).map(([key, value]) => ({ key, value }));
        const args = {
            params, message, code, errors,
        };
        $rootScope.$broadcast('broadcastError', args);
    };

    return shared;
}


//
// ViewController: code associated with the main S3 Explorer table that shows
// the contents of the current bucket/folder and allows the user to downloads
// files, delete files, and do various other S3 functions.
//
// eslint-disable-next-line no-shadow
function ViewController($scope, SharedService) {
    DEBUG.log('ViewController init');
    window.viewScope = $scope; // for debugging
    $scope.view = {
        settings: null, objectCount: 0, keys_selected: [],
    };
    $scope.stop = false;

    // Delegated event handler for S3 object/folder clicks. This is delegated
    // because the object/folder rows are added dynamically and we do not want
    // to have to assign click handlers to each and every row.
    $tb.on('click', 'a', (e) => {
        const { currentTarget: target } = e;
        e.preventDefault();
        DEBUG.log('target href:', target.href);
        DEBUG.log('target dataset:', JSON.stringify(target.dataset));

       let time = 1;
	   
		getSignedURL(time,target.dataset.s3key).then(signedURL=>{
			if(target.dataset.s3key.toLowerCase().includes('.vim')){
				window.open('https://www.veesus.com/vim/vim.html?url='+encodeURIComponent(signedURL), '_blank');
			}else{
				window.open('https://www.veesus.com/viewer.html?url='+encodeURIComponent(signedURL), '_blank');
			}
		});
		
		
        return false;
    });

    // Delegated event handler for breadcrumb clicks.
    $bc.on('click', 'a', (e) => {
        DEBUG.log('breadcrumb li click');
		e.preventDefault();
        const { currentTarget: target } = e;
        DEBUG.log('target dataset:', JSON.stringify(target.dataset));
        return false;
    });

    $scope.$on('broadcastChangeSettings', (e, args) => {
        DEBUG.log('ViewController', 'broadcast change settings:', args.settings);
        $scope.view.objectCount = 0;
        $scope.view.settings = args.settings;
        $scope.refresh();
    });

    $scope.$on('broadcastViewRefresh', () => {
        DEBUG.log('ViewController', 'broadcast view refresh');
        $scope.$apply(() => {
            $scope.refresh();
        });
    });

    $scope.renderObject = (data, _type, full) => {
        // DEBUG.log('renderObject:', JSON.stringify(full));
        
        function buildAnchor(s3key, text) {
            const a = $('<a>');
            a.attr({ 'data-s3key': s3key });
            a.text(text);
            return a.prop('outerHTML');
        }

        return buildAnchor(data, fullpath2filename(data));
    };

    $scope.progresscb = (objects, folders) => {
        DEBUG.log('ViewController', 'Progress cb objects:', objects);
        $scope.$apply(() => {
            $scope.view.objectCount += objects;
        });
    };

    $scope.refresh = () => {
        DEBUG.log('refresh');
        if ($scope.running()) {
            DEBUG.log('running, stop');
            $scope.listobjectsstop();
        } else {
            DEBUG.log('refresh', $scope.view.settings);
            $scope.view.objectCount = 0;
            $scope.listobjects(
                $scope.view.settings.delimiter,
            );
        }
    };

    $scope.upload = () => {
		DEBUG.log('Add files');
        $('#addedFiles').trigger('click');
    };

    $scope.trash = () => {
        DEBUG.log('Trash:', $scope.view.keys_selected);
        if ($scope.view.keys_selected.length > 0) {
            SharedService.trashObjects($scope.view.keys_selected);
        }
    };
	
	$scope.send = () => {
        DEBUG.log('Send:', $scope.view.keys_selected);
        if ($scope.view.keys_selected.length > 0) {
            SharedService.sendObjects($scope.view.keys_selected);
        }
    };
	
	$scope.email = () => {
        DEBUG.log('Email:', $scope.view.keys_selected);
        if ($scope.view.keys_selected.length > 0) {
            SharedService.emailObjects($scope.view.keys_selected);
        }
    };

    $scope.running = () => $bl.hasClass('fa-spin');

   
    $scope.listobjectsstop = (stop) => {
        DEBUG.log('ViewController', 'listobjectsstop:', stop || true);
        $scope.stop = stop || true;
    };

    // This is the listObjects callback
    $scope.listobjectscb = (err, data) => {
        DEBUG.log('Enter listobjectscb');
        if (err) {
            DEBUG.log('Error:', JSON.stringify(err));
            DEBUG.log('Error:', err.stack);
            $bl.removeClass('fa-spin');
            const params = {};
            SharedService.showError(params, err);
        } else {
            let marker;

            // Store marker before filtering data. Note that Marker is the
            // previous request marker, not the marker to use on the next call
            // to listObject. For the one to use on the next invocation you
            // need to use NextMarker or retrieve the key of the last item.
            if (data.IsTruncated) {
                if (data.NextMarker) {
                    marker = data.NextMarker;
                } else if (data.Contents.length > 0) {
                    marker = data.Contents[data.Contents.length - 1].Key;
                }
            }

            let count = 0;

            // NOTE: folders are returned in CommonPrefixes if delimiter is
            // supplied on the listObjects call and in Contents if delimiter
            // is not supplied on the listObjects call, so we may need to
            // source our DataTable folders from Contents or CommonPrefixes.
            // DEBUG.log("Contents", data.Contents);
            $.each(data.Contents, (index, value) => {
                    $tb.DataTable().row.add(value);
                    count++;
            });

            // Re-draw the table
            $tb.DataTable().draw();

            // Make progress callback to report objects read so far
            $scope.progresscb(count);

           DEBUG.log('Bucket', data.Name, 'listing complete');
                $bl.removeClass('fa-spin');
        }
    };

    // Start the spinner, clear the table, make an S3 listObjects request
    $scope.listobjects = (Delimiter, Marker) => {
        DEBUG.log('Enter listobjects');

        // If this is the initial listObjects
        if (!Marker) {
            // Checked on each event cycle to stop list prematurely
            $scope.stop = false;

            // Start spinner and clear table
            $scope.view.keys_selected = [];
            $bl.addClass('fa-spin');
            $tb.DataTable().clear();
        }

		getFileList().then(function(result){
			$scope.listobjectscb(null,result);	
			//syncDatabase();
		});
		
    };

    this.isfolder = path => path.endsWith('/');

    // Individual render functions so that we can control how column data appears
    this.renderSelect = (data, type, _full) => {
        if (type === 'display') {
            return '<span class="text-center"><input type="checkbox"></span>';
        }

        return '';
    };

    this.renderObject = (data, type, full) => {
        if (type === 'display') {
            return $scope.renderObject(data, type, full);
        }

        return data;
    };

    this.renderFolder = (data, type, full) => $scope.renderFolder(data, type, full);

    this.renderLastModified = (data, _type, _full) => {
        if (data) {
            return moment(data).fromNow();
        }

        return '';
    };

    this.renderTimestamp = (data, _type, _full) => {
        if (data) {
            return moment(data).local().format('YYYY-MM-DD HH:mm:ss');
        }

        return '';
    };

   

    // Object sizes are displayed in nicer format e.g. 1.2 MB but are otherwise
    // handled as simple number of bytes e.g. for sorting purposes
    this.dataSize = (source, type, _val) => {
        if (source.Size) {
            return (type === 'display') ? bytesToSize(source.Size) : source.Size;
        }

        return '';
    };

    // Initial DataTable settings (must only do this one time)
    $tb.DataTable({
        iDisplayLength: 25,
        order: [[2, 'asc'], [1, 'asc']],
        aoColumnDefs: [
            {
                aTargets: [0], mData: null, mRender: this.renderSelect, sClass: 'text-center', sWidth: '20px', bSortable: false,
            },
            {
                aTargets: [1], mData: 'Key', mRender: this.renderObject, sType: 'key',
            },
            {
                aTargets: [2], mData: 'LastModified', mRender: this.renderTimestamp,
            },
            {
                aTargets: [3], mData: this.dataSize,
            },
        ],
    });

    // Custom ascending sort for Key column so folders appear before objects
    $.fn.dataTableExt.oSort['key-asc'] = (a, b) => {
        const x = (isfolder(a) ? `0-${a}` : `1-${a}`).toLowerCase();
        const y = (isfolder(b) ? `0-${b}` : `1-${b}`).toLowerCase();
        if (x < y) return -1;
        if (x > y) return 1;
        return 0;
    };

    // Custom descending sort for Key column so folders appear before objects
    $.fn.dataTableExt.oSort['key-desc'] = (a, b) => {
        const x = (isfolder(a) ? `1-${a}` : `0-${a}`).toLowerCase();
        const y = (isfolder(b) ? `1-${b}` : `0-${b}`).toLowerCase();
        if (x < y) return 1;
        if (x > y) return -1;
        return 0;
    };

    // Handle click on selection checkbox
    $('#s3objects-table tbody').on('click', 'input[type="checkbox"]', (e1) => {
        const checkbox = e1.currentTarget;
        const $row = $(checkbox).closest('tr');
        const data = $tb.DataTable().row($row).data();
        let index = -1;

        // Prevent click event from propagating to parent
        e1.stopPropagation();

        // Find matching key in currently checked rows
        index = $scope.view.keys_selected.findIndex(e2 => e2.Key === data.Key);

        // Remove or add checked row as appropriate
        if (checkbox.checked && index === -1) {
            $scope.view.keys_selected.push(data);
        } else if (!checkbox.checked && index !== -1) {
            $scope.view.keys_selected.splice(index, 1);
        }

        $scope.$apply(() => {
            // Doing this to force Angular to update models
            DEBUG.log('Selected rows:', $scope.view.keys_selected);
        });

        if (checkbox.checked) {
            $row.addClass('selected');
        } else {
            $row.removeClass('selected');
        }
    });

    // Handle click on table cells
    //$('#s3objects-table tbody').on('click', 'td', (e) => {
      //  $(e.currentTarget).parent().find('input[type="checkbox"]').trigger('click');
    //});
}



//
// InfoController: code associated with the Info modal where the user can
// view bucket policies, CORS configuration and About text.
//
// Note: do not be tempted to correct the eslint no-unused-vars error
// with SharedService below. Doing so will break injection.
//
// eslint-disable-next-line no-shadow
function InfoController($scope) {
    DEBUG.log('InfoController init');
    window.infoScope = $scope; // for debugging
    $scope.info = {
        cors: null, policy: null, settings: null,
    };

    $scope.$on('broadcastChangeSettings', (e, args) => {
        $scope.info.settings = args.settings;
    });
}

//
// DataController: code associated with the Settings dialog where the
// user provides credentials and bucket information.
//
// eslint-disable-next-line no-shadow
function DataController($scope, SharedService) {
	
    DEBUG.log('DataController init');
    window.settingsScope = $scope; // for debugging
	
	$scope.update = () => {
        DEBUG.log('Data updated');
        $('#DataModal').modal('hide');
	};
	
	$scope.datarefresh = async() =>{
		//await syncDatabase();
		//updateGUICharts();
		//DEBUG.log('Data show');
	};
}



//
// SettingsController: code associated with the Settings dialog where the
// user provides credentials and bucket information.
//
// eslint-disable-next-line no-shadow
function SettingsController($scope, SharedService) {
	
    DEBUG.log('SettingsController init');
    window.settingsScope = $scope; // for debugging
	
	// Initialized for an unauthenticated user exploring the current bucket
    // TODO: calculate current bucket and initialize below
    $scope.settings = {
       view: 'folder', delimiter: '/',
    };
	
    // TODO: at present the Settings dialog closes after credentials have been supplied
    // even if the subsequent AWS calls fail with networking or permissions errors. It
    // would be better for the Settings dialog to synchronously make the necessary API
    // calls and ensure they succeed before closing the modal dialog.
    $scope.update = () => {
        DEBUG.log('Settings updated');
        $('#SettingsModal').modal('hide');
       
        SharedService.changeSettings($scope.settings);
    };
}

//
// UploadController: code associated with the Upload dialog where the
// user reviews the list of dropped files and request upload to S3.
//
// eslint-disable-next-line no-shadow
function UploadController($scope, SharedService) {
    DEBUG.log('UploadController init');
	
	window.uploadScope = $scope; // for debugging
    $scope.upload = {
		button: null, title: null, files: [], uploads: [],
    };

    // Cache jquery selectors
    const $btnUpload = $('#upload-btn-upload');
    const $btnCancel = $('#upload-btn-cancel');

    // Add new click handler for Cancel button
    $btnCancel.click((e2) => {
        e2.preventDefault();

        // If uploads still in progress then cancel them all
        if ($scope.upload.uploads && $scope.upload.uploads.length > 0) {
            console.log(`Cancel ${$scope.upload.uploads.length} uploads`);
            $scope.upload.uploads.forEach(upl => upl.abort());
            $scope.upload.uploads = [];
        } else {
            console.log('Close upload modal');
            $('#UploadModal').modal('hide');
			$scope.upload.uploads = [];
			SharedService.viewRefresh();
        }
    });

    //
    // Upload a list of local files to the provided bucket and prefix
    //
    $scope.uploadFiles = () => {
        $scope.$apply(() => {
            $scope.upload.uploads = [];
            $scope.upload.uploading = true;
        });
		
		let count = $scope.upload.files.length;
		
		DEBUG.log('Dropped files:', $scope.upload.files);
			
			$scope.upload.files.forEach((file, ii) => {
				DEBUG.log('File:', file);
				DEBUG.log('Index:', ii);

				$(`#upload-td-${ii}`).html(
					`<div class="progress"><span id="upload-td-progress-${ii}" class="progress-bar" data-percent="0">0%</span></div>`,
				);
				
				let uploadmap = new Map();
				let total = file.file.size;
				const totaltxt = bytesToSize(total);
				
				let onprogress = function (evt) {
					let subtotal = 0;
					
					uploadmap.forEach(function(value,xhr){
						subtotal+=value;
					});
					
					const pc = total ? ((subtotal * 100.0) / total) : 0;
					const pct = Math.round(pc);
					const pcts = `${pct}%`;
					const col = $(`#upload-td-progress-${ii}`);
					
					const uptxt = bytesToSize(subtotal);
					
					col.attr('data-percent', pct);
					col.css('width', pcts).text(pcts);
					$(`#uploaded-td-${ii}`).html(uptxt+ ' / ' +totaltxt);
					
				};
				
				let onend = function (e,xhr) {
					--count;
					
					console.log("end");
					
					DEBUG.log('Uploaded', file.file.name);
					$(`#upload-td-progress-${ii}`).addClass('progress-bar-success');
					
					// If all files complete then update buttons and refresh view
					if (count === 0) {
						$btnUpload.hide();
						$btnCancel.text('Close');
						$scope.upload.uploads = [];
					}
					
					//Update file numbers
					syncDatabase().then(	
						function(value){
							updateGUICharts();			
						});
				}
				
				let onabort = function(){
					const col = $(`#upload-td-progress-${ii}`);
					col.attr('data-percent', 100);
					col.css('width', '100%').text('Cancelled');
					col.addClass('progress-bar-danger');
					
					$btnUpload.hide();
					$btnCancel.text('Close');
					$scope.upload.uploads = [];
					
					//Update file numbers
					syncDatabase().then(	
						function(value){
							updateGUICharts();			
						});
				}
				
				//If it is a small file do form POST upload
				if(file.file.size<=5*1024*1024){
					getUploadURL(file.file).then(function(data){
						if(data){
							//let form = new FormData();
							//Object.keys(data.fields).forEach(key => form.append(key,data.fields[key]));
							//form.append('file',file.file);
							
							let xhr = new XMLHttpRequest();
					
							xhr.upload.onprogress = function(evt){
								uploadmap.set(xhr,evt.loaded);
								onprogress(evt);
							};
					
							xhr.onloadend = onend;
					
							xhr.onabort = onabort;
							
							xhr.open("PUT", data);
							xhr.send(file.file);
					
							$scope.upload.uploads.push(xhr);
							uploadmap.set(xhr,0);
						}
					});
				}
				else{
					uploadLargeFile(file.file,onprogress,onabort,onend,$scope.upload.uploads,uploadmap);
				}
			});
    };

    // Wrap readEntries in a promise to make working with readEntries easier
    async function readEntriesPromise(directoryReader) {
        try {
            return new Promise((resolve, reject) => {
                directoryReader.readEntries(resolve, reject);
            });
        } catch (err) {
            DEBUG.log(err);
            return undefined;
        }
    }

    // Get all the entries (files or sub-directories) in a directory
    // by calling readEntries until it returns empty array
    async function readAllDirectoryEntries(directoryReader) {
        const entries = [];
        let readEntries = await readEntriesPromise(directoryReader);
        while (readEntries.length > 0) {
            entries.push(...readEntries);
            // eslint-disable-next-line no-await-in-loop
            readEntries = await readEntriesPromise(directoryReader);
        }
        return entries;
    }

    // Retrieve File object from FileEntry
    async function filePromise(fileEntry) {
        try {
            return new Promise((resolve, reject) => {
                fileEntry.file(resolve, reject);
            });
        } catch (err) {
            DEBUG.log(err);
            return undefined;
        }
    }

    // Get all files recursively
    async function getAllFileEntries(dataTransferItemList) {
        const fileEntries = [];
        const queue = [];
        for (let i = 0; i < dataTransferItemList.length; i++) {
            const dtItem = dataTransferItemList[i];
            queue.push(typeof dtItem.webkitGetAsEntry === 'function' ? dtItem.webkitGetAsEntry() : dtItem.getAsEntry());
        }
        while (queue.length > 0) {
            const entry = queue.shift();
            if (entry) {
                if (entry.isFile) {
                    // eslint-disable-next-line no-await-in-loop
                    const file = await filePromise(entry);
                    file.fullPath = entry.fullPath.substring(1);
                    fileEntries.push(file);
                } else if (entry.isDirectory) {
                    const reader = entry.createReader();
                    // eslint-disable-next-line no-await-in-loop
                    queue.push(...await readAllDirectoryEntries(reader));
                }
            }
        }
        return fileEntries;
    }

    // Wrapper to get files safely
    async function getFilesList(dataTransfer) {
        if (dataTransfer.items.length > 0) {
            if (typeof dataTransfer.items[0].webkitGetAsEntry === 'function' ||
                typeof dataTransfer.items[0].getAsEntry === 'function') {
                return getAllFileEntries(dataTransfer.items);
            }

            DEBUG.log('Cannot do folders upload, falling back to files only');
            return dataTransfer.files;
        }

        return [];
    }

    //
    // Drag/drop handler for files to be uploaded
    //
    $scope.dropZone = (target) => {
        target
            .on('dragover', () => {
                target.addClass('dragover');
                return false;
            })
            .on('dragend dragleave', () => {
                target.removeClass('dragover');
                return false;
            })
            .on('drop', async (e) => {
                DEBUG.log('Dropped files');
				
				e.stopPropagation();
                e.preventDefault();

                target.removeClass('dragover');

                $bl.addClass('fa-spin');
                const files = SharedService.hasAddedFiles()
                    ? SharedService.getAddedFiles()
                    : await getFilesList(e.originalEvent.dataTransfer);
                $bl.removeClass('fa-spin');

                if (!files.length) {
                    DEBUG.log('Nothing to upload');
                    return false;
                }
				
				var storageUsed = STORAGE_USED;
				var storageLimit = STORAGE_LIMIT;
			
				$scope.$apply(() => {
                    $scope.upload.files = [];
                    for (let ii = 0; ii < files.length; ii++) {
                        const fileii = files[ii];
						storageUsed+=1;
                        // See https://github.com/awslabs/aws-js-s3-explorer/issues/71
                        if (fileii.type || fileii.size % 4096 !== 0 || fileii.size > 1048576) {
                            DEBUG.log('File:', fileii.name, 'Size:', fileii.size, 'Type:');
							$scope.upload.files.push({
                                file: fileii,
                                name: fileii.name,
                                size: bytesToSize(fileii.size),
                            });
                        }
                    }
                });

                
                

                // Remove any prior click handler from Upload button
                $btnUpload.unbind('click');

                // Add new click handler for Upload button
                $btnUpload.click((e2) => {
                    e2.preventDefault();
                    $scope.uploadFiles();
                });

                // Reset buttons for initial use
                $btnUpload.show();
                $btnCancel.text('Cancel');
				
				if(storageUsed>storageLimit){
					$btnUpload.unbind('click');
					$btnUpload.text('Storage Full');
				}else{
					$btnUpload.text('Upload ('+files.length+')');
				}

                // Bind file count into button
                $btnUpload.attr('data-filecount', files.length);
                $scope.$apply(() => {
                    $scope.upload.title = `Upload`;
                    $scope.upload.button = `Upload (${files.length})`;
                    $scope.upload.uploading = false;
                });

                // Reset files selector
                if (SharedService.hasAddedFiles()) {
                    SharedService.resetAddedFiles();
                    $('#addedFiles').val('');
                }

                // Launch the uploader modal
                $('#UploadModal').modal({ keyboard: true, backdrop: 'static' });

                return false;
            });
    };

    // Enable dropzone behavior and highlighting
    $scope.dropZone($('.dropzone'));

    // Simulate drop event on change of files selector
    $('#addedFiles').on('change', (e) => {
        SharedService.addFiles(e.target.files);
        $('.dropzone').trigger('drop');
    });
}

//
// ErrorController: code associated with displaying runtime errors.
//
function ErrorController($scope) {
    DEBUG.log('ErrorController init');
    window.errorScope = $scope; // for debugging
    $scope.error = {
        errors: [], message: '',
    };

    $scope.$on('broadcastError', (e, args) => {
        DEBUG.log('ErrorController', 'broadcast error', args);

        $scope.$apply(() => {
            Object.assign($scope.error, args);
            DEBUG.log('scope errors', $scope.error.errors);
        });

        // Launch the error modal
        $('#ErrorModal').modal({ keyboard: true, backdrop: 'static' });
    });
}

//
// TrashController: code associated with the Trash modal where the user can
// delete objects.
//
// eslint-disable-next-line no-shadow
function TrashController($scope, SharedService) {
    DEBUG.log('TrashController init');
    window.trashScope = $scope; // for debugging
    $scope.trash = { title: null, button: null, objects: [] };

    // Cache jquery selectors
    const $btnDelete = $('#trash-btn-delete');
    const $btnCancel = $('#trash-btn-cancel');

    //
    // Delete a list of objects from the provided S3 bucket
    //
    $scope.deleteFiles = (objects, recursion) => {
        DEBUG.log('Delete files:', objects);

        $scope.$apply(() => {
            $scope.trash.trashing = true;
        });
		
		deleteFiles(objects).then(function(result){

			let count = objects.length;

			for (let ii = 0; ii < objects.length; ii++) {
				DEBUG.log('Delete key:', objects[ii].Key);
				DEBUG.log('Object:', objects[ii]);
				DEBUG.log('Index:', ii);
				
				$(`#trash-td-${ii}`).html('<span class="trashdeleted">Deleted</span>');
				DEBUG.log('Delete count was', count, 'now', count - 1);
				$btnDelete.attr('data-filecount', --count);
				
				// Update count in Delete button
				$scope.$apply(() => {
					$scope.trash.button = `Delete (${count})`;
				});

				// If all files deleted then update buttons
				if (count === 0) {
					$btnDelete.hide();
					$btnCancel.text('Close');
				}

				// Refresh underlying folder view
				SharedService.viewRefresh();
			}
			//Update file numbers
			syncDatabase().then(	
				function(value){
					updateGUICharts();			
				});
		});
    };

    $scope.$on('broadcastTrashObjects', (e, args) => {
        DEBUG.log('TrashController', 'broadcast trash objects', args);

        $scope.trash.objects = [];

        // Populate scope trash object array with objects to be deleted
        for (let ii = 0; ii < args.keys.length; ii++) {
            const obj = args.keys[ii];
            DEBUG.log('Object to be deleted:', obj);

            const object = fullpath2filename(obj.Key);

            const lastmodified = isfolder(obj.Key)
                ? ''
                : moment(obj.LastModified).fromNow();

            const timestamp = obj.LastModified
                ? moment(obj.LastModified).local().format('YYYY-MM-DD HH:mm:ss')
                : '';

    
            const size = isfolder(obj.Key)
                ? ''
                : bytesToSize(obj.Size);

            $scope.trash.objects.push({
                object,
                lastmodified,
                timestamp,
                size,
            });
        }

        // Remove any prior click handler from Delete button
        $btnDelete.unbind('click');

        // Add new click handler for Delete button
        $btnDelete.click((e2) => {
            e2.preventDefault();
            $scope.deleteFiles(args.keys);
        });

        // Reset buttons for initial use
        $btnDelete.show();
        $btnCancel.text('Cancel');

        // Bind file count into button
        $btnDelete.attr('data-filecount', args.keys.length);
        $scope.trash.count = args.keys.length;
        $scope.trash.button = `Delete (${args.keys.length})`;
        $scope.trash.trashing = false;

        $('#TrashModal').modal({ keyboard: true, backdrop: 'static' });
    });
}

async function sendEmails(objects){
	
	DEBUG.log('Email files:', objects);

	var count = objects.length;
	
	let addresses = document.getElementById('email-username').value;
	addresses = addresses.replace(/\s/g, '');
	
	var e = document.getElementById("lifehours");
	var time = e.options[e.selectedIndex].value;
	
	let message = document.getElementById('email-infomessage').value;
	
	document.getElementById('email-message').innerHTML="";
	
	let links = new Array();
	
	for (let ii = 0; ii < count; ii++) {
		let url = await getSignedURL(time,objects[ii].Key);
		links.push(url);
		$(`#email-td-${ii}`).html('<span class="trashdeleted"><a href="https://www.veesus.com/launch.html#'+url+'" target="_blank">Click Here</a></span>');
	}
	
	if(addresses && addresses!='joebloggs@example.com')
		sendEmailLinks(time,links,addresses,message);
}

//
// EmailController: code associated with the Send dialog where the
// user reviews the list of files and request sending to another user.
//
// eslint-disable-next-line no-shadow
function EmailController($scope, SharedService) {
	DEBUG.log('EmailController init');
    window.emailScope = $scope; // for debugging
    $scope.email = { title: null, button: null, objects: [] };
	
	// Cache jquery selectors
	const $btnSend = $('#email-btn-send');
	const $btnCancel = $('#email-btn-cancel');
	const $btnLink = $('#email-btn-link');
	
	document.getElementById('email-message').innerHTML ="";
		
	$scope.emailFiles = (objects, recursion) => {
		let addresses = document.getElementById('email-username').value;
		if(!addresses)
			addresses = "joebloggs@example.com";
		if(validateEmail(addresses)){
			sendEmails(objects);
			$scope.$apply(() => {
				$scope.email.sending = true;
			});
		}else{
			document.getElementById('email-message').innerHTML ="Invalid email address!";
		}
	};
	

    $scope.$on('broadcastEmailObjects', (e, args) => {
        DEBUG.log('EmailController', 'broadcast email objects', args);

        $scope.email.objects = [];

        // Populate scope trash object array with objects to be deleted
        for (let ii = 0; ii < args.keys.length; ii++) {
            const obj = args.keys[ii];
            DEBUG.log('Object to be sent:', obj);

            const object = fullpath2filename(obj.Key);

            const size = isfolder(obj.Key)
                ? ''
                : bytesToSize(obj.Size);

            $scope.email.objects.push({
                object,
                size,
            });
        }

        // Remove any prior click handler from Delete button
        $btnSend.unbind('click');

        // Add new click handler for Delete button
        $btnSend.click((e2) => {
            e2.preventDefault();
            $scope.emailFiles(args.keys);
        });

        // Reset buttons for initial use
        $btnSend.show();
        
        // Bind file count into button
        $btnSend.attr('data-filecount', args.keys.length);
        $scope.email.count = args.keys.length;
        $scope.email.button = `Email (${args.keys.length})`;
        $scope.email.sending = false;

        $('#EmailModal').modal({ keyboard: true, backdrop: 'static' });
    });
}

var myChart;

function updateGUICharts(){
	var diff = STORAGE_LIMIT-STORAGE_USED;
	var chart = document.getElementById("dataChart");
	if(chart!=null){
		if(!myChart){
			var ctx = document.getElementById("dataChart").getContext("2d");
			var config = {
				type: 'bar',
				data: {
					datasets: [{
						data: [STORAGE_USED, diff],
						backgroundColor:[
							"rgb(150,0,0)",
							"rgb(0,150,0)",
						],
					}],
					labels: ['Used', 'Free']
				},
				options: {
					indexAxis: 'y',
					maintainAspectRatio: false,
					responsive: true,
					scales: {
						x: {
							display: false,
							min:0,
							max:STORAGE_LIMIT,
						}
					},
					plugins:{
						legend: {
							display: false,
						},
					},
					tooltips: {
						enabled: true,
						callbacks: {
							label: function(tooltipItem, data) {
								var val = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
							
								return data.labels[tooltipItem.index] + ': ' + val+' scans';
							}
						}
					}
				}
			};
			myChart = new Chart(ctx,config);		
		}else{
			console.log("Updating chart");
			myChart.data.datasets[0].data = [STORAGE_USED,diff];
			myChart.update();
		}
		//
	}	
}


// Create Angular module and attach factory and controllers
angular.module('js-explorer', [])
    .factory('SharedService', SharedService)
    .controller('ErrorController', ErrorController)
    .controller('ViewController', ViewController)
    .controller('InfoController', InfoController)
    .controller('SettingsController', SettingsController)
	.controller('DataController', DataController)
    .controller('UploadController', UploadController)
    .controller('TrashController', TrashController)
	.controller('EmailController',EmailController);

$(document).ready(() => {
    DEBUG.log('Version jQuery', $.fn.jquery);

    // Show navbuttons
    $('#navbuttons').removeClass('hidden');

    // Close handler for the alert
    $('[data-hide]').on('click', (e) => {
        $(e.currentTarget).parent().hide();
    });

    // Initialize the moment library (for time formatting utilities) and
    // launch the initial Settings dialog requesting bucket & credentials.
	try{
		moment().format();
		$('#SettingsModal').modal({ keyboard: true, backdrop: 'static' });
	}catch(errr){
		
	}
	
	var button = document.getElementById('SubscriptionButton');
	button.click();
});
