"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const choki = require("chokidar");
const helpers_1 = require("../common/helpers");
const decorators_1 = require("../common/decorators");
const events_1 = require("events");
const path = require("path");
const constants_1 = require("../constants");
class PrepareController extends events_1.EventEmitter {
    constructor($platformController, $hooksService, $logger, $nodeModulesDependenciesBuilder, $platformsDataService, $prepareNativePlatformService, $projectChangesService, $projectDataService, $webpackCompilerService) {
        super();
        this.$platformController = $platformController;
        this.$hooksService = $hooksService;
        this.$logger = $logger;
        this.$nodeModulesDependenciesBuilder = $nodeModulesDependenciesBuilder;
        this.$platformsDataService = $platformsDataService;
        this.$prepareNativePlatformService = $prepareNativePlatformService;
        this.$projectChangesService = $projectChangesService;
        this.$projectDataService = $projectDataService;
        this.$webpackCompilerService = $webpackCompilerService;
        this.watchersData = {};
        this.isInitialPrepareReady = false;
        this.persistedData = [];
    }
    prepare(prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$platformController.addPlatformIfNeeded(prepareData);
            this.$logger.info("Preparing project...");
            let result = null;
            const projectData = this.$projectDataService.getProjectData(prepareData.projectDir);
            const platformData = this.$platformsDataService.getPlatformData(prepareData.platform, projectData);
            if (prepareData.watch) {
                result = yield this.startWatchersWithPrepare(platformData, projectData, prepareData);
            }
            else {
                yield this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, prepareData);
                const hasNativeChanges = yield this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
                result = { hasNativeChanges, platform: prepareData.platform.toLowerCase() };
            }
            yield this.$projectChangesService.savePrepareInfo(platformData, projectData, prepareData);
            this.$logger.info(`Project successfully prepared (${prepareData.platform.toLowerCase()})`);
            return result;
        });
    }
    stopWatchers(projectDir, platform) {
        const platformLowerCase = platform.toLowerCase();
        if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) {
            this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close();
            this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null;
        }
        if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) {
            this.$webpackCompilerService.stopWebpackCompiler(platform);
            this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null;
        }
    }
    startWatchersWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.watchersData[projectData.projectDir]) {
                this.watchersData[projectData.projectDir] = {};
            }
            if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) {
                this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = {
                    nativeFilesWatcher: null,
                    webpackCompilerProcess: null
                };
            }
            yield this.startJSWatcherWithPrepare(platformData, projectData, prepareData);
            const hasNativeChanges = yield this.startNativeWatcherWithPrepare(platformData, projectData, prepareData);
            const result = { platform: platformData.platformNameLowerCase, hasNativeChanges };
            const hasPersistedDataWithNativeChanges = this.persistedData.find(data => data.platform === result.platform && data.hasNativeChanges);
            if (hasPersistedDataWithNativeChanges) {
                result.hasNativeChanges = true;
            }
            this.isInitialPrepareReady = true;
            if (this.persistedData && this.persistedData.length) {
                this.emitPrepareEvent({ files: [], hasNativeChanges: result.hasNativeChanges, hmrData: null, platform: platformData.platformNameLowerCase });
            }
            return result;
        });
    }
    startJSWatcherWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) {
                this.$webpackCompilerService.on(constants_1.WEBPACK_COMPILATION_COMPLETE, data => {
                    this.emitPrepareEvent(Object.assign({}, data, { hasNativeChanges: false, platform: platformData.platformNameLowerCase }));
                });
                const childProcess = yield this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData);
                this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess;
            }
        });
    }
    startNativeWatcherWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if ((prepareData.nativePrepare && prepareData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) {
                return false;
            }
            const patterns = yield this.getWatcherPatterns(platformData, projectData);
            const watcherOptions = {
                ignoreInitial: true,
                cwd: projectData.projectDir,
                awaitWriteFinish: {
                    pollInterval: 100,
                    stabilityThreshold: 500
                },
                ignored: ["**/.*", ".*"]
            };
            const watcher = choki.watch(patterns, watcherOptions)
                .on("all", (event, filePath) => __awaiter(this, void 0, void 0, function* () {
                filePath = path.join(projectData.projectDir, filePath);
                this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`);
                this.emitPrepareEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase });
            }));
            this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher;
            const hasNativeChanges = yield this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
            return hasNativeChanges;
        });
    }
    getWatcherPatterns(platformData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const pluginsNativeDirectories = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir)
                .filter(dep => dep.nativescript)
                .map(dep => path.join(dep.directory, constants_1.PLATFORMS_DIR_NAME, platformData.platformNameLowerCase));
            const patterns = [
                path.join(projectData.projectDir, constants_1.PACKAGE_JSON_FILE_NAME),
                path.join(projectData.getAppDirectoryPath(), constants_1.PACKAGE_JSON_FILE_NAME),
                path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName),
            ].concat(pluginsNativeDirectories);
            return patterns;
        });
    }
    emitPrepareEvent(filesChangeEventData) {
        if (this.isInitialPrepareReady) {
            this.emit(constants_1.PREPARE_READY_EVENT_NAME, filesChangeEventData);
        }
        else {
            this.persistedData.push(filesChangeEventData);
        }
    }
}
__decorate([
    decorators_1.performanceLog(),
    helpers_1.hook("prepare")
], PrepareController.prototype, "prepare", null);
__decorate([
    helpers_1.hook("watch")
], PrepareController.prototype, "startWatchersWithPrepare", null);
__decorate([
    helpers_1.hook('watchPatterns')
], PrepareController.prototype, "getWatcherPatterns", null);
exports.PrepareController = PrepareController;
$injector.register("prepareController", PrepareController);
