"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("./common/util");
var util_2 = require("./node/util");
var instrument_1 = require("./instrument");
var aspect_1 = require("@dojo/core/aspect");
var http_1 = require("http");
var path_1 = require("path");
var fs_1 = require("fs");
var mime_types_1 = require("mime-types");
var lang_1 = require("@dojo/core/lang");
var Promise_1 = require("@dojo/shim/Promise");
var WebSocket = require("ws");
var Server = (function () {
    function Server(options) {
        lang_1.mixin(this, options);
    }
    Server.prototype.start = function () {
        var _this = this;
        return new Promise_1.default(function (resolve) {
            var server = _this.server = http_1.createServer(function (request, response) {
                return _this._handler(request, response);
            });
            _this._sessions = {};
            _this._codeCache = {};
            var sockets = [];
            aspect_1.after(server, 'close', function () {
                var socket;
                while ((socket = sockets.pop())) {
                    socket.destroy();
                }
            });
            server.on('connection', function (socket) {
                sockets.push(socket);
                socket.setNoDelay(true);
                socket.on('close', function () {
                    var index = sockets.indexOf(socket);
                    index !== -1 && sockets.splice(index, 1);
                });
            });
            _this._wsServer = new WebSocket.Server({ port: _this.port + 1 });
            _this._wsServer.on('connection', function (client) {
                _this._handleWebSocket(client);
            });
            _this._wsServer.on('error', function (error) {
                _this.executor.emit('error', error);
            });
            server.listen(_this.port, resolve);
        });
    };
    Server.prototype.stop = function () {
        var _this = this;
        var promises = [];
        if (this.server) {
            promises.push(new Promise_1.default(function (resolve) {
                _this.server.close(resolve);
            }).then(function () {
                _this.server = null;
            }));
        }
        if (this._wsServer) {
            promises.push(new Promise_1.default(function (resolve) {
                _this._wsServer.close(resolve);
            }).then(function () {
                _this._wsServer = null;
            }));
        }
        return Promise_1.default.all(promises).then(function () {
            _this._codeCache = null;
        });
    };
    Server.prototype.subscribe = function (sessionId, listener) {
        var listeners = this._getSession(sessionId).listeners;
        listeners.push(listener);
        return {
            destroy: function () {
                this.destroy = function () { };
                util_1.pullFromArray(listeners, listener);
            }
        };
    };
    Server.prototype._getSession = function (sessionId) {
        var session = this._sessions[sessionId];
        if (!session) {
            session = this._sessions[sessionId] = { listeners: [] };
        }
        return session;
    };
    Server.prototype._handler = function (request, response) {
        var _this = this;
        if (request.method === 'GET') {
            if (/\.js(?:$|\?)/.test(request.url)) {
                this._handleFile(request, response, this.instrument);
            }
            else {
                this._handleFile(request, response);
            }
        }
        else if (request.method === 'HEAD') {
            this._handleFile(request, response, false, true);
        }
        else if (request.method === 'POST') {
            request.setEncoding('utf8');
            var data_1 = '';
            request.on('data', function (chunk) {
                data_1 += chunk;
            });
            request.on('end', function () {
                try {
                    var rawMessages = JSON.parse(data_1);
                    if (!Array.isArray(rawMessages)) {
                        rawMessages = [rawMessages];
                    }
                    var messages = rawMessages.map(function (messageString) {
                        return JSON.parse(messageString);
                    });
                    Promise_1.default.all(messages.map(function (message) { return _this._handleMessage(message); })).then(function () {
                        response.statusCode = 204;
                        response.end();
                    }, function () {
                        response.statusCode = 500;
                        response.end();
                    });
                }
                catch (error) {
                    response.statusCode = 500;
                    response.end();
                }
            });
        }
        else {
            response.statusCode = 501;
            response.end();
        }
    };
    Server.prototype._handleFile = function (request, response, shouldInstrument, omitContent) {
        var _this = this;
        function send(contentType, data) {
            response.writeHead(200, {
                'Content-Type': contentType,
                'Content-Length': Buffer.byteLength(data)
            });
            response.end(data);
        }
        var file = /^\/+([^?]*)/.exec(request.url)[1];
        var wholePath;
        this.executor.log('Request for', file);
        if (/^__intern\//.test(file)) {
            wholePath = path_1.join(this.executor.config.internPath, file.replace(/^__intern\//, ''));
            shouldInstrument = false;
        }
        else {
            wholePath = path_1.resolve(path_1.join(this.basePath, file));
        }
        wholePath = util_2.normalizePath(wholePath);
        if (wholePath.charAt(wholePath.length - 1) === '/') {
            wholePath += 'index.html';
        }
        if (this.excludeInstrumentation === true ||
            (this.excludeInstrumentation && this.excludeInstrumentation.test(file))) {
            shouldInstrument = false;
        }
        var contentType = mime_types_1.lookup(path_1.basename(wholePath)) || 'application/octet-stream';
        fs_1.stat(wholePath, function (error, stats) {
            if (!_this.server) {
                return;
            }
            if (error || !stats.isFile()) {
                _this.executor.log('Unable to serve', wholePath);
                _this._send404(response);
                return;
            }
            _this.executor.log('Serving', wholePath);
            if (shouldInstrument) {
                var mtime_1 = stats.mtime.getTime();
                if (_this._codeCache[wholePath] && _this._codeCache[wholePath].mtime === mtime_1) {
                    send(contentType, _this._codeCache[wholePath].data);
                }
                else {
                    fs_1.readFile(wholePath, 'utf8', function (error, data) {
                        if (!_this.server) {
                            return;
                        }
                        if (error) {
                            _this._send404(response);
                            return;
                        }
                        data = instrument_1.default(data, wholePath, _this.instrumenterOptions);
                        _this._codeCache[wholePath] = {
                            mtime: mtime_1,
                            data: data
                        };
                        send(contentType, data);
                    });
                }
            }
            else {
                response.writeHead(200, {
                    'Content-Type': contentType,
                    'Content-Length': stats.size
                });
                if (omitContent) {
                    response.end();
                }
                else {
                    fs_1.createReadStream(wholePath).pipe(response);
                }
            }
        });
    };
    Server.prototype._handleMessage = function (message) {
        this.executor.log('Received message:', message);
        var promise = this._publish(message);
        var shouldWait = util_1.getShouldWait(this.runInSync, message);
        return shouldWait ? promise : Promise_1.default.resolve();
    };
    Server.prototype._handleWebSocket = function (client) {
        var _this = this;
        client.on('message', function (data) {
            var message = JSON.parse(data);
            _this._handleMessage(message)
                .catch(function (error) { return _this.executor.emit('error', error); })
                .then(function () {
                client.send(JSON.stringify({ id: message.id }), function (error) {
                    if (error) {
                        _this.executor.emit('error', error);
                    }
                });
            });
        });
    };
    Server.prototype._publish = function (message) {
        var listeners = this._getSession(message.sessionId).listeners;
        return Promise_1.default.all(listeners.map(function (listener) { return listener(message.name, message.data); }));
    };
    Server.prototype._send404 = function (response) {
        response.writeHead(404, {
            'Content-Type': 'text/html;charset=utf-8'
        });
        response.end("<!DOCTYPE html><title>404 Not Found</title><h1>404 Not Found</h1>" +
            ("<!-- " + new Array(512).join('.') + " -->"));
    };
    return Server;
}());
exports.default = Server;
//# sourceMappingURL=Server.js.map