"use strict";
const path = require("path");
const semver = require("semver");
const os_1 = require("os");
class AndroidToolsInfo {
    constructor($childProcess, $errors, $fs, $hostInfo, $logger, $options, $adb, $staticConfig) {
        this.$childProcess = $childProcess;
        this.$errors = $errors;
        this.$fs = $fs;
        this.$hostInfo = $hostInfo;
        this.$logger = $logger;
        this.$options = $options;
        this.$adb = $adb;
        this.$staticConfig = $staticConfig;
        this.installedTargetsCache = null;
        this.androidHome = process.env["ANDROID_HOME"];
        this._cachedAndroidHomeValidationResult = null;
    }
    get androidExecutableName() {
        if (!this._androidExecutableName) {
            this._androidExecutableName = "android";
            if (this.$hostInfo.isWindows) {
                this._androidExecutableName += ".bat";
            }
        }
        return this._androidExecutableName;
    }
    getPathToAndroidExecutable(options) {
        return (() => {
            if (options) {
                this.showWarningsAsErrors = options.showWarningsAsErrors;
            }
            if (!this.pathToAndroidExecutable) {
                if (this.validateAndroidHomeEnvVariable(this.androidHome)) {
                    let androidPath = path.join(this.androidHome, "tools", this.androidExecutableName);
                    if (!this.trySetAndroidPath(androidPath).wait() && !this.trySetAndroidPath(this.androidExecutableName).wait()) {
                        this.printMessage(`Unable to find "${this.androidExecutableName}" executable file. Make sure you have set ANDROID_HOME environment variable correctly.`);
                    }
                }
                else {
                    this.$logger.trace("ANDROID_HOME environment variable is not set correctly.");
                }
            }
            return this.pathToAndroidExecutable;
        }).future()();
    }
    trySetAndroidPath(androidPath) {
        return (() => {
            let isAndroidPathCorrect = true;
            try {
                let result = this.$adb.executeCommand(["--help"], { returnChildProcess: true }).wait();
                if (result && result.stdout) {
                    this.$logger.trace(result.stdout);
                    this.pathToAndroidExecutable = androidPath;
                }
                else {
                    this.$logger.trace(`Unable to find android executable from '${androidPath}'.`);
                    isAndroidPathCorrect = false;
                }
            }
            catch (err) {
                this.$logger.trace(`Error occurred while checking androidExecutable from '${androidPath}'. ${err.message}`);
                isAndroidPathCorrect = false;
            }
            return isAndroidPathCorrect;
        }).future()();
    }
    getToolsInfo() {
        return (() => {
            if (!this.toolsInfo) {
                let infoData = Object.create(null);
                infoData.androidHomeEnvVar = this.androidHome;
                infoData.compileSdkVersion = this.getCompileSdk().wait();
                infoData.buildToolsVersion = this.getBuildToolsVersion().wait();
                infoData.targetSdkVersion = this.getTargetSdk().wait();
                infoData.supportRepositoryVersion = this.getAndroidSupportRepositoryVersion().wait();
                infoData.generateTypings = this.shouldGenerateTypings();
                this.toolsInfo = infoData;
            }
            return this.toolsInfo;
        }).future()();
    }
    validateInfo(options) {
        return (() => {
            let detectedErrors = false;
            this.showWarningsAsErrors = options && options.showWarningsAsErrors;
            let toolsInfoData = this.getToolsInfo().wait();
            let isAndroidHomeValid = this.validateAndroidHomeEnvVariable(toolsInfoData.androidHomeEnvVar);
            if (!toolsInfoData.compileSdkVersion) {
                this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`, "Run `$ android` to manage your Android SDK versions.");
                detectedErrors = true;
            }
            if (!toolsInfoData.buildToolsVersion) {
                let buildToolsRange = this.getBuildToolsRange();
                let versionRangeMatches = buildToolsRange.match(/^.*?([\d\.]+)\s+.*?([\d\.]+)$/);
                let message = `You can install any version in the following range: '${buildToolsRange}'.`;
                if (versionRangeMatches && versionRangeMatches[1] && versionRangeMatches[2] && versionRangeMatches[1] === versionRangeMatches[2]) {
                    message = `You have to install version ${versionRangeMatches[1]}.`;
                }
                let invalidBuildToolsAdditionalMsg = 'Run `android` from your command-line to install required `Android Build Tools`.';
                if (!isAndroidHomeValid) {
                    invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.';
                }
                this.printMessage("You need to have the Android SDK Build-tools installed on your system. " + message, invalidBuildToolsAdditionalMsg);
                detectedErrors = true;
            }
            if (!toolsInfoData.supportRepositoryVersion) {
                let invalidSupportLibAdditionalMsg = 'Run `$ android`  to manage the Android Support Repository.';
                if (!isAndroidHomeValid) {
                    invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.';
                }
                this.printMessage(`You need to have Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later and the latest Android Support Repository installed on your system.`, invalidSupportLibAdditionalMsg);
                detectedErrors = true;
            }
            if (options && options.validateTargetSdk) {
                let targetSdk = toolsInfoData.targetSdkVersion;
                let newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`;
                if (!_.includes(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) {
                    let supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort();
                    let minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions));
                    if (targetSdk && (targetSdk < minSupportedVersion)) {
                        this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You must target ${minSupportedVersion} or later.`);
                        detectedErrors = true;
                    }
                    else if (!targetSdk || targetSdk > this.getMaxSupportedVersion()) {
                        this.$logger.warn(`Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`);
                    }
                }
            }
            return detectedErrors || !isAndroidHomeValid;
        }).future()();
    }
    validateJavacVersion(installedJavaVersion, options) {
        return (() => {
            let hasProblemWithJavaVersion = false;
            if (options) {
                this.showWarningsAsErrors = options.showWarningsAsErrors;
            }
            let additionalMessage = "You will not be able to build your projects for Android." + os_1.EOL
                + "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + os_1.EOL +
                " described in " + this.$staticConfig.SYS_REQUIREMENTS_LINK;
            let matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX);
            if (matchingVersion && matchingVersion[1]) {
                if (semver.lt(matchingVersion[1], AndroidToolsInfo.MIN_JAVA_VERSION)) {
                    hasProblemWithJavaVersion = true;
                    this.printMessage(`Javac version ${installedJavaVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, additionalMessage);
                }
            }
            else {
                hasProblemWithJavaVersion = true;
                this.printMessage("Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", additionalMessage);
            }
            return hasProblemWithJavaVersion;
        }).future()();
    }
    getPathToAdbFromAndroidHome() {
        return (() => {
            if (this.androidHome) {
                let pathToAdb = path.join(this.androidHome, "platform-tools", "adb");
                try {
                    this.$childProcess.execFile(pathToAdb, ["help"]).wait();
                    return pathToAdb;
                }
                catch (err) {
                    this.$logger.trace(`Error while executing '${pathToAdb} help'. Error is: ${err.message}`);
                }
            }
            return null;
        }).future()();
    }
    shouldGenerateTypings() {
        return this.$options.androidTypings;
    }
    printMessage(msg, additionalMsg) {
        if (this.showWarningsAsErrors) {
            this.$errors.failWithoutHelp(msg);
        }
        else {
            this.$logger.warn(msg);
        }
        if (additionalMsg) {
            this.$logger.printMarkdown(additionalMsg);
        }
    }
    getCompileSdk() {
        return (() => {
            if (!this.selectedCompileSdk) {
                let userSpecifiedCompileSdk = this.$options.compileSdk;
                if (userSpecifiedCompileSdk) {
                    let installedTargets = this.getInstalledTargets().wait();
                    let androidCompileSdk = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${userSpecifiedCompileSdk}`;
                    if (!_.includes(installedTargets, androidCompileSdk)) {
                        this.$errors.failWithoutHelp(`You have specified '${userSpecifiedCompileSdk}' for compile sdk, but it is not installed on your system.`);
                    }
                    this.selectedCompileSdk = userSpecifiedCompileSdk;
                }
                else {
                    let latestValidAndroidTarget = this.getLatestValidAndroidTarget().wait();
                    if (latestValidAndroidTarget) {
                        let integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget);
                        if (integerVersion && integerVersion >= AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET) {
                            this.selectedCompileSdk = integerVersion;
                        }
                    }
                }
            }
            return this.selectedCompileSdk;
        }).future()();
    }
    getTargetSdk() {
        return (() => {
            let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdk().wait();
            this.$logger.trace(`Selected targetSdk is: ${targetSdk}`);
            return targetSdk;
        }).future()();
    }
    getMatchingDir(pathToDir, versionRange) {
        let selectedVersion;
        if (this.$fs.exists(pathToDir)) {
            let subDirs = this.$fs.readDirectory(pathToDir);
            this.$logger.trace(`Directories found in ${pathToDir} are ${subDirs.join(", ")}`);
            let subDirsVersions = subDirs
                .map(dirName => {
                let dirNameGroups = dirName.match(AndroidToolsInfo.VERSION_REGEX);
                if (dirNameGroups) {
                    return dirNameGroups[1];
                }
                return null;
            })
                .filter(dirName => !!dirName);
            this.$logger.trace(`Versions found in ${pathToDir} are ${subDirsVersions.join(", ")}`);
            let version = semver.maxSatisfying(subDirsVersions, versionRange);
            if (version) {
                selectedVersion = _.find(subDirs, dir => dir.indexOf(version) !== -1);
            }
        }
        this.$logger.trace("Selected version is: ", selectedVersion);
        return selectedVersion;
    }
    getBuildToolsRange() {
        return `${AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX} <=${this.getMaxSupportedVersion()}`;
    }
    getBuildToolsVersion() {
        return (() => {
            let buildToolsVersion;
            if (this.androidHome) {
                let pathToBuildTools = path.join(this.androidHome, "build-tools");
                let buildToolsRange = this.getBuildToolsRange();
                buildToolsVersion = this.getMatchingDir(pathToBuildTools, buildToolsRange);
            }
            return buildToolsVersion;
        }).future()();
    }
    getAppCompatRange() {
        return (() => {
            let compileSdkVersion = this.getCompileSdk().wait();
            let requiredAppCompatRange;
            if (compileSdkVersion) {
                requiredAppCompatRange = `>=${compileSdkVersion} <${compileSdkVersion + 1}`;
            }
            return requiredAppCompatRange;
        }).future()();
    }
    getAndroidSupportRepositoryVersion() {
        return (() => {
            let selectedAppCompatVersion;
            let requiredAppCompatRange = this.getAppCompatRange().wait();
            if (this.androidHome && requiredAppCompatRange) {
                let pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7");
                selectedAppCompatVersion = this.getMatchingDir(pathToAppCompat, requiredAppCompatRange);
            }
            this.$logger.trace(`Selected AppCompat version is: ${selectedAppCompatVersion}`);
            return selectedAppCompatVersion;
        }).future()();
    }
    getLatestValidAndroidTarget() {
        return (() => {
            let installedTargets = this.getInstalledTargets().wait();
            return _.findLast(AndroidToolsInfo.SUPPORTED_TARGETS.sort(), supportedTarget => _.includes(installedTargets, supportedTarget));
        }).future()();
    }
    parseAndroidSdkString(androidSdkString) {
        return parseInt(androidSdkString.replace(`${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-`, ""));
    }
    getInstalledTargets() {
        return (() => {
            if (!this.installedTargetsCache) {
                try {
                    let pathToAndroidExecutable = this.getPathToAndroidExecutable().wait();
                    if (pathToAndroidExecutable) {
                        let result = this.$childProcess.spawnFromEvent(pathToAndroidExecutable, ["list", "targets"], "close", {}, { throwError: false }).wait();
                        if (result && result.stdout) {
                            this.$logger.trace(result.stdout);
                            this.installedTargetsCache = [];
                            result.stdout.replace(/id: \d+ or "(.+)"/g, (m, p1) => (this.installedTargetsCache.push(p1), m));
                        }
                    }
                }
                catch (err) {
                    this.$logger.trace("Unable to get Android targets. Error is: " + err);
                }
            }
            return this.installedTargetsCache;
        }).future()();
    }
    getMaxSupportedVersion() {
        return this.parseAndroidSdkString(_.last(AndroidToolsInfo.SUPPORTED_TARGETS.sort()));
    }
    validateAndroidHomeEnvVariable(androidHomeEnvVar) {
        if (this._cachedAndroidHomeValidationResult === null) {
            this._cachedAndroidHomeValidationResult = true;
            let expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
            if (!androidHomeEnvVar || !this.$fs.exists(androidHomeEnvVar)) {
                this.printMessage("The ANDROID_HOME environment variable is not set or it points to a non-existent directory. You will not be able to perform any build-related operations for Android.", "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory.");
                this._cachedAndroidHomeValidationResult = false;
            }
            else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(androidHomeEnvVar, dir))))) {
                this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.", "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory, " +
                    "where you will find `tools` and `platform-tools` directories.");
                this._cachedAndroidHomeValidationResult = false;
            }
        }
        return this._cachedAndroidHomeValidationResult;
    }
}
AndroidToolsInfo.ANDROID_TARGET_PREFIX = "android";
AndroidToolsInfo.SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22", "android-23", "android-24", "android-25"];
AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET = 22;
AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=23";
AndroidToolsInfo.VERSION_REGEX = /((\d+\.){2}\d+)/;
AndroidToolsInfo.MIN_JAVA_VERSION = "1.8.0";
exports.AndroidToolsInfo = AndroidToolsInfo;
$injector.register("androidToolsInfo", AndroidToolsInfo);
