const { isAbsolute, join, resolve, sep } = require("path");
const { readFileSync, writeFileSync } = require("fs");

const shelljs = require("shelljs");
const semver = require("semver");

const SnapshotGenerator = require("./snapshot-generator");
const {
    CONSTANTS
} = require("./utils");
const {
    ANDROID_PROJECT_DIR,
    ANDROID_APP_PATH,
    ANDROID_CONFIGURATIONS_PATH,
    getAndroidRuntimeVersion,
    getAndroidV8Version,
    getRuntimeNdkRevision,
    getMksnapshotParams
} = require("../../helpers/androidProjectHelpers");

// min version with settings.json file specifying the V8 version
const MIN_ANDROID_RUNTIME_VERSION = "5.2.1";
const VALID_ANDROID_RUNTIME_TAGS = Object.freeze(["next", "rc"]);

const resolveRelativePath = (path) => {
    if (path)
        return isAbsolute(path) ? path : resolve(process.cwd(), path);
    return null;
};

function ProjectSnapshotGenerator(options) {
    this.options = options = options || {};
    options.projectRoot = resolveRelativePath(options.projectRoot) || process.cwd();

    console.log("Project root: " + options.projectRoot);
    console.log("Snapshots build directory: " + this.getBuildPath());

    this.validateAndroidRuntimeVersion();
}
module.exports = ProjectSnapshotGenerator;

ProjectSnapshotGenerator.calculateBuildPath = function (projectRoot) {
    return join(
        ProjectSnapshotGenerator.calculateProjectPath(projectRoot),
        "snapshot-build",
        "build"
    );
}

ProjectSnapshotGenerator.prototype.getBuildPath = function () {
    return ProjectSnapshotGenerator.calculateBuildPath(this.options.projectRoot);
}

ProjectSnapshotGenerator.calculateProjectPath = function (projectRoot) {
    return join(projectRoot, ANDROID_PROJECT_DIR);
}

ProjectSnapshotGenerator.calculateConfigurationsPath = function (projectRoot) {
    return join(projectRoot, ANDROID_CONFIGURATIONS_PATH);
}

ProjectSnapshotGenerator.calculateAppPath = function (projectRoot) {
    return join(projectRoot, ANDROID_APP_PATH);
}

ProjectSnapshotGenerator.prototype.getProjectPath = function () {
    return ProjectSnapshotGenerator.calculateProjectPath(this.options.projectRoot);
}

ProjectSnapshotGenerator.cleanSnapshotArtefacts = function (projectRoot) {
    const platformPath = ProjectSnapshotGenerator.calculateProjectPath(projectRoot);

    // Remove blob files from prepared folder
    shelljs.rm("-rf", join(platformPath, "src/main/assets/snapshots"));

    // Remove prepared include.gradle configurations
    const configurationsPath = ProjectSnapshotGenerator.calculateConfigurationsPath(projectRoot);
    shelljs.rm("-rf", join(configurationsPath, SnapshotGenerator.SNAPSHOT_PACKAGE_NANE));
}

ProjectSnapshotGenerator.installSnapshotArtefacts = function (projectRoot) {
    const buildPath = ProjectSnapshotGenerator.calculateBuildPath(projectRoot);
    const platformPath = ProjectSnapshotGenerator.calculateProjectPath(projectRoot);

    const appPath = ProjectSnapshotGenerator.calculateAppPath(projectRoot);
    const configurationsPath = ProjectSnapshotGenerator.calculateConfigurationsPath(projectRoot);
    const configDestinationPath = join(configurationsPath, SnapshotGenerator.SNAPSHOT_PACKAGE_NANE);

    // Remove build folder to make sure that the apk will be fully rebuild
    shelljs.rm("-rf", join(platformPath, "build"));

    // Copy include.gradle to the specified destination in the platforms folder
    shelljs.mkdir("-p", configDestinationPath);
    shelljs.cp(join(buildPath, "include.gradle"), join(configDestinationPath, "include.gradle"));

    if (shelljs.test("-e", join(buildPath, "ndk-build/libs"))) {
        // useLibs = true
        const libsDestinationPath = join(platformPath, "src", SnapshotGenerator.SNAPSHOT_PACKAGE_NANE, "jniLibs");

        // Copy the libs to the specified destination in the platforms folder
        shelljs.mkdir("-p", libsDestinationPath);
        shelljs.cp("-R", join(buildPath, "ndk-build/libs") + sep, libsDestinationPath);
    } else {
        // useLibs = false
        const blobsSrcPath = join(buildPath, "snapshots/blobs");
        const blobsDestinationPath = resolve(appPath, "../snapshots");
        const appPackageJsonPath = join(appPath, "package.json");

        // Copy the blobs in the prepared app folder
        shelljs.cp("-R", blobsSrcPath + sep, resolve(appPath, "../snapshots"));

        // Update the package.json file
        const appPackageJson = shelljs.test("-e", appPackageJsonPath) ? JSON.parse(readFileSync(appPackageJsonPath, 'utf8')) : {};
        appPackageJson["android"] = appPackageJson["android"] || {};
        appPackageJson["android"]["heapSnapshotBlob"] = "../snapshots";
        writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
    }
}

ProjectSnapshotGenerator.prototype.validateAndroidRuntimeVersion = function () {
    const currentRuntimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);

    if (!currentRuntimeVersion || !this.getProjectPath()) {
        throw new Error("In order to generate a V8 snapshot you must have the \"android\" platform installed - to do so please run \"tns platform add android\".");
    }

    if (!VALID_ANDROID_RUNTIME_TAGS.includes(currentRuntimeVersion) &&
        !semver.gte(currentRuntimeVersion, MIN_ANDROID_RUNTIME_VERSION)) {

        throw new Error("In order to support heap snapshots, you must have at least tns-android@" + MIN_ANDROID_RUNTIME_VERSION +
            " installed. Current Android Runtime version is: " + currentRuntimeVersion + ".");
    }
}

ProjectSnapshotGenerator.prototype.generate = function (generationOptions) {
    if (generationOptions.skipSnapshotTools) {
        console.log("Skipping snapshot tools.");
        return Promise.resolve();
    }

    generationOptions = generationOptions || {};

    console.log("Running snapshot generation with the following arguments: ");
    console.log(JSON.stringify(generationOptions, null, '\t'));

    // Clean build folder
    shelljs.rm("-rf", this.getBuildPath());
    shelljs.mkdir("-p", this.getBuildPath());

    const snapshotToolsPath = resolveRelativePath(generationOptions.snapshotToolsPath) || CONSTANTS.SNAPSHOT_TMP_DIR;
    console.log("Snapshot tools path: " + snapshotToolsPath);

    // Generate snapshots
    const generator = new SnapshotGenerator({ buildPath: this.getBuildPath() });
    const noV8VersionFoundMessage = `Cannot find suitable v8 version!`;
    const mksnapshotParams = getMksnapshotParams(this.options.projectRoot);
    const recommendedAndroidNdkRevision = getRuntimeNdkRevision(this.options.projectRoot);
    const v8Version = generationOptions.v8Version || getAndroidV8Version(this.options.projectRoot);
    if (!v8Version) {
        throw new Error(noV8VersionFoundMessage);
    }

    // NOTE: Order is important! Add new archs at the end of the array
    const defaultTargetArchs = ["arm", "arm64", "ia32", "ia64"];
    const runtimeVersion = getAndroidRuntimeVersion(this.options.projectRoot);
    if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.0.2")) {
        const indexOfIa64 = defaultTargetArchs.indexOf("ia64");
        // Before 6.0.2 version of Android runtime we supported only arm, arm64 and ia32.
        defaultTargetArchs.splice(indexOfIa64, defaultTargetArchs.length - indexOfIa64);
    }

    const options = {
        snapshotToolsPath,
        targetArchs: generationOptions.targetArchs || defaultTargetArchs,
        v8Version: generationOptions.v8Version || v8Version,
        preprocessedInputFile: generationOptions.preprocessedInputFile,
        useLibs: generationOptions.useLibs || false,
        inputFiles: generationOptions.inputFiles || [join(this.options.projectRoot, "__snapshot.js")],
        androidNdkPath: generationOptions.androidNdkPath,
        mksnapshotParams: mksnapshotParams,
        snapshotInDocker: generationOptions.snapshotInDocker,
        recommendedAndroidNdkRevision,
        runtimeVersion
    };

    return generator.generate(options).then(() => {
        console.log("Snapshots build finished succesfully!");

        if (generationOptions.install) {
            ProjectSnapshotGenerator.cleanSnapshotArtefacts(this.options.projectRoot);
            ProjectSnapshotGenerator.installSnapshotArtefacts(this.options.projectRoot);
            console.log(generationOptions.useLibs ?
                "Snapshot is included in the app as dynamically linked library (.so file)." :
                "Snapshot is included in the app as binary .blob file. The more space-efficient option is to embed it in a dynamically linked library (.so file).");
        }
    });;
}
