#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <dlfcn.h>
#include <elf.h>

#if CPUINFO_MOCK
	#include <cpuinfo-mock.h>
#endif
#include <cpuinfo.h>
#include <arm/linux/api.h>
#include <cpuinfo/log.h>

#if CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_ARM && !defined(__ANDROID__)
	#include <sys/auxv.h>
#else
	#define AT_HWCAP 16
	#define AT_HWCAP2 26
#endif


#if CPUINFO_MOCK
	static uint32_t mock_hwcap = 0;
	void cpuinfo_set_hwcap(uint32_t hwcap) {
		mock_hwcap = hwcap;
	}

	#if CPUINFO_ARCH_ARM
		static uint32_t mock_hwcap2 = 0;
		void cpuinfo_set_hwcap2(uint32_t hwcap2) {
			mock_hwcap2 = hwcap2;
		}
	#endif
#endif


#if CPUINFO_ARCH_ARM
	typedef unsigned long (*getauxval_function_t)(unsigned long);

	bool cpuinfo_arm_linux_hwcap_from_getauxval(
		uint32_t hwcap[restrict static 1],
		uint32_t hwcap2[restrict static 1])
	{
		#if CPUINFO_MOCK
			*hwcap  = mock_hwcap;
			*hwcap2 = mock_hwcap2;
			return true;
		#elif defined(__ANDROID__)
			/* Android: dynamically check if getauxval is supported */
			void* libc = NULL;
			getauxval_function_t getauxval = NULL;

			dlerror();
			libc = dlopen("libc.so", RTLD_LAZY);
			if (libc == NULL) {
				cpuinfo_log_warning("failed to load libc.so: %s", dlerror());
				goto cleanup;
			}

			getauxval = (getauxval_function_t) dlsym(libc, "getauxval");
			if (getauxval == NULL) {
				cpuinfo_log_info("failed to locate getauxval in libc.so: %s", dlerror());
				goto cleanup;
			}

			*hwcap  = getauxval(AT_HWCAP);
			*hwcap2 = getauxval(AT_HWCAP2);

		cleanup:
			if (libc != NULL) {
				dlclose(libc);
				libc = NULL;
			}
			return getauxval != NULL;
		#else
			/* GNU/Linux: getauxval is always supported */
			*hwcap  = getauxval(AT_HWCAP);
			*hwcap2 = getauxval(AT_HWCAP2);
			return true;
		#endif
	}

	#ifdef __ANDROID__
		bool cpuinfo_arm_linux_hwcap_from_procfs(
			uint32_t hwcap[restrict static 1],
			uint32_t hwcap2[restrict static 1])
		{
			#if CPUINFO_MOCK
				*hwcap  = mock_hwcap;
				*hwcap2 = mock_hwcap2;
				return true;
			#else
				uint32_t hwcaps[2] = { 0, 0 };
				bool result = false;
				int file = -1;

				file = open("/proc/self/auxv", O_RDONLY);
				if (file == -1) {
					cpuinfo_log_warning("failed to open /proc/self/auxv: %s", strerror(errno));
					goto cleanup;
				}

				ssize_t bytes_read;
				do {
					Elf32_auxv_t elf_auxv;
					bytes_read = read(file, &elf_auxv, sizeof(Elf32_auxv_t));
					if (bytes_read < 0) {
						cpuinfo_log_warning("failed to read /proc/self/auxv: %s", strerror(errno));
						goto cleanup;
					} else if (bytes_read > 0) {
						if (bytes_read == sizeof(elf_auxv)) {
							switch (elf_auxv.a_type) {
								case AT_HWCAP:
									hwcaps[0] = (uint32_t) elf_auxv.a_un.a_val;
									break;
								case AT_HWCAP2:
									hwcaps[1] = (uint32_t) elf_auxv.a_un.a_val;
									break;
							}
						} else {
							cpuinfo_log_warning(
								"failed to read %zu bytes from /proc/self/auxv: %zu bytes available",
								sizeof(elf_auxv), (size_t) bytes_read);
							goto cleanup;
						}
					}
				} while (bytes_read == sizeof(Elf32_auxv_t));

				/* Success, commit results */
				*hwcap  = hwcaps[0];
				*hwcap2 = hwcaps[1];
				result = true;

			cleanup:
				if (file != -1) {
					close(file);
					file = -1;
				}
				return result;
			#endif
		}
	#endif /* __ANDROID__ */
#elif CPUINFO_ARCH_ARM64
	uint32_t cpuinfo_arm_linux_hwcap_from_getauxval(void) {
		#if CPUINFO_MOCK
			return mock_hwcap;
		#else
			return (uint32_t) getauxval(AT_HWCAP);
		#endif
	}
#endif
