/**
  * ******************************************************
  *                                                       *
  *   Copyright (C) Microsoft. All rights reserved.       *
  *                                                       *
  * ******************************************************
  */
/// <reference path="../customTypings/vcremote-utils.d.ts" />
"use strict";
const utils = require("vcremote-utils");
var util = utils.UtilHelper;
const build = require("./build");
const fs = require("fs");
const unzipper = require("unzipper");
var tmp = require("tmp");
var uuid = require("uuid");
var _ = require("lodash");
var spawnSync = require("child_process").spawnSync;
const rimraf = require("rimraf");
const path = require("path");
const events = require("events");
const nconf = require("nconf");
var resources = utils.ResourcesManager;
var VCRemoteLib;
(function (VCRemoteLib) {
    // Will use /localization directory with translated strings if exists in npm installation, otherwise use default english strings.
    var pathToLocalizedStrings = path.join(__dirname + "../../../../", "localization/app/localization/i18n");
    if (fs.existsSync(pathToLocalizedStrings)) {
        resources.init(nconf.get("lang"), pathToLocalizedStrings);
    }
    else {
        resources.init(nconf.get("lang"), path.join(__dirname, "./resources"));
    }
    Promise.defer = function () {
        var result = {};
        result.promise = new Promise(function (resolve, reject) {
            result.resolve = resolve;
            result.reject = reject;
        });
        return result;
    };
    let ProjectState;
    (function (ProjectState) {
        ProjectState[ProjectState["UPLOADING"] = 0] = "UPLOADING";
        ProjectState[ProjectState["UPLOADED"] = 1] = "UPLOADED";
        ProjectState[ProjectState["EXTRACTING"] = 2] = "EXTRACTING";
        ProjectState[ProjectState["EXTRACTED"] = 3] = "EXTRACTED";
        ProjectState[ProjectState["CLEANING"] = 4] = "CLEANING";
        ProjectState[ProjectState["UPDATING"] = 5] = "UPDATING";
        ProjectState[ProjectState["UPDATED"] = 6] = "UPDATED";
        ProjectState[ProjectState["PENDING"] = 7] = "PENDING";
        ProjectState[ProjectState["ERROR"] = 8] = "ERROR";
        ProjectState[ProjectState["READY"] = 9] = "READY";
    })(ProjectState = VCRemoteLib.ProjectState || (VCRemoteLib.ProjectState = {}));
    let KeepPolicy;
    (function (KeepPolicy) {
        KeepPolicy[KeepPolicy["KEEP_FOREVER"] = 0] = "KEEP_FOREVER";
        KeepPolicy[KeepPolicy["KEEP_UNTIL_EXPIRED"] = 1] = "KEEP_UNTIL_EXPIRED";
        KeepPolicy[KeepPolicy["TEMPORARY"] = 2] = "TEMPORARY";
    })(KeepPolicy = VCRemoteLib.KeepPolicy || (VCRemoteLib.KeepPolicy = {}));
    class SDK {
        get platformName() {
            return this._platformName;
        }
        get version() {
            return this._version;
        }
        get name() {
            return this._name;
        }
        constructor(name) {
            this._name = "";
            this._platformName = "";
            this._version = 0;
            this._name = name;
            this.parseSdkPlatform();
            this.parseSdkVersion();
        }
        parseSdkPlatform() {
            var platformName = "";
            if (this._name.indexOf("macosx") != -1) {
                this._platformName = "macosx";
            }
            else if (this._name.indexOf("iphone") != -1) {
                if (this._name.indexOf("simulator") != -1) {
                    this._platformName = "iphonesimulator";
                }
                else {
                    this._platformName = "iphoneos";
                }
            }
        }
        parseSdkVersion() {
            var re = /\d+(\.\d+)*$/img;
            var m = re.exec(this._name);
            if (m != null && m[0] != undefined) {
                this._version = parseFloat(m[0]);
            }
        }
    }
    VCRemoteLib.SDK = SDK;
    class SDKList {
        static get list() {
            var result = spawnSync("xcodebuild", ["-showsdks"]);
            var output = result.stdout.toString("utf8", 0, result.stdout.length);
            var re = /(macosx|iphone(os|simulator))(\d+(\.\d+)*)/img;
            var m;
            var sdks = [];
            while ((m = re.exec(output)) !== null) {
                if (m.index === re.lastIndex) {
                    re.lastIndex++;
                }
                var sdk = new SDK(m[0].toLowerCase());
                sdks.push(sdk);
            }
            return sdks;
        }
    }
    VCRemoteLib.SDKList = SDKList;
    class RemoteProjectBase {
        get req() {
            return this._req;
        }
        get collection() {
            return this._collection;
        }
        set collection(collection) {
            this._collection = collection;
        }
        get state() {
            return this._state;
        }
        set state(state) {
            this._state = state;
            this._stateList.push(state);
        }
        get history() {
            return this._stateList;
        }
        get project() {
            return this._project;
        }
        set project(project) {
            this._project = project;
        }
        constructor(req) {
            this._state = ProjectState.READY;
            this._stateList = [];
            this._req = req;
        }
        retrieve() {
            var deferred = new Promise.defer;
            var self = this;
            // override the project location if asked
            if (this.req.query.loc != undefined) {
                this._project.directory = this.req.query.loc;
            }
            console.info(resources.getStringForLanguage(nconf.get("lang"), "UploadingProject", this._project.id, this._project.directory));
            this.state = ProjectState.PENDING;
            this._project.state = ProjectState.PENDING;
            var resource = new RemoteZipResource(this._req);
            resource.on("error", function (err) {
                self.state = ProjectState.ERROR;
                deferred.reject(err);
            });
            resource.on("uploading", function () {
                self.state = ProjectState.UPLOADING;
            });
            resource.on("uploaded", function () {
                self.state = ProjectState.UPLOADED;
            });
            resource.on("extracting", function () {
                self.state = ProjectState.EXTRACTING;
            });
            resource.on("extracted", function () {
                self.state = ProjectState.EXTRACTED;
                deferred.resolve(self._project);
            });
            resource.retrieve(self._project.directory);
            return deferred.promise;
        }
        update(project) {
            return project.update(this);
        }
    }
    VCRemoteLib.RemoteProjectBase = RemoteProjectBase;
    class RemoteZipResource extends events.EventEmitter {
        get req() {
            return this._req;
        }
        constructor(req) {
            super();
            this._req = req;
        }
        retrieve(dir) {
            var self = this;
            try {
                util.createDirectoryIfNecessary(dir);
            }
            catch (err) {
                console.error(resources.getStringForLanguage(nconf.get("lang"), "ErrorCreatingDir", dir));
                this.emit("error", new Error("An error occurred trying to create directory"));
                return;
            }
            this.emit("uploading");
            if (this.req.query.payload === "false") {
                self.emit("uploaded");
                self.emit("extracting");
                self.emit("extracted");
                return;
            }
            this.retrievePayload()
                .then(function (fileName) {
                self.emit("uploaded");
                self.emit("extracting");
                return self.unzipPayload(fileName, dir).then(function () {
                    self.emit("extracted");
                });
            })
                .catch(function (reason) {
                self.emit("error", reason);
            });
        }
        retrievePayload() {
            var deferred = new Promise.defer;
            var self = this;
            tmp.file({ postfix: '.zip', keep: true }, function (err, tmpFileName, fd, cleanup) {
                deferred.promise.finally(cleanup);
                if (err) {
                    deferred.reject(err);
                }
                console.info(resources.getStringForLanguage(nconf.get("lang"), "UploadingPayload", tmpFileName));
                self.req.pipe(fs.createWriteStream(tmpFileName))
                    .on("finish", function () {
                    if (fs.statSync(tmpFileName).size == 0) {
                        console.error(resources.getStringForLanguage(nconf.get("lang"), "EmptyPOSTRequest"));
                        deferred.reject(new Error(resources.getStringForLanguage(nconf.get("lang"), "EmptyPOSTRequest")));
                    }
                    else {
                        console.info(resources.getStringForLanguage(nconf.get("lang"), "FinishedUploadingPayload"));
                        deferred.resolve(tmpFileName);
                    }
                })
                    .on("error", function (err) {
                    console.error(resources.getStringForLanguage(nconf.get("lang"), "ErrorUploadingPayload", tmpFileName));
                    deferred.reject(err);
                });
            });
            return deferred.promise;
        }
        unzipPayload(zipFileName, targetDir) {
            var deferred = new Promise.defer;
            if (!fs.existsSync(zipFileName)) {
                deferred.reject("payload not found!");
                console.info(resources.getStringForLanguage(nconf.get("lang"), "PayloadNotFoundError", zipFileName));
            }
            else {
                fs.createReadStream(zipFileName)
                    .pipe(unzipper.Extract({ path: targetDir }))
                    .on("finish", function () {
                    console.info(resources.getStringForLanguage(nconf.get("lang"), "FinishedExtractingPayload"));
                    var path = targetDir + "/.lastSyncTime";
                    if (!fs.existsSync(path)) { // CodeQL [SM01514] Safe use per thread model: All code is downloaded to user device, and can be altered if granted physical access, but still presents no threats as protected by manual certificate exchange and authentication. (https://threatmodelingportal.security.azure/Service/ReviewDetails?reviewId=e38e8e6a-8826-45de-8f13-9877e8e54dd2)
                        fs.writeFileSync(path, ""); // CodeQL [SM01514] Safe use per thread model: All code is downloaded to user device, and can be altered if granted physical access, but still presents no threats as protected by manual certificate exchange and authentication. (https://threatmodelingportal.security.azure/Service/ReviewDetails?reviewId=e38e8e6a-8826-45de-8f13-9877e8e54dd2)
                    }
                    else {
                        var timeNow = Date.now() / 1000;
                        fs.utimesSync(path, timeNow, timeNow); // CodeQL [SM01514] Safe use per thread model: All code is downloaded to user device, and can be altered if granted physical access, but still presents no threats as protected by manual certificate exchange and authentication. (https://threatmodelingportal.security.azure/Service/ReviewDetails?reviewId=e38e8e6a-8826-45de-8f13-9877e8e54dd2)
                    }
                    deferred.resolve(targetDir);
                })
                    .on("error", function (err) {
                    console.error(resources.getStringForLanguage(nconf.get("lang"), "ErrorUnzippingPayload", zipFileName, targetDir));
                    deferred.reject(err);
                });
            }
            console.info(resources.getStringForLanguage(nconf.get("lang"), "UnzippingPayload", zipFileName, targetDir));
            return deferred.promise;
        }
    }
    VCRemoteLib.RemoteZipResource = RemoteZipResource;
    class RemoteSrcProject extends RemoteProjectBase {
        constructor(req) {
            super(req);
        }
        retrieve() {
            this.project = new LocalSrcProject(this.collection);
            this.project.keepPolicy = KeepPolicy[this.req.query.keep];
            return super.retrieve();
        }
    }
    VCRemoteLib.RemoteSrcProject = RemoteSrcProject;
    class RemoteCfgProject extends RemoteProjectBase {
        get srcProject() {
            return this._srcProject;
        }
        set srcProject(prj) {
            this._srcProject = prj;
        }
        constructor(req) {
            super(req);
        }
        retrieve() {
            var configName = this.req.query.config || "";
            var arch = this.req.query.arch || "";
            var sdk = this.req.query.sdk || "";
            var emulatorBuild = (this.req.query.emulatorBuild === "true") || false;
            var outputDirectory = this.req.query.outDir;
            var provisioningProfile = this.req.query.provisioning || "";
            var pbxProject = this.req.query.pbxProject || "";
            var config = new ProjectConfig(this._srcProject, { config: configName,
                arch: arch,
                sdk: sdk,
                emulatorBuild: emulatorBuild,
                outputDirectory: outputDirectory,
                provisioningProfile: provisioningProfile,
                pbxProject: pbxProject });
            if (this.srcProject.configs.hasConfig(config)) {
                return new Promise(function (resolve, reject) { reject(new Error("Project config already exists! please use updateConfig.")); });
            }
            if (this.req.query.prjDir != undefined) {
                config.projectDirectory = this.req.query.prjDir;
            }
            this.project = new LocalCfgProject(this._srcProject, config);
            if (this.req.query.loc != undefined) {
                this.project.directory = this.req.query.loc;
            }
            return super.retrieve();
        }
    }
    VCRemoteLib.RemoteCfgProject = RemoteCfgProject;
    class ProjectConfig {
        get id() {
            return this._id;
        }
        get project() {
            return this._project;
        }
        get projectDirectory() {
            return this._projectDirectory;
        }
        set projectDirectory(dir) {
            this._projectDirectory = dir;
        }
        get outputDirectory() {
            return this._outputDirectory;
        }
        get provisioningProfile() {
            return this._provisioningProfile;
        }
        get pbxProject() {
            return this._pbxProject;
        }
        get arch() {
            return this._arch;
        }
        get sdk() {
            return this._sdk;
        }
        get target() {
            return this._target;
        }
        get workspace() {
            return this._workspace;
        }
        get config() {
            return this._config;
        }
        get scheme() {
            return this._scheme;
        }
        constructor(project, context) {
            // syntesize the project config out of the user context
            this._id = uuid.v4();
            this._project = project;
            this._arch = context.arch;
            if (context.sdk == undefined || context.sdk.length == 0) {
                if (context.arch != undefined && (context.arch === "i386" || context.arch === "x86_64")) {
                    var sdk = null;
                    var latestVersion = Number.MIN_VALUE;
                    SDKList.list.forEach(function (currentSDK) {
                        if (currentSDK.name.indexOf("simulator") != -1 && currentSDK.version > latestVersion) {
                            sdk = currentSDK;
                            latestVersion = currentSDK.version;
                        }
                    });
                    if (sdk != null) {
                        this._sdk = sdk.name;
                    }
                }
            }
            else {
                this._sdk = context.sdk;
            }
            this._config = context.config;
            this._projectDirectory = project.directory;
            this._outputDirectory = context.outputDirectory || path.join(project.directory, "bin", this._config, this._arch);
            this._provisioningProfile = context.provisioningProfile;
            this._pbxProject = context.pbxProject;
        }
        equals(config) {
            if (this._id === config.id) {
                return true;
            }
            else {
                if (this._arch.toLowerCase() === config.arch.toLowerCase() &&
                    this._config.toLowerCase() === config.config.toLowerCase()) {
                    return true;
                }
            }
            return false;
        }
    }
    VCRemoteLib.ProjectConfig = ProjectConfig;
    class LocalProjectBareBase {
        get id() {
            return this._id;
        }
        set id(id) {
            this._id = id;
        }
        get directory() {
            return this._directory;
        }
        set directory(dir) {
            this._directory = dir;
        }
        get state() {
            return this._state;
        }
        set state(newState) {
            this._state = newState;
            this._stateList.push(newState);
        }
        get history() {
            return this._stateList;
        }
        constructor(collection, directory) {
            this._state = ProjectState.PENDING;
            this._stateList = [];
            this._id = uuid.v4();
            this._directory = directory || util.findUniqueDirectoryName(collection.storeDirectory);
        }
        delete() {
            rimraf.sync(this._directory);
        }
        update(remoteProject) {
            throw new Error("Not yet supported!");
        }
    }
    VCRemoteLib.LocalProjectBareBase = LocalProjectBareBase;
    class LocalProjectSlimBase extends LocalProjectBareBase {
        get collection() {
            return this._collection;
        }
        get keepPolicy() {
            return this._keepPolicy;
        }
        set keepPolicy(policy) {
            this._keepPolicy = policy;
        }
        get timestamp() {
            return this._timestamp;
        }
        set timestamp(date) {
            this._timestamp = date;
        }
        get expired() {
            // the project can be deleted if it expired or is temporary
            if (this._keepPolicy == KeepPolicy.KEEP_UNTIL_EXPIRED) {
                return (new Date(new Date() - this.timestamp).getDate() - 1) > LocalProjectSlimBase.EXPIRATION_DAYS;
            }
            else if (this._keepPolicy == KeepPolicy.TEMPORARY) {
                return true;
            }
            return false;
        }
        get build() {
            return this._build;
        }
        constructor(collection, directory) {
            super(collection, directory);
            this._keepPolicy = KeepPolicy.TEMPORARY;
            this._collection = collection;
            this._timestamp = new Date();
            this._build = new build.ProjectBuildManager(this);
        }
        deleteIfExpired() {
            if (this.expired) {
                this.delete();
            }
        }
        deserialize(obj) {
            this.id = obj.id;
            this.directory = obj.directory;
            this.keepPolicy = obj.keepPolicy;
            this.timestamp = obj.timestamp;
        }
        serialize() {
            var obj = {};
            obj.id = this.id;
            obj.directory = this.directory;
            obj.keepPolicy = this.keepPolicy;
            obj.timestamp = this.timestamp;
            return obj;
        }
    }
    LocalProjectSlimBase.EXPIRATION_DAYS = 7;
    VCRemoteLib.LocalProjectSlimBase = LocalProjectSlimBase;
    class LocalProject extends LocalProjectSlimBase {
        constructor(collection, directory) {
            super(collection);
            super.directory = this.directory;
        }
    }
    VCRemoteLib.LocalProject = LocalProject;
    class LocalCfgProjects {
        get all() {
            var configs = [];
            for (var id in this._configs) {
                configs.push(this._configs[id].config);
            }
            return configs;
        }
        constructor(project) {
            this._project = project;
            this._configs = {};
        }
        hasConfig(config) {
            for (var id in this._configs) {
                if (this._configs[id].config.equals(config)) {
                    return true;
                }
            }
            return undefined;
        }
        hasConfigId(id) {
            return this.getCfgById(id) != undefined;
        }
        getCfgById(id) {
            return this._configs[id].config;
        }
        addConfig(projectCfg) {
            projectCfg.srcProject = this._project;
            var self = this;
            return projectCfg.retrieve()
                .then(function (cfgProject) {
                self._configs[cfgProject.config.id] = cfgProject;
                return new Promise(function (resolve, reject) { resolve(cfgProject.config); });
            })
                .catch(function (err) {
                return new Promise(function (resolve, reject) { reject(err); });
            });
        }
        updateConfig(projectCfg) {
            throw new Error("Not yet implemented!");
        }
    }
    VCRemoteLib.LocalCfgProjects = LocalCfgProjects;
    class LocalSrcProject extends LocalProjectSlimBase {
        get rootDirectory() {
            return this._rootDirectory;
        }
        get configs() {
            return this._configs;
        }
        constructor(collection, directory) {
            super(collection);
            this._rootDirectory = directory || util.findUniqueDirectoryName(collection.storeDirectory);
            this.directory = path.join(this._rootDirectory, "src");
            super.directory = this.directory;
            this._configs = new LocalCfgProjects(this);
        }
        deserialize(obj) {
            super.deserialize(obj);
        }
        serialize() {
            var obj = super.serialize();
            obj.configs = [];
            var configs = this.configs.all;
            for (var i = 0; i < configs.length; i++) {
                obj.configs[i] = {};
                obj.configs[i].config = configs[i].config;
                obj.configs[i].arch = configs[i].arch;
                obj.configs[i].id = configs[i].id;
                obj.configs[i].projectDirectory = configs[i].projectDirectory;
                obj.configs[i].scheme = configs[i].scheme;
                obj.configs[i].outputDirectory = configs[i].outputDirectory;
                obj.configs[i].sdk = configs[i].sdk;
                obj.configs[i].target = configs[i].target;
                obj.configs[i].workspace = configs[i].workspace;
            }
            return obj;
        }
    }
    VCRemoteLib.LocalSrcProject = LocalSrcProject;
    class LocalCfgProject extends LocalProjectBareBase {
        get project() {
            return this._project;
        }
        get config() {
            return this._config;
        }
        constructor(project, config) {
            super(project.collection, config.projectDirectory);
            this._project = project;
            this._config = config;
            this.directory = config.projectDirectory;
        }
    }
    VCRemoteLib.LocalCfgProject = LocalCfgProject;
})(VCRemoteLib || (VCRemoteLib = {}));
module.exports = VCRemoteLib;
