/**
* ******************************************************
*                                                       *
*   Copyright (C) Microsoft. All rights reserved.       *
*                                                       *
*******************************************************
*/
/// <reference path="../typings/node.d.ts" />
/// <reference path="../typings/Q.d.ts" />
/// <reference path="../typings/vcremote-utils.d.ts" />
/// <reference path="../typings/express.d.ts" />
/// <reference path="../typings/semver.d.ts" />
"use strict";
var projects = require("./projectsystem/project-collection");
var project = require("./projectsystem/project");

var util = require("util");

var xmlbuilder = require("xmlbuilder");
var response = require("./response");
var iosDebug = require("./debugger/ios-debug");
var dd = require("./projectsystem/device-discovery");
var finder = require("./filesystem/finder");

var VCRemoteLib;
(function (VCRemoteLib) {
    var langSpecifics = null;

    function getLanguageSpecifics() {
        if (!langSpecifics) {
            langSpecifics = new VCLanguageSpecifics;
        }
        return langSpecifics;
    }
    VCRemoteLib.getLanguageSpecifics = getLanguageSpecifics;

    var VCLanguageSpecifics = (function () {
        function VCLanguageSpecifics() {
            this._initialized = false;
        }
        Object.defineProperty(VCLanguageSpecifics.prototype, "id", {
            get: function () {
                return "63f68b50-3296-46a2-bb60-419cf4475d1e";
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(VCLanguageSpecifics.prototype, "initialized", {
            get: function () {
                return this._initialized;
            },
            enumerable: true,
            configurable: true
        });

        VCLanguageSpecifics.prototype.init = function (conf) {
            this._conf = conf;
            this._projects = new projects.ProjectCollection(conf.baseDir());
            this._response = new response.VCLanguageSpecificsResponse(this._projects, this);
            console.info("VC build library initialized.");
            iosDebug.init();
            this._initialized = true;
        };

        VCLanguageSpecifics.prototype.addProject = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            var self = this;
            this._projects.add(new project.RemoteSrcProject(req)).then(function (project) {
                self._response.renderProjectInfo(res, project);
            }).fail(function (err) {
                console.error("An error has occured adding the project to the collection.");
                res.send(500, "An error has occured adding the project to the collection.");
            });
        };

        VCLanguageSpecifics.prototype.addProjectConfig = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var self = this;
                var prj = this._projects.getProjectById(req.params.id);
                prj.configs.addConfig(new project.RemoteCfgProject(req)).then(function (cfg) {
                    self._response.renderProjectCfg(res, cfg);
                });
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.updateProject = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            throw new Error("Not implemented!");
        };

        VCLanguageSpecifics.prototype.buildProject = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var prj = this._projects.getProjectById(req.params.id);
                var config;
                if (req.params.cfg != undefined) {
                    config = prj.configs.getCfgById(req.params.cfg);
                } else {
                    var context = {
                        arch: req.query.arch,
                        sdk: req.query.sdk,
                        config: req.query.cfg,
                        emulatorBuild: false,
                        outputDirectory: undefined,
                        provisioningProfile: "",
                        pbxProject: ""
                    };
                    config = new project.ProjectConfig(prj, context);
                }

                var bld;
                var buildAction = req.query.buildaction;
                if (buildAction == "clean") {
                    bld = prj.build.cleanConfig(config);
                } else if (buildAction == "rebuild") {
                    bld = prj.build.rebuildConfig(config);
                } else {
                    bld = prj.build.buildConfig(config);
                }

                this._response.renderBuildInfo(res, bld);
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.getProjects = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            this._response.renderProjects(res);
        };

        VCLanguageSpecifics.prototype.getDevices = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            this._response.renderDevices(res);
        };

        VCLanguageSpecifics.prototype.getProjectConfig = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var prj = this._projects.getProjectById(req.params.id);
                var bld = prj.build.getBuildByCfgId(req.params.cfg);
                this._response.renderProjectCfg(res, bld.config);
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.getProjectConfigs = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var project = this._projects.getProjectById(req.params.id);
                this._response.renderProjectCfgs(res, project);
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.retrieveBuild = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var project = this._projects.getProjectById(req.params.id);
                var bld = project.build.getBuildByCfgId(req.params.cfg);
                var self = this;
                bld.archive().then(function (archiveFileName) {
                    self._response.renderBuild(res, bld);
                });
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.installBuild = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var project = this._projects.getProjectById(req.params.id);
                var bld = project.build.getBuildByCfgId(req.params.cfg);
                var self = this;
                var appName = req.query.appName;
                var appPath = bld.outputDirectory + "/" + appName;
                var deviceUdid = req.query.deviceUdid;
                if (bld.config.arch === "i386" || bld.config.arch === "x86_64") {
                    dd.DeviceDiscovery.tryGetSimulatorByIdOrDefault(deviceUdid).then(function (simulator) {
                        // Install app on the first simulator, but do not bring it to the foreground.
                        // Then stop it.
                        simulator.installStartApp({
                            appPath: appPath,
                            hide: true,
                            focus: false,
                            killIfRunning: true,
                            timeout: 15,
                            autoExit: true
                        }).then(function () {
                            console.info('Successfully deployed app to simulator: \'' + simulator.name + '\'');
                            simulator.stop().then(function () {
                                console.info('Simulator \'' + simulator.name + '\' stopped successfully');
                                self._response.renderBuildInfo(res, bld);
                            }).fail(function (reason) {
                                var message = 'Failed to stop simulator \'' + simulator.name + '\'. Reason: ' + reason.message;
                                console.info(message);
                                res.send(404, message);
                            });
                        }).fail(function (reason) {
                            var message = 'Failed to deploy app to simulator \'' + simulator.name + '\'. Reason: ' + reason.message;
                            console.info(message);
                            res.send(404, message);
                        });
                    }).fail(function (reason) {
                        var message = 'Failed to fetch the first simulator. Reason: ' + reason.message;
                        console.info(message);
                        res.send(404, message);
                    });
                } else {
                    // ARM
                    dd.DeviceDiscovery.tryGetDeviceByIdOrDefault(deviceUdid).then(function (device) {
                        console.info('Installing app on device: \'' + device.name + '\'');
                        device.installApp(appPath).then(function () {
                            console.info('Installed app on device: \'' + device.name + '\'');
                            self._response.renderBuildInfo(res, bld);
                        }).fail(function (reason) {
                            var message = 'Failed to install app on device: \'' + device.name + '\'. Reason: ' + reason.message;
                            console.info(message);
                            res.send(404, message);
                        });
                    }).fail(function (reason) {
                        var message = 'Failed to fetch the first device. Reason: ' + reason.message;
                        console.info(message);
                        res.send(404, message);
                    });
                }
            } else {
                var message = util.format("Project '%s' was not found in the project collection.", req.params.id);
                console.error(message);
                res.send(404, message);
            }
        };

        VCLanguageSpecifics.prototype.getBuildLog = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var project = this._projects.getProjectById(req.params.id);
                var bld = project.build.getBuildByCfgId(req.params.cfg);
                var pos = parseInt(req.query.pos || "0"), l = parseInt(req.query.l || "0");
                this._response.renderBuildLog(res, bld, bld.log.read(pos, l), pos);
            } else {
                res.end();
            }
        };

        VCLanguageSpecifics.prototype.getBuildStatus = function (req, res) {
            if (!this.ensureCompatibleClient(req, res)) {
                return;
            }
            if (this._projects.contains(req.params.id)) {
                var project = this._projects.getProjectById(req.params.id);
                var bld = project.build.getBuildByCfgId(req.params.cfg);
                this._response.renderBuildStatus(res, project, bld);
            } else {
                res.end();
            }
        };

        VCLanguageSpecifics.prototype.execOpen = function (req, res) {
            finder.execOpen(req, res);
        };

        VCLanguageSpecifics.prototype.putFiles = function (req, res) {
            finder.putFiles(req, res);
        };

        VCLanguageSpecifics.prototype.deleteFiles = function (req, res) {
            finder.deleteFiles(req, res);
        };

        VCLanguageSpecifics.prototype.getFiles = function (req, res) {
            finder.getFiles(req, res);
        };

        VCLanguageSpecifics.prototype.setupForDebugging = function (req, res) {
            iosDebug.startDebugListenerServer(this._conf, req, res);
        };

        VCLanguageSpecifics.prototype.getAppRemotePath = function (req, res) {
            iosDebug.getAppRemotePath(req, res);
        };

        VCLanguageSpecifics.prototype.terminateDebugger = function () {
            iosDebug.terminate();
        };

        VCLanguageSpecifics.prototype.close = function () {
            this._projects.close();
        };

        // Check whether the client (Visual Studio) is supported by this version of vcremote (RTM).
        VCLanguageSpecifics.prototype.isCompatibleClient = function (req) {
            var clientVersion = parseFloat(req.query.ver);

            // If the client version is unspecified, we will support the client for compatibility purposes.
            if (isNaN(clientVersion))
                return true;
            var minVersion = parseFloat(this._projects.minimumSupportedClientVersion);
            var maxVersion = parseFloat(this._projects.maximumSupportedClientVersion);
            return (clientVersion >= minVersion && clientVersion <= maxVersion);
        };

        VCLanguageSpecifics.prototype.ensureCompatibleClient = function (req, res) {
            if (!this.isCompatibleClient(req)) {
                console.warn("Visual Studio is incompatible with the current version of this build agent. Please update vcremote using npm.");
                this._response.renderIncompatibleClient(res);
                return false;
            }
            return true;
        };
        return VCLanguageSpecifics;
    })();
    VCRemoteLib.VCLanguageSpecifics = VCLanguageSpecifics;
})(VCRemoteLib || (VCRemoteLib = {}));

module.exports = VCRemoteLib;
