"use strict";
const syncBatchLib = require("../../common/services/livesync/sync-batch");
const path = require("path");
const minimatch = require("minimatch");
const util = require("util");
const livesyncInfoFileName = ".nslivesyncinfo";
class PlatformLiveSyncServiceBase {
    constructor(_liveSyncData, $devicesService, $mobileHelper, $logger, $options, $deviceAppDataFactory, $fs, $injector, $projectFilesManager, $projectFilesProvider, $platformService, $platformsData, $devicePlatformsConstants, $projectData, $projectChangesService, $liveSyncProvider) {
        this.$devicesService = $devicesService;
        this.$mobileHelper = $mobileHelper;
        this.$logger = $logger;
        this.$options = $options;
        this.$deviceAppDataFactory = $deviceAppDataFactory;
        this.$fs = $fs;
        this.$injector = $injector;
        this.$projectFilesManager = $projectFilesManager;
        this.$projectFilesProvider = $projectFilesProvider;
        this.$platformService = $platformService;
        this.$platformsData = $platformsData;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$projectData = $projectData;
        this.$projectChangesService = $projectChangesService;
        this.$liveSyncProvider = $liveSyncProvider;
        this.batch = Object.create(null);
        this.livesyncData = Object.create(null);
        this.liveSyncData = _liveSyncData;
    }
    fullSync(postAction) {
        return (() => {
            let appIdentifier = this.liveSyncData.appIdentifier;
            let platform = this.liveSyncData.platform;
            let projectFilesPath = this.liveSyncData.projectFilesPath;
            let canExecute = this.getCanExecuteAction(platform, appIdentifier);
            let action = (device) => {
                return (() => {
                    let deviceAppData = this.$deviceAppDataFactory.create(appIdentifier, this.$mobileHelper.normalizePlatformName(platform), device);
                    let localToDevicePaths = null;
                    if (this.shouldTransferAllFiles(platform, deviceAppData)) {
                        localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, this.liveSyncData.excludedProjectDirsAndFiles);
                        this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, true).wait();
                        device.fileSystem.putFile(this.$projectChangesService.getPrepareInfoFilePath(platform), this.getLiveSyncInfoFilePath(deviceAppData)).wait();
                    }
                    if (postAction) {
                        this.finishLivesync(deviceAppData).wait();
                        return postAction(deviceAppData).wait();
                    }
                    this.refreshApplication(deviceAppData, localToDevicePaths, true).wait();
                    this.finishLivesync(deviceAppData).wait();
                }).future()();
            };
            this.$devicesService.execute(action, canExecute).wait();
        }).future()();
    }
    partialSync(event, filePath, dispatcher, afterFileSyncAction) {
        if (this.isFileExcluded(filePath, this.liveSyncData.excludedProjectDirsAndFiles)) {
            this.$logger.trace(`Skipping livesync for changed file ${filePath} as it is excluded in the patterns: ${this.liveSyncData.excludedProjectDirsAndFiles.join(", ")}`);
            return;
        }
        if (event === "add" || event === "change") {
            this.batchSync(filePath, dispatcher, afterFileSyncAction);
        }
        else if (event === "unlink") {
            this.syncRemovedFile(filePath, afterFileSyncAction).wait();
        }
    }
    getCanExecuteAction(platform, appIdentifier) {
        let isTheSamePlatformAction = ((device) => device.deviceInfo.platform.toLowerCase() === platform.toLowerCase());
        if (this.$options.device) {
            return (device) => isTheSamePlatformAction(device) && device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier;
        }
        return isTheSamePlatformAction;
    }
    refreshApplication(deviceAppData, localToDevicePaths, isFullSync) {
        return (() => {
            let deviceLiveSyncService = this.resolveDeviceSpecificLiveSyncService(deviceAppData.device.deviceInfo.platform, deviceAppData.device);
            this.$logger.info("Refreshing application...");
            deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, isFullSync).wait();
        }).future()();
    }
    finishLivesync(deviceAppData) {
        return (() => {
            this.$logger.info(`Successfully synced application ${deviceAppData.appIdentifier} on device ${deviceAppData.device.deviceInfo.identifier}.\n`);
        }).future()();
    }
    transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync) {
        return (() => {
            this.$logger.info("Transferring project files...");
            let canTransferDirectory = isFullSync && (this.$devicesService.isAndroidDevice(deviceAppData.device) || this.$devicesService.isiOSSimulator(deviceAppData.device));
            if (canTransferDirectory) {
                deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath).wait();
            }
            else {
                this.$liveSyncProvider.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync).wait();
            }
            this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", this.$logger.info);
        }).future()();
    }
    resolveDeviceSpecificLiveSyncService(platform, device) {
        return this.$injector.resolve(this.$liveSyncProvider.deviceSpecificLiveSyncServices[platform.toLowerCase()], { _device: device });
    }
    isFileExcluded(filePath, excludedPatterns) {
        let isFileExcluded = false;
        _.each(excludedPatterns, pattern => {
            if (minimatch(filePath, pattern, { nocase: true })) {
                isFileExcluded = true;
                return false;
            }
        });
        return isFileExcluded;
    }
    batchSync(filePath, dispatcher, afterFileSyncAction) {
        let platformBatch = this.batch[this.liveSyncData.platform];
        if (!platformBatch || !platformBatch.syncPending) {
            let done = () => {
                return (() => {
                    dispatcher.dispatch(() => (() => {
                        try {
                            for (let platform in this.batch) {
                                let batch = this.batch[platform];
                                batch.syncFiles(((filesToSync) => {
                                    this.$platformService.preparePlatform(this.liveSyncData.platform).wait();
                                    let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier);
                                    let deviceFileAction = (deviceAppData, localToDevicePaths) => this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, !filePath);
                                    let action = this.getSyncAction(filesToSync, deviceFileAction, afterFileSyncAction);
                                    this.$devicesService.execute(action, canExecute).wait();
                                }).future()).wait();
                            }
                        }
                        catch (err) {
                            this.$logger.warn(`Unable to sync files. Error is:`, err.message);
                        }
                    }).future()());
                }).future()();
            };
            this.batch[this.liveSyncData.platform] = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done });
            this.livesyncData[this.liveSyncData.platform] = this.liveSyncData;
        }
        this.batch[this.liveSyncData.platform].addFile(filePath);
    }
    syncRemovedFile(filePath, afterFileSyncAction) {
        return (() => {
            let deviceFilesAction = (deviceAppData, localToDevicePaths) => {
                let deviceLiveSyncService = this.resolveDeviceSpecificLiveSyncService(this.liveSyncData.platform, deviceAppData.device);
                return deviceLiveSyncService.removeFiles(this.liveSyncData.appIdentifier, localToDevicePaths);
            };
            let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier);
            let action = this.getSyncAction([filePath], deviceFilesAction, afterFileSyncAction);
            this.$devicesService.execute(action, canExecute).wait();
        }).future()();
    }
    getSyncAction(filesToSync, fileSyncAction, afterFileSyncAction) {
        let action = (device) => {
            return (() => {
                let deviceAppData = null;
                let localToDevicePaths = null;
                let isFullSync = false;
                if (this.$options.clean || this.$projectChangesService.currentChanges.changesRequireBuild) {
                    let buildConfig = { buildForDevice: !device.isEmulator };
                    let platform = device.deviceInfo.platform;
                    if (this.$platformService.shouldBuild(platform, buildConfig)) {
                        this.$platformService.buildPlatform(platform, buildConfig).wait();
                    }
                    this.$platformService.installApplication(device).wait();
                    deviceAppData = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device);
                    isFullSync = true;
                }
                else {
                    deviceAppData = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device);
                    let mappedFiles = filesToSync.map((file) => this.$projectFilesProvider.mapFilePath(file, device.deviceInfo.platform));
                    localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, this.liveSyncData.projectFilesPath, mappedFiles, this.liveSyncData.excludedProjectDirsAndFiles);
                    fileSyncAction(deviceAppData, localToDevicePaths).wait();
                }
                if (!afterFileSyncAction) {
                    this.refreshApplication(deviceAppData, localToDevicePaths, isFullSync).wait();
                }
                device.fileSystem.putFile(this.$projectChangesService.getPrepareInfoFilePath(device.deviceInfo.platform), this.getLiveSyncInfoFilePath(deviceAppData)).wait();
                this.finishLivesync(deviceAppData).wait();
                if (afterFileSyncAction) {
                    afterFileSyncAction(deviceAppData, localToDevicePaths).wait();
                }
            }).future()();
        };
        return action;
    }
    shouldTransferAllFiles(platform, deviceAppData) {
        try {
            if (this.$options.clean) {
                return false;
            }
            let fileText = this.$platformService.readFile(deviceAppData.device, this.getLiveSyncInfoFilePath(deviceAppData)).wait();
            let remoteLivesyncInfo = JSON.parse(fileText);
            let localPrepareInfo = this.$projectChangesService.getPrepareInfo(platform);
            return remoteLivesyncInfo.time !== localPrepareInfo.time;
        }
        catch (e) {
            return true;
        }
    }
    getLiveSyncInfoFilePath(deviceAppData) {
        let deviceRootPath = deviceAppData.deviceProjectRootPath;
        if (deviceAppData.device.deviceInfo.platform.toLowerCase() === this.$devicePlatformsConstants.Android.toLowerCase()) {
            deviceRootPath = path.dirname(deviceRootPath);
        }
        let deviceFilePath = path.join(deviceRootPath, livesyncInfoFileName);
        return deviceFilePath;
    }
    logFilesSyncInformation(localToDevicePaths, message, action) {
        if (localToDevicePaths && localToDevicePaths.length < 10) {
            _.each(localToDevicePaths, (file) => {
                action.call(this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow));
            });
        }
        else {
            action.call(this.$logger, util.format(message, "all files"));
        }
    }
}
exports.PlatformLiveSyncServiceBase = PlatformLiveSyncServiceBase;
$injector.register("platformLiveSyncService", PlatformLiveSyncServiceBase);
