#include "HyperTwistIPC/HyperTwistIPCProcessManager.h"

UHyperTwistIPCProcessManager::UHyperTwistIPCProcessManager()
{
}

UHyperTwistIPCProcessManager* UHyperTwistIPCProcessManager::CreateIPCManager(UObject* WorldContextObject)
{
    return NewObject<UHyperTwistIPCProcessManager>(WorldContextObject);
}

FString UHyperTwistIPCProcessManager::GetSolversDirectory()
{
    return FPaths::ConvertRelativePathToFull(
        FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/"));
}

FHyperTwistIPCResult UHyperTwistIPCProcessManager::RunProcess(const FHyperTwistIPCProcessOptions& Options)
{
    FHyperTwistIPCResult Result;

    if (!FPaths::FileExists(Options.ExecutablePath))
    {
        Result.ErrorMessage = FString::Printf(TEXT("Executable not found: %s"), *Options.ExecutablePath);
        return Result;
    }

    FString Params = FString::Join(Options.Arguments, TEXT(" "));
    FString WorkingDir = Options.WorkingDirectory.IsEmpty()
        ? FPaths::GetPath(Options.ExecutablePath)
        : Options.WorkingDirectory;

    void* ReadPipe = nullptr;
    void* WritePipe = nullptr;
    void* StdErrPipe = nullptr;

    FProcHandle ProcessHandle;
    if (Options.bCaptureOutput)
    {
        FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
        ProcessHandle = FPlatformProcess::CreateProc(
            *Options.ExecutablePath,
            *Params,
            false,
            true,
            true,
            nullptr,
            0,
            *WorkingDir,
            WritePipe,
            ReadPipe);
    }
    else
    {
        ProcessHandle = FPlatformProcess::CreateProc(
            *Options.ExecutablePath,
            *Params,
            false,
            false,
            false,
            nullptr,
            0,
            *WorkingDir,
            nullptr,
            nullptr);
    }

    if (!ProcessHandle.IsValid())
    {
        Result.ErrorMessage = TEXT("Failed to create process");
        FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
        return Result;
    }

    double StartTime = FPlatformTime::Seconds();
    FString Output;
    FString ErrorOutput;

    while (FPlatformProcess::IsProcRunning(ProcessHandle))
    {
        if (Options.bCaptureOutput && ReadPipe)
        {
            Output += FPlatformProcess::ReadPipe(ReadPipe);
        }

        float Elapsed = static_cast<float>(FPlatformTime::Seconds() - StartTime);
        if (Elapsed > Options.TimeoutSeconds)
        {
            FPlatformProcess::TerminateProc(ProcessHandle);
            Result.ErrorMessage = FString::Printf(TEXT("Process timed out after %.1f seconds"), Options.TimeoutSeconds);
            FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
            return Result;
        }

        FPlatformProcess::Sleep(0.05f);
    }

    if (Options.bCaptureOutput && ReadPipe)
    {
        Output += FPlatformProcess::ReadPipe(ReadPipe);
    }

    int32 ReturnCode = 0;
    FPlatformProcess::GetProcReturnCode(ProcessHandle, &ReturnCode);
    FPlatformProcess::CloseProc(ProcessHandle);
    FPlatformProcess::ClosePipe(ReadPipe, WritePipe);

    Result.bSuccess = (ReturnCode == 0);
    Result.ExitCode = ReturnCode;
    Result.StdOut = Output;
    Result.StdErr = ErrorOutput;

    return Result;
}

int32 UHyperTwistIPCProcessManager::StartProcess(const FHyperTwistIPCProcessOptions& Options)
{
    if (!FPaths::FileExists(Options.ExecutablePath))
    {
        return -1;
    }

    FString Params = FString::Join(Options.Arguments, TEXT(" "));
    FString WorkingDir = Options.WorkingDirectory.IsEmpty()
        ? FPaths::GetPath(Options.ExecutablePath)
        : Options.WorkingDirectory;

    void* ReadPipe = nullptr;
    void* WritePipe = nullptr;

    if (Options.bCaptureOutput)
    {
        FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
    }

    FProcHandle ProcessHandle = FPlatformProcess::CreateProc(
        *Options.ExecutablePath,
        *Params,
        false,
        true,
        true,
        nullptr,
        0,
        *WorkingDir,
        WritePipe,
        ReadPipe);

    if (!ProcessHandle.IsValid())
    {
        FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
        return -1;
    }

    int32 Id = NextProcessId++;
    FActiveProcess Proc;
    Proc.Handle = ProcessHandle;
    Proc.ReadPipe = ReadPipe;
    Proc.WritePipe = WritePipe;
    Proc.StartTime = static_cast<float>(FPlatformTime::Seconds());
    Proc.ExecutablePath = Options.ExecutablePath;
    ActiveProcesses.Add(Id, Proc);

    return Id;
}

bool UHyperTwistIPCProcessManager::WriteToProcess(int32 ProcessId, const FString& Line)
{
    FActiveProcess* Proc = ActiveProcesses.Find(ProcessId);
    if (!Proc || !Proc->WritePipe)
    {
        return false;
    }

    FString Text = Line + TEXT("\n");
    FPlatformProcess::WritePipe(Proc->WritePipe, *Text);
    return true;
}

FString UHyperTwistIPCProcessManager::ReadFromProcess(int32 ProcessId)
{
    FActiveProcess* Proc = ActiveProcesses.Find(ProcessId);
    if (!Proc || !Proc->ReadPipe)
    {
        return FString();
    }

    return FPlatformProcess::ReadPipe(Proc->ReadPipe);
}

bool UHyperTwistIPCProcessManager::TerminateProcess(int32 ProcessId)
{
    FActiveProcess* Proc = ActiveProcesses.Find(ProcessId);
    if (!Proc)
    {
        return false;
    }

    if (FPlatformProcess::IsProcRunning(Proc->Handle))
    {
        FPlatformProcess::TerminateProc(Proc->Handle);
    }

    FPlatformProcess::CloseProc(Proc->Handle);
    FPlatformProcess::ClosePipe(Proc->ReadPipe, Proc->WritePipe);
    ActiveProcesses.Remove(ProcessId);
    return true;
}

bool UHyperTwistIPCProcessManager::IsProcessRunning(int32 ProcessId)
{
    FActiveProcess* Proc = ActiveProcesses.Find(ProcessId);
    if (!Proc)
    {
        return false;
    }

    return FPlatformProcess::IsProcRunning(Proc->Handle);
}
