"use strict";
var fiberBootstrap = require("../fiber-bootstrap");
var syncBatchLib = require("./livesync/sync-batch");
var shell = require("shelljs");
var path = require("path");
var temp = require("temp");
var minimatch = require("minimatch");
var constants = require("../constants");
var util = require("util");
var gaze = require("gaze");
var LiveSyncServiceBase = (function () {
    function LiveSyncServiceBase($devicesService, $mobileHelper, $logger, $options, $deviceAppDataFactory, $fs, $injector, $hooksService, $projectFilesManager, $projectFilesProvider, $liveSyncProvider, $devicePlatformsConstants, $hostInfo, $dispatcher) {
        this.$devicesService = $devicesService;
        this.$mobileHelper = $mobileHelper;
        this.$logger = $logger;
        this.$options = $options;
        this.$deviceAppDataFactory = $deviceAppDataFactory;
        this.$fs = $fs;
        this.$injector = $injector;
        this.$hooksService = $hooksService;
        this.$projectFilesManager = $projectFilesManager;
        this.$projectFilesProvider = $projectFilesProvider;
        this.$liveSyncProvider = $liveSyncProvider;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$hostInfo = $hostInfo;
        this.$dispatcher = $dispatcher;
        this.showFullLiveSyncInformation = false;
        this.batch = Object.create(null);
        this.livesyncData = Object.create(null);
        this.fileHashes = Object.create(null);
    }
    LiveSyncServiceBase.prototype.sync = function (data, filePaths) {
        var _this = this;
        return (function () {
            _this.syncCore(data, filePaths).wait();
            if (_this.$options.watch) {
                _this.$hooksService.executeBeforeHooks('watch').wait();
                _this.partialSync(data, data[0].syncWorkingDirectory);
            }
        }).future()();
    };
    LiveSyncServiceBase.prototype.isFileExcluded = function (filePath, excludedPatterns) {
        var isFileExcluded = false;
        _.each(excludedPatterns, function (pattern) {
            if (minimatch(filePath, pattern, { nocase: true })) {
                isFileExcluded = true;
                return false;
            }
        });
        return isFileExcluded;
    };
    LiveSyncServiceBase.prototype.partialSync = function (data, syncWorkingDirectory) {
        var that = this;
        this.showFullLiveSyncInformation = true;
        gaze("**/*", { cwd: syncWorkingDirectory }, function (err, watcher) {
            this.on('all', function (event, filePath) {
                fiberBootstrap.run(function () {
                    that.$dispatcher.dispatch(function () { return (function () {
                        try {
                            if (filePath.indexOf(constants.APP_RESOURCES_FOLDER_NAME) !== -1) {
                                that.$logger.warn(("Skipping livesync for changed file " + filePath + ". This change requires a full build to update your application. ").yellow.bold);
                                return;
                            }
                            var fileHash = that.$fs.exists(filePath).wait() && that.$fs.getFsStats(filePath).wait().isFile() ? that.$fs.getFileShasum(filePath).wait() : "";
                            if (fileHash === that.fileHashes[filePath]) {
                                that.$logger.trace("Skipping livesync for " + filePath + " file with " + fileHash + " hash.");
                                return;
                            }
                            that.$logger.trace("Adding " + filePath + " file with " + fileHash + " hash.");
                            that.fileHashes[filePath] = fileHash;
                            for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
                                var dataItem = data_1[_i];
                                if (that.isFileExcluded(filePath, dataItem.excludedProjectDirsAndFiles)) {
                                    that.$logger.trace("Skipping livesync for changed file " + filePath + " as it is excluded in the patterns: " + dataItem.excludedProjectDirsAndFiles.join(", "));
                                    continue;
                                }
                                var mappedFilePath = that.$projectFilesProvider.mapFilePath(filePath, dataItem.platform);
                                that.$logger.trace("Syncing filePath " + filePath + ", mappedFilePath is " + mappedFilePath);
                                if (!mappedFilePath) {
                                    that.$logger.warn("Unable to sync " + filePath + ".");
                                    continue;
                                }
                                if (event === "added" || event === "changed" || event === "renamed") {
                                    that.batchSync(dataItem, mappedFilePath);
                                }
                                else if (event === "deleted") {
                                    that.fileHashes = (_.omit(that.fileHashes, filePath));
                                    that.syncRemovedFile(dataItem, mappedFilePath).wait();
                                }
                            }
                        }
                        catch (err) {
                            that.$logger.info(("Unable to sync file " + filePath + ". Error is:" + err.message).red.bold);
                            that.$logger.info("Try saving it again or restart the livesync operation.");
                        }
                    }).future()(); });
                });
            });
        });
        this.$dispatcher.run();
    };
    LiveSyncServiceBase.prototype.batchSync = function (data, filePath) {
        var _this = this;
        var platformBatch = this.batch[data.platform];
        if (!platformBatch || !platformBatch.syncPending) {
            var done = function () {
                return (function () {
                    setTimeout(function () {
                        fiberBootstrap.run(function () {
                            _this.$dispatcher.dispatch(function () { return (function () {
                                try {
                                    var _loop_1 = function(platformName) {
                                        var batch = _this.batch[platformName];
                                        var livesyncData = _this.livesyncData[platformName];
                                        batch.syncFiles((function (filesToSync) {
                                            _this.$liveSyncProvider.preparePlatformForSync(platformName).wait();
                                            _this.syncCore([livesyncData], filesToSync);
                                        }).future()).wait();
                                    };
                                    for (var platformName in _this.batch) {
                                        _loop_1(platformName);
                                    }
                                }
                                catch (err) {
                                    _this.$logger.warn("Unable to sync files. Error is:", err.message);
                                }
                            }).future()(); });
                        });
                    }, syncBatchLib.SYNC_WAIT_THRESHOLD);
                }).future()();
            };
            this.batch[data.platform] = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done });
            this.livesyncData[data.platform] = data;
        }
        this.batch[data.platform].addFile(filePath);
    };
    LiveSyncServiceBase.prototype.syncRemovedFile = function (data, filePath) {
        var _this = this;
        return (function () {
            var filePathArray = [filePath], deviceFilesAction = _this.getSyncRemovedFilesAction(data);
            _this.syncCore([data], filePathArray, deviceFilesAction).wait();
        }).future()();
    };
    LiveSyncServiceBase.prototype.getSyncRemovedFilesAction = function (data) {
        var _this = this;
        return function (deviceAppData, device, localToDevicePaths) {
            var platformLiveSyncService = _this.resolveDeviceLiveSyncService(data.platform, device);
            return platformLiveSyncService.removeFiles(deviceAppData.appIdentifier, localToDevicePaths);
        };
    };
    LiveSyncServiceBase.prototype.getSyncAction = function (data, filesToSync, deviceFilesAction, liveSyncOptions) {
        var _this = this;
        var appIdentifier = data.appIdentifier;
        var platform = data.platform;
        var projectFilesPath = data.projectFilesPath;
        var packageFilePath = null;
        var action = function (device) {
            return (function () {
                var shouldRefreshApplication = true;
                var deviceAppData = _this.$deviceAppDataFactory.create(appIdentifier, _this.$mobileHelper.normalizePlatformName(platform), device, liveSyncOptions);
                if (deviceAppData.isLiveSyncSupported().wait()) {
                    var platformLiveSyncService = _this.resolveDeviceLiveSyncService(platform, device);
                    if (platformLiveSyncService.beforeLiveSyncAction) {
                        platformLiveSyncService.beforeLiveSyncAction(deviceAppData).wait();
                    }
                    device.applicationManager.checkForApplicationUpdates().wait();
                    var wasInstalled = true;
                    if (!device.applicationManager.isApplicationInstalled(appIdentifier).wait() && !_this.$options.companion) {
                        _this.$logger.warn("The application with id \"" + appIdentifier + "\" is not installed on device with identifier " + device.deviceInfo.identifier + ".");
                        if (!packageFilePath) {
                            packageFilePath = _this.$liveSyncProvider.buildForDevice(device).wait();
                        }
                        device.applicationManager.installApplication(packageFilePath).wait();
                        if (platformLiveSyncService.afterInstallApplicationAction) {
                            var localToDevicePaths = _this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
                            shouldRefreshApplication = platformLiveSyncService.afterInstallApplicationAction(deviceAppData, localToDevicePaths).wait();
                        }
                        else {
                            shouldRefreshApplication = false;
                        }
                        if (device.applicationManager.canStartApplication() && !shouldRefreshApplication) {
                            device.applicationManager.startApplication(appIdentifier).wait();
                        }
                        wasInstalled = false;
                    }
                    if (shouldRefreshApplication) {
                        var localToDevicePaths = _this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
                        if (deviceFilesAction) {
                            deviceFilesAction(deviceAppData, device, localToDevicePaths).wait();
                        }
                        else {
                            _this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, !filesToSync).wait();
                        }
                        _this.$logger.info("Applying changes...");
                        platformLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, data.forceExecuteFullSync || !wasInstalled).wait();
                        _this.$logger.info("Successfully synced application " + data.appIdentifier + " on device " + device.deviceInfo.identifier + ".");
                    }
                }
                else {
                    _this.$logger.warn("LiveSync is not supported for application: " + deviceAppData.appIdentifier + " on device with identifier " + device.deviceInfo.identifier + ".");
                }
            }).future()();
        };
        return action;
    };
    LiveSyncServiceBase.prototype.syncCore = function (data, filesToSync, deviceFilesAction) {
        var _this = this;
        return (function () {
            for (var _i = 0, data_2 = data; _i < data_2.length; _i++) {
                var dataItem = data_2[_i];
                var appIdentifier = dataItem.appIdentifier;
                var platform = dataItem.platform;
                var canExecute = _this.getCanExecuteAction(platform, appIdentifier, dataItem.canExecute);
                var action = _this.getSyncAction(dataItem, filesToSync, deviceFilesAction, { isForCompanionApp: _this.$options.companion, additionalConfigurations: dataItem.additionalConfigurations, configuration: dataItem.configuration, isForDeletedFiles: false });
                _this.$devicesService.execute(action, canExecute).wait();
            }
        }).future()();
    };
    LiveSyncServiceBase.prototype.transferFiles = function (deviceAppData, localToDevicePaths, projectFilesPath, isFullSync) {
        var _this = this;
        return (function () {
            _this.$logger.info("Transferring project files...");
            _this.logFilesSyncInformation(localToDevicePaths, "Transferring %s.", _this.$logger.trace);
            var canTransferDirectory = isFullSync && (_this.$devicesService.isAndroidDevice(deviceAppData.device) || _this.$devicesService.isiOSSimulator(deviceAppData.device));
            if (canTransferDirectory) {
                var tempDir_1 = temp.mkdirSync("tempDir");
                _.each(localToDevicePaths, function (localToDevicePath) {
                    var fileDirname = path.join(tempDir_1, path.dirname(localToDevicePath.getRelativeToProjectBasePath()));
                    shell.mkdir("-p", fileDirname);
                    if (!_this.$fs.getFsStats(localToDevicePath.getLocalPath()).wait().isDirectory()) {
                        shell.cp("-f", localToDevicePath.getLocalPath(), path.join(fileDirname, path.basename(localToDevicePath.getDevicePath())));
                    }
                });
                deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, tempDir_1).wait();
            }
            else {
                _this.$liveSyncProvider.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync).wait();
            }
            _this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", _this.$logger.info);
        }).future()();
    };
    LiveSyncServiceBase.prototype.logFilesSyncInformation = function (localToDevicePaths, message, action) {
        var _this = this;
        if (this.showFullLiveSyncInformation) {
            _.each(localToDevicePaths, function (file) {
                action.call(_this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow));
            });
        }
        else {
            action.call(this.$logger, util.format(message, "all files"));
        }
    };
    LiveSyncServiceBase.prototype.resolveDeviceLiveSyncService = function (platform, device) {
        return this.$injector.resolve(this.$liveSyncProvider.deviceSpecificLiveSyncServices[platform.toLowerCase()], { _device: device });
    };
    LiveSyncServiceBase.prototype.getCanExecuteAction = function (platform, appIdentifier, canExecute) {
        var _this = this;
        canExecute = canExecute || (function (dev) { return dev.deviceInfo.platform.toLowerCase() === platform.toLowerCase(); });
        var finalCanExecute = canExecute;
        if (this.$options.device) {
            return function (device) { return canExecute(device) && device.deviceInfo.identifier === _this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier; };
        }
        if (this.$mobileHelper.isiOSPlatform(platform)) {
            if (this.$options.emulator) {
                finalCanExecute = function (device) { return canExecute(device) && _this.$devicesService.isiOSSimulator(device); };
            }
            else {
                var devices = this.$devicesService.getDevicesForPlatform(platform);
                var simulator_1 = _.find(devices, function (d) { return _this.$devicesService.isiOSSimulator(d); });
                if (simulator_1) {
                    var iOSDevices = _.filter(devices, function (d) { return d.deviceInfo.identifier !== simulator_1.deviceInfo.identifier; });
                    if (iOSDevices && iOSDevices.length) {
                        var isApplicationInstalledOnSimulator = simulator_1.applicationManager.isApplicationInstalled(appIdentifier).wait();
                        var isApplicationInstalledOnAllDevices = _.intersection.apply(null, iOSDevices.map(function (device) { return device.applicationManager.isApplicationInstalled(appIdentifier).wait(); }));
                        if (!isApplicationInstalledOnSimulator && !isApplicationInstalledOnAllDevices) {
                            finalCanExecute = function (device) { return canExecute(device) && _this.$devicesService.isiOSDevice(device); };
                        }
                    }
                }
            }
        }
        return finalCanExecute;
    };
    return LiveSyncServiceBase;
}());
$injector.register('liveSyncServiceBase', LiveSyncServiceBase);
