// Copyright (c) 2024 Orange. All rights reserved.
// This software is distributed under the BSD 3-Clause-clear License, the text of which is available
// at https://spdx.org/licenses/BSD-3-Clause-Clear.html or see the "LICENSE" file for more details.

#include "KWSystemParametersView.h"

KWSystemParametersView::KWSystemParametersView()
{
	int nErrorMessageNumber;
	int nMaxMemory;
	int nMinMemory;
	int nDefaultMemory;
	int nMaxProcNumber;
	ALString sMemoryLimit;
	longint lEnvMemoryLimit;
	int nRound;
	int i;
	ALString sTmp;

	SetIdentifier("KWSystemParameters");
	SetLabel("System parameters");
	AddBooleanField("APIMode", "API mode", false);
	AddIntField("MaxErrorMessageNumberInLog", "Max number of error messages in log", 0);
	AddIntField("OptimizationTime", "Min optimization time in seconds", 0);
	AddIntField("MemoryLimit", "Memory limit in MB", 0);
	AddBooleanField("IgnoreMemoryLimit", "Ignore memory limit", false);
	AddIntField("MaxCoreNumber", "Max number of processor cores", 1);
	AddStringField("ParallelLogFileName", "Parallel log file", "");
	AddBooleanField("ParallelSimulated", "Parallel simulated mode", false);
	AddStringField("TemporaryDirectoryName", "Temp file directory", "");

	// Parametrage des styles
	GetFieldAt("APIMode")->SetStyle("CheckBox");
	GetFieldAt("MaxErrorMessageNumberInLog")->SetStyle("Spinner");
	GetFieldAt("OptimizationTime")->SetStyle("Spinner");
	GetFieldAt("MemoryLimit")->SetStyle("Slider");
	GetFieldAt("IgnoreMemoryLimit")->SetStyle("CheckBox");
	GetFieldAt("MaxCoreNumber")->SetStyle("Slider");
	GetFieldAt("ParallelLogFileName")->SetStyle("DirectoryChooser");
	GetFieldAt("ParallelSimulated")->SetStyle("CheckBox");
	GetFieldAt("TemporaryDirectoryName")->SetStyle("DirectoryChooser");

	// Parametrage des limites du nombre max de messages d'erreur dans les log
	cast(UIIntElement*, GetFieldAt("MaxErrorMessageNumberInLog"))->SetMinValue(10);
	cast(UIIntElement*, GetFieldAt("MaxErrorMessageNumberInLog"))->SetMaxValue(10000);
	nErrorMessageNumber = Global::GetMaxErrorFlowNumber();
	if (nErrorMessageNumber < 10)
		nErrorMessageNumber = 10;
	if (nErrorMessageNumber > 10000)
		nErrorMessageNumber = 10000;
	Global::SetMaxErrorFlowNumber(nErrorMessageNumber);
	cast(UIIntElement*, GetFieldAt("MaxErrorMessageNumberInLog"))->SetDefaultValue(Global::GetMaxErrorFlowNumber());

	// Limites du temps d'optimisation
	RMResourceConstraints::SetOptimizationTime(0);
	cast(UIIntElement*, GetFieldAt("OptimizationTime"))->SetMinValue(0);
	cast(UIIntElement*, GetFieldAt("OptimizationTime"))->SetMaxValue(100000000);
	cast(UIIntElement*, GetFieldAt("OptimizationTime"))
	    ->SetDefaultValue(RMResourceConstraints::GetOptimizationTime());

	// Limites de la taille memoire en MB de chaque machine
	// Par defaut, on utilise 90% de la memoire max (notamment pour les machines n'ayant pas de fichier swap)
	// Sur un cluster c'est la max alloue a chaque machine (et non la memoire de tout le cluster)

	// Caclul du min avec un arrondi a la dizaine ou a la centaine selon la presence ou l'absence de la GUI java
	nMinMemory = int(RMResourceManager::GetSystemMemoryReserve() * (1 + MemGetAllocatorOverhead()) / lMB);
	if (UIObject::IsBatchMode())
		nRound = 10;
	else
		nRound = 100;
	nMinMemory = nRound * int(ceil(nMinMemory / (nRound * 1.0)));

	// Calcul du max : c'est la maximum de la memoire disponible sur chaque machine
	nMaxMemory = 0;
	for (i = 0; i < RMResourceManager::GetResourceSystem()->GetHostNumber(); i++)
	{
		const RMHostResource* hr = RMResourceManager::GetResourceSystem()->GetHostResourceAt(i);
		nMaxMemory = max(nMaxMemory, int(hr->GetPhysicalMemory() / lMB));
	}
	nMaxMemory = min(nMaxMemory, int(MemGetAdressablePhysicalMemory() / lMB));

	// Valeur par defaut de la memoire (variable d'environement ou 90% de le memoire max)
	sMemoryLimit = p_getenv("KHIOPS_MEMORY_LIMIT");
	lEnvMemoryLimit = 0;
	if (sMemoryLimit != "")
	{
		// Utilisation de StringToLongint car atol renvoie 0 en cas de pbm (contrairement a atoi)
		lEnvMemoryLimit = StringToLongint(sMemoryLimit);
	}
	if (sMemoryLimit != "" and lEnvMemoryLimit < nMinMemory)
		AddFatalError(
		    sTmp +
		    "The environment variable KHIOPS_MEMORY_LIMIT is ignored because it is set to a value inferior "
		    "than required for Khiops "
		    "(" +
		    LongintToReadableString(lEnvMemoryLimit) + " MB < " + LongintToReadableString(nMinMemory) + " MB)");
	if (lEnvMemoryLimit != 0 and lEnvMemoryLimit <= nMaxMemory and lEnvMemoryLimit >= nMinMemory)
	{
		nMaxMemory = (int)lEnvMemoryLimit;
		nDefaultMemory = (int)lEnvMemoryLimit;
	}
	else
		nDefaultMemory = nMaxMemory - nMaxMemory / 10;

	// Modification de la memoire par defaut selon variable d'environnement experte
	if (GetLearningDefaultMemoryLimit() != 0)
		nDefaultMemory = min(GetLearningDefaultMemoryLimit(), nMaxMemory);

	cast(UIIntElement*, GetFieldAt("MemoryLimit"))->SetMinValue(nMinMemory);
	cast(UIIntElement*, GetFieldAt("MemoryLimit"))->SetMaxValue(nMaxMemory);
	cast(UIIntElement*, GetFieldAt("MemoryLimit"))->SetDefaultValue(nDefaultMemory);
	RMResourceConstraints::SetMemoryLimit(nDefaultMemory);

	// Ignorer les limites memoire
	SetBooleanValueAt("IgnoreMemoryLimit", RMResourceConstraints::GetIgnoreMemoryLimit());

	// Controle dur des limites memoire si specifie
	if (GetLearningHardMemoryLimitMode() and not RMResourceConstraints::GetIgnoreMemoryLimit())
		MemSetMaxHeapSize(RMResourceConstraints::GetMemoryLimit() * lMB);

	// Limites du nombre de coeurs
	cast(UIIntElement*, GetFieldAt("MaxCoreNumber"))->SetMinValue(1);

	nMaxProcNumber =
	    min(RMResourceManager::GetPhysicalCoreNumber(), max(1, RMResourceManager::GetLogicalProcessNumber()));
	cast(UIIntElement*, GetFieldAt("MaxCoreNumber"))->SetMaxValue(nMaxProcNumber);
	cast(UIIntElement*, GetFieldAt("MaxCoreNumber"))->SetDefaultValue(nMaxProcNumber);

	// Mise a jour de la contrainte du nombre de processus utilisable sur le systeme
	RMResourceConstraints::SetMaxCoreNumberOnCluster(nMaxProcNumber);

	// On ne peut pas editer le nombre de process a utiliser le mode parallel n'est pas disponible
	if (not PLParallelTask::IsParallelModeAvailable())
		GetFieldAt("MaxCoreNumber")->SetEditable(false);

	// Les parametrages de l'optimisation du temps et du mode risque de gestion de la memoire ne sont pas visibles
	// par defaut
	GetFieldAt("OptimizationTime")->SetVisible(false);
	GetFieldAt("IgnoreMemoryLimit")->SetVisible(false);

	// API mode uniquement en mode expert, apres avoir force l'initialisation de la valeur par defaut par la
	// variable d'environnement
	KWResultFilePathBuilder::GetLearningApiMode();
	GetFieldAt("APIMode")->SetVisible(GetLearningExpertMode());

	// Mode parallele simule uniquement en mode expert parallele
	if (not GetParallelExpertMode())
		GetFieldAt("ParallelSimulated")->SetVisible(false);

	// Fonctionnalites avancees  en mode expert parallele
	if (not GetParallelExpertMode())
		GetFieldAt("ParallelLogFileName")->SetVisible(false);

	// Info-bulles
	GetFieldAt("APIMode")->SetHelpText("API mode for the management of results files."
					   "\n By default, the result files are stored in the train database "
					   "directory, unless an absolute path is specified,"
					   "\n and the file extension is forced if necessary."
					   "\n In API mode, the path of the result files are taken as is.");
	GetFieldAt("MaxErrorMessageNumberInLog")
	    ->SetHelpText(
		"Max number of error messages in log."
		"\n Allows to control the size of the log, by limiting the number of messages, warning or errors.");
	GetFieldAt("OptimizationTime")
	    ->SetHelpText("Min optimization in seconds."
			  "\n Allows to specify the min amount of time for the optimization algorithms."
			  "\n By default, this parameter is 0 and the algorithm stops by itself when no significant "
			  "improvement is expected."
			  "\n Otherwise, the optimization is performed at least as long as specified, then stops after "
			  "the next built solution.");
	GetFieldAt("MemoryLimit")
	    ->SetHelpText("Memory limit in MB."
			  "\n Allows to specify the max amount of memory available for the data analysis algorithms."
			  "\n By default, this parameter is set to the limit of the available RAM."
			  "\n This parameter can be decreased in order to keep memory for the other applications.");
	GetFieldAt("IgnoreMemoryLimit")
	    ->SetHelpText("Ignore memory limits."
			  "\n Expert only: risky setting.");
	if (PLParallelTask::IsParallelModeAvailable())
		GetFieldAt("MaxCoreNumber")->SetHelpText("Max number of processor cores to use.");
	else
		GetFieldAt("MaxCoreNumber")
		    ->SetHelpText("Number of processor cores."
				  "\n Tasks cannot run in parallel in this configuration.");
	GetFieldAt("ParallelSimulated")->SetHelpText("Emulation of parallel mode for debug (expert).");
	GetFieldAt("ParallelLogFileName")->SetHelpText("Parallel log file name (expert).");
	GetFieldAt("TemporaryDirectoryName")
	    ->SetHelpText("Name of the directory to use for temporary files."
			  "\n Default: none, the system default temp file directory is then used.");
}

KWSystemParametersView::~KWSystemParametersView() {}

void KWSystemParametersView::EventUpdate(Object* object)
{
	// On parametre directement les variables statiques correspondantes
	// en ignorant l'objet passe en parametres
	KWResultFilePathBuilder::SetLearningApiMode(GetBooleanValueAt("APIMode"));
	Global::SetMaxErrorFlowNumber(GetIntValueAt("MaxErrorMessageNumberInLog"));
	RMResourceConstraints::SetOptimizationTime(GetIntValueAt("OptimizationTime"));
	RMResourceConstraints::SetMemoryLimit(GetIntValueAt("MemoryLimit"));
	RMResourceConstraints::SetIgnoreMemoryLimit(GetBooleanValueAt("IgnoreMemoryLimit"));

	// Controle dur des limites memoire si specifie
	if (GetLearningHardMemoryLimitMode() and not RMResourceConstraints::GetIgnoreMemoryLimit())
		MemSetMaxHeapSize(RMResourceConstraints::GetMemoryLimit() * lMB);

	// Calcul du nombre de processus utilises
	RMResourceConstraints::SetMaxCoreNumberOnCluster(GetIntValueAt("MaxCoreNumber"));
	PLParallelTask::SetParallelSimulated(GetBooleanValueAt("ParallelSimulated"));
	PLParallelTask::SetParallelLogFileName(GetStringValueAt("ParallelLogFileName"));
	FileService::SetUserTmpDir(GetStringValueAt("TemporaryDirectoryName"));
}

void KWSystemParametersView::EventRefresh(Object* object)
{
	// On parametre directement les variables statiques correspondantes
	// en ignorant l'objet passe en parametres
	SetBooleanValueAt("APIMode", KWResultFilePathBuilder::GetLearningApiMode());
	SetIntValueAt("MaxErrorMessageNumberInLog", Global::GetMaxErrorFlowNumber());
	SetIntValueAt("OptimizationTime", RMResourceConstraints::GetOptimizationTime());
	SetIntValueAt("MemoryLimit", RMResourceConstraints::GetMemoryLimit());
	SetBooleanValueAt("IgnoreMemoryLimit", RMResourceConstraints::GetIgnoreMemoryLimit());
	SetIntValueAt("MaxCoreNumber", RMResourceConstraints::GetMaxCoreNumberOnCluster());
	SetBooleanValueAt("ParallelSimulated", PLParallelTask::GetParallelSimulated());
	SetStringValueAt("ParallelLogFileName", PLParallelTask::GetParallelLogFileName());
	SetStringValueAt("TemporaryDirectoryName", FileService::GetUserTmpDir());
}

const ALString KWSystemParametersView::GetClassLabel() const
{
	return "System parameters";
}
