(function () {
    /*global JSZip, TICloudAgent*/
    'use strict';
    angular.module('uniflash').service('ufFilePackageCreator', filePackageCreator);
    filePackageCreator.$inject = ['ufSession', 'ufPropertiesValue', 'ufPrograms', 'ufProgess', 'ticAgentAPI', '$http', '$q', '$log'];
    function filePackageCreator(ufSession, ufPropertiesValue, ufPrograms, ufProgess, ticAgentAPI, $http, $q, $log) {
        var installerOS = getOSType();
        var includeSettings = false;
        var includeImages = false;
        var includeDrivers = false;
        var includeSessions = false;
        var deviceStr = '';
        var isMSP = false;
        var curCCXMLFileName = '';
        var curSettingsFileName = '';
        var cores = [];
        var curOperations = '';
        var progressDialog;
        var cliScriptName = 'dslite';
        var extensionName = [];
        extensionName['win'] = '.bat';
        extensionName['linux'] = '.sh';
        extensionName['osx'] = '.sh';
        var defaultMode = 'flash';
        var quote = (getOSType() === 'win') ? "\"" : "\\\"";
        function getOSType() {
            return TICloudAgent.getOS();
        }
        function setOSType(type) {
            if (type === 'win' || type === 'osx' || type === 'linux') {
                installerOS = type;
            }
        }
        function setPackageOptions(images, settings, drivers, sessions) {
            includeSettings = settings;
            includeImages = images;
            includeDrivers = drivers;
            includeSessions = sessions;
        }
        function setDevice(device) {
            deviceStr = device.toLowerCase();
            isMSP = ((deviceStr.substring(0, 6) === 'msp430') ||
                (deviceStr.substring(0, 5) === 'cc430') ||
                (deviceStr.substring(0, 5) === 'rf430'));
        }
        function blob2abAsync(blob) {
            var arrayBuffer;
            var deferred = $q.defer();
            var fileReader = new FileReader();
            fileReader.onload = function () {
                arrayBuffer = this.result;
                deferred.resolve(arrayBuffer);
            };
            fileReader.readAsArrayBuffer(blob);
            return deferred.promise;
        }
        function addFileToZip(zipFileContents, file, fileContents, filePermissions) {
            var deferred = $q.defer();
            $log.debug('adding ' + file + ' ' + fileContents + ' to zipFileContents');
            var options = {};
            if (filePermissions) {
                options.unixPermissions = filePermissions;
            }
            if (fileContents instanceof Blob) {
                blob2abAsync(fileContents)
                    .then(function (arrayBuffer) {
                    zipFileContents.push({
                        fileName: file,
                        contents: arrayBuffer,
                        options: options
                    });
                    deferred.resolve();
                });
            }
            else if (fileContents instanceof ArrayBuffer) {
                zipFileContents.push({
                    fileName: file,
                    contents: fileContents,
                    options: options
                });
                deferred.resolve();
            }
            else if (fileContents instanceof Uint8Array) {
                zipFileContents.push({
                    fileName: file,
                    contents: fileContents.buffer,
                    options: options
                });
                deferred.resolve();
            }
            else if (typeof fileContents === 'string') {
                // string look the same as array buffer here just because the api is set to this way
                // but just keep them separated in case any handling is needed for any of these types and 
                // to show that they are really different
                zipFileContents.push({
                    fileName: file,
                    contents: fileContents,
                    options: options
                });
                deferred.resolve();
            }
            else {
                deferred.reject('unknown file contents type.');
            }
            return deferred.promise;
        }
        function getChmodVal(octalStr) {
            return octalStr ? parseInt(octalStr, 8) : null;
        }
        function downloadError(file, err) {
            throw new Error('Error fetching ' + file + ', ' + err.status + ' ' + err.statusText + '.');
        }
        function getFileContentResponse(path, contentType, version) {
            if (!contentType) {
                contentType = ti_common.file.ContentType.Text;
            }
            if (!version) {
                version = "LATEST";
            }
            return ticAgentAPI.getFileContent(installerOS, path, contentType, version);
        }
        function downloadFile(file, version, zipFileContents) {
            $log.debug('Fetching ' + file);
            return getFileContentResponse(file, ti_common.file.ContentType.Blob, version)
                .then(function (response) {
                var permissionsStr = response.headers()['x-ticloudagent-permissions'];
                // Do not set permissions on osx symbolic link for now due to a bug with getting symlink content
                if (permissionsStr === '120755') {
                    permissionsStr = {};
                }
                var permissions = getChmodVal(permissionsStr);
                return addFileToZip(zipFileContents, file, response.data, permissions);
            })
                .catch(function (err) {
                downloadError(file, err);
            });
        }
        function getSupportingFiles() {
            $log.debug('Getting install info... ');
            var ccxmlContent = ufSession.getCCXML();
            return (function () {
                return ticAgentAPI.getSupportingFiles(ccxmlContent, installerOS, 64);
            })()
                .then(function (response) {
                return JSON.parse(response.data);
            });
        }
        function getFilePackage() {
            return getSupportingFiles()
                .then(function (installInfo) {
                return getFilePackageImpl(installInfo.files);
            });
        }
        function addSettingsFiles(zipFileContents) {
            if (includeSettings) {
                var settings = ufPropertiesValue.saveValuesAsJson(true);
                curSettingsFileName = 'user_files/settings/' + 'generated.ufsettings';
                return addFileToZip(zipFileContents, curSettingsFileName, settings);
            }
            else {
                return $q.when();
            }
        }
        function getImagesStr(core) {
            var programList = core.programs;
            var curImage = '';
            // generate the imagePath command
            _.each(programList, function (curProgram) {
                if (curProgram.isSelected) {
                    curImage += quote + 'user_files/images/' + curProgram.file.name;
                    if (curProgram.isBinary) {
                        curImage += ',';
                        curImage += (curProgram.loadAddress === undefined || curProgram.loadAddress.length === 0) ? '0x0' : curProgram.loadAddress;
                    }
                    curImage += quote + ' ';
                }
            });
            return curImage;
        }
        function addImageFiles(zipFileContents, cores) {
            if (includeImages) {
                return $q.all(_.map(cores, function (core) {
                    var programList = core.programs;
                    // go through each of the programs in the list, get the contents, and add it to the zip
                    return processArray(programList, function (curProgram) {
                        if (curProgram.isSelected) {
                            return curProgram.file.getContents(ti_common.file.ContentType.Blob)
                                .then(function (content) {
                                return addFileToZip(zipFileContents, 'user_files/images/' + curProgram.file.name, content);
                            });
                        }
                        else {
                            return $q.when();
                        }
                    });
                }));
            }
            else {
                return $q.when();
            }
        }
        // helper function for serially evaluating all items in the collection before returning 
        function processArray(collection, predicate) {
            var index = 0;
            function processNext() {
                if (index < collection.length) {
                    // process the current item and then move to the next item when done
                    return predicate(collection[index++]).then(function () {
                        processNext();
                    });
                }
                else {
                    // all done
                    return $q.resolve();
                }
            }
            return processNext();
        }
        function addCCXMLFile(zipFileContents) {
            var ccxml = ufSession.getCCXML();
            curCCXMLFileName = 'user_files/configs/' + ufSession.getDeviceXMLFile() + '.ccxml';
            return addFileToZip(zipFileContents, curCCXMLFileName, ccxml);
        }
        function addLaunchScript(zipFileContents, cores) {
            return $q.all(_.map(cores, function (core) {
                var fileName = cliScriptName;
                var debuggableCores = ufSession.queryConfig().debuggableCores;
                // UNIFLASH-1144, only create 2 scripts for new sessions
                var finalFileName = (debuggableCores && debuggableCores.length > 1) ? cliScriptName + '-' + core.name : cliScriptName;
                fileName += extensionName[installerOS];
                finalFileName += extensionName[installerOS];
                var coreCounter;
                if (debuggableCores) {
                    coreCounter = _.findIndex(debuggableCores, function (debuggableCore) {
                        return debuggableCore.name === core.name;
                    });
                }
                /////////////////////////////
                $log.debug('addLaunchScript ' + finalFileName);
                //this will work because the template file is hosted in the app itself
                return $http.get('file_package_creator/templates/' + installerOS + '/' + fileName)
                    .then(function (response) {
                    // update name of the ccxml file
                    var generatedCommand = '-c ' + curCCXMLFileName;
                    var additionals = '';
                    // update settings information
                    if (includeSettings) {
                        generatedCommand += ' -l ' + curSettingsFileName;
                    }
                    generatedCommand += ' -s VerifyAfterProgramLoad=' + quote + 'No verification' + quote;
                    // figure out the default mode to use
                    if (ufSession.isSerial()) {
                        defaultMode = 'serial';
                    }
                    else {
                        defaultMode = 'flash';
                    }
                    // workaround for the awr/iwr16 core case
                    if ((ufSession.getDeviceXMLFile().indexOf('awr16') !== -1) || (ufSession.getDeviceXMLFile().indexOf('iwr16') !== -1)) {
                        generatedCommand += ' -n 1';
                    }
                    else if (coreCounter) {
                        additionals = '-n ' + coreCounter;
                    }
                    // no longer used, but this is an example of how to use the 'additionals'
                    // if ((ufSession.getDeviceXMLFile().indexOf('cc26') !== -1) ||
                    // 	(ufSession.getDeviceXMLFile().indexOf('cc13') !== -1)) {
                    // 	additionals = '--lprf';
                    // }
                    // update images information
                    var curImagesStr = getImagesStr(core);
                    if (includeImages && curImagesStr) {
                        if (defaultMode === 'serial') {
                            generatedCommand += ' -e -f ' + curImagesStr;
                        }
                        else {
                            generatedCommand += ' -e -f -v ' + curImagesStr;
                        }
                    }
                    // replace in the file data string
                    var fileData = response.data.replace('%%generatedCommand%%', generatedCommand);
                    fileData = fileData.replace('%%defaultMode1%%', defaultMode);
                    fileData = fileData.replace('%%additionals%%', additionals);
                    return addFileToZip(zipFileContents, finalFileName, fileData, 33279);
                })
                    .catch(function (err) {
                    downloadError(finalFileName, err);
                });
            }));
        }
        function addDriverScript(zipFileContents) {
            var osArc = 64;
            if (installerOS === 'win') {
                var fileName_1 = 'one_time_setup.bat';
                var driverInfo_1;
                var dpinstFileName_1;
                return getSupportingFiles()
                    .then(function (installInfo) {
                    driverInfo_1 = installInfo.drivers;
                    if (!driverInfo_1) {
                        return;
                    }
                    dpinstFileName_1 = 'dpinst_' + osArc + '_eng.exe';
                    return getFileContentResponse('ticloudagent/setup/' + dpinstFileName_1, ti_common.file.ContentType.Blob);
                })
                    .then(function (dpInstResponse) {
                    return addFileToZip(zipFileContents, dpinstFileName_1, dpInstResponse.data, null);
                })
                    .then(function () {
                    //this will work because the template file is hosted in the app itself
                    return $http.get('file_package_creator/templates/' + installerOS + '/' + fileName_1);
                })
                    .then(function (templateResponse) {
                    return (_.template(templateResponse.data))({ drivers: driverInfo_1, osArch: osArc });
                })
                    .then(function (scriptData) {
                    return addFileToZip(zipFileContents, fileName_1, scriptData, null);
                })
                    .catch(function (err) {
                    downloadError('one_time_setup.bat', err);
                });
            }
            else if (installerOS === 'linux') {
                var fileName_2 = 'one_time_setup.sh';
                return $http.get('file_package_creator/templates/' + installerOS + '/' + fileName_2)
                    .then(function (scriptData) {
                    return addFileToZip(zipFileContents, fileName_2, scriptData.data, 33279);
                })
                    .catch(function (err) {
                    downloadError('one_time_setup.sh', err);
                });
            }
        }
        function addExtraFiles(zipFileContents) {
            if (isMSP && (installerOS === 'win')) {
                var fileName_3 = 'ccs_base/DebugServer/drivers/MSP430Flasher.exe';
                return $http({
                    url: 'bin/' + installerOS + '/MSP430Flasher.exe',
                    method: 'GET',
                    responseType: 'blob'
                }).then(function (response) {
                    return blob2abAsync(response.data)
                        .then(function (arrayBuffer) {
                        return addFileToZip(zipFileContents, fileName_3, arrayBuffer, 33279);
                    });
                })
                    .catch(function (err) {
                    downloadError('MSP430Flasher.exe', err);
                });
            }
        }
        function getFilePackageImpl(files) {
            var zipFileContents = [];
            var deferred = $q.defer();
            ufProgess.createProgress('Generating standalone package').then(function (newProgressDialog) {
                progressDialog = newProgressDialog;
                var initUpdate = {
                    id: -1,
                    name: 'Please wait...',
                    subActivity: 'Getting the list of files to package.',
                    isFirstUpdate: true,
                    isComplete: false
                };
                progressDialog.handleEvent(initUpdate);
            });
            $log.debug('Fetching ' + files.length + ' files');
            var isCancelled = false;
            var cancelationDeferred = $q.defer();
            var downloads = [];
            var index = 0;
            curCCXMLFileName = '';
            curSettingsFileName = '';
            cores = [];
            curOperations = '';
            var cancel = function () {
                $log.info('Cancelling generate package.');
                isCancelled = true;
                return cancelationDeferred.promise;
            };
            var downloadNext = function () {
                if (!isCancelled && files.length > index) {
                    var file_1 = files[index++];
                    return downloadFile(file_1.name, file_1.version, zipFileContents)
                        .then(function () {
                        var percentageDone = Math.floor(index * 100 / files.length);
                        var progressMsg = 'Fetching ' + index + '/' + files.length + ': ' + file_1.name;
                        deferred.notify({
                            message: progressMsg,
                            percent: percentageDone
                        });
                        if (progressDialog) {
                            progressDialog.handleEvent({
                                subActivity: progressMsg,
                                percent: percentageDone / 2,
                                isCancellable: true
                            }, cancel);
                        }
                        return downloadNext();
                    });
                }
            };
            // We only download 10 at a time.  Our server seems to handle 
            // downloading all of them at once, but if we scale up the number
            // of users...?  Plus, it doesn't seem to give much benefit to 
            // download all at once...
            for (var i = 0; i < 10; ++i) {
                downloads.push(downloadNext());
            }
            $q.all(downloads)
                .then(function () {
                if (isCancelled) {
                    $log.debug('Generate package was cancelled.');
                }
                else {
                    cores = ufPrograms.getAllGUICoresData();
                    return addSettingsFiles(zipFileContents)
                        .then(function () {
                        return addImageFiles(zipFileContents, cores);
                    })
                        .then(function () {
                        return addCCXMLFile(zipFileContents);
                    })
                        .then(function () {
                        return addLaunchScript(zipFileContents, cores);
                    })
                        .then(function () {
                        return addDriverScript(zipFileContents);
                    })
                        .then(function () {
                        return addExtraFiles(zipFileContents);
                    })
                        .then(function () {
                        deferred.resolve(zipFileContents);
                    });
                }
            })
                .catch(function (err) {
                deferred.reject(err);
                progressDialog.close();
            })
                .finally(function () {
                cancelationDeferred.resolve();
            });
            return deferred.promise;
        }
        function getProgess() {
            return progressDialog;
        }
        return {
            getFilePackage: getFilePackage,
            getFiles: getFilePackageImpl,
            getOSType: getOSType,
            setOSType: setOSType,
            setPackageOptions: setPackageOptions,
            setDevice: setDevice,
            getProgess: getProgess
        };
    }
})();
