"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 decorators_1 = require("../../common/decorators");
const helpers_1 = require("../../common/helpers");
const constants_1 = require("../../common/constants");
class AnalyticsService {
    constructor($logger, $options, $processService, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $childProcess, $projectDataService, $mobileHelper, $projectHelper) {
        this.$logger = $logger;
        this.$options = $options;
        this.$processService = $processService;
        this.$staticConfig = $staticConfig;
        this.$prompter = $prompter;
        this.$userSettingsService = $userSettingsService;
        this.$analyticsSettingsService = $analyticsSettingsService;
        this.$childProcess = $childProcess;
        this.$projectDataService = $projectDataService;
        this.$mobileHelper = $mobileHelper;
        this.$projectHelper = $projectHelper;
        this.shouldDisposeInstance = true;
        this.analyticsStatuses = {};
    }
    setShouldDispose(shouldDispose) {
        this.shouldDisposeInstance = shouldDispose;
    }
    checkConsent() {
        return __awaiter(this, void 0, void 0, function* () {
            if (yield this.$analyticsSettingsService.canDoRequest()) {
                const initialTrackFeatureUsageStatus = yield this.getStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME);
                let trackFeatureUsage = initialTrackFeatureUsageStatus === "enabled";
                if ((yield this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME)) && helpers_1.isInteractive()) {
                    this.$logger.out("Do you want to help us improve "
                        + this.$analyticsSettingsService.getClientName()
                        + " by automatically sending anonymous usage statistics? We will not use this information to identify or contact you."
                        + " You can read our official Privacy Policy at");
                    const message = this.$analyticsSettingsService.getPrivacyPolicyLink();
                    trackFeatureUsage = yield this.$prompter.confirm(message, () => true);
                    yield this.setStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, trackFeatureUsage);
                    yield this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: trackFeatureUsage });
                }
                const isErrorReportingUnset = yield this.isNotConfirmed(this.$staticConfig.ERROR_REPORT_SETTING_NAME);
                const isUsageReportingConfirmed = !(yield this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME));
                if (isErrorReportingUnset && isUsageReportingConfirmed) {
                    yield this.setStatus(this.$staticConfig.ERROR_REPORT_SETTING_NAME, trackFeatureUsage);
                }
            }
        });
    }
    setStatus(settingName, enabled) {
        return __awaiter(this, void 0, void 0, function* () {
            this.analyticsStatuses[settingName] = enabled ? "enabled" : "disabled";
            yield this.$userSettingsService.saveSetting(settingName, enabled.toString());
        });
    }
    isEnabled(settingName) {
        return __awaiter(this, void 0, void 0, function* () {
            const analyticsStatus = yield this.getStatus(settingName);
            return analyticsStatus === "enabled";
        });
    }
    getStatusMessage(settingName, jsonFormat, readableSettingName) {
        if (jsonFormat) {
            return this.getJsonStatusMessage(settingName);
        }
        return this.getHumanReadableStatusMessage(settingName, readableSettingName);
    }
    trackAcceptFeatureUsage(settings) {
        return __awaiter(this, void 0, void 0, function* () {
            const acceptTracking = !!(settings && settings.acceptTrackFeatureUsage);
            const googleAnalyticsEventData = {
                googleAnalyticsDataType: "event",
                action: "Accept Tracking",
                label: acceptTracking.toString()
            };
            yield this.forcefullyTrackInGoogleAnalytics(googleAnalyticsEventData);
        });
    }
    trackInGoogleAnalytics(gaSettings) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.initAnalyticsStatuses();
            if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === "enabled") {
                return this.forcefullyTrackInGoogleAnalytics(gaSettings);
            }
        });
    }
    trackEventActionInGoogleAnalytics(data) {
        return __awaiter(this, void 0, void 0, function* () {
            const device = data.device;
            const platform = device ? device.deviceInfo.platform : data.platform;
            const normalizedPlatform = platform ? this.$mobileHelper.normalizePlatformName(platform) : platform;
            const isForDevice = device ? !device.isEmulator : data.isForDevice;
            let label = "";
            label = this.addDataToLabel(label, normalizedPlatform);
            if (isForDevice !== null && isForDevice !== undefined) {
                const deviceType = isForDevice ? constants_1.DeviceTypes.Device : (this.$mobileHelper.isAndroidPlatform(platform) ? constants_1.DeviceTypes.Emulator : constants_1.DeviceTypes.Simulator);
                label = this.addDataToLabel(label, deviceType);
            }
            if (device) {
                label = this.addDataToLabel(label, device.deviceInfo.version);
            }
            if (data.additionalData) {
                label = this.addDataToLabel(label, data.additionalData);
            }
            const customDimensions = {};
            this.setProjectRelatedCustomDimensions(customDimensions, data.projectDir);
            const googleAnalyticsEventData = {
                googleAnalyticsDataType: "event",
                action: data.action,
                label,
                customDimensions
            };
            yield this.trackInGoogleAnalytics(googleAnalyticsEventData);
        });
    }
    forcefullyTrackInGoogleAnalytics(gaSettings) {
        gaSettings.customDimensions = gaSettings.customDimensions || {};
        gaSettings.customDimensions["cd5"] = this.$options.analyticsClient || (helpers_1.isInteractive() ? "CLI" : "Unknown");
        this.setProjectRelatedCustomDimensions(gaSettings.customDimensions);
        const googleAnalyticsData = _.merge({ type: "googleAnalyticsData", category: "CLI" }, gaSettings);
        this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
        return this.sendMessageToBroker(googleAnalyticsData);
    }
    setProjectRelatedCustomDimensions(customDimensions, projectDir) {
        if (!projectDir) {
            try {
                projectDir = this.$projectHelper.projectDir;
            }
            catch (err) {
                this.$logger.trace("Unable to get the projectDir from projectHelper", err);
            }
        }
        if (projectDir) {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            customDimensions["cd2"] = projectData.projectType;
            customDimensions["cd9"] = projectData.isShared.toString();
        }
        return customDimensions;
    }
    dispose() {
        if (this.brokerProcess && this.shouldDisposeInstance) {
            this.brokerProcess.disconnect();
        }
    }
    addDataToLabel(label, newData) {
        if (newData && label) {
            return `${label}_${newData}`;
        }
        return label || newData || "";
    }
    getAnalyticsBroker() {
        return new Promise((resolve, reject) => {
            const brokerProcessArgs = this.getBrokerProcessArgs();
            const broker = this.$childProcess.spawn(process.execPath, brokerProcessArgs, {
                stdio: ["ignore", "ignore", "ignore", "ipc"],
                detached: true
            });
            broker.unref();
            let isSettled = false;
            const timeoutId = setTimeout(() => {
                if (!isSettled) {
                    reject(new Error("Unable to start Analytics Broker process."));
                }
            }, AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT);
            broker.on("error", (err) => {
                clearTimeout(timeoutId);
                if (!isSettled) {
                    isSettled = true;
                    reject(err);
                }
            });
            broker.on("message", (data) => {
                if (data === "BrokerReadyToReceive") {
                    clearTimeout(timeoutId);
                    if (!isSettled) {
                        isSettled = true;
                        this.$processService.attachToProcessExitSignals(this, () => {
                            broker.send({
                                type: "finish"
                            });
                        });
                        this.brokerProcess = broker;
                        resolve(broker);
                    }
                }
            });
        });
    }
    getBrokerProcessArgs() {
        const brokerProcessArgs = [
            path.join(__dirname, "analytics-broker-process.js"),
            this.$staticConfig.PATH_TO_BOOTSTRAP,
        ];
        if (this.$options.analyticsLogFile) {
            brokerProcessArgs.push(this.$options.analyticsLogFile);
        }
        return brokerProcessArgs;
    }
    sendInfoForTracking(trackingInfo, settingName) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.initAnalyticsStatuses();
            if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[settingName] === "enabled") {
                return this.sendMessageToBroker(trackingInfo);
            }
        });
    }
    sendMessageToBroker(message) {
        return __awaiter(this, void 0, void 0, function* () {
            let broker;
            try {
                broker = yield this.getAnalyticsBroker();
            }
            catch (err) {
                this.$logger.trace("Unable to get broker instance due to error: ", err);
                return;
            }
            return new Promise((resolve, reject) => {
                if (broker && broker.connected) {
                    try {
                        broker.send(message, (error) => resolve());
                    }
                    catch (err) {
                        this.$logger.trace("Error while trying to send message to broker:", err);
                        resolve();
                    }
                }
                else {
                    this.$logger.trace("Broker not found or not connected.");
                    resolve();
                }
            });
        });
    }
    initAnalyticsStatuses() {
        return __awaiter(this, void 0, void 0, function* () {
            if (yield this.$analyticsSettingsService.canDoRequest()) {
                this.$logger.trace("Initializing analytics statuses.");
                const settingsNames = [this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, this.$staticConfig.ERROR_REPORT_SETTING_NAME];
                for (const settingName of settingsNames) {
                    yield this.getStatus(settingName);
                }
                this.$logger.trace("Analytics statuses: ", this.analyticsStatuses);
            }
        });
    }
    getStatus(settingName) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!_.has(this.analyticsStatuses, settingName)) {
                const settingValue = yield this.$userSettingsService.getSettingValue(settingName);
                if (settingValue) {
                    const isEnabled = helpers_1.toBoolean(settingValue);
                    if (isEnabled) {
                        this.analyticsStatuses[settingName] = "enabled";
                    }
                    else {
                        this.analyticsStatuses[settingName] = "disabled";
                    }
                }
                else {
                    this.analyticsStatuses[settingName] = "not confirmed";
                }
            }
            return this.analyticsStatuses[settingName];
        });
    }
    isNotConfirmed(settingName) {
        return __awaiter(this, void 0, void 0, function* () {
            const analyticsStatus = yield this.getStatus(settingName);
            return analyticsStatus === "not confirmed";
        });
    }
    getHumanReadableStatusMessage(settingName, readableSettingName) {
        return __awaiter(this, void 0, void 0, function* () {
            let status = null;
            if (yield this.isNotConfirmed(settingName)) {
                status = "disabled until confirmed";
            }
            else {
                status = yield this.getStatus(settingName);
            }
            return `${readableSettingName} is ${status}.`;
        });
    }
    getJsonStatusMessage(settingName) {
        return __awaiter(this, void 0, void 0, function* () {
            const status = yield this.getStatus(settingName);
            const enabled = status === "not confirmed" ? null : status === "enabled";
            return JSON.stringify({ enabled });
        });
    }
    trackException(exception, message) {
        const data = {
            type: "exception",
            exception,
            message
        };
        return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME);
    }
}
AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT = 10 * 1000;
__decorate([
    decorators_1.cache()
], AnalyticsService.prototype, "getAnalyticsBroker", null);
__decorate([
    decorators_1.cache()
], AnalyticsService.prototype, "initAnalyticsStatuses", null);
exports.AnalyticsService = AnalyticsService;
$injector.register("analyticsService", AnalyticsService);
