"use strict";

function trapError(a, b) {
    var c = a.url, d = function(a) {
        return console.error("trapError:url=" + c, a);
    };
    a.on("error", d), b.on("error", d);
}

function getPathParts(a) {
    var b = mUrl.parse(a).pathname;
    try {
        b = decodeURI(b);
    } catch (c) {
        console.warn("getPathParts: decodeURI failed, use as-is: " + a, c);
    }
    return b.split("/").filter(function(a) {
        return !!a;
    });
}

function readRequestBody(a, b) {
    return b || a.setEncoding("UTF-8"), new Promise(function(c, d) {
        var e = "", f = [];
        a.on("data", function(a) {
            "string" == typeof a ? e += a : a.length && f.push(a);
        }), a.on("end", function() {
            return c(b ? f : e);
        }), a.on("error", function(a) {
            console.error("readRequestBody:error:", a), d(a);
        });
    });
}

function buffersLength(a) {
    return a.reduce(function(a, b) {
        return a + b.length;
    }, 0);
}

function sendBinaryBody(a, b) {
    var c = 0, d = function(e) {
        if (e || c >= b.length) e && console.log("sendBinaryBody:step:err:", e), a.end(); else {
            var f = b[c];
            c += 1, a.write(f, d);
        }
    };
    d();
}

function toSseData(a) {
    return ("string" == typeof a ? a : JSON.stringify(a)).replace(/[\r\n]/g, " ");
}

function sendSSE(a, b) {
    if (a.writable) if (null != b) {
        var c = toSseData(b);
        a.write("data:" + c + "\n\n");
    } else a.write(":\n\n");
}

function killProcess(a) {
    return new Promise(function(b, c) {
        a.on("close", function(a) {
            return b(a);
        }), a.kill(), setTimeout(function() {
            return c("killProcess:timeout:" + a.pid);
        }, 3e3);
    });
}

function isSpecialUid(a) {
    return /^admin$|^device\-/.test(a);
}

function onRequest(a, b) {
    b.socket = a.socket, console.log(a.method + " " + a.url);
    var c = getPathParts(a.url);
    mUrl.parse(a.url);
    allowCors(a) && b.setHeader("Access-Control-Allow-Origin", a.headers.origin);
    var d = router[a.method], e = d ? d[c[0]] : null;
    if (d && !e && (e = d["@default"]), e) try {
        e(a, b);
    } catch (f) {
        b.writeHead(503), b.end(), console.error(f);
    } else b.writeHead(404), b.end();
}

function handleCorsPreflight(a, b) {
    allowCors(a) && a.headers["access-control-request-method"] ? (b.setHeader("Access-Control-Allow-Origin", a.headers.origin), b.writeHead(200, {
        "Access-Control-Allow-Origin": "null",
        "Access-Control-Allow-Methods": "POST, GET",
        "Access-Control-Max-Age": 600,
        "Access-Control-Allow-Headers": "Content-Type"
    }), b.end()) : (b.writeHead(404), b.end());
}

function allowCors(a) {
    var b = a.headers.origin;
    return ("null" === b || b && b.startsWith("resource:")) && isLocalHost(extractIpAddress(a.socket.localAddress));
}

function initClassroomState() {
    classroomState = JSON.parse(JSON.stringify(defaultClassroomState)), classInfo.teams = classroomState.teams.teams, updateClassroomStateMessages(), sendFileState = Object.create(null), 
    answerSheets = Object.create(null), answerSheetMetadata = Object.create(null), userContents = Object.create(null);
}

function handleGetClassInfo(a, b) {
    isLocalHost(extractIpAddress(a.socket.localAddress)) ? (b.writeHead(200, {
        "Content-Type": "application/json"
    }), b.write(JSON.stringify(classInfo)), b.end()) : (b.writeHead(403), b.end());
}

function extractIpAddress(a) {
    if ("::1" === a) return "127.0.0.1";
    var b = a.match(/(\d+\.){3,3}\d+$/);
    return b && b[0] || "";
}

function handleLocalAddressChange(a) {
    var b = extractIpAddress(a);
    b && b !== localIP && "127.0.0.1" !== b && (classroomState && classroomState.screenBroadcast.url && (classroomState.screenBroadcast.url.replace(localIP, b), updateClassroomStateMessages(), 
    broadcastSSE("admin", classroomState.screenBroadcast)), localIP = b, console.log("handleLocalAddressChange: changed to " + b));
}

function handleSSE(a, b) {
    console.log("handleSSE: " + a.url), handleLocalAddressChange(a.socket.localAddress);
    var c = getPathParts(a.url), d = c[1], e = clients[d];
    if (!e) {
        if (!isSpecialUid(d)) return b.writeHead(200, "no such user", sseHeaders), void b.end();
        e = clients[d] = {
            userInfo: {
                uid: d,
                uname: d
            },
            responses: new Set()
        }, 0 === d.indexOf("device-") && classInfo.devices.push(e.userInfo);
    }
    b.setTimeout(0), b.on("close", function() {
        console.log("handleSSE: on get req close:", d), e.responses["delete"](b), e.responses.size || (e.userInfo.connected = !1, console.log("on get req close: no connection:", d), 
        unicastSSE("admin", {
            type: "Leave",
            uid: d
        }), maybeDropPresenter(d));
    }), b.writeHead(200, sseHeaders), b.write("\n"), e.responses.add(b);
    var f = e.userInfo.ipAddress = extractIpAddress(a.socket.remoteAddress);
    1 === e.responses.size && (e.userInfo.connected = !0, "admin" !== d && unicastSSE("admin", {
        type: "Join",
        uid: d,
        ipAddress: f
    })), "admin" !== d && classroomState.ping.on && startPing(e, !0), sendClassroomStateSSE(d, b), "admin" === d && (sendReceiveFilesProgresses(b), sendQuizState(b));
}

function maybeDropPresenter(a) {
    a === classroomState.presenter.id && (classroomState.presenter = Object.assign({}, defaultClassroomState.presenter), updateClassroomStateMessages(), broadcastSSE("", classroomState.presenter));
}

function unicastSSE(a, b) {
    var c = clients[a];
    return c ? (b = toSseData(b), void c.responses.forEach(function(a) {
        sendSSE(a, b);
    })) : void console.error("unicastSSE:no such client:" + a);
}

function broadcastSSE(a, b) {
    console.log("broadcastSSE from: " + a + ", ", b);
    var c = toSseData(b);
    for (var d in clients) a !== d && unicastSSE(d, c);
}

function broadcastSSE2Admins(a, b) {
    console.log("broadcastSSE from: " + a + ", ", b);
    var c = toSseData(b);
    for (var d in clients) a === d || "admin" !== d && !d.startsWith("device-remote") || unicastSSE(d, c);
}

function updateClassroomStateMessages() {
    classroomStateMessages = Object.keys(classroomState).filter(function(a) {
        return "screenCapture" !== a && "screenBroadcast" !== a;
    }).map(function(a) {
        return toSseData(classroomState[a]);
    });
}

function sendClassroomStateSSE(a, b) {
    if (classroomStateMessages || updateClassroomStateMessages(), classroomStateMessages.forEach(function(c) {
        a ? b ? sendSSE(b, c) : unicastSSE(a, c) : broadcastSSE("", c);
    }), a) sendScreenCaptureSSE(a, b), sendScreenBroadcastSSE(a, b); else for (var c in clients) sendScreenCaptureSSE(c), sendScreenBroadcastSSE(c);
}

function sendScreenBroadcastSSE(a, b) {
    var c;
    c = classroomState.screenCapture.on && classroomState.screenCapture.ignoreScreenBroadcast && (!classroomState.screenCapture.uids || classroomState.screenCapture.uids.indexOf(a) >= 0) ? {
        type: "ScreenBroadcast",
        on: !1
    } : classroomState.screenBroadcast, b ? sendSSE(b, c) : unicastSSE(a, c);
}

function sendScreenCaptureSSE(a, b) {
    var c;
    "admin" !== a && classroomState.screenCapture.uids ? classroomState.screenCapture.on && classroomState.screenCapture.uids.indexOf(a) >= 0 ? (c = Object.assign({}, classroomState.screenCapture), 
    delete c.uids) : c = defaultScreenCaptureOption : c = classroomState.screenCapture, b ? sendSSE(b, c) : unicastSSE(a, c);
}

function sendReceiveFilesProgresses(a) {
    var b = {
        type: "ReceiveFilesProgresses",
        progresses: {}
    };
    for (var c in sendFileState) b.progresses[c] = sendFileState[c].progresses;
    sendSSE(a, b);
}

function sendQuizState(a) {
    if (classroomState.quiz.on) {
        var b = {
            type: "QuizState",
            id: classroomState.quiz.id,
            answerSheets: answerSheets,
            answerSheetMetadata: answerSheetMetadata
        };
        sendSSE(a, b);
    }
}

function startPing(a, b) {
    if (!b || !a.pingAgent || a.userInfo.ipAddress !== a.pingAgent.targetIp) {
        stopPing(a);
        var c = a.userInfo.ipAddress, d = a.userInfo.uid;
        if (c) try {
            var e = a.pingAgent = new PingAgent_1.PingAgent(c);
            e.start(), e.onResponse = function(a, b) {
                unicastSSE("admin", {
                    type: "Pong",
                    time: b,
                    error: a,
                    uid: d,
                    ipAddress: c,
                    sumTime: e.sumTime,
                    maxTime: e.maxTime,
                    okCount: e.okCount,
                    errorCount: e.errorCount,
                    pingCount: e.pingCount
                });
            };
        } catch (f) {
            console.error("startPing: error:", f);
        }
    }
}

function stopPing(a) {
    a.pingAgent && (a.pingAgent.stop(), a.pingAgent = null);
}

function handleUnicastMessage(a, b) {
    var c = getPathParts(a.url), d = c[1];
    clients[d];
    readRequestBody(a).then(function(c) {
        console.log("handleUnicastMessage: " + a.url + " , " + c), unicastSSE(d, c), b.writeHead(200), b.end();
    }, function(a) {
        console.error(a), b.end();
    });
}

function handleBroadcastMessage(a, b) {
    var c = getPathParts(a.url), d = c[1];
    readRequestBody(a).then(function(c) {
        console.log("handleBroadcastMessage: " + a.url + " , " + c), broadcastSSE(d, c), b.writeHead(200), b.end();
    }, function(a) {
        console.error(a), b.end();
    });
}

function handlePostScreenCapture(a, b) {
    console.log("handlePostScreenCapture: " + a.url);
    var c = getPathParts(a.url), d = c[1], e = c[2];
    if (!classroomState || !classroomState.screenCapture.on && !e) return console.log("ScreenCapture is off, rejected: " + d), b.writeHead(404), void b.end();
    readRequestBody(a, !0).then(function(a) {
        console.log("ScreenCapture from: " + d), b.writeHead(200), b.end();
        var c = clients[d];
        c && (a && a.length && (c.screenCaptureImage = a), e && e === c.userInfo.status || unicastSSE("admin", {
            type: "UpdateScreenCapture",
            uid: d,
            status: e
        }), c.userInfo.status = e);
    });
}

function handleGetScreenCapture(a, b) {
    var c = getPathParts(a.url), d = c[1], e = clients[d];
    e && e.screenCaptureImage ? (b.writeHead(200, {
        "Content-Type": "image/png",
        "Cache-Control": "no-cache",
        "Content-Length": buffersLength(e.screenCaptureImage)
    }), sendBinaryBody(b, e.screenCaptureImage)) : (b.writeHead(404), b.end(), console.log("handleGetScreenCapture:", d, ", not found"));
}

function handleClassroomControl(a, b) {
    var c = getPathParts(a.url), d = c[1];
    readRequestBody(a).then(function(c) {
        var e = 0;
        try {
            var f = JSON.parse(c);
            console.log(">>>>> ", f.type, " >>>>>");
            var g = f;
            if ("string" == typeof g.remoteKey && ("" == remoteKey || g.remoteKey !== remoteKey) && !isLocalAddress(a.socket.remoteAddress)) return b.writeHead(403), void b.end("wrong remoteKey");
            delete g.remoteKey;
            var h = classroomControlHandler[f.type];
            h(f, d), e = 200;
        } catch (i) {
            e = 503, console.error(i);
        }
        b.writeHead(e), b.end();
    });
}

function handlePostFinishRace(a, b) {
    var c = classroomState.race;
    if (c.on && c.id) {
        var d = getPathParts(a.url), e = d[1], f = d[2];
        if (e === c.id && clients[f]) if (c.winner) b.writeHead(403), b.end(); else {
            var g = clients[f].userInfo;
            c.winner = {
                uid: g.uid,
                uname: g.uname
            }, updateClassroomStateMessages(), b.writeHead(200), b.end(), broadcastSSE("", c);
        } else b.writeHead(404), b.end();
    } else b.writeHead(404), b.end();
}

function handleLessonRecord(a, b) {
    var c = classroomState.lessonRecord;
    if (c.on && c.expired) {
        var d = getPathParts(a.url), e = d[2];
        clients[e] ? readRequestBody(a).then(function(a) {
            JSON.parse(a);
            b.writeHead(200), b.end();
            var c = {
                type: "LessonRecord",
                on: !1
            };
            unicastSSE("admin", c);
        })["catch"](function(a) {
            b.writeHead(500, a.toString()), b.end();
        }) : (b.writeHead(404), b.end());
    } else b.writeHead(404), b.end();
}

function handlePostAnswerSheet(a, b) {
    var c = classroomState.quiz;
    if (c.on && c.id && !c.expired) {
        var d = getPathParts(a.url), e = d[1], f = d[2];
        e === c.id && clients[f] ? answerSheets[f] ? (b.writeHead(403), b.end()) : readRequestBody(a).then(function(a) {
            var d = JSON.parse(a);
            answerSheets[f] = d, b.writeHead(200), b.end();
            var e = {
                type: "QuizState",
                id: c.id,
                answerSheets: (g = {}, g[f] = d, g),
                answerSheetMetadata: (h = {}, h[f] = {
                    timeUsed: Date.now() - c.startTime
                }, h)
            };
            unicastSSE("admin", e);
            var g, h;
        })["catch"](function(a) {
            b.writeHead(500, a.toString()), b.end();
        }) : (b.writeHead(404), b.end());
    } else b.writeHead(404), b.end();
}

function handlePostSelectTeam(a, b) {
    readRequestBody(a).then(function(c) {
        if (!classroomState.createTeams.on) return b.writeHead(403), void b.end();
        var d = getPathParts(a.url), e = d[1], f = JSON.parse(c), g = f.tid;
        if (g >= 0 && g <= classroomState.createTeams.teamCount) {
            f.uid = e;
            var h = classroomState.teams.teams;
            h.forEach(function(a) {
                return delete a.members[e];
            }), g > 0 && (h[g - 1].members[e] = ""), unicastSSE("admin", f), broadcastSSE2Admins("", classroomState.teams), updateClassroomStateMessages(), b.writeHead(200);
        } else b.writeHead(400);
        b.end();
    })["catch"](function(a) {
        b.writeHead(500, a.toString()), b.end();
    });
}

function handlePostPresenter(a, b) {
    readRequestBody(a).then(function(c) {
        var d = JSON.parse(c), e = d;
        if (!(e.remoteKey && e.remoteKey === remoteKey || isLocalAddress(a.socket.remoteAddress))) return b.writeHead(403), void b.end("no remoteKey or wrong remoteKey");
        delete e.remoteKey;
        var f = presenterHandlers[d.type];
        f(d, a, b), b.headersSent || (b.writeHead(200), b.end());
    }, function(a) {
        b.writeHead(500), b.end(a.toString());
    });
}

function handlePresenterState(a) {
    Object.assign(classroomState.presenter, a), updateClassroomStateMessages(), broadcastSSE("", a);
}

function handleHelloPresenter(a) {
    broadcastSSE("", a);
}

function handleNavigatePresenter(a) {
    a.id || (a.id = classroomState.presenter.id), unicastSSE(classroomState.presenter.id, a);
}

function handleImageCast(a) {
    broadcastSSE("", a);
}

function handleVideoCast(a) {
    broadcastSSE("", a);
}

function handleSetRemoteKey(a) {
    remoteKey = a.value || "", broadcastSSE("", {
        type: "SetRemoteKey",
        value: ""
    });
}

function handleGetClassroomState(a, b) {}

function handlePostStatistics(a, b) {
    console.log("handlePostStatistics: " + a.url);
    var c = getPathParts(a.url), d = c[1], e = clients[d];
    if (console.log("post req to:", d), e) {
        var f = "";
        a.on("data", function(a) {
            f += a;
        }), a.on("end", function() {
            b.writeHead(200), b.end(), console.log("received Statistics from:", d, ", content: " + f), e.statistics = f;
            var a = clients.admin;
            a && a.responses.forEach(function(a) {
                sendSSE(a, "updateStatistics");
            });
        });
    } else b.writeHead(404), b.end(), console.log("post req to:", d, ", not found");
}

function handleGetStatistics(a, b) {
    console.log("handleGetStatistics: " + a.url);
    var c = getPathParts(a.url), d = c[1];
    if (d) b.writeHead(404), b.end(); else {
        b.writeHead(200, {
            "Content-Type": "application/json",
            "Cache-Control": "no-cache"
        });
        var e = "[" + Object.keys(clients).map(function(a) {
            return clients[a].statistics ? clients[a].statistics : '{ "clientId": "' + a + '"}';
        }).join(",") + "]";
        b.end(e);
    }
}

function handleClearStatistics(a, b) {
    console.log("handleClearStatistics: " + a.url);
    for (var c in clients) clients[c].statistics = null;
    b.writeHead(200), b.end();
}

function sendFile(a, b, c) {
    fs.stat(a, function(d, e) {
        if (d) b.writeHead(404), b.end(), console.warn("sendFile:not found:" + a); else if (e.isFile()) {
            var f = fs.createReadStream(a), g = a.lastIndexOf("."), h = g > 0 ? a.slice(g + 1) : "", i = mimeMap[h] || "application/oct-stream";
            b.writeHead(200, {
                "Content-Type": i,
                "Cache-Control": "no-cache",
                "Content-Length": e.size,
                "Last-Modified": e.mtime.toUTCString()
            }), f.pipe(b), f.on("end", function() {
                return c && c(e.size, e.size);
            }), console.log("sendFile:sending:" + a);
        } else b.writeHead(403), b.end(), console.warn("sendFile:not file:" + a);
    });
}

function handleStatic(a, b) {
    console.log("handleStatic: " + a.url);
    var c = mUrl.parse(a.url).pathname, d = getPathParts(a.url), e = staticFileMap[c] ? staticFileMap[c] : "test" === d[0] || "client" === d[0] ? mPath.join.apply(mPath, [ __dirname, ".." ].concat(d)) : mPath.join.apply(mPath, [ process.cwd() ].concat(d));
    console.log("handleStatic:path: " + e), sendFile(e, b);
}

function handlePutUserContent(a, b) {
    var c = parseUserContentUrl(a.url);
    clients[c.uid] && c.subPath ? readRequestBody(a, !0).then(function(a) {
        userContents[c.uid] ? userContents[c.uid][c.subPath] = a : userContents[c.uid] = (d = {}, d[c.subPath] = a, d), maybeDropUserContentLater(c.uid, c.subPath, a), 
        console.log("handlePutUserContent:uid=" + c.uid + ", subPath=" + c.subPath), b.writeHead(200), b.end();
        var d;
    })["catch"](function(a) {
        b.writeHead(500), b.end();
    }) : (b.writeHead(404), b.end());
}

function maybeDropUserContentLater(a, b, c) {
    a.startsWith("device-remote") && setTimeout(function() {
        userContents[a] && userContents[a][b] === c && (delete userContents[a][b], console.log("auto-removed /userContent/" + a + "/" + b));
    }, 18e4);
}

function parseUserContentUrl(a) {
    var b = getPathParts(a), c = b[1], d = b.slice(2).join("/");
    return "userContent" === b[0] ? {
        uid: c,
        subPath: d
    } : {};
}

function handleGetUserContent(a, b) {
    var c = parseUserContentUrl(a.url), d = userContents[c.uid] && userContents[c.uid][c.subPath];
    d ? (b.writeHead(200, {
        "content-length": buffersLength(d)
    }), sendBinaryBody(b, d)) : (b.writeHead(404), b.end());
}

function handleGetReceiveFiles(a, b) {
    var c = getPathParts(a.url), d = c[1], e = c[2], f = (c[3], sendFileState[e]);
    4 === c.length && clients[d] && f ? (f.progresses[d] = 0, sendFile(f.path, b, function(a, b) {
        f.progresses[d] = a, unicastSSE("admin", {
            type: "ReceiveFilesProgresses",
            progresses: (c = {}, c[e] = (g = {}, g[d] = a, g), c)
        });
        var c, g;
    })) : (b.writeHead(404), b.end());
}

function stat(a) {
    return new Promise(function(b, c) {
        fs.stat(a, function(a, d) {
            a ? c(a) : b(d);
        });
    });
}

function startSendFiles(a) {
    var b = Array.isArray(a) ? a : [ a ];
    Promise.all(b.map(function(a) {
        return stat(a);
    })).then(function(a) {
        var c = Object.create(null);
        b.forEach(function(b, d) {
            var e = mPath.basename(b), f = getSendFileTaskId();
            classroomState.receiveFiles.tasks.push({
                id: f,
                fileName: e,
                fileSize: a[d].size,
                startTime: Date.now()
            });
            var g = Object.create(null);
            g.path = b, g.progresses = Object.create(null);
            for (var h in clients) "admin" !== h && (g.progresses[h] = 0);
            sendFileState[f] = g, c[f] = g.progresses;
        }), updateClassroomStateMessages(), broadcastSSE("", classroomState.receiveFiles), unicastSSE("admin", {
            type: "ReceiveFilesProgresses",
            progresses: c
        });
    }, function(a) {
        console.error("startSendFile:", b, a), unicastSSE("admin", {
            type: "SendFiles",
            paths: b,
            error: a
        });
    });
}

function startSendInstallFile(a) {
    fs.stat(a, function(b, c) {
        if (b) console.error("startSendInstallFile:" + a, b), unicastSSE("admin", {
            type: "SendInstallFile",
            path: a,
            error: b
        }); else {
            var d = mPath.basename(a), e = "/" + encodeURIComponent(d), f = "http://" + localIP + ":" + kMainPort + e;
            staticFileMap[e] = a, broadcastSSE("admin", {
                type: "Install",
                url: f
            });
        }
    });
}

function startSendUrls(a) {
    var b = classroomState.receiveFiles;
    a.forEach(function(a) {
        return b.urlTasks.push({
            id: getSendFileTaskId(),
            url: a.url,
            tags: a.tags,
            fileName: a.name,
            fileSize: a.size || 0,
            startTime: Date.now()
        });
    }), updateClassroomStateMessages(), broadcastSSE("", classroomState.receiveFiles);
}

function getChunk(a) {
    if (console.log("getChunk: " + a), chunks.has(a)) return chunks.get(a);
    if (a >= 0 && 1e7 >= a) {
        console.log("getChunk: creating " + a), chunks.size > 4 && chunks.clear();
        for (var b = new Buffer(a), c = 0; c < b.length; c++) b[c] = 256 * Math.random();
        return chunks.set(a, b), b;
    }
    return console.error("getChunk: illegal size"), null;
}

function handleChunk(a, b) {
    var c = getPathParts(a.url), d = (c[1], c[2]), e = getChunk(parseInt(d));
    e ? (b.writeHead(200, "ok", {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/oct-stream",
        "Cache-Control": "no-store",
        "Content-Length": "" + e.length
    }), b.end(e)) : (b.writeHead(403), b.end());
}

function startScreenBroadcast(a) {
    var b = a.width || kScreenBroadcastMaxWidth, c = a.height || kScreenBroadcastMaxHeight, d = "min(" + b + "/iw\\," + c + "/ih)", e = " -threads 2 -f gdigrab -framerate 10 -i desktop -filter:v fps=10 -filter:v scale=w=trunc(iw*" + d + "/2)*2:h=trunc(ih*" + d + "/2)*2 -f mpegts -muxdelay 0 -c:v libx264 -b:v " + (a.bitrate || 500) + "K -g 30 -pix_fmt yuv420p -tune zerolatency -x264opts intra-refresh=1:slice-max-size=1300 pipe:", f = "-vvv -I dummy --dummy-quiet udp://@" + (kStreamMulticastAddress || "127.0.0.1") + ":" + kStreamSourcePort + " :sout=#duplicate{dst=http{mux=ffmpeg{mux=mpegts},dst=:" + kScreenBroadcastPort + "/}}", g = a.ffmpeg || "ffmpeg", h = a.vlc || "vlc";
    return killScreenBroadcast().then(function() {
        return console.log("handleScreenBroadcast:" + g + " " + e), console.log("handleScreenBroadcast:" + h + " " + f), procFFmpeg = child_process_1.spawn(a.ffmpeg || "ffmpeg", e.split(" ").filter(function(a) {
            return !!a;
        }), {
            encoding: "buffer"
        }), console.log(">>>>ffoptions: ", e.split(" ").filter(function(a) {
            return !!a;
        })), procFFmpeg.on("error", function(a) {
            console.warn("procFFmpeg error: " + a), unicastSSE("admin", {
                type: "ScreenBroadcast",
                error: "视频广播启动失败"
            }), killScreenBroadcast();
        }), procFFmpeg.on("exit", function(a) {
            console.warn("procFFmpeg exit: " + a), killScreenBroadcast();
        }), kUseVlc ? waitFFmpeg() : null;
    }).then(function() {
        return monitorProc(procFFmpeg, "ffmpeg"), kUseVlc ? (a.url = "http://" + localIP + ":" + kScreenBroadcastPort, procVlc = child_process_1.spawn(a.vlc || "vlc", f.split(" ").filter(function(a) {
            return !!a;
        })), monitorProc(procVlc, "vlc", !0), procVlc.on("error", killScreenBroadcast), procVlc.on("exit", killScreenBroadcast), new Promise(function(a, b) {
            return setTimeout(a, 1e3);
        })) : (a.url = "http://" + localIP + ":" + kMainPort + "/screenBroadcast", a.urls = {
            mrdp: a.disableMrdp ? void 0 : "mrdp://" + kScreenBroadcastMulticastAddress + ":" + kScreenBroadcastPort,
            http: a.url
        }, httpStreamer.readStream(procFFmpeg.stdout, a.disableMrdp ? null : {
            outputMulticastAddress: kScreenBroadcastMulticastAddress,
            outputPort: kScreenBroadcastPort
        }), void 0);
    }).then(function() {
        if (!procFFmpeg || !procVlc && kUseVlc) throw unicastSSE("admin", {
            type: "ScreenBroadcast",
            error: "ffmpeg/vlc 启动失败"
        }), "handleScreenBroadcast: ffmpeg/vlc 启动失败";
        classroomState.screenBroadcast = a;
        for (var b in clients) sendScreenBroadcastSSE(b);
        return "ok";
    })["catch"](function(a) {
        throw killScreenBroadcast(), a;
    });
}

function waitFFmpeg() {
    return new Promise(function(a, b) {
        function c(b) {
            console.log(">>>waitFFmpeg: " + b), b.indexOf("frame=") >= 0 && (procFFmpeg && procFFmpeg.stderr.removeListener("data", c), a(null));
        }
        procFFmpeg ? procFFmpeg.stderr.on("data", c) : b("no procFFmpeg"), setTimeout(function() {
            procFFmpeg && procFFmpeg.stderr.removeListener("data", c), b("timeout");
        }, 2e3);
    });
}

function monitorProc(a, b, c) {
    a.on("error", function() {
        console.error("[tserver:" + b + "][E] exit!");
    }), c && a.stdout.on("data", function(a) {
        console.log("[tserver:" + b + "] " + a + "\n");
    }), a.stderr.on("data", function(a) {
        console.error("[tserver:" + b + "][E] " + a.toString() + "\n");
    });
}

function killScreenBroadcast(a) {
    (a || classroomState.screenBroadcast.on) && (classroomState.screenBroadcast = JSON.parse(JSON.stringify(defaultClassroomState.screenBroadcast)), broadcastSSE("", classroomState.screenBroadcast));
    var b = [];
    procFFmpeg && b.push(procFFmpeg), procVlc && b.push(procVlc), procFFmpeg = procVlc = null;
    var c = b.map(function(a) {
        return killProcess(a).then(function(b) {
            return console.log(a.pid + " killed");
        }, function(a) {
            return console.error(a);
        });
    });
    return kUseVlc || c.concat(httpStreamer.stop()), Promise.all(c);
}

function handleGetScreenBroadcast(a, b) {
    console.log("handleGetScreenBroadcast: " + a.url);
    var c = getPathParts(a.url), d = c[1] || extractIpAddress(a.socket.remoteAddress);
    b.writeHead(200, "ok", {
        "Content-Type": "video/M2PT",
        "Cache-Control": "no-store"
    }), httpStreamer.addHttpClient(b, d);
}

function startSignalling() {
    stopSignalling();
    var a = signallingSocket = dgram.createSocket("udp4");
    a.addMembership(kSignallingMulticastAddress), console.log("startSignalling:" + kSignallingMulticastAddress + ":" + kSignallingPort);
    var b = new Buffer(JSON.stringify({
        type: "Signalling"
    })), c = setInterval(function() {
        a.send(b, 0, b.length, kSignallingPort, kSignallingMulticastAddress);
    }, 500), d = function(b) {
        a === signallingSocket && (signallingSocket = null), clearInterval(c), b ? console.error("startSignalling:error", b) : console.log("startSignalling:closed");
    };
    a.on("close", d), a.on("error", d);
}

function stopSignalling() {
    signallingSocket && (signallingSocket.close(), signallingSocket = null);
}

function handlePostShutdown(a, b) {
    if (!isLocalHost(extractIpAddress(a.socket.localAddress))) {
        b.writeHead(403);
        var c = "handlePostShutdown: not allowed from remote address " + a.socket.localAddress;
        return b.end(c), void console.error(c);
    }
    console.error("handlePostShutdown: accepted from address " + a.socket.localAddress), b.writeHead(200), b.end("", shutdown);
}

function handleInteruptSSE(a, b) {
    if (!isLocalHost(extractIpAddress(a.socket.localAddress))) {
        b.writeHead(403);
        var c = "handleInteruptSSE: not allowed from remote address " + a.socket.localAddress;
        return b.end(c), void console.error(c);
    }
    console.error("handleInteruptSSE: accepted from address " + a.socket.localAddress), b.writeHead(200), b.end("");
    for (var d in clients) clients[d].responses.forEach(function(a) {
        return a.socket.destroy();
    });
}

function shutdown() {
    console.log("[shutdown] kill screen broadcast"), killScreenBroadcast(), console.log("[shutdown] close server"), server.close(), inboundSockets.forEach(function(a) {
        return a.end();
    }), console.log("[shutdown] exit"), process.exit(0);
}

function onConnection(a) {
    inboundSockets.add(a), a.on("close", function() {
        inboundSockets["delete"](a), console.log("an inbound socket closed.");
    });
}

function isLocalAddress(a) {
    return !!a && (a.endsWith("127.0.0.1") || "::1" === a || "localhost" === a);
}

Object.defineProperty(exports, "__esModule", {
    value: !0
});

var http_1 = require("http"), mUrl = require("url"), fs = require("fs"), mPath = require("path"), child_process_1 = require("child_process"), os_1 = require("os"), dgram = require("dgram"), HttpStreamer_1 = require("./HttpStreamer"), PingAgent_1 = require("./PingAgent"), version = "1.9", kMainPort = 7017, kSignallingPort = kMainPort + 4, kSignallingMulticastAddress = "236.0.0.2", kScreenBroadcastPort = kMainPort + 2, kScreenBroadcastMulticastAddress = "236.0.0.2", kStreamSourcePort = kMainPort + 1, kStreamMulticastAddress = "", kUseVlc = !1, kScreenBroadcastMaxWidth = 1280, kScreenBroadcastMaxHeight = 720, sseHeaders = {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-store"
}, signallingSocket, clients = Object.create(null), classInfo = {
    className: "?",
    students: [],
    devices: [],
    teams: []
}, defaultClassroomState = {
    screenCapture: {
        type: "ScreenCapture",
        on: !1
    },
    screenBroadcast: {
        type: "ScreenBroadcast",
        on: !1,
        disableMrdp: !0
    },
    lockScreen: {
        type: "LockScreen",
        on: !1
    },
    receiveFiles: {
        type: "ReceiveFiles",
        tasks: [],
        urlTasks: []
    },
    race: {
        type: "Race",
        on: !1
    },
    quiz: {
        type: "Quiz",
        on: !1
    },
    createTeams: {
        type: "CreateTeams",
        on: !1
    },
    teams: {
        type: "Teams",
        teams: []
    },
    ping: {
        type: "Ping",
        on: !1
    },
    presenter: {
        type: "Presenter",
        id: ""
    },
    lessonRecord: {
        type: "LessonRecord",
        on: !1,
        data: null
    },
    countDown: {
        type: "CountDown",
        on: !1
    },
    selectStudent: {
        type: "SelectStudent",
        on: !1
    }
}, defaultScreenCaptureOption = {
    type: "ScreenCapture",
    on: !0,
    fps: .25,
    maxWidth: 250,
    maxHeight: 150
}, classroomState = null, classroomStateMessages = null, sendFileState = Object.create(null), answerSheets = Object.create(null), answerSheetMetadata = Object.create(null), userContents = Object.create(null), staticFileMap = {
    "/test.htm": mPath.join(__dirname, "../test/mbnetworktest.htm"),
    "/": mPath.join(__dirname, "../test/index.htm")
}, procFFmpeg = null, procVlc = null, localIP = "127.0.0.1", httpStreamer = new HttpStreamer_1.HttpStreamer(), inboundSockets = new Set(), router = {
    __proto__: null,
    GET: {
        __proto__: null,
        messages: handleSSE,
        classInfo: handleGetClassInfo,
        screenCapture: handleGetScreenCapture,
        classroomState: handleGetClassroomState,
        screenBroadcast: handleGetScreenBroadcast,
        receiveFiles: handleGetReceiveFiles,
        userContent: handleGetUserContent,
        statistics: handleGetStatistics,
        chunk: handleChunk,
        testAlive: function(a, b) {
            b.writeHead(200), b.write("version: " + version), b.end();
        },
        time: function(a, b) {
            b.writeHead(200, {
                "Content-Type": "application/json"
            }), b.write(JSON.stringify({
                type: "Time",
                value: Date.now()
            })), b.end();
        },
        "@default": handleStatic
    },
    POST: {
        __proto__: null,
        broadcast: handleBroadcastMessage,
        unicast: handleUnicastMessage,
        screenCapture: handlePostScreenCapture,
        classroomControl: handleClassroomControl,
        finishRace: handlePostFinishRace,
        answerSheet: handlePostAnswerSheet,
        selectTeam: handlePostSelectTeam,
        presenter: handlePostPresenter,
        imageCast: handlePostPresenter,
        statistics: handlePostStatistics,
        clearStatistics: handleClearStatistics,
        shutdown: handlePostShutdown,
        interuptSSE: handleInteruptSSE,
        lessonRecord: handleLessonRecord
    },
    PUT: {
        __proto__: null,
        userContent: handlePutUserContent
    },
    OPTIONS: {
        "@default": handleCorsPreflight
    }
}, remoteKey = "", classroomControlHandler = {
    Reset: function(a) {
        initClassroomState(), (a.classInfo || a.users) && (a.classInfo ? (classInfo.className = a.classInfo.className, classInfo.students = a.classInfo.students, a.classInfo.teams && (classInfo.teams = classroomState.teams.teams = a.classInfo.teams)) : a.users && (classInfo.students = a.users), 
        console.log("Reset:users: "), Object.keys(clients).forEach(function(a) {
            if (!isSpecialUid(a)) {
                var b = clients[a];
                b.responses.forEach(function(a) {
                    return a.end();
                }), stopPing(b), delete clients[a];
            }
        }), a.classInfo.students.forEach(function(a) {
            var b = clients[a.uid] = {
                userInfo: a,
                responses: new Set()
            };
            classroomState.ping.on && startPing(b);
        })), sendClassroomStateSSE();
    },
    ScreenCapture: function(a) {
        var b = classroomState.screenCapture;
        classroomState.screenCapture = a, a.uids || (defaultScreenCaptureOption = a);
        for (var c in clients) a.ignoreScreenBroadcast && classroomState.screenBroadcast.on && a.uids && a.uids.indexOf(c) >= 0 && unicastSSE(c, {
            type: "ScreenBroadcast",
            on: !1
        }), sendScreenCaptureSSE(c);
        (!a.on || !a.uids) && b.on && b.uids && b.ignoreScreenBroadcast && classroomState.screenBroadcast.on && b.uids.forEach(function(a) {
            return sendScreenBroadcastSSE(a);
        });
    },
    LockScreen: function(a) {
        classroomState.lockScreen = a, updateClassroomStateMessages(), broadcastSSE("", a);
    },
    ScreenBroadcast: function(a) {
        var b = defaultClassroomState.screenBroadcast;
        null != a.disableMrdp && (b.disableMrdp = a.disableMrdp), null != a.width && (b.width = a.width), null != a.height && (b.height = a.height), null != a.bitrate && (b.bitrate = a.bitrate), 
        null != a.ffmpeg && (b.ffmpeg = a.ffmpeg), a.on ? startScreenBroadcast(Object.assign({}, b, a)) : killScreenBroadcast(!0);
    },
    SendFile: function(a) {
        return startSendFiles(a.path);
    },
    SendFiles: function(a) {
        return startSendFiles(a.paths);
    },
    SendInstallFile: function(a) {
        return startSendInstallFile(a.path);
    },
    SendUrls: function(a) {
        return startSendUrls(a.items);
    },
    DropPacket: function(a) {
        httpStreamer && (httpStreamer.packetDropRatio = a.ratio);
    },
    ClearCache: function(a) {
        broadcastSSE("", a);
    },
    Signalling: function(a) {
        a.on ? startSignalling() : stopSignalling();
    },
    Race: function(a) {
        a.on ? console.log("start race: " + a.id) : a.id || console.log("end race"), classroomState.race = a, updateClassroomStateMessages(), broadcastSSE("", a);
    },
    CountDown: function(a) {
        classroomState.countDown = a, updateClassroomStateMessages(), broadcastSSE("", a);
    },
    SelectStudent: function(a) {
        classroomState.selectStudent = a, updateClassroomStateMessages(), broadcastSSE("", a);
    },
    Quiz: function(a) {
        if (classroomState.quiz.on) if (a.on) if (a.expired) {
            if (classroomState.quiz.expired) throw "bad message";
            classroomState.quiz.expired = !0, console.log("quiz expired manually: " + classroomState.quiz.id);
        } else console.log("end quiz: " + classroomState.quiz.id), broadcastSSE("", {
            type: "Quiz",
            on: !1
        }), classroomState.quiz = a; else {
            if (a.on) throw "bad message";
            console.log("end quiz:" + classroomState.quiz.id), classroomState.quiz = {
                type: "Quiz",
                on: !1
            };
        } else {
            if (!a.on || a.expired) throw "bad message";
            classroomState.quiz = a;
        }
        if (a = a, a.on && !a.expired || !a.on) {
            answerSheets = Object.create(null), answerSheetMetadata = Object.create(null);
            var b = new Set(), c = userContents.admin;
            if (a.on && c) try {
                a.questions.forEach(function(a) {
                    a.body && a.body.images.forEach(function(a) {
                        var d = parseUserContentUrl(a);
                        "admin" === d.uid && c[d.subPath] && b.add(d.subPath);
                    });
                });
            } catch (d) {
                console.error(d);
            }
            if (b.size) for (var e in c) b.has(e) ? console.log("userContent admin/" + e + " retained") : (delete c[e], console.log("userContent admin/" + e + " removed")); else userContents = Object.create(null);
        }
        if (classroomState.quiz.on && !classroomState.quiz.expired) {
            var f = classroomState.quiz.id;
            classroomState.quiz.startTime = Date.now(), classroomState.quiz.lifeSpan > 0 && setTimeout(function() {
                f !== classroomState.quiz.id || classroomState.quiz.expired || (classroomState.quiz.expired = !0, console.log("quiz expired: " + f), broadcastSSE("", classroomState.quiz));
            }, classroomState.quiz.lifeSpan), console.log("start quiz: " + f);
        }
        updateClassroomStateMessages(), broadcastSSE("", classroomState.quiz);
    },
    LessonRecord: function(a) {
        classroomState.lessonRecord = a, classroomState.lessonRecord.lifeSpan > 0 && setTimeout(function() {
            classroomState.lessonRecord.expired = !0, classroomState.lessonRecord.expired && broadcastSSE("", classroomState.lessonRecord);
        }, classroomState.lessonRecord.lifeSpan), console.log("tserver: handler LessonRecord's liftSpan: ", classroomState.lessonRecord.lifeSpan), updateClassroomStateMessages(), 
        broadcastSSE("", classroomState.lessonRecord);
    },
    CreateTeams: function(a) {
        if (a.on && !(a.teamCount > 1)) throw "Bad teamCount:" + a.teamCount;
        classroomState.createTeams = a, broadcastSSE("", a);
        var b = classroomState.teams.teams;
        if (b.length = 0, a.on) {
            for (var c = 1; c <= a.teamCount; c++) b[c - 1] = {
                tid: c,
                score: 0,
                members: {}
            };
            broadcastSSE2Admins("", classroomState.teams);
        }
        updateClassroomStateMessages();
    },
    Teams: function(a) {
        classroomState.teams = a, classInfo.teams = a.teams, broadcastSSE2Admins("", a), updateClassroomStateMessages();
    },
    Ping: function(a) {
        classroomState.ping = a;
        for (var b in clients) {
            var c = clients[b];
            "admin" !== b && a.on ? startPing(c) : stopPing(c);
        }
        updateClassroomStateMessages(), unicastSSE("admin", classroomState.ping);
    }
}, presenterHandlers = {
    Presenter: handlePresenterState,
    NavigatePresenter: handleNavigatePresenter,
    ImageCast: handleImageCast,
    VideoCast: handleVideoCast,
    SetRemoteKey: handleSetRemoteKey,
    HelloPresenter: handleHelloPresenter
}, mimeMap = {
    __proto__: null,
    js: "text/javascript",
    css: "text/css",
    htm: "text/html",
    html: "text/html"
}, getSendFileTaskId = function() {
    var a = 0, b = Date.now();
    return function() {
        return b + "-" + a++;
    };
}(), chunks = new Map(), isLocalHost = function() {
    var a = os_1.networkInterfaces(), b = Date.now();
    return function(c) {
        var d = Date.now();
        if (d - b > 300 && (a = os_1.networkInterfaces(), b = d), "localhost" === c.toLowerCase()) return !0;
        for (var e in a) for (var f = a[e], g = 0, h = f; g < h.length; g++) {
            var i = h[g];
            if (i.address === c) return !0;
        }
        return !1;
    };
}();

initClassroomState();

var server = http_1.createServer().on("request", trapError).on("request", onRequest).on("connection", onConnection).listen(kMainPort);

console.log("listening " + kMainPort), setInterval(function() {
    for (var a in clients) clients[a].responses.forEach(function(a) {
        return sendSSE(a, null);
    });
}, 1e4), process.stdin.setEncoding("utf8"), process.stdin.on("readable", function() {
    for (var a; a = process.stdin.read(); ) {
        var b = a.replace(/\r|\n/g, "");
        "shutdown" === b && shutdown();
    }
});
//# sourceMappingURL=tserver.js.map