"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.experiment_router = void 0;
const express_1 = require("express");
const express_async_handler_1 = __importDefault(require("express-async-handler"));
function experiment_router(language, renderPage, _logger) {
    async function getPspuBpuGroup(req) {
        const devices = await req.apiClient.listDevices();
        const deviceGroups = devices.filter((d) => d.type === "group");
        const pspuGroupUrl = deviceGroups.find((d) => d.name.toLowerCase() === "pspu")?.url;
        const bpuGroupUrl = deviceGroups.find((d) => d.name.toLowerCase() === "bpu")?.url;
        if (!pspuGroupUrl) {
            throw new Error("Could not find pspu group");
        }
        if (!bpuGroupUrl) {
            throw new Error("Could not find bpu group");
        }
        const pspuGroup = await req.apiClient.getDevice(pspuGroupUrl);
        if (pspuGroup.type !== "group") {
            throw new Error("Device is not a group");
        }
        const bpuGroup = await req.apiClient.getDevice(bpuGroupUrl);
        if (bpuGroup.type !== "group") {
            throw new Error("Device is not a group");
        }
        return { pspuGroup, bpuGroup };
    }
    async function experimentSelection(req, res, _next) {
        if (!req.user) {
            return renderPage("experiment/selection", language, res, req.user);
        }
        if (req.method === "POST") {
            const experiment = await buildSimpleExperiment(req);
            console.log(experiment);
            experiment.status = "running";
            const response = await req.apiClient.createExperiment(experiment);
            console.log(response);
            if (response.status === "setup" && response.url) {
                return experimentSetup(req, res, _next, response);
            }
        }
        try {
            const { pspuGroup, bpuGroup } = await getPspuBpuGroup(req);
            const pspus = await Promise.all(pspuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            const bpus = await Promise.all(bpuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            return renderPage("experiment/selection", language, res, req.user, {
                experiment,
                pspus,
                bpus,
            });
        }
        catch {
            return renderPage("experiment/selection", language, res, req.user, {
                experiment,
                pspus: [],
                bpus: [],
            });
        }
    }
    async function experiment(req, res, _next) {
        const templateUrl = req.query.template;
        // TODO: maybe check somewhere else and return a specific page experiment_401.html
        if (!req.user) {
            return renderPage("experiment/developer", language, res, req.user);
        }
        let experiment;
        if (typeof templateUrl === "string") {
            experiment = {
                ...(await req.apiClient.getTemplate(templateUrl)).configuration,
                status: "running",
            };
        }
        if (req.method === "GET") {
            try {
                if (!experiment) {
                    experiment = await buildSimpleExperiment(req);
                }
                else {
                    const response = await req.apiClient.createExperiment(experiment);
                    if (response.status === "setup" && response.url) {
                        return experimentSetup(req, res, _next, response);
                    }
                }
            }
            catch (e) {
                //ignore
            }
        }
        if (req.method === "POST") {
            if (req.body.experiment)
                experiment = JSON.parse(req.body.experiment);
            if (!experiment)
                throw new Error("No experiment provided");
            experiment.status = "running";
            console.log(JSON.stringify(experiment, null, 2));
            const response = await req.apiClient.createExperiment(experiment);
            if (response.status === "setup" && response.url) {
                return experimentSetup(req, res, _next, response);
            }
        }
        try {
            const { pspuGroup, bpuGroup } = await getPspuBpuGroup(req);
            const pspus = await Promise.all(pspuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            const bpus = await Promise.all(bpuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            return renderPage("experiment/developer", language, res, req.user, {
                experiment,
                pspus,
                bpus,
            });
        }
        catch {
            return renderPage("experiment/developer", language, res, req.user, {
                experiment,
                pspus: [],
                bpus: [],
            });
        }
    }
    async function experimentSetup(req, res, _next, experiment) {
        let display = req.query.display ?? "link";
        if (experiment.status !== "setup") {
            throw new Error("Experiment is not in setup phase");
        }
        const instances = [];
        for (const device of experiment.instantiatedDevices ?? []) {
            const deviceDetails = await req.apiClient.getDevice(device.instanceOf);
            if (deviceDetails.type === "edge instantiable" &&
                device.url &&
                device.token &&
                deviceDetails.codeUrl) {
                instances.push({
                    codeUrl: deviceDetails.codeUrl,
                    instanceUrl: device.url,
                    deviceToken: device.token,
                });
            }
        }
        if (instances.length <= 1) {
            display = "iframe";
        }
        //await req.apiClient.updateExperiment(experiment.url!, { devices: experiment.devices })
        return renderPage("experiment/show", language, res, req.user, {
            experiment,
            instances,
            display,
        });
    }
    async function runExperiment(req, res, _next) {
        const resp = await req.apiClient.updateExperiment(req.query.url, {
            status: "running",
        });
        res.send(resp);
        //res.redirect(303, '/' + language + '/index.html');
    }
    async function bookExperiment(req, res, _next) {
        if (!req.user) {
            return renderPage("experiment-reservation", language, res, req.user);
        }
        let experiment;
        if (req.method === "GET") {
            try {
                if (!experiment)
                    experiment = await buildSimpleExperiment(req);
            }
            catch (e) {
                //ignore
            }
        }
        if (req.method === "POST") {
            if (req.body.experiment)
                experiment = JSON.parse(req.body.experiment);
            if (!experiment)
                throw new Error("No experiment provided");
            console.log(req.body);
            experiment.status = "booked";
            experiment.bookingTime = {
                startTime: req.body.startTime,
                endTime: req.body.endTime,
            };
            const response = await req.apiClient.createExperiment(experiment);
            if (response.status === "booked" && response.url) {
                return res.redirect(`/${language}/reservations`);
            }
        }
        try {
            const { pspuGroup, bpuGroup } = await getPspuBpuGroup(req);
            const pspus = await Promise.all(pspuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            const bpus = await Promise.all(bpuGroup.devices.map((d) => req.apiClient.getDevice(d.url)));
            return renderPage("experiment-reservation", language, res, req.user, {
                experiment,
                pspus,
                bpus,
            });
        }
        catch {
            return renderPage("experiment-reservation", language, res, req.user, {
                experiment,
                pspus: [],
                bpus: [],
            });
        }
    }
    async function experimentReservations(req, res, _next) {
        if (!req.user) {
            return renderPage("reservations", language, res, req.user);
        }
        if (req.method === "POST") {
            if (!req.body.url)
                throw new Error("No experiment url provided!");
            const response = await req.apiClient.updateExperiment(req.body.url, {
                status: "running",
            });
            if (response.status === "setup" && response.url) {
                return experimentSetup(req, res, _next, response);
            }
        }
        const experiments = await Promise.all((await req.apiClient.listExperiments({
        //status: "booked", TODO: Does not work with the current API??
        }))
            .filter((experimentOverview) => experimentOverview.status === "booked")
            .map(async (experimentOverview) => {
            const experiment = await req.apiClient.getExperiment(experimentOverview.url);
            const devices = await Promise.all(experiment.devices.map(async (device) => {
                return {
                    ...(await req.apiClient.getDevice(device.device)),
                    role: device.role,
                };
            }));
            const experimentData = {
                ...experiment,
                bookingTime: {
                    startTime: new Date(experiment.bookingTime?.startTime).toISOString(),
                    endTime: new Date(experiment.bookingTime?.endTime).toISOString(),
                },
                devices,
                isStartable: Date.now() > Date.parse(experiment.bookingTime?.startTime) &&
                    Date.now() < Date.parse(experiment.bookingTime?.endTime),
            };
            return experimentData;
        }));
        return renderPage("reservations", language, res, req.user, { experiments });
    }
    async function deleteExperiment(req, res, _next) {
        const experimentUrl = req.query["experimentUrl"]?.toString();
        if (!experimentUrl)
            return res.status(400).send();
        await req.apiClient.deleteExperiment(experimentUrl);
        return res.status(204).send();
    }
    async function configTool(req, res, _next) {
        const templateUrl = req.query.template;
        let experiment;
        if (typeof templateUrl === "string") {
            experiment = {
                ...(await req.apiClient.getTemplate(templateUrl)).configuration,
            };
        }
        if (req.method === "POST") {
            if (req.body.experiment)
                experiment = JSON.parse(req.body.experiment);
            if (!experiment)
                throw new Error("No experiment provided");
            experiment.status = "running";
            experiment.devices =
                experiment.devices ??
                    experiment.roles?.map((r) => ({
                        device: r.template_device,
                        role: r.name,
                    }));
            console.log(JSON.stringify(experiment, null, 2));
            const response = await req.apiClient.createExperiment(experiment);
            if (response.status === "setup" && response.url) {
                return experimentSetup(req, res, _next, response);
            }
        }
        return renderPage("experiment/configtool", language, res, req.user, {
            token: req.apiClient.accessToken,
            api: req.apiClient.url,
            experiment,
        });
    }
    const router = (0, express_1.Router)();
    router.get("/selection", (0, express_async_handler_1.default)(experimentSelection));
    router.post("/selection", (0, express_async_handler_1.default)(experimentSelection));
    router.get("/developer", (0, express_async_handler_1.default)(experiment));
    router.post("/developer", (0, express_async_handler_1.default)(experiment));
    router.get("/configtool", (0, express_async_handler_1.default)(configTool));
    router.post("/configtool", (0, express_async_handler_1.default)(configTool));
    router.delete("/experiment", deleteExperiment);
    router.get("/book-experiment", (0, express_async_handler_1.default)(bookExperiment));
    router.post("/book-experiment", (0, express_async_handler_1.default)(bookExperiment));
    router.get("/run-experiment", (0, express_async_handler_1.default)(runExperiment));
    router.get("/reservations", (0, express_async_handler_1.default)(experimentReservations));
    router.post("/reservations", (0, express_async_handler_1.default)(experimentReservations));
    return router;
}
exports.experiment_router = experiment_router;
const ecpServiceDescription = [
    {
        serviceType: "https://api.goldi-labs.de/serviceTypes/electrical",
        serviceId: "electrical",
        serviceDirection: "prosumer",
        interfaces: [
            { interfaceType: "gpio", availableSignals: { gpio: ["_ANY_"] } },
        ],
    },
    {
        serviceId: "webcam",
        serviceType: "https://api.goldi-labs.de/serviceTypes/webcam",
        serviceDirection: "consumer",
    },
    {
        serviceId: "file",
        serviceType: "https://api.goldi-labs.de/serviceTypes/file",
        serviceDirection: "producer",
    },
];
async function buildSimpleExperiment(req) {
    const pspu = req.query.pspu;
    const bpu = req.query.bpu;
    const ecp = (await req.apiClient.listDevices()).find((device) => device.type === "edge instantiable" && device.name.toLowerCase() === "ecp")?.url;
    if (!ecp) {
        throw new Error("Could not find ecp!");
    }
    const devices = [
        { device: pspu, role: "pspu" },
        { device: bpu, role: "bpu" },
        { device: ecp, role: "ecp" },
    ].filter(({ device: url }) => url !== undefined && url !== "");
    const roles = devices.map((d) => ({
        name: d.role,
    }));
    const roleServices = await Promise.all(devices.map(({ device: url, role }) => req.apiClient.getDevice(url).then((device) => ({
        role,
        services: role === "ecp"
            ? ecpServiceDescription
            : device.services,
    }))));
    const serviceConfigurations = [];
    const serviceTypes = new Set();
    roleServices
        .flatMap(({ services }) => services.map(({ serviceType }) => serviceType))
        .forEach((t) => serviceTypes.add(t));
    console.log({ serviceTypes });
    for (const serviceType of serviceTypes) {
        let participants = [];
        for (const { role, services } of roleServices) {
            for (const service of services.filter((s) => s.serviceType === serviceType)) {
                participants.push({
                    role,
                    serviceId: service.serviceId,
                    description: service,
                });
            }
        }
        if (participants.length >= 2) {
            if (serviceType === "https://api.goldi-labs.de/serviceTypes/electrical") {
                const rDI = (s) => {
                    try {
                        return s.replace("#", "");
                    }
                    catch (e) {
                        return s;
                    }
                }; // remove _DRIVEN_ from signal name
                const interfaces = participants.flatMap((p) => {
                    if (p.description.interfaces)
                        return p.description.interfaces.map((i) => ({
                            role: p.role,
                            interface: i,
                        }));
                    else
                        return [];
                });
                const gpioInterfaces = interfaces.filter((i) => i.interface.interfaceType === "gpio");
                const buses = gpioInterfaces.flatMap((i) => {
                    const isDriven = ["inout", "out"].includes(i.interface.direction);
                    if (i.role === "ecp")
                        return [];
                    if (isDriven) {
                        return i.interface.availableSignals.gpio
                            .filter((s) => s.length > 4)
                            .map((s) => ["#" + s]);
                    }
                    else {
                        return i.interface.availableSignals.gpio
                            .filter((s) => s.length > 4)
                            .map((s) => [s]);
                    }
                });
                for (const i of gpioInterfaces) {
                    if (i.role === "ecp")
                        continue;
                    const isDriven = ["inout", "out"].includes(i.interface.direction);
                    const freeSignals = i.interface.availableSignals.gpio.filter((s) => s.length <= 4);
                    for (const bus of buses) {
                        const signal = freeSignals.shift();
                        const isBusDriven = bus.some((s) => s.startsWith("#"));
                        if (signal) {
                            if (isDriven && !isBusDriven) {
                                bus.push("#" + signal);
                            }
                            else {
                                bus.push(signal);
                            }
                        }
                    }
                }
                let id = 0;
                for (const participant of participants) {
                    if (participant.role === "ecp") {
                        participant.config = {
                            interfaces: buses.map((bus) => ({
                                interfaceId: (++id).toString(),
                                interfaceType: "gpio",
                                signals: { gpio: bus.map(rDI).join(" / ") },
                                busId: rDI(bus[0]),
                                direction: bus.some((s) => s.startsWith("#"))
                                    ? "in"
                                    : "inout",
                                driver: participant.role,
                            })),
                        };
                    }
                    else {
                        const interfaces = [participant].flatMap((p) => {
                            if (p.description.interfaces)
                                return p.description.interfaces.map((i) => ({
                                    role: p.role,
                                    interface: i,
                                }));
                            else
                                return [];
                        });
                        const gpioInterfaces = interfaces.filter((i) => i.interface.interfaceType === "gpio");
                        const signals = gpioInterfaces.flatMap((i) => i.interface.availableSignals.gpio.map((s) => {
                            const isDriven = ["inout", "out"].includes(i.interface.direction);
                            if (isDriven) {
                                return "#" + s;
                            }
                            else {
                                return s;
                            }
                        }));
                        const mappedSignals = signals.filter((s) => buses.find((b) => b.map(rDI).includes(rDI(s))));
                        // check if signals are driven in bus:
                        const mappedBusSignals = mappedSignals.map((s) => buses
                            .find((b) => b.map(rDI).includes(rDI(s)))
                            .find((bs) => rDI(bs) === rDI(s)));
                        console.log({ signals, mappedBusSignals, buses });
                        participant.config = {
                            interfaces: mappedBusSignals.map((signal) => ({
                                interfaceId: (++id).toString(),
                                interfaceType: "gpio",
                                signals: { gpio: rDI(signal) },
                                busId: rDI(buses.find((b) => b.map(rDI).includes(rDI(signal)))[0]),
                                direction: signal.startsWith("#") ? "out" : "in",
                                driver: participant.role,
                            })),
                        };
                    }
                }
            }
            else {
                participants = participants.map((p) => ({ ...p, config: {} }));
            }
            participants = participants.map((p) => ({
                ...p,
                description: undefined,
            }));
            serviceConfigurations.push({
                serviceType,
                configuration: {},
                participants,
            });
        }
    }
    return { status: "created", devices, roles, serviceConfigurations };
}
//# sourceMappingURL=experiment.js.map