/*
    PIP - Platform Independent Primitives
    System information
    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 "pisysteminfo.h"

#include "picrc.h"
#include "pidir.h"
#include "piincludes_p.h"

#ifdef ESP_PLATFORM
#  include "esp_spi_flash.h"
#  include "esp_system.h"
#endif

#define SALT_SIZE 8


PISystemInfo * PISystemInfo::instance() {
	static PISystemInfo ret;
	return &ret;
}


PIStringList PISystemInfo::mountRoots() {
	PIStringList ret;
#ifdef WINDOWS
	char letters[1024];
	DWORD ll = GetLogicalDriveStringsA(1023, letters);
	PIString clet;
	for (uint i = 0; i < ll; ++i) {
		if (letters[i] == '\0') {
			if (clet.size_s() > 2) ret << clet.cutRight(1);
			clet.clear();
		} else
			clet += PIChar(letters[i]);
	}
#else
#  ifdef LINUX
	PIString s_df, s_m;
	char in[1024];
	piZeroMemory(in, 1024);
	FILE * fp = popen("mount -l", "r");
	PIStringList tl;
	if (fp) {
		while (fgets(in, 1024, fp)) {
			tl = PIString(in).trim().replaceAll("  ", " ").split(" ");
			if (tl.size_s() < 2) continue;
			for (int i = 0; i < tl.size_s() - 1; ++i)
				if (tl[i] == "on") {
					if (!ret.contains(tl[i + 1])) ret << tl[i + 1];
					break;
				}
		}
		pclose(fp);
	}
#  else
#  endif
#endif
	return ret;
}


struct String3 {
	PIString mp, type, label;
};

PIVector<PISystemInfo::MountInfo> PISystemInfo::mountInfo(bool ignore_cache) {
	static PIVector<PISystemInfo::MountInfo> cache;
	static PITimeMeasurer tm;
	static bool first = true;
	if (!ignore_cache) {
		if (tm.elapsed_m() < piMountInfoRefreshIntervalMs && !first) return cache;
	}
	first = false;
	tm.reset();
	cache.clear();

	PIVector<PISystemInfo::MountInfo> ret;
	MountInfo m;
#ifdef WINDOWS
	char letters[1024], volname[1024], volfs[1024];
	DWORD ll = GetLogicalDriveStringsA(1023, letters);
	PIString clet;
	for (DWORD i = 0; i < ll; ++i) {
		if (letters[i] == '\0') {
			if (GetVolumeInformationA(clet.data(), volname, 1023, 0, 0, 0, volfs, 1023)) {
				m.mount_point = clet;
				m.filesystem  = volfs;
				m.label       = volname;
				DWORD spc, bps, free_cl, all_cl;
				if (GetDiskFreeSpaceA(clet.data(), &spc, &bps, &free_cl, &all_cl)) {
					ullong bpc   = ullong(spc) * ullong(bps);
					m.space_all  = bpc * ullong(all_cl);
					m.space_free = bpc * ullong(free_cl);
					m.space_used = m.space_all - m.space_free;
				} else
					m.space_all = m.space_free = m.space_used = 0U;
				if (GetDriveTypeA(clet.dataAscii()) == DRIVE_REMOVABLE) m.removable = true;
				clet.cutRight(1);
				int qdd = QueryDosDeviceA(clet.data(), volfs, 1023);
				if (qdd > 0)
					m.device = volfs;
				else
					m.device.clear();
				ret << m;
			}
			clet.clear();
		} else
			clet += PIChar(letters[i]);
	}
#endif

#ifdef LINUX
	PIString s_df, s_m;
	char in[1024];
	piZeroMemory(in, 1024);
	// piCout << "mountInfo 0";
	FILE * fp = popen("df -B1", "r");
	PIStringList l_df;
	PIMap<PIString, String3> fs;
	if (fp) {
		while (fgets(in, 1024, fp))
			l_df << PIString(in).trim();
		pclose(fp);
		fp = 0;
	}
	// piCout << "mountInfo 1";
	piZeroMemory(in, 1024);
	fp = popen("mount -l", "r");
	PIStringList tl;
	if (fp) {
		while (fgets(in, 1024, fp)) {
			tl = PIString(in).trim().replaceAll("  ", " ").split(" ");
			if (tl.size_s() < 2) continue;
			String3 me;
			PIString dev;
			dev = tl.front();
			for (int i = 0; i < tl.size_s() - 1; ++i) {
				if (tl[i] == "on") {
					me.mp = tl[i + 1];
					++i;
					continue;
				}
				if (tl[i] == "type") {
					me.type = tl[i + 1];
					++i;
					continue;
				}
			}
			if (tl.back().startsWith("[")) {
				me.label = tl.back();
				me.label.cutLeft(1).cutRight(1);
			}
			fs[dev] = me;
			// piCout << fsmp << fstl;
		}
		pclose(fp);
		fp = 0;
	}
	if (l_df.size_s() < 2) return ret;
	l_df.pop_front();
	for (const auto & s: l_df) {
		PIStringList ml(s.replacedAll("  ", " ").split(" "));
		if (ml.size_s() < 3) continue;
		if (ml.front() == "none") continue;
		m.space_all   = ml[1].toULLong();
		m.space_used  = ml[2].toULLong();
		m.space_free  = m.space_all - m.space_used;
		String3 s3    = fs.value(ml.front());
		m.device      = ml.front();
		m.filesystem  = s3.type;
		m.mount_point = s3.mp;
		m.label       = s3.label;
		ret << m;
		// piCout << ml;
	}
#endif

#ifdef ESP_PLATFORM
	esp_chip_info_t chip_info;
	esp_chip_info(&chip_info);
	m.device      = ((chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded SPI flash" : "external SPI flash");
	m.space_all   = spi_flash_get_chip_size();
	m.mount_point = "/";
	ret << m;
#endif
	cache = ret;
	return ret;
}

PIString confDir() {
	return
#ifdef WINDOWS
		PIDir::home().path() + "/AppData/Local"
#elif defined(ANDROID)
		""
#else
		PIDir::home().path() + "/.config"
#endif
		;
}


PIByteArray generateSalt() {
	PIByteArray ret;
	piForTimes(SALT_SIZE) {
		piMSleep(randomi() % 10);
		randomize();
		ret << uchar(randomi() % 0x100);
	}
	return ret;
}


PIString PISystemInfo::machineKey() {
	static PIString ret;
	if (ret.isEmpty()) {
		PISystemInfo * si = instance();
		PIByteArray salt;
		PIString conf = confDir() + "/.pip_machine_salt";
		if (PIFile::isExists(conf)) salt = PIFile::readAll(conf);
		if (salt.size_s() != SALT_SIZE) {
			salt = generateSalt();
			PIFile::writeAll(conf, salt);
		}
		ret = si->OS_name + "_" + si->architecture + "_" + si->hostname + "_" + salt.toHex();
	}
	return ret;
}


uint PISystemInfo::machineID() {
	static uint ret = 0;
	if (ret == 0) {
		CRC_32 crc = standardCRC_32();
		ret        = crc.calculate(machineKey().toByteArray());
		// piCout << "machineID \"" << machineKey() << "\" =" << PICoutManipulators::Hex << ret;
	}
	return ret;
}
