// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
/*global FormData */
define(['hp/model/UploadManager',
        'hp/services/UploadService',
        'hp/core/EventDispatcher',
        'hp/core/Localizer',
        'hp/services/Log',
        'jquery',
        'hp/view/FormUploader'],

function(UploadManager, service, EventDispatcher, localizer, Log) {"use strict";
    var Upload = (function() {

        function Upload() {
            var self = this;
            var id = null;
            var formOptions = null;
            var dispatcher = new EventDispatcher();
            var useForm = false;
            var xhr = null;
            var options = {};
            var files = [];
            var startTime = 0;
            var stopTime = 0;
            var uploading = false;
            var dataTotal = -1;
            var dataLoaded = -1;
            var results = {};
            var error = {};
            var aborted = false;   
            var progress = {
                value:0,
                max:0
            };

            function uploadSuccess(data, uploadInfo) {
                // check the data to make sure there is no error. If
                // it does, dispatch a error event instead
                if (data.errorCode || data.recommendedActions) {
                    dispatcher.fire('error', {
                        errorInfo: data,
                        uploadInfo: uploadInfo
                    });
                } else {
                    Log.log('Upload: Upload success - ' + uploadInfo.files[0].name);
                    dispatcher.fire('success', {
                        data: data,
                        uploadInfo: uploadInfo
                    });
                }
            }

            function uploadError(errorInfo, uploadInfo) {
                Log.log('Upload: error - ' + errorInfo.message);
                Log.log('Upload: error - ' + uploadInfo.files[0].name);
                dispatcher.fire('error', {
                    errorInfo: errorInfo,
                    uploadInfo: uploadInfo
                });
            }

            function uploadCancel(data, uploadInfo) {
                Log.log('Upload: cancel - ' + uploadInfo.files[0].name);
                dispatcher.fire('cancel', {
                    data: data,
                    uploadInfo: uploadInfo
                });
            }

            function getElapsedTime() {
                var current = new Date().getTime();
                var result = 0;
                
                if (uploading) {
                    result = current - startTime;
                } else {
                    result = stopTime - startTime;
                    if (result < 0) {
                        result = 0;
                    }
                }
                return result;
            }
            
            function getRemainingTime() {
                var result = -1; // unknown 
                var speed = 0; // rate of upload
                var dataRemain = 0;
                
                // Remaining time is meaningful only if upload has actually
                // started. For IE 8 and 9, dataLoaded will never be set greater
                // than or equal to zero because they do not report progress. In 
                // which case, this method returns -1 for unknown.
                if (uploading && dataLoaded > 0) {
                    speed = dataLoaded/getElapsedTime();
                    dataRemain = dataTotal-dataLoaded;
                    result = dataRemain/speed;
                } 
                
                return result;
            }
            
            function uploadProgress(data, uploadInfo) {
                var percent = Math.round((data.loaded/data.total) * 100);
                var filename = uploadInfo.files[0].name;
                dataLoaded = data.loaded;
                dataTotal = data.total;
                
                Log.log('Upload: ' + filename + ' upload completed ' + percent + '% Elapsed time: ' + 
                        getElapsedTime() + 'ms Remaining time: ' + (getRemainingTime() | 0) + 'ms');

                dispatcher.fire('progressChange', {
                    data: data,
                    uploadInfo: uploadInfo
                });
            }

            function beforeUploadSend(transport) {
                // Save an instance of the transport here so that
                // it can be used to cancel the upload
                xhr = transport;
            }

            function buildFormData() {
                var formData;
                if (! useForm) {
                    formData = new FormData();
                    formData.append("file", files[0]);
                }
                return formData;
            }

            function doUpload() {
                var formData = buildFormData();
                var uri = null;
                var uploadInfo = {
                        files: $.extend(true, {}, files)
                    };

                if (!options.uploadUri){
                    Log.error("Unable to upload file. The uploadUri has not been specified.");
                    return null;
                }

                formOptions = options.formOptions;

                if (!formOptions) {
                    formOptions = {};
                }

                if (useForm) {
                    formOptions.fileInput = options.fileinput;
                    formOptions.dataType = 'iframe json';
                }

                uri = options.uploadUri;
                uploading = true;
                startTime = new Date().getTime();
                id = UploadManager.add(self);

                service.upload(uri, formData, {
                        success : function(data) {
                            stopTime = new Date().getTime();
                            uploading = false;
                            results = data;
                            uploadSuccess(data, uploadInfo);
                        },
                        error : function(errorInfo) {
                            stopTime = new Date().getTime();
                            uploading = false;
                            error = errorInfo;
                            uploadError(errorInfo, uploadInfo);
                        },
                        beforeSend : function(xhr) {
                            beforeUploadSend(xhr);
                        },
                        onAbort : function() {
                            stopTime = new Date().getTime();
                            uploading = false;
                            aborted = true;
                            var abortErrorInfo = {
                               errorMessage : localizer.getString('core.file.upload.cancel.canceled'),
                               message : localizer.getString('core.file.upload.cancel.canceled')
                            };
                            error = abortErrorInfo;
                            uploadCancel(abortErrorInfo, uploadInfo);
                        },
                        progressHandler : function(data) {
                            if(data.lengthComputable) {
                                progress.value = data.loaded;
                                progress.max = data.total;
                            }
                            uploadProgress(data, uploadInfo);
                        }
                    },
                    formOptions
                );
                return id;
            }

            /**
             * Start the upload process. 
             */
            this.start = function() {
                files = options.files;
                if (!files) {
                    Log.error("Unable to upload, no file has been selected.");
                    return null;
                } 
                if (typeof FormData === 'undefined') {
                    // IE doesn't have FormData so we have to use the iframe transport in a form submit
                    useForm = true;
                }
                return doUpload(this);
            };

            /**
             * Retrieve the unique id for this instance.
             * @return {string} the instance id
             */
            this.getId = function() {
                return id;
            };
            
            /**
             * Set the unique id for this instance. This call should
             * only be made by the UploadManager
             * @param {string} newId The unique id of this instance
             */
            this.setId = function(newId) {
                id = newId;
            };
            
            /**
             * Cancel upload of if it is still in progress.
             */
            this.cancel = function() {
                if (xhr) {
                    xhr.abort();
                }
            };

            /**
             * Check to see if the upload process has been cancelled.
             * @return Return true if the upload process has been cancelled, or false if not.
             */
            this.isCanceled = function(){
                return aborted;
            };

            /**
             * Check to see if the upload process is in progress.
             * @return Return true if the upload process is in progress, or false if not.
             */
            this.isUploading = function() {
                return uploading;
            };

            /**
             * Retrieve the result object of the upload.
             * @return Return the result of the upload. If the upload has not been completed, 
             * then the returned object is an empty object.
             */
            this.getResults = function() {
                return results;
            };

            /**
             * Retrieve the start time of the upload process.
             * @return Return the start time of the upload process. If the upload process
             * has not been started, the start time is set to 0.
             */
            this.getStartTime = function() {
                return startTime;
            };

            /**
             * Retrieve the stop time of the upload process.
             * @return Return the stop time of the upload process. If the upload process
             * has not been started or still in progress, the stop time is set to 0.
             */
            this.getStopTime = function() {
                return stopTime;
            };
            
            /**
             * Set the stop time of the upload progress. This call should
             * only be made by the UI-Core components only
             * @param {int} t The time for setting this object's stop time to
             */
            this.setStopTime = function(t) {
                stopTime = t;
            };

            /**
             * Retrieve the error of the upload process.
             * @return Return the error of the upload process. If the upload has not been 
             * completed,  then the returned object is an empty object.
             */
            this.getError = function() {
                return error;
            };   

            /**
             * Retrieve the progress object of the upload process. 
             * The progress object has a 'value' property, which stores the number of bytes
             * that have been uploaded, and a 'max' property, which stores the size of the
             * file to be uploaded. These values are provided by the browsers, and not all
             * browsers support them.
             * @return Return the progress object of the upload process. 
             */
            this.getProgress = function() {
                return progress;
            };

            /**
             * Retrieve an array of files object to be uploaded by this object
             * return {[file]} Return an array of file object to be uploaded by
             * this upload object. Note that in the current release, the max number
             * of files that can be uploaded by an upload object is 1. Therefore, the 
             * returned array should not have a length greater than 1.
             */
            this.getFiles = function(){
                return files;
            };

            /**
             * Retrieve the ISO string representing the start time of the upload process.
             * @return Return the ISO string representing start time of the upload process. 
             */
            this.getStartTimeStr = function() {
                return (new Date(startTime)).toISOString();
            };

            /**
             * Retrieve the elapsed time of the upload process. If the upload is still in
             * progress, the elapsed time is computed using the current time. If the upload
             * has been comleted, the elapsed time is computed using the stop time.
             * @return Return the elapsed time of the upload process. 
             */
            this.getElapsedTime = getElapsedTime;
            
            /**
             * Retrieve the estimated remaining time of the upload process. If the upload is still in
             * progress, the remaining time is computed by sampling the upload rate using the data 
             * that have already been uploaded, and the remaining data. If the upload is no longer
             * in progress, the remaining time is 0. 
             */
            this.getRemainingTime = getRemainingTime;

            /**
             * Add a listener for a specified event.
             * @param {string} eventName The name of the event.
             * @param {function(...)}
             * Possible events
             * 'success'
             * 'error'
             * 'cancel'
             * 'progressChange'
             */
            this.on = function (eventName, callback) {
                dispatcher.on(eventName, callback);
            };

            this.off = function (eventName, callback) {
                dispatcher.off(eventName, callback);
            };
            
            /**
             * Initialize the upload object
             * @param {object} opts An option object containing the 'uploadUri' property.
             */
            this.init = function (opts) {
                if (opts) {
                    options = opts;
                }
            };
        }

        return Upload;
    }());
    return Upload;
});