"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 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.batch = null;
        this.fileHashes = Object.create(null);
    }
    LiveSyncServiceBase.prototype.getPlatform = function (platform) {
        var _this = this;
        return (function () {
            _this.$devicesService.initialize({ platform: platform, deviceId: _this.$options.device }).wait();
            return platform || _this.$devicesService.platform;
        }).future()();
    };
    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);
            }
        }).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) {
        var that = this;
        gaze("**/*", { cwd: data.syncWorkingDirectory }, function (err, watcher) {
            this.on('all', function (event, filePath) {
                fiberBootstrap.run(function () {
                    that.$dispatcher.dispatch(function () { return (function () {
                        try {
                            if (that.isFileExcluded(filePath, data.excludedProjectDirsAndFiles)) {
                                that.$logger.trace("Skipping livesync for changed file " + filePath + " as it is excluded in the patterns: " + data.excludedProjectDirsAndFiles.join(", "));
                                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;
                            var mappedFilePath = that.$projectFilesProvider.mapFilePath(filePath, data.platform);
                            that.$logger.trace("Syncing filePath " + filePath + ", mappedFilePath is " + mappedFilePath);
                            if (!mappedFilePath) {
                                that.$logger.warn("Unable to sync " + filePath + ".");
                                return;
                            }
                            data.canExecuteFastSync = that.$liveSyncProvider.canExecuteFastSync(filePath, data.platform);
                            if (event === "added" || event === "changed" || event === "renamed") {
                                that.syncAddedOrChangedFile(data, mappedFilePath).wait();
                            }
                            else if (event === "deleted") {
                                that.fileHashes = (_.omit(that.fileHashes, filePath));
                                that.syncRemovedFile(data, mappedFilePath).wait();
                            }
                        }
                        catch (err) {
                            that.$logger.warn("Unable to sync file " + filePath + ". Error is:", err.message);
                            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;
        if (!this.batch || !this.batch.syncPending) {
            var done = function () {
                return (function () {
                    setTimeout(function () {
                        fiberBootstrap.run(function () {
                            _this.$dispatcher.dispatch(function () { return (function () {
                                try {
                                    _this.$liveSyncProvider.preparePlatformForSync(data.platform).wait();
                                    _this.batch.syncFiles(function (filesToSync) { return _this.syncCore(data, filesToSync); }).wait();
                                }
                                catch (err) {
                                    _this.$logger.warn("Unable to sync files. Error is:", err.message);
                                }
                            }).future()(); });
                        });
                    }, syncBatchLib.SYNC_WAIT_THRESHOLD);
                }).future()();
            };
            this.batch = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done });
        }
        this.batch.addFile(filePath);
    };
    LiveSyncServiceBase.prototype.fastSync = function (data, filePath) {
        var _this = this;
        return (function () {
            _this.$liveSyncProvider.preparePlatformForSync(data.platform).wait();
            _this.syncCore(data, [filePath]).wait();
        }).future()();
    };
    LiveSyncServiceBase.prototype.syncAddedOrChangedFile = function (data, filePath) {
        var _this = this;
        return (function () {
            if (data.canExecuteFastSync) {
                _this.fastSync(data, filePath).wait();
            }
            else {
                _this.batchSync(data, filePath);
            }
        }).future()();
    };
    LiveSyncServiceBase.prototype.syncRemovedFile = function (data, filePath) {
        var _this = this;
        return (function () {
            var deviceFilesAction = function (device, localToDevicePaths) {
                var platformLiveSyncService = _this.resolvePlatformLiveSyncService(data.platform, device);
                return platformLiveSyncService.removeFiles(data.appIdentifier, localToDevicePaths);
            };
            _this.syncCore(data, [filePath], deviceFilesAction).wait();
        }).future()();
    };
    LiveSyncServiceBase.prototype.syncCore = function (data, filesToSync, deviceFilesAction) {
        var _this = this;
        return (function () {
            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);
                    if (deviceAppData.isLiveSyncSupported().wait()) {
                        var platformLiveSyncService = _this.resolvePlatformLiveSyncService(platform, device);
                        if (platformLiveSyncService.beforeLiveSyncAction) {
                            platformLiveSyncService.beforeLiveSyncAction(deviceAppData).wait();
                        }
                        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.reinstallApplication(appIdentifier, packageFilePath).wait();
                            if (device.applicationManager.canStartApplication()) {
                                device.applicationManager.startApplication(appIdentifier).wait();
                            }
                            if (platformLiveSyncService.afterInstallApplicationAction) {
                                var localToDevicePaths = _this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles);
                                platformLiveSyncService.afterInstallApplicationAction(deviceAppData, localToDevicePaths).wait();
                            }
                            shouldRefreshApplication = false;
                        }
                        if (shouldRefreshApplication) {
                            var localToDevicePaths = _this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles);
                            if (deviceFilesAction) {
                                deviceFilesAction(device, localToDevicePaths).wait();
                            }
                            else {
                                _this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, !filesToSync).wait();
                            }
                            _this.$logger.info("Applying changes...");
                            platformLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, data.canExecuteFastSync).wait();
                            _this.$logger.info("Successfully synced application " + data.appIdentifier + " on device " + device.deviceInfo.identifier + ".");
                        }
                    }
                }).future()();
            };
            var canExecute = _this.getCanExecuteAction(platform, appIdentifier, data.canExecute);
            _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...");
            var canTransferDirectory = isFullSync && (_this.$devicesService.isAndroidDevice(deviceAppData.device) || _this.$devicesService.isiOSSimulator(deviceAppData.device));
            if (canTransferDirectory) {
                var tempDir = temp.mkdirSync("tempDir");
                shell.cp("-Rf", path.join(projectFilesPath, "*"), tempDir);
                _this.$projectFilesManager.processPlatformSpecificFiles(tempDir, deviceAppData.platform).wait();
                deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, tempDir).wait();
            }
            else {
                deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths).wait();
            }
            _this.$logger.info("Successfully transferred all project files.");
        }).future()();
    };
    LiveSyncServiceBase.prototype.resolvePlatformLiveSyncService = function (platform, device) {
        return this.$injector.resolve(this.$liveSyncProvider.platformSpecificLiveSyncServices[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 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 = _.find(devices, function (d) { return _this.$devicesService.isiOSSimulator(d); });
                if (simulator) {
                    var iOSDevices = _.filter(devices, function (d) { return d.deviceInfo.identifier !== simulator.deviceInfo.identifier; });
                    if (iOSDevices && iOSDevices.length) {
                        var isApplicationInstalledOnSimulator = simulator.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);
