/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "Host/SystemInfo.hpp"
#include "Base/Logger.hpp"
#include <sstream>

#include <mach/mach.h>
#include <os/proc.h>

#import <Foundation/NSProcessInfo.h>
#import <UIKit/UIApplication.h>
#import <UIKit/UIDevice.h>
#import <UIKit/UIWindowScene.h>

namespace OpenVulkano
{
	Event<> SystemInfo::OnLowPowerModeChanged;
	Event<> SystemInfo::OnBatteryStateChanged;
	Event<> SystemInfo::OnBatteryLevelChanged;
	Event<> SystemInfo::OnDeviceOrientationChanged;

	size_t SystemInfo::GetSystemRam()
	{
		return [NSProcessInfo processInfo].physicalMemory;
	}

	size_t SystemInfo::GetSystemRamAvailable()
	{
		return os_proc_available_memory();
	}

	size_t SystemInfo::GetAppRamMax()
	{
		return GetAppRamAvailable() + GetAppRamUsed();
	}

	size_t SystemInfo::GetAppVirtualMemoryMax()
	{
		return INT64_MAX;
	}

	size_t SystemInfo::GetAppRamAvailable()
	{
		return os_proc_available_memory();
	}

	size_t SystemInfo::GetAppRamUsed()
	{
		struct task_basic_info info;
		mach_msg_type_number_t size = sizeof(info);
		kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
		if( kerr == KERN_SUCCESS )
		{
			return info.resident_size;
		}
		Logger::PERF->error("Failed to read memory consumption: {}", mach_error_string(kerr));
		return 0;
	}

	std::string SystemInfo::GetUserName()
	{
		return "";
	}

	std::string SystemInfo::GetHostName()
	{
		return [[NSProcessInfo processInfo].hostName UTF8String];
	}

	std::string SystemInfo::GetDeviceName()
	{
		return [[[UIDevice currentDevice] name] UTF8String];
	}

	std::string SystemInfo::GetDeviceVendorName()
	{
		return "Apple";
	}

	std::string SystemInfo::GetDeviceModelName()
	{
		return [[[UIDevice currentDevice] model] UTF8String];
	}

	std::string SystemInfo::GetOsName()
	{
		return [[[UIDevice currentDevice] systemName] UTF8String];
	}

	OsVersion   SystemInfo::GetOsVersion()
	{
		NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
		return { static_cast<int>(osVersion.majorVersion), static_cast<int>(osVersion.minorVersion), static_cast<int>(osVersion.patchVersion), 0 };
	}

	std::string SystemInfo::GetOsNameHumanReadable()
	{
		std::stringstream name;
		name << GetOsName() << ' ' << GetOsVersion().major;
		return name.str();
	}

	DeviceType  SystemInfo::GetDeviceType()
	{
		switch ([UIDevice currentDevice].userInterfaceIdiom)
		{
			case UIUserInterfaceIdiomPhone: return DeviceType::Phone;
			case UIUserInterfaceIdiomPad: return DeviceType::Tablet;
			case UIUserInterfaceIdiomTV: return DeviceType::TV;
			case UIUserInterfaceIdiomMac: return DeviceType::PC;
			case UIUserInterfaceIdiomVision: return DeviceType::VR;
			default: break;
		}
		return DeviceType::Unknown;
	}

	size_t SystemInfo::GetCpuCoreCount()
	{
		return [NSProcessInfo processInfo].processorCount;
	}

	size_t SystemInfo::GetCpuThreadCount()
	{
		return [NSProcessInfo processInfo].activeProcessorCount;
	}

	int32_t SystemInfo::GetCpuTemperature()
	{
		switch([NSProcessInfo processInfo].thermalState)
		{
			case NSProcessInfoThermalStateNominal: return 20;
			case NSProcessInfoThermalStateFair: return 50;
			case NSProcessInfoThermalStateSerious: return 80;
			case NSProcessInfoThermalStateCritical: return 100;
		}
		return -1;
	}

	CpuThermalState SystemInfo::GetCpuThermalState()
	{
		switch([NSProcessInfo processInfo].thermalState)
		{
			case NSProcessInfoThermalStateNominal: return CpuThermalState::Normal;
			case NSProcessInfoThermalStateFair: return CpuThermalState::Fair;
			case NSProcessInfoThermalStateSerious: return CpuThermalState::Serious;
			case NSProcessInfoThermalStateCritical: return CpuThermalState::Critical;
		}
		return CpuThermalState::Normal;
	}

	bool SystemInfo::IsDeviceInLowPowerMode()
	{
		return [NSProcessInfo processInfo].lowPowerModeEnabled;
	}

	void SystemInfo::EnableEnergyEvents()
	{
		[[NSNotificationCenter defaultCenter] addObserverForName:NSProcessInfoPowerStateDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
			SystemInfo::OnLowPowerModeChanged.NotifyAll();
		}];
		[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceBatteryLevelDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
			SystemInfo::OnBatteryLevelChanged.NotifyAll();
		}];
		[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceBatteryStateDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
			SystemInfo::OnBatteryStateChanged.NotifyAll();
		}];
	}

	BatteryState SystemInfo::GetDeviceBatteryState()
	{
		switch([UIDevice currentDevice].batteryState)
		{
			case UIDeviceBatteryStateUnplugged: return BatteryState::Unplugged;
			case UIDeviceBatteryStateCharging: return BatteryState::Charging;
			case UIDeviceBatteryStateFull: return BatteryState::ChargingFull;
			default: break;
		}
		return BatteryState::Unavailable;
	}

	float SystemInfo::GetDeviceBatteryLevel()
	{
		return [UIDevice currentDevice].batteryLevel;
	}

	bool SystemInfo::IsMultitaskingSupported()
	{
		return [UIDevice currentDevice].multitaskingSupported;
	}

	DeviceOrientation SystemInfo::GetDeviceOrientation()
	{
		switch([UIDevice currentDevice].orientation)
		{
			case UIDeviceOrientationPortrait: return DeviceOrientation::Portrait;
			case UIDeviceOrientationPortraitUpsideDown: return DeviceOrientation::PortraitUpsideDown;
			case UIDeviceOrientationLandscapeLeft: return DeviceOrientation::LandscapeLeft;
			case UIDeviceOrientationLandscapeRight: return DeviceOrientation::LandscapeRight;
			case UIDeviceOrientationFaceUp: return DeviceOrientation::FaceUp;
			case UIDeviceOrientationFaceDown: return DeviceOrientation::FaceDown;
			default: break;
		}
		return DeviceOrientation::Unknown;
	}

	void SystemInfo::EnableDeviceOrientationEvents()
	{
		[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
			SystemInfo::OnDeviceOrientationChanged.NotifyAll();
		}];
		[[UIDevice currentDevice]beginGeneratingDeviceOrientationNotifications];
	}

	InterfaceOrientation SystemInfo::GetInterfaceOrientation()
	{
		if (@available(iOS 15.0, *))
		{ //TODO support multi scene at some point
			UIInterfaceOrientation interfaceOrientation;
			for (UIScene *scene in [UIApplication sharedApplication].connectedScenes)
			{
				if ([scene isKindOfClass:[UIWindowScene class]])
				{
					interfaceOrientation = ((UIWindowScene *)scene).interfaceOrientation;
					break;
				}
			}
			switch(interfaceOrientation)
			{
				case UIInterfaceOrientationPortrait: return InterfaceOrientation::Portrait;
				case UIInterfaceOrientationPortraitUpsideDown: return InterfaceOrientation::PortraitUpsideDown;
				case UIInterfaceOrientationLandscapeLeft: return InterfaceOrientation::LandscapeLeft;
				case UIInterfaceOrientationLandscapeRight: return InterfaceOrientation::LandscapeRight;
				default: break;
			}
		}
		else
		{
			switch(GetDeviceOrientation())
			{
				case DeviceOrientation::Portrait: return InterfaceOrientation::Portrait;
				case DeviceOrientation::PortraitUpsideDown: return InterfaceOrientation::PortraitUpsideDown;
				case DeviceOrientation::LandscapeLeft: return InterfaceOrientation::LandscapeRight;
				case DeviceOrientation::LandscapeRight: return InterfaceOrientation::LandscapeLeft;
				default: break;
			}
		}
		return InterfaceOrientation::Landscape;
	}
}
