///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var url = require("url");
var Config = require("./Config");
var Context = require("./Context");
var ExceptionTracking = require("../AutoCollection/Exceptions");
var ContractsModule = require("../Library/Contracts");
var Channel = require("./Channel");
var ServerRequestTracking = require("../AutoCollection/ServerRequests");
var ClientRequestTracking = require("../AutoCollection/ClientRequests");
var Sender = require("./Sender");
var Util = require("./Util");
var Logging = require("./Logging");
var Client = (function () {
    /**
     * Constructs a new client of the client
     * @param iKey the instrumentation key to use (read from environment variable if not specified)
     */
    function Client(iKey) {
        this._sequencePrefix = Util.int32ArrayToBase64([
            Util.random32(),
            Util.random32(),
            Util.random32(),
            Util.random32()]) +
            ":";
        this._sequenceNumber = 0;
        this._telemetryProcessors = [];
        var config = new Config(iKey);
        this.config = config;
        this.context = new Context();
        this.commonProperties = {};
        var sender = new Sender(function () { return config.endpointUrl; });
        this.channel = new Channel(function () { return config.disableAppInsights; }, function () { return config.maxBatchSize; }, function () { return config.maxBatchIntervalMs; }, sender);
    }
    /**
     * Log a user action or other occurrence.
     * @param   name    A string to identify this event in the portal.
     * @param   properties  map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
     * @param   measurements    map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
     */
    Client.prototype.trackEvent = function (name, properties, measurements) {
        var event = new ContractsModule.Contracts.EventData();
        event.name = name;
        event.properties = properties;
        event.measurements = measurements;
        var data = new ContractsModule.Contracts.Data();
        data.baseType = "EventData";
        data.baseData = event;
        this.track(data);
    };
    /**
     * Log a trace message
     * @param   message    A string to identify this event in the portal.
     * @param   properties  map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
     */
    Client.prototype.trackTrace = function (message, severityLevel, properties) {
        var trace = new ContractsModule.Contracts.MessageData();
        trace.message = message;
        trace.properties = properties;
        if (!isNaN(severityLevel)) {
            trace.severityLevel = severityLevel;
        }
        else {
            trace.severityLevel = ContractsModule.Contracts.SeverityLevel.Information;
        }
        var data = new ContractsModule.Contracts.Data();
        data.baseType = "MessageData";
        data.baseData = trace;
        this.track(data);
    };
    /**
     * Log an exception you have caught.
     * @param   exception   An Error from a catch clause, or the string error message.
     * @param   properties  map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
     * @param   measurements    map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
     */
    Client.prototype.trackException = function (exception, properties, measurements) {
        if (!Util.isError(exception)) {
            exception = new Error(exception);
        }
        var data = ExceptionTracking.getExceptionData(exception, true, properties, measurements);
        this.track(data);
    };
    /**
     * * Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
     * To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
     * telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
     *
     * @param name   A string that identifies the metric.
     * @param value  The value of the metric
     * @param count  the number of samples used to get this value
     * @param min    the min sample for this set
     * @param max    the max sample for this set
     * @param stdDev the standard deviation of the set
     */
    Client.prototype.trackMetric = function (name, value, count, min, max, stdDev, properties) {
        var metrics = new ContractsModule.Contracts.MetricData(); // todo: enable client-batching of these
        metrics.metrics = [];
        var metric = new ContractsModule.Contracts.DataPoint();
        metric.count = !isNaN(count) ? count : 1;
        metric.kind = ContractsModule.Contracts.DataPointType.Aggregation;
        metric.max = !isNaN(max) ? max : value;
        metric.min = !isNaN(min) ? min : value;
        metric.name = name;
        metric.stdDev = !isNaN(stdDev) ? stdDev : 0;
        metric.value = value;
        metrics.metrics.push(metric);
        metrics.properties = properties;
        var data = new ContractsModule.Contracts.Data();
        data.baseType = "MetricData";
        data.baseData = metrics;
        this.track(data);
    };
    Client.prototype.trackRequestSync = function (request, response, ellapsedMilliseconds, properties, error) {
        ServerRequestTracking.trackRequestSync(this, request, response, ellapsedMilliseconds, properties, error);
    };
    Client.prototype.trackRequest = function (request, response, properties) {
        ServerRequestTracking.trackRequest(this, request, response, properties);
    };
    Client.prototype.trackDependencyRequest = function (requestOptions, request, properties) {
        ClientRequestTracking.trackRequest(this, requestOptions, request, properties);
    };
    Client.prototype.trackDependency = function (name, commandName, elapsedTimeMs, success, dependencyTypeName, properties, dependencyKind, async, dependencySource, target) {
        if (properties === void 0) { properties = {}; }
        if (dependencyKind === void 0) { dependencyKind = ContractsModule.Contracts.DependencyKind.Other; }
        if (async === void 0) { async = false; }
        if (dependencySource === void 0) { dependencySource = ContractsModule.Contracts.DependencySourceType.Undefined; }
        if (target === void 0) { target = null; }
        if (!target && commandName) {
            target = url.parse(commandName).host;
        }
        var remoteDependency = new ContractsModule.Contracts.RemoteDependencyData();
        remoteDependency.name = name;
        remoteDependency.commandName = commandName;
        remoteDependency.target = target;
        remoteDependency.value = elapsedTimeMs;
        remoteDependency.success = success;
        remoteDependency.dependencyTypeName = dependencyTypeName;
        remoteDependency.properties = properties;
        remoteDependency.dependencyKind = dependencyKind;
        remoteDependency.async = async;
        remoteDependency.dependencySource = dependencySource;
        var data = new ContractsModule.Contracts.Data();
        data.baseType = "RemoteDependencyData";
        data.baseData = remoteDependency;
        this.track(data);
    };
    /**
     * Immediately send all queued telemetry.
     */
    Client.prototype.sendPendingData = function (callback) {
        this.channel.triggerSend(false, callback);
    };
    Client.prototype.getEnvelope = function (data, tagOverrides) {
        if (data && data.baseData) {
            data.baseData.ver = 2;
            // if no properties are specified just add the common ones
            if (!data.baseData.properties) {
                data.baseData.properties = this.commonProperties;
            }
            else {
                // otherwise, check each of the common ones
                for (var name in this.commonProperties) {
                    // only override if the property `name` has not been set on this item
                    if (!data.baseData.properties[name]) {
                        data.baseData.properties[name] = this.commonProperties[name];
                    }
                }
            }
        }
        // sanitize properties
        data.baseData.properties = Util.validateStringMap(data.baseData.properties);
        var iKey = this.config.instrumentationKey;
        var envelope = new ContractsModule.Contracts.Envelope();
        envelope.data = data;
        envelope.appVer = this.context.tags[this.context.keys.applicationVersion];
        envelope.iKey = iKey;
        // this is kind of a hack, but the envelope name is always the same as the data name sans the chars "data"
        envelope.name =
            "Microsoft.ApplicationInsights." +
                iKey.replace(/-/g, "") +
                "." +
                data.baseType.substr(0, data.baseType.length - 4);
        envelope.os = this.context.tags[this.context.keys.deviceOS];
        envelope.osVer = this.context.tags[this.context.keys.deviceOSVersion];
        envelope.seq = this._sequencePrefix + (this._sequenceNumber++).toString();
        envelope.tags = tagOverrides || this.context.tags;
        envelope.time = (new Date()).toISOString();
        envelope.ver = 1;
        return envelope;
    };
    /**
     * Generic track method for all telemetry types
     * @param data the telemetry to send
     * @param tagOverrides the context tags to use for this telemetry which overwrite default context values
     */
    Client.prototype.track = function (data, tagOverrides) {
        var envelope = this.getEnvelope(data, tagOverrides);
        var accepted = this.runTelemetryProcessors(envelope);
        if (accepted) {
            this.channel.send(envelope);
        }
    };
    /**
     * Adds telemetry processor to the collection. Telemetry processors will be called one by one
     * before telemetry item is pushed for sending and in the order they were added.
     *
     * @param telemetryProcessor function, takes Envelope, returns boolean
     */
    Client.prototype.addTelemetryProcessor = function (telemetryProcessor) {
        this._telemetryProcessors.push(telemetryProcessor);
    };
    /*
     * Removes all telemetry processors
     */
    Client.prototype.clearTelemetryProcessors = function () {
        this._telemetryProcessors = [];
    };
    /**
     * Parse an envelope sequence.
     */
    Client.parseSeq = function (seq) {
        var array = seq.split(":");
        return [array[0], parseInt(array[1])];
    };
    Client.prototype.runTelemetryProcessors = function (envelope) {
        var accepted = true;
        var telemetryProcessorsCount = this._telemetryProcessors.length;
        if (telemetryProcessorsCount === 0) {
            return accepted;
        }
        for (var i = 0; i < telemetryProcessorsCount; ++i) {
            try {
                var processor = this._telemetryProcessors[i];
                if (processor) {
                    if (processor.apply(null, [envelope]) === false) {
                        accepted = false;
                        break;
                    }
                }
            }
            catch (error) {
                accepted = false;
                Logging.warn("One of telemetry processors failed, telemetry item will not be sent.", error, envelope);
            }
        }
        return accepted;
    };
    return Client;
}());
module.exports = Client;
