/*
 * NPLL - Misc utilities
 *
 * Copyright (C) 2025-2026 Techflash
 */

#ifndef _UTILS_H
#define _UTILS_H

#include <npll/console.h>
#include <npll/log_internal.h>
#include <npll/types.h>

/* branch predictor helpers */
#define __assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#define __likely(cond) __builtin_expect((cond), 1)
#define __unlikely(cond) __builtin_expect((cond), 0)

/* stringification helpers */
#define __stringify(str) #str
#define __stringifyResult(str) __stringify(str)

/* memory helpers */
#define CACHED_BASE        0x80000000u
#define UNCACHED_BASE      0xc0000000u

static inline void *_physToCached(void *_addr) {
	u32 addr = (u32)_addr;
	addr |= CACHED_BASE;
	return (void *)addr;
}

static inline void *_physToUncached(void *_addr) {
	u32 addr = (u32)_addr;
	addr |= UNCACHED_BASE;
	return (void *)addr;
}

static inline void *_virtToPhys(void *_addr) {
	u32 addr = (u32)_addr;
	addr &= ~UNCACHED_BASE; /* works for both cached and uncached */
	return (void *)addr;
}

#define physToCached(x) _physToCached((void *)(x))
#define physToUncached(x) _physToUncached((void *)(x))
#define cachedToUncached(x) physToUncached(x)
#define uncachedToCached(x) physToCached(uncachedToPhys(x))
#define virtToPhys(x) _virtToPhys((void *)(x))
#define cachedToPhys(x) virtToPhys(x)
#define uncachedToPhys(x) virtToPhys(x)

#define MEM1_PHYS_BASE     0x00000000u
#define MEM1_CACHED_BASE   ((u32)physToCached(MEM1_PHYS_BASE))
#define MEM1_UNCACHED_BASE ((u32)physToUncached(MEM1_PHYS_BASE))

#define MEM2_PHYS_BASE     0x10000000u
#define MEM2_CACHED_BASE   ((u32)physToCached(MEM2_PHYS_BASE))
#define MEM2_UNCACHED_BASE ((u32)physToUncached(MEM2_PHYS_BASE))

#define MEM1_SIZE_GCN      0x01800000u
#define MEM1_SIZE_WII      MEM1_SIZE_GCN
#define MEM1_SIZE_NDEV     0x04000000u
#define MEM1_SIZE_WIIU     0x02000000u

#define MEM2_SIZE_WII      0x04000000u
#define MEM2_SIZE_NDEV     0x08000000u
/* lie, but this is all that we map */
#define MEM2_SIZE_WIIU     0x10000000u


static inline bool addrIsValidCached(void *_addr) {
	u32 addr = (u32)_addr;

	switch (H_ConsoleType) {
	case CONSOLE_TYPE_GAMECUBE: {
		return (addr >= MEM1_CACHED_BASE && addr < (MEM1_CACHED_BASE + MEM1_SIZE_GCN));
	}
	case CONSOLE_TYPE_WII: {
		return ((addr >= MEM1_CACHED_BASE && addr < (MEM1_CACHED_BASE + MEM1_SIZE_WII)) ||
			(addr >= MEM2_CACHED_BASE && addr < (MEM2_CACHED_BASE + MEM2_SIZE_WII)));
	}
	case CONSOLE_TYPE_WII_U: {
		return ((addr >= MEM1_CACHED_BASE && addr < (MEM1_CACHED_BASE + MEM1_SIZE_WIIU)) ||
			(addr >= MEM2_CACHED_BASE && addr < (MEM2_CACHED_BASE + MEM2_SIZE_WIIU)));
	}
	}
	__builtin_unreachable();
}

static inline bool addrIsValidUncached(void *_addr) {
	u32 addr = (u32)_addr;

	switch (H_ConsoleType) {
	case CONSOLE_TYPE_GAMECUBE: {
		return (addr >= MEM1_UNCACHED_BASE && addr < (MEM1_UNCACHED_BASE + MEM1_SIZE_GCN));
	}
	case CONSOLE_TYPE_WII: {
		return ((addr >= MEM1_UNCACHED_BASE && addr < (MEM1_UNCACHED_BASE + MEM1_SIZE_WII)) ||
			(addr >= MEM2_UNCACHED_BASE && addr < (MEM2_UNCACHED_BASE + MEM2_SIZE_WII)));
	}
	case CONSOLE_TYPE_WII_U: {
		return ((addr >= MEM1_UNCACHED_BASE && addr < (MEM1_UNCACHED_BASE + MEM1_SIZE_WIIU)) ||
			(addr >= MEM2_UNCACHED_BASE && addr < (MEM2_UNCACHED_BASE + MEM2_SIZE_WIIU)));
	}
	}
	__builtin_unreachable();
}

/* don't check >= MEM1_PHYS_BASE, since it inherently must be at least 0 */
static inline bool addrIsValidPhys(void *_addr) {
	u32 addr = (u32)_addr;

	switch (H_ConsoleType) {
	case CONSOLE_TYPE_GAMECUBE: {
		return addr < (MEM1_PHYS_BASE + MEM1_SIZE_GCN);
	}
	case CONSOLE_TYPE_WII: {
		return ((addr < (MEM1_PHYS_BASE + MEM1_SIZE_WII)) ||
			(addr >= MEM2_PHYS_BASE && addr < (MEM2_PHYS_BASE + MEM2_SIZE_WII)));
	}
	case CONSOLE_TYPE_WII_U: {
		return ((addr < (MEM1_PHYS_BASE + MEM1_SIZE_WIIU)) ||
			(addr >= MEM2_PHYS_BASE && addr < (MEM2_PHYS_BASE + MEM2_SIZE_WIIU)));
	}
	}
	__builtin_unreachable();
}

#ifdef DO_TRACE
/* to avoid the lack of MODULE */
#define TRACE() _log_printf("TRACE: %s entered\r\n", __func__)
#else
#define TRACE() (void)0
#endif

#define ALIGN(x) __attribute__((aligned(x)))
#define BIT(nr) (1u << (nr))
#define VISIBLE __attribute__((visibility("default")))
#define HIDDEN __attribute__((visibility("hidden")))


static inline bool ptrAligned(const void *ptr, u32 align) {
	if (!align)
		return true;

	return ((u32)ptr % align) == 0;
}

static inline u64 alignDownU64(u64 val, u32 align) {
	return val - (val % align);
}

static inline u64 alignUpU64(u64 val, u32 align) {
	u64 rem;

	rem = val % align;
	if (!rem)
		return val;

	return val + (align - rem);
}

static inline u32 alignDownU32(u32 val, u32 align) {
	return val - (val % align);
}

static inline u32 alignUpU32(u32 val, u32 align) {
	u32 rem;

	rem = val % align;
	if (!rem)
		return val;

	return val + (align - rem);
}


#endif /* _UTILS_H */
