"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 path = require("path");
const shell = require("shelljs");
const constants = require("../constants");
const semver = require("semver");
const projectServiceBaseLib = require("./platform-project-service-base");
const device_android_debug_bridge_1 = require("../common/mobile/android/device-android-debug-bridge");
const constants_1 = require("../common/constants");
const decorators_1 = require(".././common/decorators");
class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase {
    constructor($androidToolsInfo, $errors, $fs, $logger, $projectDataService, $injector, $devicePlatformsConstants, $androidPluginBuildService, $platformEnvironmentRequirements, $androidResourcesMigrationService, $filesHashService, $gradleCommandService, $gradleBuildService) {
        super($fs, $projectDataService);
        this.$androidToolsInfo = $androidToolsInfo;
        this.$errors = $errors;
        this.$logger = $logger;
        this.$injector = $injector;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$androidPluginBuildService = $androidPluginBuildService;
        this.$platformEnvironmentRequirements = $platformEnvironmentRequirements;
        this.$androidResourcesMigrationService = $androidResourcesMigrationService;
        this.$filesHashService = $filesHashService;
        this.$gradleCommandService = $gradleCommandService;
        this.$gradleBuildService = $gradleBuildService;
        this._platformData = null;
    }
    getPlatformData(projectData) {
        if (!projectData && !this._platformData) {
            throw new Error("First call of getPlatformData without providing projectData.");
        }
        if (projectData && projectData.platformsDir) {
            const projectRoot = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME);
            const appDestinationDirectoryArr = [projectRoot, constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.ASSETS_DIR];
            const configurationsDirectoryArr = [projectRoot, constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.MANIFEST_FILE_NAME];
            const deviceBuildOutputArr = [projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.APK_DIR];
            const packageName = this.getProjectNameFromId(projectData);
            this._platformData = {
                frameworkPackageName: constants.TNS_ANDROID_RUNTIME_NAME,
                normalizedPlatformName: "Android",
                platformNameLowerCase: "android",
                appDestinationDirectoryPath: path.join(...appDestinationDirectoryArr),
                platformProjectService: this,
                projectRoot: projectRoot,
                getBuildOutputPath: (buildOptions) => {
                    if (buildOptions.androidBundle) {
                        return path.join(projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.BUNDLE_DIR);
                    }
                    return path.join(...deviceBuildOutputArr);
                },
                getValidBuildOutputData: (buildOptions) => {
                    const buildMode = buildOptions.release ? constants_1.Configurations.Release.toLowerCase() : constants_1.Configurations.Debug.toLowerCase();
                    if (buildOptions.androidBundle) {
                        return {
                            packageNames: [
                                `${constants.APP_FOLDER_NAME}${constants.AAB_EXTENSION_NAME}`
                            ]
                        };
                    }
                    return {
                        packageNames: [
                            `${packageName}-${buildMode}${constants.APK_EXTENSION_NAME}`,
                            `${projectData.projectName}-${buildMode}${constants.APK_EXTENSION_NAME}`,
                            `${projectData.projectName}${constants.APK_EXTENSION_NAME}`,
                            `${constants.APP_FOLDER_NAME}-${buildMode}${constants.APK_EXTENSION_NAME}`
                        ],
                        regexes: [new RegExp(`${constants.APP_FOLDER_NAME}-.*-(${constants_1.Configurations.Debug}|${constants_1.Configurations.Release})${constants.APK_EXTENSION_NAME}`, "i"), new RegExp(`${packageName}-.*-(${constants_1.Configurations.Debug}|${constants_1.Configurations.Release})${constants.APK_EXTENSION_NAME}`, "i")]
                    };
                },
                frameworkFilesExtensions: [".jar", ".dat", ".so"],
                configurationFileName: constants.MANIFEST_FILE_NAME,
                configurationFilePath: path.join(...configurationsDirectoryArr),
                relativeToFrameworkConfigurationFilePath: path.join(constants.SRC_DIR, constants.MAIN_DIR, constants.MANIFEST_FILE_NAME),
                fastLivesyncFileExtensions: [".jpg", ".gif", ".png", ".bmp", ".webp"]
            };
        }
        return this._platformData;
    }
    getCurrentPlatformVersion(platformData, projectData) {
        const currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName);
        return currentPlatformData && currentPlatformData[constants.VERSION_STRING];
    }
    validateOptions() {
        return __awaiter(this, void 0, void 0, function* () {
            return true;
        });
    }
    getAppResourcesDestinationDirectoryPath(projectData) {
        const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectData.getAppResourcesDirectoryPath());
        if (appResourcesDirStructureHasMigrated) {
            return this.getUpdatedAppResourcesDestinationDirPath(projectData);
        }
        else {
            return this.getLegacyAppResourcesDestinationDirPath(projectData);
        }
    }
    validate(projectData, options, notConfiguredEnvOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            this.validatePackageName(projectData.projectIdentifiers.android);
            this.validateProjectName(projectData.projectName);
            const checkEnvironmentRequirementsOutput = yield this.$platformEnvironmentRequirements.checkEnvironmentRequirements({
                platform: this.getPlatformData(projectData).normalizedPlatformName,
                projectDir: projectData.projectDir,
                options,
                notConfiguredEnvOptions
            });
            this.$androidToolsInfo.validateTargetSdk({ showWarningsAsErrors: true });
            return {
                checkEnvironmentRequirementsOutput
            };
        });
    }
    createProject(frameworkDir, frameworkVersion, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
                this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`);
            }
            this.$fs.ensureDirectoryExists(this.getPlatformData(projectData).projectRoot);
            const androidToolsInfo = this.$androidToolsInfo.getToolsInfo();
            const targetSdkVersion = androidToolsInfo && androidToolsInfo.targetSdkVersion;
            this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`);
            this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "*", "-R");
            this.cleanResValues(targetSdkVersion, projectData);
        });
    }
    cleanResValues(targetSdkVersion, projectData) {
        const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData);
        const directoriesInResFolder = this.$fs.readDirectory(resDestinationDir);
        const directoriesToClean = directoriesInResFolder
            .map(dir => {
            return {
                dirName: dir,
                sdkNum: parseInt(dir.substr(AndroidProjectService.VALUES_VERSION_DIRNAME_PREFIX.length))
            };
        })
            .filter(dir => dir.dirName.match(AndroidProjectService.VALUES_VERSION_DIRNAME_PREFIX)
            && dir.sdkNum
            && (!targetSdkVersion || (targetSdkVersion < dir.sdkNum)))
            .map(dir => path.join(resDestinationDir, dir.dirName));
        this.$logger.trace("Directories to clean:");
        this.$logger.trace(directoriesToClean);
        _.map(directoriesToClean, dir => this.$fs.deleteDirectory(dir));
    }
    interpolateData(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.interpolateConfigurationFile(projectData);
            const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
            let stringsFilePath;
            const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData);
            if (this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath)) {
                stringsFilePath = path.join(appResourcesDestinationDirectoryPath, constants.MAIN_DIR, constants.RESOURCES_DIR, 'values', 'strings.xml');
            }
            else {
                stringsFilePath = path.join(appResourcesDestinationDirectoryPath, 'values', 'strings.xml');
            }
            shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath);
            shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath);
            const gradleSettingsFilePath = path.join(this.getPlatformData(projectData).projectRoot, "settings.gradle");
            shell.sed('-i', /__PROJECT_NAME__/, this.getProjectNameFromId(projectData), gradleSettingsFilePath);
            try {
                const appGradleContent = this.$fs.readText(projectData.appGradlePath);
                if (appGradleContent.indexOf(constants.PACKAGE_PLACEHOLDER_NAME) !== -1) {
                    shell.sed('-i', new RegExp(constants.PACKAGE_PLACEHOLDER_NAME), projectData.projectIdentifiers.android, projectData.appGradlePath);
                }
            }
            catch (e) {
                this.$logger.trace(`Templates updated and no need for replace in app.gradle.`);
            }
        });
    }
    interpolateConfigurationFile(projectData) {
        const manifestPath = this.getPlatformData(projectData).configurationFilePath;
        shell.sed('-i', /__PACKAGE__/, projectData.projectIdentifiers.android, manifestPath);
    }
    getProjectNameFromId(projectData) {
        let id;
        if (projectData && projectData.projectIdentifiers && projectData.projectIdentifiers.android) {
            const idParts = projectData.projectIdentifiers.android.split(".");
            id = idParts[idParts.length - 1];
        }
        return id;
    }
    afterCreateProject(projectRoot) {
        return null;
    }
    canUpdatePlatform(newInstalledModuleDir, projectData) {
        return true;
    }
    updatePlatform(currentVersion, newVersion, canUpdate, projectData, addPlatform, removePlatforms) {
        return __awaiter(this, void 0, void 0, function* () {
            if (semver.eq(newVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
                const platformLowercase = this.getPlatformData(projectData).normalizedPlatformName.toLowerCase();
                yield removePlatforms([platformLowercase.split("@")[0]]);
                yield addPlatform(platformLowercase);
                return false;
            }
            return true;
        });
    }
    buildProject(projectRoot, projectData, buildConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            const platformData = this.getPlatformData(projectData);
            yield this.$gradleBuildService.buildProject(platformData.projectRoot, buildConfig);
            const outputPath = platformData.getBuildOutputPath(buildConfig);
            yield this.$filesHashService.saveHashesForProject(this._platformData, outputPath);
        });
    }
    buildForDeploy(projectRoot, projectData, buildConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.buildProject(projectRoot, projectData, buildConfig);
        });
    }
    isPlatformPrepared(projectRoot, projectData) {
        return this.$fs.exists(path.join(this.getPlatformData(projectData).appDestinationDirectoryPath, constants.APP_FOLDER_NAME));
    }
    getFrameworkFilesExtensions() {
        return [".jar", ".dat"];
    }
    prepareProject() {
        return __awaiter(this, void 0, void 0, function* () {
        });
    }
    ensureConfigurationFileInAppResources(projectData) {
        const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath;
        const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath);
        let originalAndroidManifestFilePath;
        if (appResourcesDirStructureHasMigrated) {
            originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "src", "main", this.getPlatformData(projectData).configurationFileName);
        }
        else {
            originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName);
        }
        const manifestExists = this.$fs.exists(originalAndroidManifestFilePath);
        if (!manifestExists) {
            this.$logger.warn('No manifest found in ' + originalAndroidManifestFilePath);
            return;
        }
        if (!appResourcesDirStructureHasMigrated) {
            this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath);
        }
    }
    prepareAppResources(appResourcesDirectoryPath, projectData) {
        this.cleanUpPreparedResources(appResourcesDirectoryPath, projectData);
    }
    preparePluginNativeCode(pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME);
            if (this.$fs.exists(pluginPlatformsFolderPath)) {
                const options = {
                    projectDir: projectData.projectDir,
                    pluginName: pluginData.name,
                    platformsAndroidDirPath: pluginPlatformsFolderPath,
                    aarOutputDir: pluginPlatformsFolderPath,
                    tempPluginDirPath: path.join(projectData.platformsDir, "tempPlugin")
                };
                if (yield this.$androidPluginBuildService.buildAar(options)) {
                    this.$logger.info(`Built aar for ${options.pluginName}`);
                }
                this.$androidPluginBuildService.migrateIncludeGradle(options);
            }
        });
    }
    processConfigurationFilesFromAppResources() {
        return __awaiter(this, void 0, void 0, function* () {
            return;
        });
    }
    removePluginNativeCode(pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
        });
    }
    beforePrepareAllPlugins(projectData, dependencies) {
        return __awaiter(this, void 0, void 0, function* () {
            if (dependencies) {
                dependencies = this.filterUniqueDependencies(dependencies);
                this.provideDependenciesJson(projectData, dependencies);
            }
        });
    }
    handleNativeDependenciesChange(projectData, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            return;
        });
    }
    filterUniqueDependencies(dependencies) {
        const depsDictionary = dependencies.reduce((dict, dep) => {
            const collision = dict[dep.name];
            if (!collision || collision.depth > dep.depth) {
                dict[dep.name] = dep;
            }
            return dict;
        }, {});
        return _.values(depsDictionary);
    }
    provideDependenciesJson(projectData, dependencies) {
        const platformDir = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME);
        const dependenciesJsonPath = path.join(platformDir, constants.DEPENDENCIES_JSON_NAME);
        const nativeDependencies = dependencies
            .filter(AndroidProjectService.isNativeAndroidDependency)
            .map(({ name, directory }) => ({ name, directory: path.relative(platformDir, directory) }));
        const jsonContent = JSON.stringify(nativeDependencies, null, 4);
        this.$fs.writeFile(dependenciesJsonPath, jsonContent);
    }
    static isNativeAndroidDependency({ nativescript }) {
        return nativescript && (nativescript.android || (nativescript.platforms && nativescript.platforms.android));
    }
    stopServices(projectRoot) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield this.$gradleCommandService.executeCommand(["--stop", "--quiet"], {
                cwd: projectRoot,
                message: "Gradle stop services...",
                stdio: "pipe"
            });
            return result;
        });
    }
    cleanProject(projectRoot, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$gradleBuildService.cleanProject(projectRoot, { release: false });
        });
    }
    cleanDeviceTempFolder(deviceIdentifier, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const adb = this.$injector.resolve(device_android_debug_bridge_1.DeviceAndroidDebugBridge, { identifier: deviceIdentifier });
            const deviceRootPath = `${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${projectData.projectIdentifiers.android}`;
            yield adb.executeShellCommand(["rm", "-rf", deviceRootPath]);
        });
    }
    checkForChanges() {
        return __awaiter(this, void 0, void 0, function* () {
        });
    }
    getDeploymentTarget(projectData) { return; }
    copy(projectRoot, frameworkDir, files, cpArg) {
        const paths = files.split(' ').map(p => path.join(frameworkDir, p));
        shell.cp(cpArg, paths, projectRoot);
    }
    validatePackageName(packageName) {
        if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(packageName)) {
            this.$errors.fail("Package name must look like: com.company.Name");
        }
        if (/\b[Cc]lass\b/.test(packageName)) {
            this.$errors.fail("class is a reserved word");
        }
    }
    validateProjectName(projectName) {
        if (projectName === '') {
            this.$errors.fail("Project name cannot be empty");
        }
        if (/^[0-9]/.test(projectName)) {
            this.$errors.fail("Project name must not begin with a number");
        }
    }
    getLegacyAppResourcesDestinationDirPath(projectData) {
        const resourcePath = [constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR];
        return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
    }
    getUpdatedAppResourcesDestinationDirPath(projectData) {
        const resourcePath = [constants.APP_FOLDER_NAME, constants.SRC_DIR];
        return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
    }
    cleanUpPreparedResources(appResourcesDirectoryPath, projectData) {
        let resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName);
        if (this.$androidResourcesMigrationService.hasMigrated(projectData.appResourcesDirectoryPath)) {
            resourcesDirPath = path.join(resourcesDirPath, constants.MAIN_DIR, constants.RESOURCES_DIR);
        }
        const valuesDirRegExp = /^values/;
        if (this.$fs.exists(resourcesDirPath)) {
            const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp));
            const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData);
            _.each(resourcesDirs, resourceDir => {
                this.$fs.deleteDirectory(path.join(appResourcesDestinationDirectoryPath, resourceDir));
            });
        }
    }
}
AndroidProjectService.VALUES_DIRNAME = "values";
AndroidProjectService.VALUES_VERSION_DIRNAME_PREFIX = AndroidProjectService.VALUES_DIRNAME + "-v";
AndroidProjectService.ANDROID_PLATFORM_NAME = "android";
AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE = "1.5.0";
__decorate([
    decorators_1.performanceLog()
], AndroidProjectService.prototype, "buildProject", null);
exports.AndroidProjectService = AndroidProjectService;
$injector.register("androidProjectService", AndroidProjectService);
