#include "HyperTwistSolverLibrary.h"

#include "HAL/PlatformFilemanager.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"

#if PLATFORM_WINDOWS
#include <direct.h>
#define chdir _chdir
#else
#include <unistd.h>
#endif

#include "ThirdParty/rob-twophase/face.h"
#include "ThirdParty/rob-twophase/move.h"
#include "ThirdParty/rob-twophase/cubie.h"
#include "ThirdParty/rob-twophase/coord.h"
#include "ThirdParty/rob-twophase/sym.h"
#include "ThirdParty/rob-twophase/prun.h"
#include "ThirdParty/rob-twophase/solve.h"

#include <mutex>

static bool bSolverInitialized = false;
static std::once_flag InitOnceFlag;

static void DoInitSolver()
{
	// rob-twophase saves/loads pruning tables from the current working directory.
	// Temporarily switch to ProjectSavedDir so tables are persisted across runs.
	FString SavedDir = FPaths::ProjectSavedDir();
	FString OriginalDir = FPaths::ProjectDir();
	
	if (FPaths::DirectoryExists(SavedDir))
	{
		chdir(TCHAR_TO_UTF8(*SavedDir));
	}

	face::init();
	move::init();
	coord::init();
	sym::init();
	prun::init(true); // true = try to load tables, generate if missing

	// Restore working directory
	chdir(TCHAR_TO_UTF8(*OriginalDir));

	bSolverInitialized = true;
}

bool UHyperTwistSolverLibrary::IsSolverInitialized()
{
	return bSolverInitialized;
}

bool UHyperTwistSolverLibrary::InitializeSolver()
{
	std::call_once(InitOnceFlag, DoInitSolver);
	return bSolverInitialized;
}

TArray<FString> UHyperTwistSolverLibrary::SolveClassicState(
	const FString& FaceletString,
	int32 TimeLimitMs,
	int32 MaxLength,
	int32 NumSolutions
)
{
	TArray<FString> Result;

	if (!InitializeSolver())
	{
		UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Failed to initialize solver"));
		return Result;
	}

	std::string Facelets(TCHAR_TO_UTF8(*FaceletString));
	cubie::cube Cube;
	int Err = face::to_cubie(Facelets, Cube);
	if (Err != 0)
	{
		UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Invalid facelet string, error code %d"), Err);
		return Result;
	}

	if (cubie::check_cube(Cube) != 0)
	{
		UE_LOG(LogTemp, Error, TEXT("HyperTwistSolver: Cube state is not solvable"));
		return Result;
	}

	solve::Engine Solver(1, TimeLimitMs, NumSolutions, MaxLength, 1);
	Solver.prepare();

	std::vector<std::vector<int>> Solutions;
	Solver.solve(Cube, Solutions);
	Solver.finish();

	if (Solutions.empty())
	{
		UE_LOG(LogTemp, Warning, TEXT("HyperTwistSolver: No solution found within time limit"));
		return Result;
	}

	// Return the shortest solution
	const std::vector<int>& Best = Solutions[0];
	for (int Move : Best)
	{
		if (Move >= 0 && Move < move::COUNT)
		{
			Result.Add(FString(UTF8_TO_TCHAR(move::names[Move].c_str())));
		}
	}

	return Result;
}

FString UHyperTwistSolverLibrary::GetMoveName(int32 MoveIndex)
{
	if (MoveIndex >= 0 && MoveIndex < move::COUNT)
	{
		return FString(UTF8_TO_TCHAR(move::names[MoveIndex].c_str()));
	}
	return FString();
}

int32 UHyperTwistSolverLibrary::GetMoveCount()
{
	return move::COUNT;
}

bool UHyperTwistSolverLibrary::VerifyFaceletString(const FString& FaceletString)
{
	if (FaceletString.Len() != 54)
	{
		return false;
	}

	std::string Facelets(TCHAR_TO_UTF8(*FaceletString));
	cubie::cube Cube;
	return face::to_cubie(Facelets, Cube) == 0;
}
