/*
    * IoRedir.hpp
    * SYS_SPAWN_REDIR, SYS_CHILDIO_READ, SYS_CHILDIO_WRITE,
    * SYS_CHILDIO_WRITEKEY, SYS_CHILDIO_SETTERMSZ syscalls
    * Copyright (c) 2026 Daniel Hammer
*/

#pragma once
#include <Sched/Scheduler.hpp>
#include "Syscall.hpp"
#include "Common.hpp"
#include "Path.hpp"

namespace Montauk {

    static int Sys_SpawnRedir(const char* path, const char* args) {
        char resolved[256];
        if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;

        int parentSlot = Ipc::CurrentSlot();
        int childPid = Sched::Spawn(resolved, args, false);
        if (childPid < 0) return -1;

        auto* child = Sched::GetProcessByPid(childPid);
        int childSlot = Ipc::SlotForPid(childPid);
        if (child == nullptr || childSlot < 0 || parentSlot < 0) {
            Sched::KillProcess(childPid);
            return -1;
        }

        Ipc::Stream* outStream = Ipc::CreateStream();
        Ipc::Stream* inStream = Ipc::CreateStream();
        Ipc::Mailbox* keyMailbox = Ipc::CreateMailbox();
        if (outStream == nullptr || inStream == nullptr || keyMailbox == nullptr) {
            if (outStream != nullptr) Ipc::ReleaseStream(outStream, false, false);
            if (inStream != nullptr) Ipc::ReleaseStream(inStream, false, false);
            if (keyMailbox != nullptr) Ipc::ReleaseMailbox(keyMailbox, false, false);
            Sched::KillProcess(childPid);
            return -1;
        }

        int parentOutHandle = Ipc::InstallHandleForSlot(
            parentSlot, (Ipc::Object*)outStream, Ipc::HandleType::Stream, Ipc::RightRead);
        int parentInHandle = Ipc::InstallHandleForSlot(
            parentSlot, (Ipc::Object*)inStream, Ipc::HandleType::Stream, Ipc::RightWrite);
        int parentKeyHandle = Ipc::InstallHandleForSlot(
            parentSlot, (Ipc::Object*)keyMailbox, Ipc::HandleType::Mailbox, Ipc::RightSend);

        child->ioOutHandle = Ipc::InstallHandleForSlot(
            childSlot, (Ipc::Object*)outStream, Ipc::HandleType::Stream,
            Ipc::RightWrite | Ipc::RightWait | Ipc::RightDup);
        child->ioInHandle = Ipc::InstallHandleForSlot(
            childSlot, (Ipc::Object*)inStream, Ipc::HandleType::Stream,
            Ipc::RightRead | Ipc::RightWait | Ipc::RightDup);
        child->ioKeyHandle = Ipc::InstallHandleForSlot(
            childSlot, (Ipc::Object*)keyMailbox, Ipc::HandleType::Mailbox,
            Ipc::RightRecv | Ipc::RightWait | Ipc::RightDup);

        if (parentOutHandle < 0 || parentInHandle < 0 || parentKeyHandle < 0 ||
            child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
            !ConfigureRedirWaitsetForSlot(childSlot, child)) {
            if (parentOutHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentOutHandle);
            if (parentInHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentInHandle);
            if (parentKeyHandle >= 0) Ipc::CloseHandleForSlot(parentSlot, parentKeyHandle);
            if (child->ioOutHandle < 0) Ipc::ReleaseStream(outStream, false, false);
            if (child->ioInHandle < 0) Ipc::ReleaseStream(inStream, false, false);
            if (child->ioKeyHandle < 0) Ipc::ReleaseMailbox(keyMailbox, false, false);
            Sched::KillProcess(childPid);
            return -1;
        }

        child->redirected = true;
        child->parentPid = Sched::GetCurrentPid();
        child->termCols = 80;
        child->termRows = 25;
        if (Sched::StartProcess(childPid) < 0) {
            Sched::KillProcess(childPid);
            return -1;
        }

        return childPid;
    }

    static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
        auto* child = Sched::GetProcessByPid(childPid);
        Ipc::Stream* stream = GetRedirOutStream(child);
        if (child == nullptr || !child->redirected || stream == nullptr) return -1;
        return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
    }

    static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
        auto* child = Sched::GetProcessByPid(childPid);
        Ipc::Stream* stream = GetRedirInStream(child);
        if (child == nullptr || !child->redirected || stream == nullptr) return -1;
        return WriteAllToStream(stream, (const uint8_t*)data, len);
    }

    static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
        if (key == nullptr) return -1;
        auto* child = Sched::GetProcessByPid(childPid);
        Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child);
        if (child == nullptr || !child->redirected || mailbox == nullptr) return -1;

        for (;;) {
            int rc = Ipc::MailboxSend(mailbox, 0, key, sizeof(KeyEvent));
            if (rc < 0) return -1;
            if (rc == 0) {
                Sched::BlockOnObject(mailbox, 0);
                continue;
            }
            return 0;
        }
    }

    static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
        auto* child = Sched::GetProcessByPid(childPid);
        if (child == nullptr || !child->redirected) return -1;
        child->termCols = cols;
        child->termRows = rows;
        return 0;
    }
};
