"use strict";
const syncBatchLib = require("../../common/services/livesync/sync-batch");
const shell = require("shelljs");
const path = require("path");
const temp = require("temp");
const minimatch = require("minimatch");
const constants = require("../../common/constants");
const util = require("util");
class PlatformLiveSyncServiceBase {
    constructor(_liveSyncData, $devicesService, $mobileHelper, $logger, $options, $deviceAppDataFactory, $fs, $injector, $projectFilesManager, $projectFilesProvider, $platformService, $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.$liveSyncProvider = $liveSyncProvider;
        this.showFullLiveSyncInformation = false;
        this.batch = Object.create(null);
        this.livesyncData = Object.create(null);
        this.liveSyncData = _liveSyncData;
        this.fileHashes = Object.create(null);
    }
    partialSync(event, filePath, dispatcher, afterFileSyncAction) {
        if (filePath.indexOf(constants.APP_RESOURCES_FOLDER_NAME) !== -1) {
            this.$logger.warn(`Skipping livesync for changed file ${filePath}. This change requires a full build to update your application. `.yellow.bold);
            return;
        }
        let fileHash = this.$fs.exists(filePath) && this.$fs.getFsStats(filePath).isFile() ? this.$fs.getFileShasum(filePath).wait() : "";
        if (fileHash === this.fileHashes[filePath]) {
            this.$logger.trace(`Skipping livesync for ${filePath} file with ${fileHash} hash.`);
            return;
        }
        this.$logger.trace(`Adding ${filePath} file with ${fileHash} hash.`);
        this.fileHashes[filePath] = fileHash;
        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;
        }
        let mappedFilePath = this.$projectFilesProvider.mapFilePath(filePath, this.liveSyncData.platform);
        this.$logger.trace(`Syncing filePath ${filePath}, mappedFilePath is ${mappedFilePath}`);
        if (!mappedFilePath) {
            this.$logger.warn(`Unable to sync ${filePath}.`);
            return;
        }
        if (event === "added" || event === "changed" || event === "renamed") {
            this.batchSync(mappedFilePath, dispatcher, afterFileSyncAction);
        }
        else if (event === "deleted") {
            this.fileHashes = (_.omit(this.fileHashes, filePath));
            this.syncRemovedFile(mappedFilePath, 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;
    }
    tryInstallApplication(device, deviceAppData) {
        return (() => {
            device.applicationManager.checkForApplicationUpdates().wait();
            let appIdentifier = this.liveSyncData.appIdentifier;
            if (!device.applicationManager.isApplicationInstalled(appIdentifier).wait()) {
                this.$logger.warn(`The application with id "${appIdentifier}" is not installed on device with identifier ${device.deviceInfo.identifier}.`);
                let packageFilePath = this.$liveSyncProvider.buildForDevice(device).wait();
                device.applicationManager.installApplication(packageFilePath).wait();
                return true;
            }
            return false;
        }).future()();
    }
    refreshApplication(deviceAppData, localToDevicePaths) {
        return (() => {
            let deviceLiveSyncService = this.resolveDeviceSpecificLiveSyncService(deviceAppData.device.deviceInfo.platform, deviceAppData.device);
            this.$logger.info("Applying changes...");
            deviceLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, this.liveSyncData.forceExecuteFullSync).wait();
        }).future()();
    }
    finishLivesync(deviceAppData) {
        return (() => {
            this.$logger.info(`Successfully synced application ${deviceAppData.appIdentifier} on device ${deviceAppData.device.deviceInfo.identifier}.`);
        }).future()();
    }
    transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync) {
        return (() => {
            this.$logger.info("Transferring project files...");
            this.logFilesSyncInformation(localToDevicePaths, "Transferring %s.", this.$logger.trace);
            let canTransferDirectory = isFullSync && (this.$devicesService.isAndroidDevice(deviceAppData.device) || this.$devicesService.isiOSSimulator(deviceAppData.device));
            if (canTransferDirectory) {
                let tempDir = temp.mkdirSync("tempDir");
                shell.cp("-Rf", path.join(projectFilesPath, "*"), tempDir);
                this.$projectFilesManager.processPlatformSpecificFiles(tempDir, deviceAppData.platform);
                deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, tempDir).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 platformName in this.batch) {
                                let batch = this.batch[platformName];
                                batch.syncFiles(((filesToSync) => {
                                    this.$platformService.preparePlatform(platformName, false, !this.$options.syncAllFiles).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);
                                }).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 = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device);
                let localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, this.liveSyncData.projectFilesPath, filesToSync, this.liveSyncData.excludedProjectDirsAndFiles);
                fileSyncAction(deviceAppData, localToDevicePaths).wait();
                if (!afterFileSyncAction) {
                    this.refreshApplication(deviceAppData, localToDevicePaths).wait();
                }
                this.finishLivesync(deviceAppData).wait();
                if (afterFileSyncAction) {
                    afterFileSyncAction(deviceAppData, localToDevicePaths).wait();
                }
            }).future()();
        };
        return action;
    }
    logFilesSyncInformation(localToDevicePaths, message, action) {
        if (this.showFullLiveSyncInformation) {
            _.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;
