// --------------------------------------------------------------
// ---------- Community Hub Drag and Drop File Upload ----------
// --------------------------------------------------------------
// Facilitates the uploading of files to Community Hub in a modern manner, utilizing Apex Remote Actions.
// --------------------------------------------------------------
var DEFAULT_LOADING_TEXT = '<i class="fa fa-circle-o-notch fa-spin fa-fw"></i> Uploading...';
var DEFAULT_FRIENDLY_ERROR = 'An error occurred while trying to upload your file, please try again later.';
var DEFAULT_FILE_SIZE_ERROR = 'The file you are trying to upload is too large in size.';
var DEFAULT_FILE_TYPE_ERROR = 'The file you are tyring to upload is not a supported file type.';
var DEFAULT_ON_DRAG_CLASS = 's-hover';
var DEFAULT_SAVING_CLASS = 's-saving';
var DEFAULT_ERROR_CLASS = 's-error';
var DEFAULT_ACCEPTABLE_MIME_TYPES = [
    'image/jpeg',
    'image/gif',
    'image/png'
];
// Default file size limit, in megabytes.
var DEFAULT_LIMIT = 2;
var MEGABYTES_TO_BYTES_MULTIPLIER = 1000000;

/**
 * @description Plugin syntax for jQuery.
 */
(function ($) {
    /**
     * @description Sets plugin namespace for method calling.
     *              Eg. $('#myDiv').nuDragAndDropFileUpload();
     * @param userOptions Options which can be passed in by whatever is calling this. See the plugin defaults at
     *        the bottom for more information.
     * @return An instantiated drag and drop plugin.
     */
    $.fn.nuDragAndDropFileUpload = function(userOptions) {
        var options = $.extend({}, $.fn.nuDragAndDropFileUpload.defaults, userOptions);
        var dropZone = $(this);
        var originalMessage = dropZone.html();

        if (!canPluginInitialize()) {
            return false;
        }

        // Hook into any elements which require cancel events.
        if (options.onClickClearElements !== null && options.onClickClearElements !== '') {
            addClearListenersToElements();
        }

        // Plugin can initialize, show the drop zone!
        dropZone.show();

        // Props for this slick jQuery fix...
        // https://tbranyen.com/post/missing-jquery-events-while-rendering
        jQuery.event.props.push('dataTransfer');

        var fileInputElement = $('[id="' + options.fileInputId + '"]');

        // Then the user clicks on the drop zone, we trigger click on the invisible input element.
        dropZone.on('click', function(e) {
            e.preventDefault();

            // Trigger a click event on the input to allow them to select a file.
            fileInputElement.click();
        })

        // Upon entering the drop zone, highlight the container via CSS class.
        dropZone.on('dragover', function(e) {
            e.preventDefault();

            clearAllStatusClasses();
            dropZone.addClass(options.onDragClass);
        });

        // Upon leaving the drop zone, remove the container highlight CSS class.
        dropZone.on('dragleave', function(e) {
            e.preventDefault();

            clearAllStatusClasses();
        });

        // On drag end, remove the container highlight CSS class.
        dropZone.on('dragend', function(e) {
            e.preventDefault();

            clearAllStatusClasses();
        });

        // On drop, validate the file, send it to the remote apex action.
        dropZone.on('drop', function(e) {
            e.preventDefault();

            clearAllStatusClasses();
            dropZone.addClass(options.savingClass);

            var chosenFile = e.dataTransfer.files[0];

            if (!isFileValid(chosenFile)) {
                return false;
            }

            readFileAndUpload(chosenFile);
        });

        // This hooks into the onchange of the input to grab the file, validate and upload.
        fileInputElement.on('change', function(e) {
            e.preventDefault();

            if (typeof fileInputElement[0] !== 'undefined' && fileInputElement[0].files.length > 0) {
                clearAllStatusClasses();
                dropZone.addClass(options.savingClass);

                var chosenFile = fileInputElement[0].files[0];

                if (!isFileValid(chosenFile)) {
                    return false;
                }

                // Upload the file chosen in the dialog.
                readFileAndUpload(chosenFile);
            }
        })

        /**
         * @description Adds a click listener to each element Id specified in options.onClickClearElements which will
         *              call clearAllStatusClasses() when clicked.
         */
        function addClearListenersToElements() {
            var elementIds = options.onClickClearElements.split(',');

            if (elementIds !== null && elementIds.length > 0) {
                $.each(elementIds, function(key, value) {
                    $('[id="' + value + '"]').on('click', function() {
                        clearAllStatusClasses();
                    });
                });
            }
        }

        /**
         * @description Checks that all required options are set, and returns true. If any are unset, returns false.
         * @return True if validation is passed and the plugin is ready to run, else false.
         */
        function canPluginInitialize() {
            if (options.uploadServiceImplementation === null) {
                return false;
            }

            if (options.sObjectId === null) {
                return false;
            }

            if (options.fileInputId === null) {
                return false;
            }

            if (options.uploadFunctionName === null) {
                return false;
            }

            return true;
        }

        /**
         * @description Removes all status CSS classes on the drop zone.
         */
        function clearAllStatusClasses() {
            dropZone.removeClass(options.onDragClass);
            dropZone.removeClass(options.errorClass);
            dropZone.removeClass(options.savingClass);
            dropZone.html(originalMessage);
        }

        /**
         * @description Determines if the specified file is valid. If it is, returns true. If it is not, returns
         *              false and displays an error.
         * @param fileToValidate The file you wish to validate.
         * @return True if the specified file mime type / size are valid, else false.
         */
        function isFileValid(fileToValidate) {
            // Validate the mime type is acceptable.
            if (!options.acceptableMimeTypes.includes(fileToValidate.type)) {
                return validationFailed(options.fileTypeErrorMessage);
            }

            // Calculate the file size limit in bytes.
            var limit = options.limit * MEGABYTES_TO_BYTES_MULTIPLIER;

            if (fileToValidate.size > limit) {
                return validationFailed(options.fileSizeErrorMessage);
            }

            // File is valid.
            return true;
        }

        /**
         * @description Outputs the specified error message, setting the error CSS class on the drop zone.
         * @param errorMessage The error message to output.
         */
        function validationFailed(errorMessage) {
            // Output error message.
            clearAllStatusClasses();
            dropZone.addClass(options.errorClass);
            dropZone.html(errorMessage);

            return false;
        }

        /**
         * @description Reads the specified file, extracting the base64 encoded data, sends it off to the
         *              Apex:ActionFunction component specified in options.uploadFunctionName. Which, in turn, then
         *              sends it off to an Apex action method.
         * @param fileToUpload The file you wish to upload!
         */
        function readFileAndUpload(fileToUpload) {
            var reader = new FileReader();

            // The reader needs a callback for when the load event is complete.
            // Reader.result contains the base64 encoded file data.
            reader.addEventListener('load', function () {
                dropZone.html(options.loadingText);

                // Trigger the upload apex function.
                window[options.uploadFunctionName](options.uploadServiceImplementation, options.sObjectId,
                    fileToUpload.name, reader.result);
            }, false);

            // This makes the reader read the file, and in turn triggers our load listener.
            reader.readAsDataURL(fileToUpload);
        }

        return this;
    };

    // Plugin defaults. The defaults as a property allow easier manipulation outside of the plugin itself.
    $.fn.nuDragAndDropFileUpload.defaults = {
        onDragClass: DEFAULT_ON_DRAG_CLASS,
        savingClass: DEFAULT_SAVING_CLASS,
        errorClass: DEFAULT_ERROR_CLASS,
        loadingText: DEFAULT_LOADING_TEXT,
        friendlyErrorMessage: DEFAULT_FRIENDLY_ERROR,
        fileSizeErrorMessage: DEFAULT_FILE_SIZE_ERROR,
        fileTypeErrorMessage: DEFAULT_FILE_TYPE_ERROR,
        uploadServiceImplementation: null,
        remoteActionName: null,
        sObjectId: null,
        fileInputId: null,
        limit: DEFAULT_LIMIT,
        acceptableMimeTypes: DEFAULT_ACCEPTABLE_MIME_TYPES,
        onClickClearElements: null,
        uploadFunctionName: null
    };
}(jQuery));