#include "HyperTwistIPC/HyperTwistSolverOracleLibrary.h"
#include "HyperTwistSolverLibrary.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "HAL/PlatformFilemanager.h"
#include "UObject/Package.h"

FString UHyperTwistSolverOracleLibrary::GetBrownanSolverPath()
{
    return FPaths::ConvertRelativePathToFull(
        FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan-solver.exe"));
}

FString UHyperTwistSolverOracleLibrary::GetBrownanWrapperPath()
{
    return FPaths::ConvertRelativePathToFull(
        FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan_solver_wrapper.py"));
}

FString UHyperTwistSolverOracleLibrary::GetCahidenesWrapperPath()
{
    return FPaths::ConvertRelativePathToFull(
        FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/cahidenes_solver_wrapper.py"));
}

bool UHyperTwistSolverOracleLibrary::AreSolverOraclesAvailable()
{
    return FPaths::FileExists(GetBrownanSolverPath())
        && FPaths::FileExists(GetBrownanWrapperPath())
        && FPaths::FileExists(GetCahidenesWrapperPath());
}

TArray<FString> UHyperTwistSolverOracleLibrary::SolveWithBrownanOracle(const FString& FaceletString)
{
    TArray<FString> EmptyResult;
    FString WrapperPath = GetBrownanWrapperPath();
    if (!FPaths::FileExists(WrapperPath))
    {
        UE_LOG(LogTemp, Warning, TEXT("Brownan wrapper not found at %s"), *WrapperPath);
        return EmptyResult;
    }

    UHyperTwistIPCProcessManager* IPC = UHyperTwistIPCProcessManager::CreateIPCManager(
        GetTransientPackage());

    FHyperTwistIPCProcessOptions Options;
    Options.ExecutablePath = TEXT("python.exe");
    Options.Arguments.Add(WrapperPath);
    Options.Arguments.Add(FaceletString);
    Options.TimeoutSeconds = 60.0f;

    FHyperTwistIPCResult Result = IPC->RunProcess(Options);
    if (!Result.bSuccess)
    {
        UE_LOG(LogTemp, Warning, TEXT("Brownan solver failed: %s"), *Result.ErrorMessage);
        return EmptyResult;
    }

    FString Output = Result.StdOut.TrimStartAndEnd();
    TArray<FString> Moves;
    Output.ParseIntoArrayWS(Moves);
    return Moves;
}

bool UHyperTwistSolverOracleLibrary::VerifySolutionWithBrownanOracle(const FString& CubeState, const TArray<FString>& Moves)
{
    TArray<FString> BrownanMoves = SolveWithBrownanOracle(CubeState);
    FString Output = FString::Join(BrownanMoves, TEXT(" ")).TrimStartAndEnd();
    FString Expected = FString::Join(Moves, TEXT(" ")).TrimStartAndEnd();
    return Output.Equals(Expected, ESearchCase::IgnoreCase);
}

TArray<FString> UHyperTwistSolverOracleLibrary::SolveWithCahidenesOracle(const FString& FaceletString)
{
    TArray<FString> EmptyResult;
    FString WrapperPath = GetCahidenesWrapperPath();
    if (!FPaths::FileExists(WrapperPath))
    {
        UE_LOG(LogTemp, Warning, TEXT("Cahidenes wrapper not found at %s"), *WrapperPath);
        return EmptyResult;
    }

    UHyperTwistIPCProcessManager* IPC = UHyperTwistIPCProcessManager::CreateIPCManager(
        GetTransientPackage());

    FHyperTwistIPCProcessOptions Options;
    Options.ExecutablePath = TEXT("python.exe");
    Options.Arguments.Add(WrapperPath);
    Options.Arguments.Add(FaceletString);
    Options.TimeoutSeconds = 30.0f;

    FHyperTwistIPCResult Result = IPC->RunProcess(Options);
    if (!Result.bSuccess)
    {
        UE_LOG(LogTemp, Warning, TEXT("Cahidenes solver failed: %s"), *Result.ErrorMessage);
        return EmptyResult;
    }

    FString Output = Result.StdOut.TrimStartAndEnd();
    TArray<FString> Moves;
    Output.ParseIntoArrayWS(Moves);
    return Moves;
}

bool UHyperTwistSolverOracleLibrary::CompareSolvers(const FString& FaceletString)
{
    // rob-twophase result
    TArray<FString> RobTwophaseMoves = UHyperTwistSolverLibrary::SolveClassicState(FaceletString, 5000, 25, 1);

    // cahidenes result
    TArray<FString> CahidenesMoves = SolveWithCahidenesOracle(FaceletString);

    // brownan result
    TArray<FString> BrownanMoves = SolveWithBrownanOracle(FaceletString);

    const int32 RobCount = RobTwophaseMoves.Num();
    const int32 CahCount = CahidenesMoves.Num();
    const int32 BrownCount = BrownanMoves.Num();

    if (RobCount > 0 && CahCount > 0 && BrownCount > 0)
    {
        return RobCount == CahCount && CahCount == BrownCount;
    }

    // If any solver failed, consider comparison inconclusive but not a hard failure
    return true;
}
