/*
    PIP - Platform Independent Primitives
    Initialization
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "piinit.h"

#include "piincludes_p.h"
#ifndef MICRO_PIP

#  include "pidir.h"
#  include "piobject.h"
#  include "piprocess.h"
#  include "piresourcesstorage.h"
#  include "pisignals.h"
#  include "pisysteminfo.h"
#  include "pitime.h"
#  ifdef ESP_PLATFORM
#	include "esp_system.h"
#  endif
#  ifdef WINDOWS
#	include <winsock2.h>
extern FILETIME __pi_ftjan1970;
extern PINtQueryTimerResolution getTimerResolutionAddr;
extern PINtSetTimerResolution setTimerResolutionAddr;
void __PISetTimerResolution() {
	if (setTimerResolutionAddr == NULL || getTimerResolutionAddr == NULL) return;
	ULONG _max(0), _min(0), _cur(0);
	// printf("getTimerResolution ...\n");
	LONG q = getTimerResolutionAddr(&_max, &_min, &_cur);
	// printf("getTimerResolution %d %lu %lu %lu\n", q, _min, _max, _cur);
	if (q == 0) setTimerResolutionAddr(_min, TRUE, &_cur);
	// printf("setTimerResolution %lu\n", cur);
}
#  else
#	include <pthread.h>
#	include <pwd.h>
#	include <sys/utsname.h>
#	ifdef BLACKBERRY
#	  include <signal.h>
#	else
#	  include <csignal>
#	endif
#  endif
#  ifdef MAC_OS
#	include <mach/clock.h>
#	include <mach/mach.h>
#	include <mach/mach_traps.h>
extern clock_serv_t __pi_mac_clock;
#  endif
#  ifdef PIP_ICU
#	define U_NOEXCEPT
#	include <unicode/uclean.h>
#	include <unicode/ucnv.h>
#  endif


#  ifdef HAS_LOCALE
static locale_t currentLocale_t = 0;
#  endif

PRIVATE_DEFINITION_START(PIInit)
#  ifdef WINDOWS
	HMODULE ntlib;
	ULONG prev_res;
#  endif
	bool delete_locs;
PRIVATE_DEFINITION_END(PIInit)

void __sighandler__(PISignals::Signal s) {
	// piCout << Hex << int(s);
	if (s == PISignals::StopTTYInput || s == PISignals::StopTTYOutput) piMSleep(10);
	if (s == PISignals::UserDefined1)
		dumpApplicationToFile(PIDir::home().path() + PIDir::separator + PIStringAscii("_PIP_DUMP_") +
		                      PIString::fromNumber(PIProcess::currentPID()));
}


PIInit::PIInit() {
	PISystemInfo * sinfo = PISystemInfo::instance();
	sinfo->execDateTime  = PIDateTime::current();
#  ifndef ANDROID
	PISignals::setSlot(__sighandler__);
	PISignals::grabSignals(PISignals::UserDefined1);
#	ifndef WINDOWS
	PISignals::grabSignals(PISignals::StopTTYInput | PISignals::StopTTYOutput);
	sigset_t ss;
	sigemptyset(&ss);
	sigaddset(&ss, SIGALRM);
	sigprocmask(SIG_BLOCK, &ss, 0);
	pthread_sigmask(SIG_BLOCK, &ss, 0);
	signal(SIGPIPE, SIG_IGN);
	PIStringList ifpathes;
	ifpathes << PIStringAscii("/bin/ifconfig") << PIStringAscii("/sbin/ifconfig") << PIStringAscii("/usr/bin/ifconfig")
			 << PIStringAscii("/usr/sbin/ifconfig");
	for (const auto & i: ifpathes) {
		if (fileExists(i)) {
			sinfo->ifconfigPath = i;
			break;
		}
	}
#	else  // WINDOWS
	// OS version
	DWORD dwVersion      = GetVersion();
	DWORD dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
	DWORD dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
	sinfo->OS_version    = PIString::fromNumber(dwMajorVersion) + "." + PIString::fromNumber(dwMinorVersion);

	// WinSock inint
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	// Timers init
	SYSTEMTIME jan1970 = {1970, 1, 4, 1, 0, 0, 0, 0};
	SystemTimeToFileTime(&jan1970, &__pi_ftjan1970);
	LARGE_INTEGER pf;
	pf.QuadPart = -1;
	if (QueryPerformanceFrequency(&pf) != 0) __pi_perf_freq = pf.QuadPart;
	if (__pi_perf_freq == 0) __pi_perf_freq = -1;

	// Sleep precision init
	PRIVATE->ntlib = LoadLibraryA("ntdll.dll");
	if (PRIVATE->ntlib) {
		getTimerResolutionAddr = (PINtQueryTimerResolution)GetProcAddress(PRIVATE->ntlib, "NtQueryTimerResolution");
		setTimerResolutionAddr = (PINtSetTimerResolution)GetProcAddress(PRIVATE->ntlib, "NtSetTimerResolution");
		__PISetTimerResolution();
	}
#	endif // WINDOWS
#	ifdef HAS_LOCALE
	// std::cout << "has locale" << std::endl;
	if (currentLocale_t != 0) {
		freelocale(currentLocale_t);
		currentLocale_t = 0;
	}
	currentLocale_t = newlocale(LC_ALL, setlocale(LC_ALL, "C"), 0);
	setlocale(LC_CTYPE, "en_US.UTF-8");
#	else  // HAS_LOCALE
	setlocale(LC_ALL, "");
	setlocale(LC_NUMERIC, "C");
#	endif // HAS_LOCALE
#  endif   // ANDROID
	PRIVATE->delete_locs = false;
	__syslocname__ = __sysoemname__ = 0;
	__utf8name__                    = const_cast<char *>("UTF-8");
#  ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	u_init(&e);
#	ifdef WINDOWS
	PRIVATE->delete_locs = true;
	CPINFOEX cpinfo;
	int l = 0;
	GetCPInfoEx(CP_OEMCP, 0, &cpinfo);
	for (l = 0; l < MAX_PATH; ++l)
		if (cpinfo.CodePageName[l] == '\0' || cpinfo.CodePageName[l] == ' ') break;
	__sysoemname__ = new char[256];
	piZeroMemory(__sysoemname__, 256);
	memcpy(__sysoemname__, "ibm-", 4);
	memcpy(&(__sysoemname__[4]), cpinfo.CodePageName, l);
#	else
	/*PIString en(getenv("LANG"));
	if (!en.isEmpty())
	    en = en.mid(en.find(".") + 1);
	PIByteArray enba = en.toByteArray();
	memcpy(__syslocname__, enba.data(), enba.size_s());*/
#	endif
	// piCout << __syslocname__;
	// piCout << __sysoemname__;
#  else // PIP_ICU
#	ifdef WINDOWS
	__syslocname__         = (char *)CP_ACP;
	__sysoemname__         = (char *)CP_OEMCP;
	__utf8name__           = (char *)CP_UTF8;
#	endif
#  endif // PIP_ICU
#  ifdef MAC_OS
	host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &__pi_mac_clock);
#  endif
	char cbuff[1024];
	piZeroMemory(cbuff, 1024);
	if (gethostname(cbuff, 1023) == 0) {
		sinfo->hostname = cbuff;
	}
#  ifdef WINDOWS
	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);
	sinfo->processorsCount = sysinfo.dwNumberOfProcessors;
	switch (sysinfo.wProcessorArchitecture) {
	case PROCESSOR_ARCHITECTURE_AMD64: sinfo->architecture = PIStringAscii("x86_64"); break;
	case PROCESSOR_ARCHITECTURE_ARM: sinfo->architecture = PIStringAscii("arm"); break;
	case PROCESSOR_ARCHITECTURE_IA64: sinfo->architecture = PIStringAscii("Intel Itanium-based"); break;
	case PROCESSOR_ARCHITECTURE_INTEL: sinfo->architecture = PIStringAscii("x86"); break;
	case PROCESSOR_ARCHITECTURE_UNKNOWN:
	default: sinfo->architecture = PIStringAscii("unknown"); break;
	}
	int argc_(0);
	wchar_t ** argv_ = CommandLineToArgvW(GetCommandLineW(), &argc_);
	if (argc_ > 0 && argv_ != 0) sinfo->execCommand = argv_[0];
	LocalFree(argv_);
	piZeroMemory(cbuff, 1024);
	ulong unlen = 1023;
	if (GetUserNameA(cbuff, &unlen) != 0) sinfo->user = cbuff;
#  else  // WINDOWS
	sinfo->processorsCount = piMaxi(1, int(sysconf(_SC_NPROCESSORS_ONLN)));
	passwd * ps            = getpwuid(getuid());
	if (ps)
		sinfo->user = ps->pw_name;
	else {
		piZeroMemory(cbuff, 1024);
		char * l = getlogin();
		if (l) sinfo->user = l;
	}
	struct utsname uns;
	if (uname(&uns) == 0) {
		sinfo->OS_version   = uns.release;
		sinfo->architecture = uns.machine;
	}
#  endif // WINDOWS

#  ifdef ESP_PLATFORM
	esp_chip_info_t chip_info;
	esp_chip_info(&chip_info);
	sinfo->processorsCount = chip_info.cores;
	sinfo->architecture    = "Xtensa LX6";
	// printf("silicon revision %d, ", chip_info.revision);
	sinfo->OS_version      = esp_get_idf_version();
#  endif
	sinfo->OS_name =
#  ifdef WINDOWS
		PIStringAscii("Windows");
#  elif defined(QNX)
		PIStringAscii("QNX");
#  elif defined(MAC_OS)
		PIStringAscii("MacOS");
#  elif defined(ANDROID)
		PIStringAscii("Android");
#  elif defined(FREE_BSD)
		PIStringAscii("FreeBSD");
#  elif defined(FREERTOS)
		PIStringAscii("FreeRTOS");
#  elif defined(MICRO_PIP)
		PIStringAscii("MicroPIP");
#  else
		uns.sysname;
#  endif
}


PIInit::~PIInit() {
	PIResourcesStorage::instance()->clear();
#  ifdef WINDOWS
	WSACleanup();
	if (PRIVATE->ntlib) FreeLibrary(PRIVATE->ntlib);
	PRIVATE->ntlib = 0;
#  endif
#  ifdef MAC_OS
	mach_port_deallocate(mach_task_self(), __pi_mac_clock);
#  endif
	if (PRIVATE->delete_locs) {
		if (__syslocname__) delete[] __syslocname__;
		if (__sysoemname__) delete[] __sysoemname__;
	}
#  ifdef PIP_ICU
	u_cleanup();
#  endif
}


bool PIInit::isBuildOptionEnabled(PIInit::BuildOption o) {
	switch (o) {
	case boICU:
		return
#  ifdef PIP_ICU
			true;
#  else
			false;
#  endif
	case boUSB:
		return
#  ifdef PIP_USB
			true;
#  else
			false;
#  endif
	case boCrypt:
		return
#  ifdef PIP_CRYPT
			true;
#  else
			false;
#  endif
	case boIntrospection:
		return
#  ifdef PIP_INTROSPECTION
			true;
#  else
			false;
#  endif
	case boFFTW:
		return
#  ifdef PIP_FFTW
			true;
#  else
			false;
#  endif
	case boCompress:
		return
#  ifdef PIP_COMPRESS
			true;
#  else
			false;
#  endif
	case boOpenCL:
		return
#  ifdef PIP_OPENCL
			true;
#  else
			false;
#  endif
	case boCloud:
		return
#  ifdef PIP_CLOUD
			true;
#  else
			false;
#  endif
	case boConsole:
		return
#  ifdef PIP_CONSOLE
			true;
#  else
			false;
#  endif
	default: return false;
	}
	return false;
}


PIStringList PIInit::buildOptions() {
	PIStringList ret;
	if (isBuildOptionEnabled(boICU)) ret << "ICU";
	if (isBuildOptionEnabled(boUSB)) ret << "USB";
	if (isBuildOptionEnabled(boCrypt)) ret << "Crypt";
	if (isBuildOptionEnabled(boIntrospection)) ret << "Introspection";
	if (isBuildOptionEnabled(boFFTW)) ret << "FFTW";
	if (isBuildOptionEnabled(boCompress)) ret << "Compress";
	if (isBuildOptionEnabled(boOpenCL)) ret << "OpenCL";
	if (isBuildOptionEnabled(boCloud)) ret << "Cloud";
	if (isBuildOptionEnabled(boConsole)) ret << "Console";
	return ret;
}


bool PIInit::fileExists(const PIString & p) {
	FILE * f = fopen(p.data(), "r");
	if (f == 0) return false;
	fclose(f);
	return true;
}


int __PIInit_Initializer__::count_(0);
PIInit * __PIInit_Initializer__::__instance__(0);


__PIInit_Initializer__::__PIInit_Initializer__() {
	count_++;
	if (count_ > 1) return;
	// piCout << "create PIInit";
	__instance__ = new PIInit();
}


__PIInit_Initializer__::~__PIInit_Initializer__() {
	count_--;
	if (count_ > 0) return;
	// piCout << "delete PIInit";
	if (__instance__ != 0) {
		delete __instance__;
		__instance__ = 0;
	}
}

#endif // MICRO_PIP
