/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * 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
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#import "ObjFW.h"
#import "ObjFWTest.h"

@interface OFSystemInfoTests: OTTestCase
@end

#ifdef OF_HAVE_SOCKETS
static void
appendAddresses(OFMutableString *string, OFData *addresses, bool *firstAddress)
{
	size_t count = addresses.count;

	for (size_t i = 0; i < count; i++) {
		const OFSocketAddress *address = [addresses itemAtIndex: i];

		if (!*firstAddress)
			[string appendString: @", "];

		*firstAddress = false;

		[string appendString: OFSocketAddressString(address)];
	}
}
#endif

@implementation OFSystemInfoTests
+ (OFArray OF_GENERIC(OFPair OF_GENERIC(OFString *, id) *) *)summary
{
	OFMutableArray *summary = [OFMutableArray array];
#ifdef OF_HAVE_SOCKETS
	OFDictionary *networkInterfaces;
	OFMutableString *networkInterfacesString;
	bool firstInterface = true;
#endif

#define ADD(name, value)						\
	[summary addObject: [OFPair pairWithFirstObject: name		\
					   secondObject: value]];
#define ADD_UINT(name, value)						\
	ADD(name, [OFNumber numberWithUnsignedInt: value]);
#define ADD_ULONGLONG(name, value)					\
	ADD(name, [OFNumber numberWithUnsignedLongLong: value]);
#define ADD_BOOL(name, value)						\
	ADD(name, [OFNumber numberWithBool: value]);

	ADD(@"ObjFW version", [OFSystemInfo ObjFWVersion])
	ADD_UINT(@"ObjFW version major", [OFSystemInfo ObjFWVersionMajor])
	ADD_UINT(@"ObjFW version minor", [OFSystemInfo ObjFWVersionMinor])
	ADD(@"Operating system name", [OFSystemInfo operatingSystemName]);
	ADD(@"Operating system version", [OFSystemInfo operatingSystemVersion]);
#ifdef OF_WINDOWS
	ADD(@"Wine version", [OFSystemInfo wineVersion]);
#endif
	ADD_ULONGLONG(@"Page size", [OFSystemInfo pageSize]);
	ADD_ULONGLONG(@"Number of CPUs", [OFSystemInfo numberOfCPUs]);
	ADD(@"User config IRI", [OFSystemInfo userConfigIRI].string);
	ADD(@"User data IRI", [OFSystemInfo userDataIRI].string);
	ADD(@"Temporary directory IRI",
	    [OFSystemInfo temporaryDirectoryIRI].string);
	ADD(@"CPU vendor", [OFSystemInfo CPUVendor]);
	ADD(@"CPU model", [OFSystemInfo CPUModel]);

#if defined(OF_AMD64) || defined(OF_X86)
	ADD_BOOL(@"Supports MMX", [OFSystemInfo supportsMMX]);
	ADD_BOOL(@"Supports 3DNow!", [OFSystemInfo supports3DNow]);
	ADD_BOOL(@"Supports enhanced 3DNow!",
	    [OFSystemInfo supportsEnhanced3DNow]);
	ADD_BOOL(@"Supports SSE", [OFSystemInfo supportsSSE]);
	ADD_BOOL(@"Supports SSE2", [OFSystemInfo supportsSSE2]);
	ADD_BOOL(@"Supports SSE3", [OFSystemInfo supportsSSE3]);
	ADD_BOOL(@"Supports SSSE3", [OFSystemInfo supportsSSSE3]);
	ADD_BOOL(@"Supports SSE4.1", [OFSystemInfo supportsSSE41]);
	ADD_BOOL(@"Supports SSE4.2", [OFSystemInfo supportsSSE42]);
	ADD_BOOL(@"Supports AVX", [OFSystemInfo supportsAVX]);
	ADD_BOOL(@"Supports AVX2", [OFSystemInfo supportsAVX2]);
	ADD_BOOL(@"Supports AES-NI", [OFSystemInfo supportsAESNI]);
	ADD_BOOL(@"Supports SHA extensions",
	    [OFSystemInfo supportsSHAExtensions]);
	ADD_BOOL(@"Supports fused multiply-add",
	    [OFSystemInfo supportsFusedMultiplyAdd]);
	ADD_BOOL(@"Supports F16C", [OFSystemInfo supportsF16C]);
	ADD_BOOL(@"Supports AVX-512 Foundation",
	    [OFSystemInfo supportsAVX512Foundation]);
	ADD_BOOL(@"Supports AVX-512 Conflict Detection Instructions",
	    [OFSystemInfo supportsAVX512ConflictDetectionInstructions]);
	ADD_BOOL(@"Supports AVX-512 Exponential and Reciprocal Instructions",
	    [OFSystemInfo supportsAVX512ExponentialAndReciprocalInstructions]);
	ADD_BOOL(@"Supports AVX-512 Prefetch Instructions",
	    [OFSystemInfo supportsAVX512PrefetchInstructions]);
	ADD_BOOL(@"Supports AVX-512 Vector Length Extensions",
	    [OFSystemInfo supportsAVX512VectorLengthExtensions]);
	ADD_BOOL(@"Supports AVX-512 Doubleword and Quadword Instructions",
	    [OFSystemInfo supportsAVX512DoublewordAndQuadwordInstructions]);
	ADD_BOOL(@"Supports AVX-512 Byte and Word Instructions",
	    [OFSystemInfo supportsAVX512ByteAndWordInstructions]);
	ADD_BOOL(@"Supports AVX-512 Integer Fused Multiply Add",
	    [OFSystemInfo supportsAVX512IntegerFusedMultiplyAdd]);
	ADD_BOOL(@"Supports AVX-512 Vector Byte Manipulation Instructions",
	    [OFSystemInfo supportsAVX512VectorByteManipulationInstructions]);
	ADD_BOOL(@"Supports AVX-512 Vector Population Count Instruction",
	    [OFSystemInfo supportsAVX512VectorPopulationCountInstruction]);
	ADD_BOOL(@"Supports AVX-512 Vector Neutral Network Instructions",
	    [OFSystemInfo supportsAVX512VectorNeuralNetworkInstructions]);
	ADD_BOOL(@"Supports AVX-512 Vector Byte Manipulation Instructions 2",
	    [OFSystemInfo supportsAVX512VectorByteManipulationInstructions2]);
	ADD_BOOL(@"Supports AVX-512 Bit Algorithms",
	    [OFSystemInfo supportsAVX512BitAlgorithms]);
	ADD_BOOL(@"Supports AVX-512 Float16 Instructions",
	    [OFSystemInfo supportsAVX512Float16Instructions]);
	ADD_BOOL(@"Supports AVX-512 BFloat16 Instructions",
	    [OFSystemInfo supportsAVX512BFloat16Instructions]);
#endif

#if defined(OF_POWERPC) || defined(OF_POWERPC64)
	ADD_BOOL(@"Supports AltiVec", [OFSystemInfo supportsAltiVec]);
#endif

#ifdef OF_LOONGARCH64
	ADD_BOOL(@"Supports LSX", [OFSystemInfo supportsLSX]);
	ADD_BOOL(@"Supports LASX", [OFSystemInfo supportsLASX]);
#endif

#undef ADD
#undef ADD_UINT
#undef ADD_ULONGLONG
#undef ADD_BOOL

#ifdef OF_HAVE_SOCKETS
	networkInterfaces = [OFSystemInfo networkInterfaces];
	networkInterfacesString = [OFMutableString string];
	for (OFString *name in networkInterfaces) {
		bool firstAddress = true;
		OFNetworkInterface interface;
		OFData *hardwareAddress;

		if (!firstInterface)
			[networkInterfacesString appendString: @"; "];

		firstInterface = false;

		[networkInterfacesString appendFormat: @"%@(", name];

		interface = [networkInterfaces objectForKey: name];

		appendAddresses(networkInterfacesString,
		    [interface objectForKey: OFNetworkInterfaceIPv4Addresses],
		    &firstAddress);
# ifdef OF_HAVE_IPV6
		appendAddresses(networkInterfacesString,
		    [interface objectForKey: OFNetworkInterfaceIPv6Addresses],
		    &firstAddress);
# endif
# ifdef OF_HAVE_IPX
		appendAddresses(networkInterfacesString,
		    [interface objectForKey: OFNetworkInterfaceIPXAddresses],
		    &firstAddress);
# endif
# ifdef OF_HAVE_APPLETALK
		appendAddresses(networkInterfacesString,
		    [interface objectForKey:
		    OFNetworkInterfaceAppleTalkAddresses], &firstAddress);
# endif

		hardwareAddress = [interface
		    objectForKey: OFNetworkInterfaceHardwareAddress];
		if (hardwareAddress != nil) {
			const unsigned char *bytes = hardwareAddress.items;
			size_t length = hardwareAddress.count;

			if (!firstAddress)
				[networkInterfacesString appendString: @", "];

			for (size_t i = 0; i < length; i++) {
				if (i > 0)
					[networkInterfacesString
					    appendString: @":"];

				[networkInterfacesString
				    appendFormat: @"%02X", bytes[i]];
			}
		}

		[networkInterfacesString appendString: @")"];
	}
	[summary addObject:
	    [OFPair pairWithFirstObject: @"Network interfaces"
			   secondObject: networkInterfacesString]];
#endif

	return summary;
}
@end
