/*
 *  UwUfetch is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifdef __APPLE__
	#include <TargetConditionals.h> // for checking iOS
#endif
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(__APPLE__) || defined(__BSD__)
	#include <sys/sysctl.h>
	#if defined(__OPENBSD__)
		#include <sys/time.h>
	#else
		#include <time.h>
	#endif // defined(__OPENBSD__)
#else		 // defined(__APPLE__) || defined(__BSD__)
	#ifdef __BSD__
	#else // defined(__BSD__) || defined(_WIN32)
		#ifndef _WIN32
			#ifndef __OPENBSD__
				#include <sys/sysinfo.h>
			#else	 // __OPENBSD__
			#endif // __OPENBSD__
		#else		 // _WIN32
			#include <sysinfoapi.h>
		#endif // _WIN32
	#endif	 // defined(__BSD__) || defined(_WIN32)
#endif		 // defined(__APPLE__) || defined(__BSD__)
#ifndef _WIN32
	#include <sys/ioctl.h>
	#include <sys/utsname.h>
	#include <pthread.h> // linux only right now
#else									 // _WIN32
	#include <windows.h>
CONSOLE_SCREEN_BUFFER_INFO csbi;
#endif // _WIN32

#define LIBFETCH_INTERNAL // to do certain things only when included from the library itself
#include "fetch.h"
#define BUFFER_SIZE 256
#ifdef __DEBUG__
static bool verbose_enabled = false;
bool* get_verbose_handle() { return &verbose_enabled; }
#endif

#ifndef PKGPATH
	#ifdef __APPLE__
		#define PKGPATH "/usr/local/bin/"
	#else
		#define PKGPATH "/usr/bin/"
	#endif
#endif

#ifdef __APPLE__
// buffers where data fetched from sysctl are stored
	#define CPUBUFFERLEN 128

char cpu_buffer[CPUBUFFERLEN];
size_t cpu_buffer_len = CPUBUFFERLEN;

// Installed RAM
int64_t mem_buffer		= 0;
size_t mem_buffer_len = sizeof(mem_buffer);

// uptime
struct timeval time_buffer;
size_t time_buffer_len = sizeof(time_buffer);
#endif // __APPLE__

struct package_manager {
	char* command_path;
	char* command_string; // command to get number of packages installed
	char* pkgman_name;		// name of the package manager
};

// truncates the given string
void truncate_str(char* string, int target_width) {
	char arr[target_width];
	for (int i = 0; i < target_width; i++) arr[i] = string[i];
	string = arr;
}

// remove square brackets (for gpu names)
void remove_brackets(char* str) {
	int i = 0, j = 0;
	while (i < (int)strlen(str))
		if (str[i] == '[' || str[i] == ']')
			for (j = i; j < (int)strlen(str); j++) str[j] = str[j + 1];
		else
			i++;
}

void get_twidth(struct info* user_info) {
	LOG_I("getting terminal width");
	// get terminal width used to truncate long names
#ifndef _WIN32
	ioctl(STDOUT_FILENO, TIOCGWINSZ, &user_info->win);
	user_info->target_width = user_info->win.ws_col - 30;
	LOG_V(user_info->target_width);
#else	 // _WIN32
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
	user_info->ws_col	 = csbi.srWindow.Right - csbi.srWindow.Left - 29;
	user_info->ws_rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
	LOG_V(user_info->ws_col);
	LOG_V(user_info->ws_rows);
#endif // _WIN32
}

void get_sys(struct info* user_info) {
	LOG_I("getting sys_var struct");
#ifndef _WIN32
	uname(&user_info->sys_var);
#endif // _WIN32
#ifndef __APPLE__
	#ifndef __BSD__
		#ifndef _WIN32
	sysinfo(&user_info->sys);
		#else
	GetSystemInfo(&user_info->sys);
		#endif
	#endif
#endif
}

// tries to get cpu name
void* get_cpu(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[0]) return 0;
	char* buffer					 = ((struct thread_varg*)argp)->buffer;
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
	FILE* cpuinfo					 = ((struct thread_varg*)argp)->cpuinfo;
	LOG_I("getting cpu name");
	if (cpuinfo) {
		while (fgets(buffer, BUFFER_SIZE, cpuinfo)) {
#ifdef __BSD__
			if (sscanf(buffer, "hw.model"
	#ifdef __FREEBSD__
												 ": "
	#elif defined(__OPENBSD__)
												 "="
	#endif
												 "%[^\n]",
								 user_info->cpu_model))
				break;
#else
			if (sscanf(buffer, "model name    : %[^\n]", user_info->cpu_model)) break;
#endif // __BSD__
		}
	}
	if (strlen(user_info->cpu_model) == 0) {
		LOG_E("failed to get cpu name");
		rewind(cpuinfo);
		char cores[4] = "";
		while (fgets(buffer, BUFFER_SIZE, cpuinfo)) // get the last core number
			sscanf(buffer, "processor%*[    |	]: %[^\n]", cores);
		cores[strlen(cores) - 1] += 1; // should be a number
		sprintf(user_info->cpu_model, "%s Cores", cores);
	}
	LOG_V(user_info->cpu_model);
	return 0;
}

// tries to get memory usage
void* get_ram(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[1]) return 0;
	LOG_I("getting ram");
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
#ifndef __APPLE__
	#ifdef _WIN32
	FILE* mem_used_fp			 = popen("wmic os get freevirtualmemory", "r");			 // free memory
	FILE* mem_total_fp		 = popen("wmic os get totalvirtualmemorysize", "r"); // total memory
	char mem_used_ch[2137] = {0}, mem_total_ch[2137] = {0};

	while (fgets(mem_total_ch, sizeof(mem_total_ch), mem_total_fp) != NULL) {
		if (strstr(mem_total_ch, "TotalVirtualMemorySize") != 0)
			continue;
		else if (strstr(mem_total_ch, "  ") == 0)
			continue;
		else
			user_info->ram_total = atoi(mem_total_ch) / 1024;
	}
	LOG_V(user_info->ram_total);
	while (fgets(mem_used_ch, sizeof(mem_used_ch), mem_used_fp) != NULL) {
		if (strstr(mem_used_ch, "FreeVirtualMemory") != 0)
			continue;
		else if (strstr(mem_used_ch, "  ") == 0)
			continue;
		else
			user_info->ram_used = user_info->ram_total - (atoi(mem_used_ch) / 1024);
	}
	LOG_V(user_info->ram_used);
	pclose(mem_used_fp);
	pclose(mem_total_fp);
	#else // if not _WIN32
	char* buffer = ((struct thread_varg*)argp)->buffer;
	FILE* meminfo;

		#ifdef __BSD__
			#ifndef __OPENBSD__
	meminfo = popen("LANG=EN_us freecolor -om 2> /dev/null", "r"); // free alternative for freebsd
			#else
	meminfo = popen("LANG=EN_us vmstat 2> /dev/null | grep -v 'procs' | grep -v 'r' | awk '{print $3 "
									"\" / \" $4}'",
									"r"); // free alternative for openbsd
			#endif
		#else
	// getting memory info from /proc/meminfo: https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716
	meminfo = fopen("/proc/meminfo",
									"r"); // popen("LANG=EN_us free -m 2> /dev/null", "r"); // get ram info with free
		#endif
	// brackets are here to restrict the access to this int variables, which are temporary
	{
		#ifndef __OPENBSD__
		int memtotal = 0, shmem = 0, memfree = 0, buffers = 0, cached = 0, sreclaimable = 0;
		#endif
		while (fgets(buffer, BUFFER_SIZE, meminfo)) {
		#ifndef __OPENBSD__
			sscanf(buffer, "MemTotal:       %d", &memtotal);
			sscanf(buffer, "Shmem:             %d", &shmem);
			sscanf(buffer, "MemFree:        %d", &memfree);
			sscanf(buffer, "Buffers:          %d", &buffers);
			sscanf(buffer, "Cached:          %d", &cached);
			sscanf(buffer, "SReclaimable:     %d", &sreclaimable);
		#else
			sscanf(buffer, "%dM / %dM", &user_info->ram_used, &user_info->ram_total);
		#endif
		}
		#ifndef __OPENBSD__
		user_info->ram_total = memtotal / 1024;
		user_info->ram_used = ((memtotal + shmem) - (memfree + buffers + cached + sreclaimable)) / 1024;
		#endif
		LOG_V(user_info->ram_total);
		LOG_V(user_info->ram_used);
	}

	fclose(meminfo);
	#endif
#else // if __APPLE__
	// Used
	FILE *mem_wired_fp, *mem_active_fp, *mem_compressed_fp;
	mem_wired_fp			= popen("vm_stat | awk '/wired/ { printf $4 }' | cut -d '.' -f 1", "r");
	mem_active_fp			= popen("vm_stat | awk '/active/ { printf $3 }' | cut -d '.' -f 1", "r");
	mem_compressed_fp = popen("vm_stat | awk '/occupied/ { printf $5 }' | cut -d '.' -f 1", "r");
	char mem_wired_ch[2137], mem_active_ch[2137], mem_compressed_ch[2137];
	while (fgets(mem_wired_ch, sizeof(mem_wired_ch), mem_wired_fp) != NULL)
		while (fgets(mem_active_ch, sizeof(mem_active_ch), mem_active_fp) != NULL)
			while (fgets(mem_compressed_ch, sizeof(mem_compressed_ch), mem_compressed_fp) != NULL)
				;

	pclose(mem_wired_fp);
	pclose(mem_active_fp);
	pclose(mem_compressed_fp);

	int mem_wired			 = atoi(mem_wired_ch);
	int mem_active		 = atoi(mem_active_ch);
	int mem_compressed = atoi(mem_compressed_ch);

	// Total
	sysctlbyname("hw.memsize", &mem_buffer, &mem_buffer_len, NULL, 0);
	user_info->ram_used	 = ((mem_wired + mem_active + mem_compressed) * 4 / 1024);
	user_info->ram_total = mem_buffer / 1024 / 1024;
	LOG_V(user_info->ram_total);
	LOG_V(user_info->ram_used);
#endif
	return 0;
}

// tries to get installed gpu(s)
void* get_gpu(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[2]) return 0;
	char* buffer					 = ((struct thread_varg*)argp)->buffer;
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
	int gpuc							 = 0; // gpu counter
#ifndef _WIN32
	setenv("LANG", "en_US", 1); // force language to english
#endif
	FILE* gpu;
#ifndef _WIN32
	LOG_I("getting gpus with lshw");
	gpu = popen("lshw -class display 2> /dev/null", "r");

	// add all gpus to the array gpu_model
	while (fgets(buffer, BUFFER_SIZE, gpu))
		if (sscanf(buffer, "    product: %[^\n]", user_info->gpu_model[gpuc])) gpuc++;
#endif

	if (strlen(user_info->gpu_model[0]) < 2) {
		// get gpus with lspci command
		if (strcmp(user_info->os_name, "android") != 0) {
#ifndef __APPLE__
	#ifdef _WIN32
			gpu = popen("wmic PATH Win32_VideoController GET Name", "r");
	#else
			gpu = popen("lspci -mm 2> /dev/null | grep \"VGA\" | awk -F '\"' '{print $4 $5 $6}'", "r");
	#endif
#else
			gpu = popen(
					"system_profiler SPDisplaysDataType | awk -F ': ' '/Chipset Model: /{ print $2 }'", "r");
#endif
		} else
			gpu = popen("getprop ro.hardware.vulkan 2> /dev/null", "r"); // for android
	}

	// get all the gpus
	while (fgets(buffer, BUFFER_SIZE, gpu)) {
		// windows
		if (strstr(buffer, "Name") || (strlen(buffer) == 2))
			continue;
		else if (sscanf(buffer, "%[^\n]", user_info->gpu_model[gpuc]))
			gpuc++;
	}
	fclose(gpu);

	// format gpu names
	for (int i = 0; i < gpuc; i++) {
		remove_brackets(user_info->gpu_model[i]);
		truncate_str(user_info->gpu_model[i], user_info->target_width);
		LOG_V(user_info->gpu_model[i]);
	}
	return 0;
}

// tries to get screen resolution
#ifndef _WIN32
void* get_res(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[3]) return 0;
	LOG_I("getting resolution");
	char* buffer					 = ((struct thread_varg*)argp)->buffer;
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
	FILE* resolution			 = popen("xwininfo -root 2> /dev/null | grep -E 'Width|Height'", "r");
	while (fgets(buffer, BUFFER_SIZE, resolution)) {
		sscanf(buffer, "  Width: %d", &user_info->screen_width);
		sscanf(buffer, "  Height: %d", &user_info->screen_height);
	}
	LOG_V(user_info->screen_width);
	LOG_V(user_info->screen_height);
#else
void* get_res() {
	// TODO: get resolution on windows
#endif
	return 0;
}

// tries to get the installed package count and package managers name
void* get_pkg(void* argp) { // this is just a function that returns the total of installed packages
	if (!((struct thread_varg*)argp)->thread_flags[4]) return 0;
	LOG_I("getting pkgs");
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
	user_info->pkgs				 = 0;
#ifndef __APPLE__
	#ifndef _WIN32
	// all supported package managers
	struct package_manager pkgmans[] = {
			{PKGPATH "apt", "apt list --installed 2> /dev/null | wc -l", "(apt)"},
			{PKGPATH "apk", "apk info 2> /dev/null | wc -l", "(apk)"},
			// {PKGPATH"dnf","dnf list installed 2> /dev/null | wc -l", "(dnf)"}, // according to https://stackoverflow.com/questions/48570019/advantages-of-dnf-vs-rpm-on-fedora, dnf and rpm return the same number of packages
			{PKGPATH "qlist", "qlist -I 2> /dev/null | wc -l", "(emerge)"},
			{PKGPATH "flatpak", "flatpak list 2> /dev/null | wc -l", "(flatpak)"},
			{PKGPATH "snap", "snap list 2> /dev/null | wc -l", "(snap)"},
			{PKGPATH "guix", "guix package --list-installed 2> /dev/null | wc -l", "(guix)"},
			{PKGPATH "nix-store", "nix-store -q --requisites /run/current-system/sw 2> /dev/null | wc -l", "(nix)"},
			{PKGPATH "pacman", "pacman -Qq 2> /dev/null | wc -l", "(pacman)"},
			{PKGPATH "pkg", "pkg info 2>/dev/null | wc -l", "(pkg)"},
			{PKGPATH "pkg_info", "pkg_info 2>/dev/null | wc -l | sed \"s/ //g\"", "(pkg)"},
			{PKGPATH "port", "port installed 2> /dev/null | tail -n +2 | wc -l", "(port)"},
			{PKGPATH "brew", "find $(brew --cellar 2>/dev/stdout) -maxdepth 1 -type d 2> /dev/null | wc -l | awk '{print $1}'", "(brew-cellar)"},
			{PKGPATH "brew", "find $(brew --caskroom 2>/dev/stdout) -maxdepth 1 -type d 2> /dev/null | wc -l | awk '{print $1}'", "(brew-cask)"},
			{PKGPATH "rpm", "rpm -qa --last 2> /dev/null | wc -l", "(rpm)"},
			{PKGPATH "xbps-query", "xbps-query -l 2> /dev/null | wc -l", "(xbps)"},
			{PKGPATH "zypper", "zypper -q se --installed-only 2> /dev/null | wc -l", "(zypper)"}};
	#endif
#else
	struct package_manager pkgmans[] = {{"/usr/local/bin/brew", "find $(brew --cellar 2>/dev/stdout) -maxdepth 1 -type d 2> /dev/null | wc -l | awk '{print $1}' > /tmp/uwufetch_brew_tmp", "(brew-cellar)"},
																			{"/usr/local/bin/brew", "find $(brew --caskroom 2>/dev/stdout) -maxdepth 1 -type d 2> /dev/null | wc -l | awk '{print $1}' > /tmp/uwufetch_brew_tmp", "(brew-cask)"}};
#endif
#ifndef _WIN32
	const int pkgman_count = sizeof(pkgmans) / sizeof(pkgmans[0]); // number of package managers
	int comma_separator		 = 0;
	for (int i = 0; i < pkgman_count; i++) {
		struct package_manager* current = &pkgmans[i]; // pointer to current package manager

		unsigned int pkg_count = 0;
		LOG_I("trying pkgman %d: %s", i, current->pkgman_name);
		LOG_V(current->command_path);
		if (access(current->command_path, F_OK) != -1) {
	#ifndef __APPLE__
			FILE* fp = popen(current->command_string, "r"); // trying current package manager
	#else
			system(current->command_string); // writes to a temporary file: for some reason popen() does not intercept the stdout, so i have to read from a temporary file
			FILE* fp = fopen("/tmp/uwufetch_brew_tmp", "r");
	#endif
			if (fscanf(fp, "%u", &pkg_count) == 3) continue;

	#ifndef __APPLE__
			pclose(fp);
	#else
			// remove("/tmp/uwufetch_brew_tmp");
			fclose(fp);
	#endif
		}
	#ifdef __DEBUG__
		else
			LOG_W("pkgman %s executable not found!", current->pkgman_name);
	#endif

		// adding a package manager with its package count to user_info->pkgman_name
		user_info->pkgs += pkg_count;
		if (pkg_count > 0) {
			if (comma_separator++) strcat(user_info->pkgman_name, ", ");
			char spkg_count[16];
			sprintf(spkg_count, "%u", pkg_count);
			strcat(user_info->pkgman_name, spkg_count);
			strcat(user_info->pkgman_name, " ");
			strcat(user_info->pkgman_name, current->pkgman_name);
			LOG_V(user_info->pkgman_name);
		}
	}
#else	 // _WIN32
	// chocolatey for windows
	FILE* fp = popen("choco list -l --no-color 2> nul", "r");
	unsigned int pkg_count;
	char buffer[7562] = {0};
	while (fgets(buffer, BUFFER_SIZE, fp)) {
		sscanf(buffer, "%u packages installed.", &pkg_count);
	}
	if (fp) pclose(fp);

	user_info->pkgs = pkg_count;
	char spkg_count[16];
	sprintf(spkg_count, "%u", pkg_count);
	strcat(user_info->pkgman_name, spkg_count);
	strcat(user_info->pkgman_name, " ");
	strcat(user_info->pkgman_name, "(chocolatey)");
	LOG_V(user_info->pkgman_name);
#endif // _WIN32
	return 0;
}

void* get_model(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[5]) return 0;
	LOG_I("getting model");
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
	char* buffer					 = ((struct thread_varg*)argp)->buffer;
	FILE* model_fp;
#ifdef _WIN32
	// all the previous files obviously did not exist on windows
	model_fp = popen("wmic computersystem get model", "r");
	while (fgets(buffer, BUFFER_SIZE, model_fp)) {
		if (strstr(buffer, "Model") != 0)
			continue;
		else {
			sprintf(user_info->model, "%s", buffer);
			user_info->model[strlen(user_info->model) - 2] = '\0';
			break;
		}
	}
#elif defined(__BSD__) || defined(__APPLE__)
	#if defined(__BSD__) && !defined(__OPENBSD__)
		#define HOSTCTL "hw.hv_vendor"
	#elif defined(__APPLE__)
		#define HOSTCTL "hw.model"
	#elif defined(__OPENBSD__)
		#define HOSTCTL "hw.product"
	#endif
	model_fp = popen("sysctl " HOSTCTL, "r");
	while (fgets(buffer, BUFFER_SIZE, model_fp))
		if (sscanf(buffer,
							 HOSTCTL
	#ifdef __OPENBSD__
							 "="
	#else
							 ": "
	#endif
							 "%[^\n]",
							 user_info->model))
			break;
	pclose(model_fp);
#else
	char model_filename[4][256] = {
			"/sys/devices/virtual/dmi/id/product_version",
			"/sys/devices/virtual/dmi/id/product_name",
			"/sys/devices/virtual/dmi/id/board_name",
			"getprop ro.product.vendor.marketname 2>/dev/null",
	};

	char tmp_model[4][BUFFER_SIZE] = {0}; // temporary variable to store the contents of all 3 files
	int longest_model = 0, best_len = 0, currentlen = 0;
	FILE* (*tocall[])(const char*, const char*) = {fopen, fopen, fopen, popen}; // open a process or a file, depending on the model_filename
	int (*tocall_close[])(FILE*)								= {fclose, fclose, fclose, pclose};
	for (int i = 0; i < 4; i++) {
		// read file
		model_fp = tocall[i](model_filename[i], "r");
		if (model_fp) {
			fgets(tmp_model[i], BUFFER_SIZE, model_fp);
			tmp_model[i][strlen(tmp_model[i]) - 1] = '\0';
			tocall_close[i](model_fp);
		}
		LOG_V(tmp_model[i]);
		// choose the file with the longest name
		currentlen = strlen(tmp_model[i]);
		if (currentlen > best_len) {
			best_len			= currentlen;
			longest_model = i;
		}
	}
	if (strlen(tmp_model[longest_model]) == 0) {
		model_fp = popen("lscpu 2>/dev/null", "r");
		while (fgets(buffer, BUFFER_SIZE, model_fp))
			if (sscanf(buffer, "Model name:%*[           |		]%[^\n]", tmp_model[longest_model]) == 1) break;
		pclose(model_fp);
		LOG_V(tmp_model[longest_model]);
		if (strcmp(tmp_model[longest_model], "Icestorm") == 0) sprintf(tmp_model[longest_model], "Apple MacBook Air (M1)");
	}
	sprintf(user_info->model, "%s", tmp_model[longest_model]);
	LOG_V(user_info->model);
#endif
	return 0;
}

void* get_ker(void* argp) {
	if (!((struct thread_varg*)argp)->thread_flags[6]) return 0;
	LOG_I("getting kernel");
	struct info* user_info = ((struct thread_varg*)argp)->user_info;

#ifndef _WIN32
	truncate_str(user_info->sys_var.release, user_info->target_width);
	sprintf(user_info->kernel, "%s %s %s", user_info->sys_var.sysname, user_info->sys_var.release, user_info->sys_var.machine); // kernel name
	truncate_str(user_info->kernel, user_info->target_width);
	LOG_V(user_info->kernel);
#else	 // _WIN32
	// windows version
	FILE* kernel_fp = popen("wmic computersystem get systemtype", "r");
	char* buffer		= ((struct thread_varg*)argp)->buffer;
	while (fgets(buffer, BUFFER_SIZE, kernel_fp)) {
		if (strstr(buffer, "SystemType") != 0)
			continue;
		else {
			sprintf(user_info->kernel, "%s", buffer);
			user_info->kernel[strlen(user_info->kernel) - 2] = '\0';
			break;
		}
	}
	if (kernel_fp) pclose(kernel_fp);
#endif // _WIN32
	return 0;
}

void* get_upt(void* argp) {
	LOG_V(((struct thread_varg*)argp)->thread_flags[7]);
	if (!((struct thread_varg*)argp)->thread_flags[7]) return 0;
	LOG_I("getting uptime");
	struct info* user_info = ((struct thread_varg*)argp)->user_info;
#ifdef __APPLE__
	int mib[2] = {CTL_KERN, KERN_BOOTTIME};
	sysctl(mib, 2, &time_buffer, &time_buffer_len, NULL, 0);

	time_t bsec = time_buffer.tv_sec;
	time_t csec = time(NULL);

	user_info->uptime = difftime(csec, bsec);
#else
	#ifdef __BSD__
	// https://github.com/coreutils/coreutils/blob/master/src/uptime.c
	int boot_time					= 0;
	static int request[2] = {CTL_KERN, KERN_BOOTTIME};
	struct timeval result;
	size_t result_len = sizeof result;

	if (sysctl(request, 2, &result, &result_len, NULL, 0) >= 0) boot_time = result.tv_sec;
	int time_now			= time(NULL);
	user_info->uptime = time_now - boot_time;
	#else
		#ifdef _WIN32
	user_info->uptime = GetTickCount() / 1000;
		#else	 // _WIN32
	user_info->uptime = user_info->sys.uptime;
		#endif // _WIN32
	#endif
#endif
	LOG_V(user_info->uptime);
	return 0;
}

// Retrieves system information
void get_info(struct flags flags, struct info* user_info) {
	char buffer[BUFFER_SIZE]; // line buffer
	get_twidth(user_info);
	// os version, cpu and board info
#ifdef __OPENBSD__
	FILE* os_release = popen("echo ID=openbsd", "r"); // os-release does not exist in OpenBSD
#else
	FILE* os_release	= fopen("/etc/os-release", "r"); // os name file
#endif
#ifndef __BSD__
	FILE* cpuinfo = fopen("/proc/cpuinfo", "r"); // cpu name file for not-freebsd systems
#else
	FILE* cpuinfo			= popen("sysctl hw.model", "r"); // cpu name command for freebsd
#endif
	// trying to get some kind of information about the name of the computer (hopefully a product full name)
	if (os_release) { // get normal vars if os_release exists
		if (flags.os) {
			LOG_I("getting os name from /etc/os-release");
			while (fgets(buffer, BUFFER_SIZE, os_release) &&
						 !(sscanf(buffer, "\nID=\"%s\"", user_info->os_name) ||
							 sscanf(buffer, "\nID=%s", user_info->os_name)))
				;
			// sometimes for some reason sscanf reads the last '\"' too
			int os_name_len = strlen(user_info->os_name);
			if (user_info->os_name[os_name_len - 1] == '\"') {
				user_info->os_name[os_name_len - 1] = '\0';
			}
			// trying to detect amogos because in its os-release file ID value is just "debian", will be removed when amogos will have an os-release file with ID=amogos
			if (strcmp(user_info->os_name, "debian") == 0 ||
					strcmp(user_info->os_name, "raspbian") == 0) {
				DIR* amogos_plymouth = opendir("/usr/share/plymouth/themes/amogos");
				if (amogos_plymouth) {
					closedir(amogos_plymouth);
					sprintf(user_info->os_name, "amogos");
					LOG_V(user_info->os_name);
				}
			}
			LOG_V(user_info->os_name);
		}
	} else { // try for android vars, next for Apple var, or unknown system
					 // android
		DIR* system_app			 = opendir("/system/app/");
		DIR* system_priv_app = opendir("/system/priv-app/");
		DIR* library				 = opendir("/Library/");
		if (system_app && system_priv_app) {
			closedir(system_app);
			closedir(system_priv_app);
			if (flags.os) sprintf(user_info->os_name, "android");
			LOG_V(user_info->os_name);
			if (flags.user) {
				// username
				FILE* whoami = popen("whoami", "r");
				if (fscanf(whoami, "%s", user_info->user) == 3) {
				}
				LOG_V(user_info->user);
				pclose(whoami);
			}
		} else if (library) { // Apple
			closedir(library);
#ifdef __APPLE__
			if (flags.cpu) {
				sysctlbyname("machdep.cpu.brand_string", &cpu_buffer, &cpu_buffer_len, NULL,
										 0); // cpu name
				sprintf(user_info->cpu_model, "%s", cpu_buffer);
			}
			if (flags.os) {
	#ifndef __IPHONE__
				sprintf(user_info->os_name, "macos");
	#else
				sprintf(user_info->os_name, "ios");
	#endif
			}
#endif
		} else // if no option before is working, the system is unknown
			sprintf(user_info->os_name, "unknown");
	}
#ifndef __BSD__
#endif
#ifndef _WIN32
	// getting username and hostname
	if (flags.user) {
		LOG_I("getting username and hostname");
		gethostname(user_info->host, 256);
		LOG_V(user_info->host);
		char* tmp_user = getenv("USER");
		LOG_V(tmp_user);
		if (tmp_user == NULL)
			sprintf(user_info->user, "%s", "");
		else
			sprintf(user_info->user, "%s", tmp_user);
		LOG_V(user_info->user);
		if (os_release) fclose(os_release);
	}
	if (flags.shell) {
		LOG_I("getting shell");
		char* tmp_shell = getenv("SHELL"); // shell name
		LOG_V(tmp_shell);
		if (!tmp_shell)
			sprintf(user_info->shell, "%s", "");
		else
			sprintf(user_info->shell, "%s", tmp_shell);
	#ifdef __linux__
		if (strlen(user_info->shell) > 16) // android shell name was too long
			memmove(&user_info->shell, &user_info->shell[27], strlen(user_info->shell));
	#endif
		LOG_V(user_info->shell);
	}
#else	 // if _WIN32
	// cpu name
	if (flags.cpu) {
		cpuinfo = popen("wmic cpu get caption", "r");
		while (fgets(buffer, BUFFER_SIZE, cpuinfo)) {
			if (strstr(buffer, "Caption") != 0)
				continue;
			else {
				sprintf(user_info->cpu_model, "%s", buffer);
				user_info->cpu_model[strlen(user_info->cpu_model) - 2] = '\0';
				break;
			}
		}
	}
	// username
	if (flags.user) {
		FILE* user_host_fp = popen("wmic computersystem get username", "r");
		while (fgets(buffer, BUFFER_SIZE, user_host_fp)) {
			if (strstr(buffer, "UserName") != 0)
				continue;
			else {
				sscanf(buffer, "%[^\\]%s", user_info->host, user_info->user);
				memmove(user_info->user, user_info->user + 1, sizeof(user_info->user) - 1);
				break;
			}
		}
	}
	// powershell version
	if (flags.shell) {
		FILE* shell_fp = popen("powershell $PSVersionTable", "r");
		sprintf(user_info->shell, "PowerShell ");
		char tmp_shell[64];
		while (fgets(buffer, BUFFER_SIZE, shell_fp) &&
					 sscanf(buffer, "PSVersion                      %s", tmp_shell) == 0)
			;
		strcat(user_info->shell, tmp_shell);
	}
#endif // _WIN32

#ifdef _WIN32
	if (flags.os) sprintf(user_info->os_name, "windows");
#endif
	get_sys(user_info);
	// are threads overpowered? nah
	void* (*fnptrs[])(void*) = {get_cpu, get_ram, get_gpu, get_res, get_pkg, get_model, get_ker, get_upt};
	struct thread_varg args =
			(struct thread_varg){buffer,
													 user_info,
													 cpuinfo,
													 {flags.cpu, flags.ram, flags.gpu, flags.resolution, flags.pkgs, flags.model, flags.kernel, flags.uptime}};
#define THREAD_COUNT 8
#ifndef _WIN32
	pthread_t tids[THREAD_COUNT] = {0};
#endif
	for (int i = 0; i < THREAD_COUNT; i++) {
		LOG_I("STARTING thread %d", i);
#ifdef _WIN32
		fnptrs[i](&args);
#else
		pthread_create(&tids[i], NULL, fnptrs[i], &args);
#endif
	}
#ifndef _WIN32
	for (int i = 0; i < THREAD_COUNT; i++) {
		if (tids[i] != 0) pthread_join(tids[i], NULL);
		LOG_I("JOINING thread %d", i);
	}
#endif
	fclose(cpuinfo);
}
